Help us understand the problem. What is going on with this article?

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

はじめに

自作の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
private Texture2D GetUVMap(Mesh mesh, int subMeshIndex, Texture2D texture)
{
    var triangles = mesh.GetTriangles(subMeshIndex);
    var uvs = mesh.uv;

    if (uvs.Count() <= 0) return null;

    ComputeShader cs = Instantiate(Resources.Load<ComputeShader>("getUVMap")) as ComputeShader;
    int kernel = cs.FindKernel("CSMain");

    RenderTexture uvMapRT = new RenderTexture(texture.width, texture.height, 0);
    uvMapRT.enableRandomWrite = true;
    uvMapRT.Create();

    var triangleBuffer = new ComputeBuffer(triangles.Count(), sizeof(int));
    var uvBuffer = new ComputeBuffer(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();

    var uvMapTex = new Texture2D(texture.width, texture.height, TextureFormat.RGB24, false);
    uvMapTex.name = texture.name;

    // RenderTextureからTexture2Dに変換
    var original = RenderTexture.active;
    RenderTexture.active = uvMapRT;
    uvMapTex.ReadPixels(new Rect(0, 0, uvMapRT.width, uvMapRT.height), 0, 0);
    uvMapTex.Apply();
    RenderTexture.active = original;

    uvMapRT.Release();

    return uvMapTex;
}

処理の解説

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頂点のインデックスを取得
int p1Index = Triangles[id.x * 3];
int p2Index = Triangles[id.x * 3 + 1];
int p3Index = Triangles[id.x * 3 + 2];

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

// 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);

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

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

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

// 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;  
    }
}

cshape

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

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

ComputeShader cs = Instantiate(Resources.Load<ComputeShader>("getUVMap")) as ComputeShader;
int kernel = cs.FindKernel("CSMain");

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

var triangleBuffer = new ComputeBuffer(triangles.Count(), sizeof(int));
var uvBuffer = new ComputeBuffer(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に出力されています。

gatosyocora
高知で大学生やっています。 VRに興味ありでVR学会認定VR技術者です
https://twitter.com/gatosyocora
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした