Skip to content

Instantly share code, notes, and snippets.

@TSUMIKISEISAKU
Last active September 27, 2023 01:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TSUMIKISEISAKU/dea0a0a77a091ba48fc85a6442425082 to your computer and use it in GitHub Desktop.
Save TSUMIKISEISAKU/dea0a0a77a091ba48fc85a6442425082 to your computer and use it in GitHub Desktop.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using System.Runtime.InteropServices;
/// <summary>
/// スカルプトの制御
/// </summary>
public class SculptManager : MonoBehaviour
{
[SerializeField, Tooltip("スカルプトのComputeShader")]
private ComputeShader _computeShader;
[SerializeField, Tooltip("操作対象のメッシュ")]
private MeshFilter _meshFilter;
[SerializeField, Tooltip("スカルプト位置のガイド テスト用")]
private Transform _sculptPosition;
[SerializeField, Tooltip("ノミ先との接触判定用Collider")]
private MeshCollider _meshCollider;
[SerializeField, Tooltip("スカルプト用メッシュのMesh Renderer")]
private MeshRenderer _meshRenderer;
// 元のメッシュ
private Mesh _originalMesh;
// 編集後のメッシュ
private Mesh _sculptedMesh;
// 接触判定用(MeshCollider用)のメッシュ
private Mesh _colliderMesh;
// 頂点のバッファ
private GraphicsBuffer _vertexBufferWrite;
private GraphicsBuffer _vertexBufferRead;
private GraphicsBuffer _indexBuffer;
// トポロジーのバッファ
private GraphicsBuffer _topologyBuffer;
// 頂点カラーのバッファ
private GraphicsBuffer _colorBuffer;
// 頂点読み取り用
private GraphicsBuffer _displaceVertexBuffer;
// UVオフセット用のRenderTexture
private RenderTexture _uvOffsetTex;
// UVオフセット用のRenderTextureのサイズ
private int _uvOffsetTexSize = 512;
// スカルプト用メッシュに割り当てるマテリアル
private Material _sculptMeshMat;
// 乱数シード値 UVオフセット用
private int _randomSeed = 0;
// スカルプトの処理に付随するデータ管理
private KernelParamsHandler _sculptKernel;
private string _sculptKernelName = "Sculpt";
// スレッドグループのスレッドのサイズ
private const int SIMULATION_BLOCK_SIZE = 256;
// 入力位置の名称
private string _sculptPosName = "_sculptPos";
// 入力方向の名称
private string _sculptDirName = "_sculptDir";
// スカルプトの強度の名称
private string _intensityName = "_intensity";
// スカルプトの範囲の名称
private string _sculptRadiusName = "_sculptRadius";
// 頂点のバッファの名称
private string _vertexBufferWriteName = "_vertexBufferWrite";
private string _vertexBufferReadName = "_vertexBufferRead";
private string _indexBufferName = "_indexBuffer";
private string _topologyBufferName = "_topologyBuffer";
private string _colorBufferName = "_colorBuffer";
private string _displaceVertexBufferName = "_displaceVertexBuffer";
// UVオフセット用RenderTextureの名称
private string _uvOffsetTexName = "_uvOffsetTex";
// UVオフセット値の名称
private string _uvOffsetName = "_uvOffset";
// UVオフセット用RenderTextureのサイズの名称
private string _uvOffsetTexSizeName = "_uvOffsetTexSize";
// 頂点数の名称
private string _vertexCountName = "_vertexCount";
// 個々の頂点のデータ型
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct DisplaceVertex
{
public Vector3 position;
public Vector3 normal;
public Color32 color;
public Vector2 uv;
public Vector2 uv2;
}
// ある頂点に連結している頂点数を元にトポロジー受け渡し用のデータ型を用意
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
struct Topology
{
public int v1, v2, v3, v4, v5, v6, v7, v8, v9, v10;
}
// 読み取った頂点情報をC#側計算用に格納
private Vector3[] _vertData;
/// <summary>
/// 初期化
/// </summary>
private void Start()
{
InitMesh();
InitBuffer();
InitKernel();
SetBuffer();
InitMaterial();
InitRenderTexture();
InitParams();
}
/// <summary>
/// バッファの解放
/// </summary>
private void OnDestroy()
{
_indexBuffer.Dispose();
_vertexBufferWrite.Dispose();
_vertexBufferRead.Dispose();
_uvOffsetTex.Release();
_topologyBuffer.Dispose();
_colorBuffer.Dispose();
}
/// <summary>
/// バッファの初期化
/// </summary>
private void InitBuffer()
{
// Compute Shader用バッファを初期化
_vertexBufferWrite = _sculptedMesh.GetVertexBuffer(0);
_vertexBufferRead = new GraphicsBuffer(GraphicsBuffer.Target.Raw, _vertexBufferWrite.count, _vertexBufferWrite.stride);
_indexBuffer = _originalMesh.GetIndexBuffer();
// C#側読み取り用バッファを初期化
_displaceVertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _vertexBufferWrite.count, Marshal.SizeOf(typeof(Vector3)));
SetDisplaceVertexData(_displaceVertexBuffer);
// ComputeShader トポロジー用バッファを初期化
_topologyBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _vertexBufferWrite.count, Marshal.SizeOf(typeof(Topology)));
SetTopologyData(_topologyBuffer);
// 頂点カラー用バッファを初期化
_colorBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _vertexBufferWrite.count, Marshal.SizeOf(typeof(Vector4)));
SetVertexColorData(_colorBuffer);
}
/// <summary>
/// トポロジーの情報をバッファに格納
/// </summary>
/// <param name="topologyBuffer"></param>
private void SetTopologyData(GraphicsBuffer topologyBuffer)
{
// verticesの頂点番号をキーとしてverticesの番号で繋がっている頂点を記録
var tempTopologyDict = new Dictionary<int, SortedSet<int>>();
for (int i = 0; i < _sculptedMesh.triangles.Length; i += 3)
{
// verticesの番号を取得
int vertice1 = _sculptedMesh.triangles[i];
int vertice2 = _sculptedMesh.triangles[i + 1];
int vertice3 = _sculptedMesh.triangles[i + 2];
// SortedSetが無ければ初期化
if (!tempTopologyDict.ContainsKey(vertice1))
{
tempTopologyDict[vertice1] = new SortedSet<int>();
}
if (!tempTopologyDict.ContainsKey(vertice2))
{
tempTopologyDict[vertice2] = new SortedSet<int>();
}
if (!tempTopologyDict.ContainsKey(vertice3))
{
tempTopologyDict[vertice3] = new SortedSet<int>();
}
// 頂点の接続関係を格納
tempTopologyDict[vertice1].Add(vertice2);
tempTopologyDict[vertice2].Add(vertice1);
tempTopologyDict[vertice2].Add(vertice3);
tempTopologyDict[vertice3].Add(vertice2);
tempTopologyDict[vertice3].Add(vertice1);
tempTopologyDict[vertice1].Add(vertice3);
}
// topologyDataを初期化
List<Topology> topologyData = new List<Topology>();
Topology initTopology = InitTopology();
for (int i = 0; i < _sculptedMesh.vertexCount; i++)
{
topologyData.Add(initTopology);
}
// トポロジーをtopologyDataに整理・格納
foreach (int key in tempTopologyDict.Keys)
{
Topology topology = InitTopology();
IEnumerator<int> connectedVertex = tempTopologyDict[key].GetEnumerator();
int connectionCount = 0;
while (connectedVertex.MoveNext())
{
int value = connectedVertex.Current;
SetTopologyValue(connectionCount, value, ref topology);
connectionCount++;
}
topologyData[key] = topology;
}
// トポロジーを_topologyBufferに格納
topologyBuffer.SetData<Topology>(topologyData);
}
/// <summary>
/// 頂点カラーの情報をバッファに格納
/// </summary>
/// <param name="colorBuffer"></param>
private void SetVertexColorData(GraphicsBuffer colorBuffer)
{
var colors = _sculptedMesh.colors32;
List<Vector4> colorData = new List<Vector4>();
// 頂点カラーの値は正規化して渡す(ComputeShader側での処理を減らす)
for (int i = 0; i < colors.Length; i++)
{
Vector4 color = new Vector4();
color.x = (float)colors[i].r / 255f;
color.y = (float)colors[i].g / 255f;
color.z = (float)colors[i].b / 255f;
color.w = (float)colors[i].a / 255f;
colorData.Add(color);
}
colorBuffer.SetData<Vector4>(colorData);
}
/// <summary>
/// Collider用メッシュに使うバッファに情報を格納
/// </summary>
/// <param name="displaceVertexBuffer"></param>
private void SetDisplaceVertexData(GraphicsBuffer displaceVertexBuffer)
{
var positions = _colliderMesh.vertices;
_vertData = new Vector3[positions.Length];
}
/// <summary>
/// ComputeShaderのKernelの初期化
/// </summary>
private void InitKernel()
{
int threadGroupSize = Mathf.CeilToInt((float)(_meshFilter.sharedMesh.triangles.Length / 3 + SIMULATION_BLOCK_SIZE - 1) / (float)SIMULATION_BLOCK_SIZE);
_sculptKernel = new KernelParamsHandler(_computeShader, _sculptKernelName, threadGroupSize, 1, 1);
}
/// <summary>
/// スカルプト用メッシュ初期化
/// </summary>
private void InitMesh()
{
_originalMesh = CreateMesh(_meshFilter.sharedMesh);
_originalMesh.name = "originalMesh";
_sculptedMesh = CreateMesh(_meshFilter.sharedMesh);
_sculptedMesh.name = "sculptedMesh";
_colliderMesh = CreateColliderMesh(_meshFilter.sharedMesh);
_colliderMesh.name = "colliderMesh";
_meshFilter.mesh = _sculptedMesh;
_meshCollider.sharedMesh = _colliderMesh;
}
/// <summary>
/// スカルプト用メッシュ生成
/// </summary>
/// <param name="original"></param>
/// <returns></returns>
private Mesh CreateMesh(Mesh original)
{
// 元のメッシュから必要なパラメータをコピーしていく
var originalTriangles = original.triangles;
var originalPositions = original.vertices;
var originalNormals = original.normals;
var originalColors = original.colors32;
var originalUV = original.uv;
var originalUV2 = original.uv2;
var mesh = new Mesh();
mesh.indexBufferTarget |= GraphicsBuffer.Target.Raw;
mesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
var pDesc = new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3);
var nDesc = new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3);
var colorDesc = new VertexAttributeDescriptor(VertexAttribute.Color, VertexAttributeFormat.UNorm8, 4);
var uvDesc = new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2);
var uv2Desc = new VertexAttributeDescriptor(VertexAttribute.TexCoord1, VertexAttributeFormat.Float32, 2);
mesh.SetVertexBufferParams(original.vertexCount, pDesc, nDesc, colorDesc, uvDesc, uv2Desc);
mesh.SetIndexBufferParams(originalTriangles.Length, IndexFormat.UInt32);
mesh.SetSubMesh(0, new SubMeshDescriptor(0, originalTriangles.Length), MeshUpdateFlags.DontRecalculateBounds);
mesh.SetIndexBufferData(originalTriangles, 0, 0, originalTriangles.Length);
var vertices = new DisplaceVertex[original.vertexCount];
for (var i = 0; i < original.vertexCount; ++i)
{
vertices[i].position = originalPositions[i];
vertices[i].normal = originalNormals[i];
if (i < originalColors.Length)
{
// can get vertex color from the original mesh properly
vertices[i].color = originalColors[i];
}
else
{
vertices[i].color = new Color32(1, 0, 0, 0);
if (i == original.vertexCount - 1)
{
Debug.Log("cannot get vertex color properly!");
}
}
vertices[i].uv = originalUV[i];
vertices[i].uv2 = originalUV2[i];
}
mesh.SetVertexBufferData(vertices, 0, 0, original.vertexCount);
mesh.bounds = original.bounds;
return mesh;
}
/// <summary>
/// 接触判定用メッシュの生成
/// </summary>
/// <param name="original"></param>
/// <returns></returns>
private Mesh CreateColliderMesh(Mesh original)
{
Mesh mesh = Instantiate(original);
return mesh;
}
/// <summary>
/// ComputeShaderにバッファを割り当て
/// </summary>
private void SetBuffer()
{
_computeShader.SetBuffer(_sculptKernel._index, _indexBufferName, _indexBuffer);
_computeShader.SetBuffer(_sculptKernel._index, _vertexBufferWriteName, _vertexBufferWrite);
_computeShader.SetBuffer(_sculptKernel._index, _vertexBufferReadName, _vertexBufferRead);
_computeShader.SetBuffer(_sculptKernel._index, _topologyBufferName, _topologyBuffer);
_computeShader.SetBuffer(_sculptKernel._index, _colorBufferName, _colorBuffer);
_computeShader.SetBuffer(_sculptKernel._index, _displaceVertexBufferName, _displaceVertexBuffer);
}
/// <summary>
/// UVオフセット用RenderTextureの初期化
/// </summary>
private void InitRenderTexture()
{
// RenderTextureの生成
_uvOffsetTex = new RenderTexture(_uvOffsetTexSize, _uvOffsetTexSize, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
_uvOffsetTex.enableRandomWrite = true;
_uvOffsetTex.Create();
RenderTexture.active = _uvOffsetTex;
GL.Clear(true, true, Color.black);
RenderTexture.active = null;
// ComputeShaderへ割り当て
_computeShader.SetTexture(_sculptKernel._index, _uvOffsetTexName, _uvOffsetTex);
_computeShader.SetFloat(_uvOffsetTexSizeName, _uvOffsetTexSize);
// マテリアルへ割り当て
if(_sculptMeshMat == null)
{
InitMaterial();
}
_sculptMeshMat.SetTexture(_uvOffsetTexName, _uvOffsetTex);
}
/// <summary>
/// マテリアルの取得
/// </summary>
private void InitMaterial()
{
_sculptMeshMat = _meshRenderer.material;
}
/// <summary>
/// パラメータの初期化
/// </summary>
private void InitParams()
{
_computeShader.SetInt(_vertexCountName, _sculptedMesh.vertexCount);
}
/// <summary>
/// コンクリートを削る
/// </summary>
/// <param name="worldPos">ワールド座標系でのノミ先の位置</param>
public void Sculpt(Vector3 worldPos, Vector3 worldDir, float damage = 0.01f, float range = 0.05f)
{
// 各種パラメータを設定
_computeShader.SetVector(_sculptPosName, _meshFilter.transform.InverseTransformPoint(worldPos));
_computeShader.SetVector(_sculptDirName, _meshFilter.transform.InverseTransformDirection(worldDir));
_computeShader.SetFloat(_intensityName, damage);
_computeShader.SetFloat(_sculptRadiusName, range);
_computeShader.SetVector(_uvOffsetName, GetRandomUVOffset());
// バッファを割り当て
SetBuffer();
// カーネル実行
_computeShader.Dispatch(_sculptKernel._index, _sculptKernel._x, _sculptKernel._y, _sculptKernel._z);
// MeshColliderに変更を適用
UpdateMeshCollider();
}
/// <summary>
/// 干渉判定用MeshColliderの更新
/// </summary>
private void UpdateMeshCollider()
{
// 頂点情報読み取り
_displaceVertexBuffer.GetData(_vertData);
// MeshCollider更新
_colliderMesh.vertices = _vertData;
_meshCollider.sharedMesh = null;
_meshCollider.sharedMesh = _colliderMesh;
}
/// <summary>
/// 干渉判定用MeshColliderを外部から取得
/// </summary>
/// <returns></returns>
public MeshCollider GetMeshCollider()
{
return _meshCollider;
}
/// <summary>
/// ランダムなUVオフセット値を取得
/// </summary>
/// <returns></returns>
private Vector2 GetRandomUVOffset()
{
_randomSeed++;
Random.InitState(_randomSeed);
float x = Random.value;
float y = Random.value;
return new Vector2(x, y);
}
/// <summary>
/// トポロジーのデータを初期化
/// </summary>
/// <returns></returns>
private Topology InitTopology()
{
Topology topology = new Topology();
topology.v1 = -1;
topology.v2 = -1;
topology.v3 = -1;
topology.v4 = -1;
topology.v5 = -1;
topology.v6 = -1;
topology.v7 = -1;
topology.v8 = -1;
topology.v9 = -1;
topology.v10 = -1;
return topology;
}
/// <summary>
/// Topologyに値をセット 配列をループで回した中で使う用
/// </summary>
/// <param name="index"></param>
/// <param name="value"></param>
/// <param name="topology"></param>
private void SetTopologyValue(int index, int value, ref Topology topology)
{
if(index == 0)
{
topology.v1 = value;
}
else if (index == 1)
{
topology.v2 = value;
}
else if(index == 2)
{
topology.v3 = value;
}
else if (index == 3)
{
topology.v4 = value;
}
else if (index == 4)
{
topology.v5 = value;
}
else if (index == 5)
{
topology.v6 = value;
}
else if (index == 6)
{
topology.v7 = value;
}
else if (index == 7)
{
topology.v8 = value;
}
else if (index == 8)
{
topology.v9 = value;
}
else if (index == 9)
{
topology.v10 = value;
}
}
/// <summary>
/// スカルプト用メッシュの参照取得
/// </summary>
/// <returns></returns>
public Mesh GetSculptMesh()
{
return _sculptedMesh;
}
/// <summary>
/// スカルプト領域のテクスチャの取得
/// </summary>
/// <returns></returns>
public Texture GetUVOffsetMap()
{
return _uvOffsetTex;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment