Unity Compute Shader - 4 传输数据到Compute Shader

在前几章中,我们在C#端使用 SetTextureSetInt 方法向目标Compute Shader传输了数据,在这几章中,我们将继续熟悉向Compute Shader传值的一些方法,并绘制一些更加复杂的图形

准备工作

在本章中,我们将先绘制一个简单的环形,并且整张图片的背景色和环形的颜色,都是可以自定义的,效果图下图:

按照这个思路,先创建一个新的C#脚本,命名为 DrawCircle,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DrawCircle : MonoBehaviour
{
//要操作的 Compute Shader
public ComputeShader shader;

//使用图片的分辨率
public int texResolution;

//背景色
public Color clearColor;
//圆环的颜色
public Color circleColor;

//Quad的MeshRenderer组件
private MeshRenderer meshRend;
//Compute Shader输出的图片
private RenderTexture outputTexture;

//背景色内核索引
private int circleHandle;
//圆环内核索引
private int clearHandle;

private void Start()
{
meshRend = gameObject.GetComponent<MeshRenderer>();

outputTexture = new RenderTexture(texResolution, texResolution, 0);
outputTexture.enableRandomWrite = true;
outputTexture.Create();

InitShader();

DispatchKernels();
}

private void InitShader()
{
circleHandle = shader.FindKernel("Circles");
clearHandle = shader.FindKernel("Clear");

shader.SetInt("texResolution", texResolution);
shader.SetVector("clearColor", clearColor);
shader.SetVector("circleColor", circleColor);

shader.SetTexture(circleHandle, "Result", outputTexture);
shader.SetTexture(clearHandle, "Result", outputTexture);

meshRend.material.SetTexture("_MainTex", outputTexture);
}

private void DispatchKernels()
{
//先绘制背景色,再绘制圆环,顺序反了的话,圆环就会被覆盖掉了
shader.Dispatch(clearHandle, texResolution / 8, texResolution / 8, 1);
shader.Dispatch(circleHandle, 1, 1, 1);
}
}

这里C#代码部分,基本和之前的代码原理相同,传输颜色数据,用到了 SetVector 方法。
还有一点比较重要,就是在 DispatchKernels 中绘制背景色和绘制圆环的内核调用顺序,如果最后调用背景色绘制内核,就会覆盖掉之前的图案。

使用Compute Shader绘制环形

接下来创建一个Compute Shader脚本,命名为 DrawCircleShader,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#pragma kernel Circles
#pragma kernel Clear

shared RWTexture2D<float4> Result;

int texResolution;

float4 clearColor;
float4 circleColor;

void plot1(int x, int y, int2 centre)
{
Result[uint2(centre.x + x, centre.y + y)] = circleColor;
}

void plot8(int x, int y, int2 centre)
{
plot1(x, y, centre);
plot1(y, x, centre);
plot1(x, -y, centre);
plot1(y, -x, centre);
plot1(-x, -y, centre);
plot1(-y, -x, centre);
plot1(-x, y, centre);
plot1(-y, x, centre);
}

void drawCircle(int2 centre, int radius)
{
int x = 0;
int y = radius;
int d = 1 - radius;

while (x < y)
{
if (d < 0)
{
d += 2 * x + 3;
}
else
{
d += 2 * (x - y) + 5;
y--;
}

plot8(x, y, centre);

x++;
}
}

[numthreads(8, 8, 1)]
void Clear(uint3 id : SV_DispatchThreadID)
{
Result[id.xy] = clearColor;
}

[numthreads(1, 1, 1)]
void Circles(uint3 id : SV_DispatchThreadID)
{
int2 center = (texResolution >> 1);
int radius = 180;

drawCircle(center, radius);
}

这里 drawCircle方法可能看起来很奇怪,其实这是中点圆算法(Midpoint circle algorithm),就是将一个环形分为了8个部分,通过x,y的正负和镜像取值,来绘制一个环形,如下图所示:

这里要注意,我们绘制背景色的方法,使用的线程组是 [numthreads(8, 8, 1)],而在绘制环形时使用的是 [numthreads(1, 1, 1)]。这就意味着整个 Circles 值运行了一次,就完成了整个环形的绘制。如果从传统定点/片元着色器来看,可能会觉得很奇怪,但Compute Shader其实完全控制着对RWTexture2D的写入。我们使用单一调用就可以绘制出整个环形,而不是单个像素。

绘制多个随机环形

现在我们在DrawCircleShader脚本中刚添加一些生成随机数的方法,让 Circles 方法可以在图片上随机绘制半径随机的多个环形,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
float time;

//用于随机生成半径
float random(float value, float seed = 0.546)
{
float random = (frac(sin(value + seed) * 143758.5453)); // + 1.0)/2.0;
return random;
}

//用于随机生成圆心
float2 random2(float value)
{
return float2(
random(value, 3.9812),
random(value, 7.1536)
);
}

...

[numthreads(32, 1, 1)]
void Circles(uint3 id : SV_DispatchThreadID)
{
int2 center = (int2) (random2((float) id.x + time) * (float) texResolution);
int radius = (int) (random((float) id.x) * 30);

drawCircle(center, radius);
}

这里随机生成算法并不是很重要,只需要知道这里调用 random2random 方法,分别生成了一个环形的圆形和半径即可。重点在 Circles 上方的线程组声明上 [numthreads(32, 1, 1)],根据绘制一个环形时的房线程组声明我们可以知道,线程组内只有一个线程绘制一次,就会绘制出一个环形,现在我们使用32个线程同时绘制,也就是要在当前你图片上绘制32个大小位置随机的环形
同时,我们将 DrawCircle脚本中的调用方法修改一下,让其多次调用绘制环形的方法,让整个图片基本被环形填充,代码如下:

1
2
3
4
5
private void DispatchKernels()
{
...
shader.Dispatch(circleHandle, 10, 1, 1);
}

这样,我们就得到了一张绘制了320个随机大小位置环形,并且可自定义颜色的图片,效果如下:

在下一章中,我们将让这些环形想气泡一样都运动起来

相关资料

RasterisingLinesCircles

Midpoint circle algorithm - wiki