mypro334 发表于 2022-4-22 16:06

Unity学习笔记:使用 Compute Shader 绘制贴图

Compute Shader 作为现在比较流行的技术,笔者作为美术转行的 TA,之前并没有怎么接触过,本着不学习就会被淘汰的原则,学习了一下 Compute Shader,顺便也和大家分下心得。
Compute Shader 是什么,知乎上很多大佬已经分享过了,也就不用我这个菜鸡给大家介绍了,这里就分享下我用 Compute shader 采样并绘制贴图的心得,废话不多说,先上效果:


这是一个 Unity 自带的 cube 模型,贴图采样的是利用 Compute Shader 绘制的 256 * 256 像素的 Render Texture。
使用 Compute Shader 画贴图,首先需要安排一个可读写的缓存区,这里给到一个可读写的 Texture2D:
Compute Shader:
RWTexture2D<float4> Result;C#:
_tex = new RenderTexture(256, 256, 24); /* 设置 RenderTexture 尺寸 */
_tex.enableRandomWrite = true;          /* 设置 RenderTexture 可写入 */
_tex.Create();
接着就要考虑到这个 Compute Shader 的 kernel 函数的线程数量,因为绘制目标是一个 256 *256 像素的正方形贴图,所以 numthread 也分配一块 2D 正方形数量的线程,并且要被 256 像素整除,原则上是:
ThreadGroupX * ThreadsX = RWTexture.width;
ThreadGroupY * ThreadsY = RWTexture.height;所以,在 Compute Shader 里,我们分配 8*8 个 Thread,在C# 里分配 32*32 个 ThreadGroup;
Compute Shader:

void CSMain (uint2 id : SV_DispatchThreadID)
{
    // Do something
}C#:
......
ComputeShader.Dispatch(_kernelHandle, 32, 32, 1);
......
接下来就是在贴图上画图形了,如何在贴图指定的位置画上指定的颜色呢?比如:
float4 color = float4(1,1,1,1);/* 白色 */
Result = color;/* 表示在像素坐标(100,100)上画白色 */也就是说,我们只需要在 Resul 采样里告诉 GPU 画在什么像素坐标上,画什么颜色即可;
以上图右边的同心圆效果为例,根据半圆的公式:


我们在 256 * 256 (0,255)区间的画布中心点 c 上画一个直径 256 的整圆则是:




Compute Shadr:
float r = 127.5;
float r_squared = r * r;
uint upper_semicircle_y = uint(sqrt(r_squared-(x-127.5)*(x-127.5))+127.5);   /* 计算上半圆的 y 坐标 */
uint lower_semicircle_y = uint(-sqrt(r_squared-(x-127.5)*(x-127.5))+127.5);/* 计算下半圆的 y 坐标 */
   
float4 color = float4(1,1,1,1);                                              /* 白色 */

if(y == upper_semicircle_y || y == lower_semicircle_y)
    Result = color;                                                      /* 根据圆的坐标对应采样,画白色 */结果如下:


画同心圆的画只要 for 循环多次即可:


完整 Compute shader:
#pragma kernel CSMain

RWTexture2D<float4> Result;


void CSMain (uint2 id : SV_DispatchThreadID)
{
    uint x = id.x;
    uint y = id.y;
    float4 color0 = float4(1,1,1,1);
   
    /* Draw circle */
    float r = 40;
    float r_squared = r * r;
    for (int i = 0; i < 15; ++i)
    {
      uint upper_semicircle_y = uint(sqrt(r_squared-(x-127.5)*(x-127.5))+127.5);
      uint lower_semicircle_y = uint(-sqrt(r_squared-(x-127.5)*(x-127.5))+127.5);
   
      if(y == upper_semicircle_y)
            Result = float4((float)x/256,(float)y/256,1,1);
      else if(y == lower_semicircle_y)
            Result = float4((float)x/256,(float)lower_semicircle_y/256,1,1);
      r_squared += 2000;
    }

    /* Draw square */
    {
      bool drawSquare = 0;
      uint2 edge = { uint2(0,y), uint2(255,y), uint2(x,0), uint2(x,255) };
      for (int i = 0; i < 4; ++i)
      {
            if (x == edge.x && y == edge.y)
            {
                drawSquare = true;
                break;
            }
      }
      if (drawSquare)
            Result = color0;
    }
   
}完整 C#:
class DrawTex : MonoBehaviour
{
    public ComputeShader ComputeShader;
    public Material Mat;
    RenderTexture _tex ;
    private int _kernelID;
   
    private void Awake()
    {
      _tex = new RenderTexture(256, 256, 24);            /* 设置 RenderTexture 尺寸 */
      _tex.enableRandomWrite = true;                     /* 设置 RenderTexture 可写入 */
      _tex.Create();
      _kernelID = ComputeShader.FindKernel("CSMain");
      ComputeShader.SetTexture(_kernelID, "Result", _tex);
      ComputeShader.Dispatch(_kernelID, 32, 32, 1);
      
      Mat.mainTexture = _tex;                              /* Compute shader 不能直接渲染
                                                            * 需要使用一个普通 shader 来接收 compute shader 处理的结果
                                                            * 这里使用的是一个双面渲染的 Unlit shader 材质 */
    }

    private void OnDestroy()
    {
      _tex.Release();
    }
}
页: [1]
查看完整版本: Unity学习笔记:使用 Compute Shader 绘制贴图