这次我们想要在一个平面中生成随机运动的圆形。之前传递数据都是通过setInt,setVector等方法进行的,但是这些方法并不能一下传递大量数据,比如一个结构体数组,一个数据块。所以这次的主要内容就是通过buffer传递大量数据。
首先是绘制圆形的代码,参数为圆心和半径
具体原理参考下面连接
RasterisingLinesCircles.pdf (sunshine2k.de)
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++;
}
}
接着先要绘制随机分布的静态的圆形。
先定义两个随机函数用来随机半径和圆心,
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)
);
}
接着编写核函数。
需要设置圆圈以及背景的颜色,要在同一个脚本中同时实现背景以及圆圈的绘制,需要编写两个核函数,并且先调用背景核函数,然后调用圆圈核函数覆盖背景颜色,这里两个核函数共用一张共享纹理。
第一个核函数每个线程随机绘制一个圆形,一个线程组有32个线程。同时为了保证圆圈在随机运动,又加了时间对圆心的影响
[numthreads(32,1,1)]
void Circles (uint3 id : SV_DispatchThreadID)
{
int2 centre = (int2)(random2((float)id.x+time)*(float)texResolution);
int radius = (int)(random((float)id.x)*30.0);
drawCircle( centre, radius );
}
[numthreads(8,8,1)]
void Clear (uint3 id : SV_DispatchThreadID)
{
Result[id.xy]=clearColor;
}
在代码中先启用设置背景颜色的核函数,接着启用绘制圆圈的核函数。这里在x方向上分配了十个线程组,所以会绘制出320个圆形
private void DispatchKernels(int count)
{
shader.Dispatch(clearHandle, texResolution / 8, texResolution / 8, 1);
shader.SetFloat("time", Time.time);
shader.Dispatch(circlesHandle, count, 1, 1);
}
void Update()
{
DispatchKernels(10);
}
下面是输出结果
目前圆圈的运动难以控制,所以接下来我们要使用buffer来控制圆圈的运动。
下面是使用buffer的主要步骤
首先要做的是创建一个结构体来存储数据的单个实例,这里我们主要想实现控制圆的运动并将圆转回原点.,circledata表示所有圆圈的数据,buffer
用于在 GPU 中存储和操作这些圆圈的数据。
struct Circle
{
public Vector2 origin;
public Vector2 velocity;
public float radius;
}
int count = 10;
Circle[] circleData;
ComputeBuffer buffer;
接下来要初始化数据,分别设置每个圆形的圆心,半径,移动速度,其中Random.value 会返回一个0-1之间的值, shader.GetKernelThreadGroupSizes(circlesHandle, out threadGroupSizeX, out _, out _);这个方法会获取我们在shader代码中设置的线程组在x方向上的值并输出到threadGroupSizeX这个变量
void InitData()
{
circlesHandle = shader.FindKernel("Circles");
uint threadGroupSizeX;
shader.GetKernelThreadGroupSizes(circlesHandle, out threadGroupSizeX, out _, out _);
int total = (int)threadGroupSizeX * count;
circleData = new Circle[total];
float speed = 100;
float halfSpeed = speed * 0.5f;
float minRadius = 10.0f;
float maxRadius = 30.0f;
float radiusRange = maxRadius- minRadius;
for (int i = 0; i < total; i++)
{
Circle circle = circleData[i];
circle.origin.x = Random.value * texResolution;
circle.origin.y = Random.value * texResolution;
circle.velocity.x = (Random.value * speed) - halfSpeed;
circle.velocity.y = (Random.value * speed) - halfSpeed;
circle.radius = Random.value * radiusRange + minRadius;
circleData[i] = circle;
}
}
在传递数据之前,还需要在shader代码中设置接收数据的缓冲区
struct circle{
float2 origin;
float2 velocity;
float radius ;
};
StructuredBuffer<circle> circlesBuffer;
接下来回到脚本代码中准备传递buffer到shader中.
第一行代码计算出单个结构体的大小
下面是创建buffer的构造函数,具体参数以及含义如下图
然后使用数据填充buffer,最后将缓冲区传递给着色器
int stride = (2 + 2 + 1) * sizeof(float);
buffer = new ComputeBuffer(circleData.Length, stride);
buffer.SetData(circleData);
shader.SetBuffer(circlesHandle, "circlesBuffer", buffer);
接下来将原来核函数的值替换成buffer的值
[numthreads(32,1,1)]
void Circles (uint3 id : SV_DispatchThreadID)
{
int2 centre = (int2)(circlesBuffer[id.x].origin+circlesBuffer[id.x].velocity*time);
int radius = (int)(circlesBuffer[id.x].radius);
drawCircle( centre, radius );
}
目前所有圆圈的运动平滑了很多,但是还有一个问题就是最终所有的圆圈都会飞出屏幕。要解决这个问题也很简单,只需要当圆圈跑到屏幕外的时候,将圆心重新设置到屏幕内
[numthreads(32,1,1)]
void Circles (uint3 id : SV_DispatchThreadID)
{
int2 centre = (int2)(circlesBuffer[id.x].origin+circlesBuffer[id.x].velocity*time);
int radius = (int)(circlesBuffer[id.x].radius);
while(centre.x>texResolution) centre.x-=texResolution;
while(centre.x<0) centre.x+=texResolution;
while(centre.y>texResolution) centre.y-=texResolution;
while(centre.y<0) centre.y+=texResolution;
drawCircle( centre, radius );
}
总结:使用computer buffer 的步骤
完整代码:
PassData.cs
using UnityEngine;
using System.Collections;
public class PassData : MonoBehaviour
{
public ComputeShader shader;
public int texResolution = 1024;
Renderer rend;
RenderTexture outputTexture;
int circlesHandle;
int clearHandle;
struct Circle
{
public Vector2 origin;
public Vector2 velocity;
public float radius;
}
int count = 10;
Circle[] circleData;
ComputeBuffer buffer;
public Color clearColor = new Color();
public Color circleColor = new Color();
// Use this for initialization
void Start()
{
outputTexture = new RenderTexture(texResolution, texResolution, 0);
outputTexture.enableRandomWrite = true;
outputTexture.Create();
rend = GetComponent<Renderer>();
rend.enabled = true;
InitData();
InitShader();
}
void InitData()
{
circlesHandle = shader.FindKernel("Circles");
uint threadGroupSizeX;
shader.GetKernelThreadGroupSizes(circlesHandle, out threadGroupSizeX, out _, out _);
int total = (int)threadGroupSizeX * count;
circleData = new Circle[total];
float speed = 100;
float halfSpeed = speed * 0.5f;
float minRadius = 10.0f;
float maxRadius = 30.0f;
float radiusRange = maxRadius- minRadius;
for (int i = 0; i < total; i++)
{
Circle circle = circleData[i];
circle.origin.x = Random.value * texResolution;
circle.origin.y = Random.value * texResolution;
circle.velocity.x = (Random.value * speed) - halfSpeed;
circle.velocity.y = (Random.value * speed) - halfSpeed;
circle.radius = Random.value * radiusRange + minRadius;
circleData[i] = circle;
}
}
private void InitShader()
{
clearHandle = shader.FindKernel("Clear");
shader.SetInt( "texResolution", texResolution);
shader.SetTexture( circlesHandle, "Result", outputTexture);
shader.SetTexture(clearHandle, "Result", outputTexture);
shader.SetVector("clearColor", clearColor);
shader.SetVector("circleColor", circleColor);
int stride = (2 + 2 + 1) * sizeof(float);
buffer = new ComputeBuffer(circleData.Length, stride);
buffer.SetData(circleData);
shader.SetBuffer(circlesHandle, "circlesBuffer", buffer);
rend.material.SetTexture("_MainTex", outputTexture);
}
private void DispatchKernels(int count)
{
shader.Dispatch(clearHandle, texResolution / 8, texResolution / 8, 1);
shader.SetFloat("time", Time.time);
shader.Dispatch(circlesHandle, count, 1, 1);
}
void Update()
{
DispatchKernels(10);
}
}
PassData.compute
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel Circles
#pragma kernel Clear
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
int texResolution;
float4 clearColor ;
float4 circleColor;
float time;
struct circle{
float2 origin;
float2 velocity;
float radius ;
};
StructuredBuffer<circle> circlesBuffer;
/*Returns pseudo random number in range 0 <= x < 1 */
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)
);
}
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(32,1,1)]
void Circles (uint3 id : SV_DispatchThreadID)
{
int2 centre = (int2)(circlesBuffer[id.x].origin+circlesBuffer[id.x].velocity*time);
int radius = (int)(circlesBuffer[id.x].radius);
while(centre.x>texResolution) centre.x-=texResolution;
while(centre.x<0) centre.x+=texResolution;
while(centre.y>texResolution) centre.y-=texResolution;
while(centre.y<0) centre.y+=texResolution;
drawCircle( centre, radius );
}
[numthreads(8,8,1)]
void Clear (uint3 id : SV_DispatchThreadID)
{
Result[id.xy]=clearColor;
}