【Unity】コンピュートシェーダーで頂点ごとにパラメーターを持たせる方法を紹介します!
この記事はコンピュートシェーダーでメッシュを変形させる方法の続きの記事になります。
コンピュートシェーダーで頂点ごとにパラメーターを持たせる方法を紹介します。
まずはGraphicsBufferにパラメーターを詰めて転送する方法がありますが、
調整不要のパラメーターやデバッグ用途以外はあまりオススメできません。
続いてテクスチャーにパラメーターを持たせる方法です。
- RGBAの各要素に4つのパラメーターを持たせることができる
- 可視的で理解しやすい
- テクスチャー取り替えるだけでパラメーターを取り替え可能
- 分業できる
などなど利点が多いのでテクスチャーにパラメーターを持たせる方法をオススメします。
パラメーター用テクスチャーを作る
パラメーター用のテクスチャーを作成します。
今回はRGBA全ての要素を0から1のランダム値で埋めます。
ランダムテクスチャーは以下のコードで作成することができます。
Texture2D tex = new Texture2D(256, 256);
for(int i = 0; i <; 256; i++)
{
for(int j = 0; j <; 256; j++)
{
tex.SetPixel(j, i, new Color(Random.value, Random.value, Random.value, Random.value));
}
}
tex.Apply();
System.IO.File.WriteAllBytes("ramdom.png", tex.EncodeToPNG());
コンピュートシェーダーの作成
条件下で頂点を伸ばす(トゲを作る)コンピュートシェーダーを作成します。
SphereShader.compute
#pragma kernel CSMain
float Scale;
float Threshold;
float2 Offset;
Texture2D<float4> ParamTex;
StructuredBuffer<float3> Vertices;
StructuredBuffer<float2> Coords;
RWStructuredBuffer<float3> Result;
[numthreads(8,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
float3 normal = normalize(Vertices[id.x]);
float4 param = ParamTex[255 * (frac(Coords[id.x] + Offset))];
float scale = Scale * param.y * step(Threshold, param.x);
Result[id.x] = scale * normal + Vertices[id.x];
}
シェーダーコードの簡単な解説をします。
float3 normal = normalize(Vertices[id.x]);
球体なので位置を正規化すれば法線が求まります。
float4 param = ParamTex[255 * (frac(Coords[id.x] + Offset))];
パラメーターテクスチャからパラメーターを取り出します。
UV値とオフセット値を足した値をfrac関数で小数点以下のみ残して0.0~1.0の範囲にしています。
テクスチャーのサイズが256x256なので255をかけて0から255の範囲にしています。
float scale = Scale * param.y * step(Threshold, param.x);
頂点に対するスケール値を計算します。
Scaleは全体のスケール値、
param.yは各頂点のスケール値、
step(Threshold, param.x)はparam.xがThreshold未満の場合0になりそれ以外は1になります。
Result[id.x] = scale * normal + Vertices[id.x];
法線方向にscale分頂点を伸ばしてトゲを作ります。
スクリプトの修正
コンピュートシェーダーに合わせてスクリプトを修正します。
前回からはランダムテクスチャーを含むパラメーターが少し増えました。
他にはUV値をバッファーでコンピュートシェーダーに転送するくらいです。
SphereTest.cs
using UnityEngine;
public class SphereTest : MonoBehaviour
{
[SerializeField]
private ComputeShader shader;
[SerializeField]
private Texture2D paramTex;
[SerializeField, Range(0, 1)]
private float scale = 0.5f;
[SerializeField, Range(0, 1)]
private float threshold = 0.5f;
[SerializeField]
private Vector2 offset = Vector2.zero;
private GraphicsBuffer buffer;
private GraphicsBuffer vertexBuffer;
private GraphicsBuffer coordBuffer;
private int groupCount;
private Vector3[] vertices;
private Mesh mesh;
void Start()
{
MeshFilter meshFileter = GetComponent<MeshFilter>();
mesh = Instantiate(meshFileter.sharedMesh);
meshFileter.sharedMesh = mesh;
groupCount = (mesh.vertices.Length + 7) / 8;
int count = 8 * groupCount;
vertices = new Vector3[count];
buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, 12);
vertexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, 12);
vertexBuffer.SetData(mesh.vertices);
coordBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, 8);
coordBuffer.SetData(mesh.uv);
}
void OnDestroy()
{
buffer.Release();
vertexBuffer.Release();
coordBuffer.Release();
}
void Update()
{
shader.SetFloat("Scale", scale);
shader.SetFloat("Threshold", threshold);
shader.SetVector("Offset", offset);
int kernel = shader.FindKernel("CSMain");
shader.SetTexture(kernel, "ParamTex", paramTex);
shader.SetBuffer(kernel, "Vertices", vertexBuffer);
shader.SetBuffer(kernel, "Coords", coordBuffer);
shader.SetBuffer(kernel, "Result", buffer);
shader.Dispatch(kernel, groupCount, 1, 1);
buffer.GetData(vertices);
mesh.vertices = vertices;
}
}
結果は以下の通りです。
ワイヤーフレーム表示は以下の感じです。
ウニのようにトゲトゲさせるには頂点数が少なすぎて調整が難しいですね。
次回はコンピュートシェーダーで頂点数を増やして再チャレンジしてみたいと思います。
関連ページ
こちらのページも合わせてご覧下さい。