-
-
Save TSUMIKISEISAKU/dea0a0a77a091ba48fc85a6442425082 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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