Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9366

ComputeShaderを使ってUVマップ画像を作成する

$
0
0

はじめに

自作のEditor拡張でUVマップを表示しました。
そのときにComputeShaderを使用したのでその詳細を記していきます。

ComputeShaderとは

HLSLやGLSLといった描画用のシェーダーがありますが、ComputeShaderはGPUを使った数値計算をする仕組みです。
GPUは単純な計算を並列実行できるので、処理によってはCPUに比べて高速に処理が実行できます。
Unityで使う場合には事前にC#のプログラム上で必要なデータや出力先を指定してComputeShaderを実行させます。
https://docs.unity3d.com/ja/2018.4/Manual/class-ComputeShader.html

メッシュのUVマップを取得する実際のコードを見ながら簡単に解説します。

実際のコード

実際のコードです。
今回のComputeShaderではポリゴン単位で並列で計算させています。

getUVMap.compute
#pragma kernel CSMain

// 出力先テクスチャ
RWTexture2D<float4> UVMap;

// 入力データ
StructuredBuffer<float2> UVs;
StructuredBuffer<int> Triangles;
int Width;
int Height;

CGPROGRAM
// 2点間に線を引く
void drawline(uint2 p1, uint2 p2, float4 color) {
    int2 diffp12 = int2(p2.x-p1.x, p2.y-p1.y);
    float distp12 = distance(p1, p2);
    for (int i = 0; i < distp12; i++) 
    {
        UVMap[p1 + diffp12 / distp12 * i] = color;  
    }
}
ENDCG

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    // 3角ポリゴンをつくる3頂点のインデックスを取得
    int p1Index = Triangles[id.x * 3];
    int p2Index = Triangles[id.x * 3 + 1];
    int p3Index = Triangles[id.x * 3 + 2];

    // 3頂点に対応したuv座標を取得
    float2 uv1 = UVs[p1Index];
    float2 uv2 = UVs[p2Index];
    float2 uv3 = UVs[p3Index];

    // テクスチャの座標に変換
    uint2 p1Pos = uint2(uv1.x * Width, uv1.y * Height);
    uint2 p2Pos = uint2(uv2.x * Width, uv2.y * Height);
    uint2 p3Pos = uint2(uv3.x * Width, uv3.y * Height);

    float4 color = float4(1, 1, 1, 1);

    // 3頂点が示すテクスチャ上の点間に線を引く
    drawline(p1Pos, p2Pos, color);
    drawline(p2Pos, p3Pos, color);
    drawline(p3Pos, p1Pos, color);
}
cprog.cs
privateTexture2DGetUVMap(Meshmesh,intsubMeshIndex,Texture2Dtexture){vartriangles=mesh.GetTriangles(subMeshIndex);varuvs=mesh.uv;if(uvs.Count()<=0)returnnull;ComputeShadercs=Instantiate(Resources.Load<ComputeShader>("getUVMap"))asComputeShader;intkernel=cs.FindKernel("CSMain");RenderTextureuvMapRT=newRenderTexture(texture.width,texture.height,0);uvMapRT.enableRandomWrite=true;uvMapRT.Create();vartriangleBuffer=newComputeBuffer(triangles.Count(),sizeof(int));varuvBuffer=newComputeBuffer(uvs.Count(),Marshal.SizeOf(typeof(Vector2)));triangleBuffer.SetData(triangles);uvBuffer.SetData(uvs);cs.SetTexture(kernel,"UVMap",uvMapRT);cs.SetInt("Width",texture.width);cs.SetInt("Height",texture.height);cs.SetBuffer(kernel,"Triangles",triangleBuffer);cs.SetBuffer(kernel,"UVs",uvBuffer);cs.Dispatch(kernel,triangles.Length/3,1,1);triangleBuffer.Release();uvBuffer.Release();varuvMapTex=newTexture2D(texture.width,texture.height,TextureFormat.RGB24,false);uvMapTex.name=texture.name;// RenderTextureからTexture2Dに変換varoriginal=RenderTexture.active;RenderTexture.active=uvMapRT;uvMapTex.ReadPixels(newRect(0,0,uvMapRT.width,uvMapRT.height),0,0);uvMapTex.Apply();RenderTexture.active=original;uvMapRT.Release();returnuvMapTex;}

処理の解説

compute shader

このComputeShaderで実行される部分はCSMainの部分です。

[numthreads(1,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)

上についているnumthreadsは処理単位のブロックみたいなものですが、今回は特に考えないのですべて1にしています。

そうした場合、引数のidには並列処理ごとに割り振られた異なるidが入力されます。
[numthreads(1,1,1)]としたのでid.xが異なる値でyとzはすべて同じ値になっています。

そのidを元にメッシュの3頂点を特定するためのインデックスを取得します。

// 3角ポリゴンをつくる3頂点のインデックスを取得intp1Index=Triangles[id.x*3];intp2Index=Triangles[id.x*3+1];intp3Index=Triangles[id.x*3+2];

取得したインデックスを元にUV座標を取得してテクスチャ座標に変換します。

// 3頂点に対応したuv座標を取得float2uv1=UVs[p1Index];float2uv2=UVs[p2Index];float2uv3=UVs[p3Index];// テクスチャの座標に変換uint2p1Pos=uint2(uv1.x*Width,uv1.y*Height);uint2p2Pos=uint2(uv2.x*Width,uv2.y*Height);uint2p3Pos=uint2(uv3.x*Width,uv3.y*Height);

そして、2頂点間に線を引いていきます。

drawline(p1Pos, p2Pos, color);
drawline(p2Pos, p3Pos, color);
drawline(p3Pos, p1Pos, color);

2頂点間に線を引くコードはこちらです。

// 2点間に線を引くvoiddrawline(uint2p1,uint2p2,float4color){int2diffp12=int2(p2.x-p1.x,p2.y-p1.y);floatdistp12=distance(p1,p2);for(inti=0;i<distp12;i++){UVMap[p1+diffp12/distp12*i]=color;}}

cshape

C#コード側ではこのComputeShaderに必要なデータを渡して、実行させています。

始めに使用するComputeShaderをResourcesフォルダから読み込んで、実行するKernelを取得します。

ComputeShadercs=Instantiate(Resources.Load<ComputeShader>("getUVMap"))asComputeShader;intkernel=cs.FindKernel("CSMain");

次に使用するデータを渡すためにBufferを確保して、データを設定します。

vartriangleBuffer=newComputeBuffer(triangles.Count(),sizeof(int));varuvBuffer=newComputeBuffer(uvs.Count(),Marshal.SizeOf(typeof(Vector2)));triangleBuffer.SetData(triangles);uvBuffer.SetData(uvs);cs.SetTexture(kernel,"UVMap",uvMapRT);cs.SetInt("Width",texture.width);cs.SetInt("Height",texture.height);cs.SetBuffer(kernel,"Triangles",triangleBuffer);cs.SetBuffer(kernel,"UVs",uvBuffer);

そして、ComputeShaderを実行します。
Dispatch(kernel, x, y, z)はComputeShaderの[numthreads(x, y, z)]に対応しています。

cs.Dispatch(kernel,triangles.Length/3,1,1);

これで計算結果がテクスチャとしてuvMapTexに出力されています。


Viewing all articles
Browse latest Browse all 9366

Latest Images

Trending Articles