在前几章中,我们在C#端使用 SetTexture
和 SetInt
方法向目标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); }
|
这里随机生成算法并不是很重要,只需要知道这里调用 random2
和 random
方法,分别生成了一个环形的圆形和半径即可。重点在 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