首页 > 嗟来之食 > CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator – BIT祝威 –
2016
08-12

CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator – BIT祝威 –

CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator
我还没有用过Compute Shader,所以现在把红宝书里的例子拿来了,加入CSharpGL中。
效果图
如下图所示。

或者看视频演示。

下面是红宝书原版的代码效果。

下载
CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https://github.com/bitzhuwei/CSharpGL)
Compute Shader
Compute Shader的运行与Vertex Shader等不同:它不在pipeline上运行。调用它时用的是这样的命令:

1 void glDispatchCompute(GLuint um_groups_x, Luint num_groups_y, GLuint num_groups_z);

Compute Shader把并行的计算单元看做一个global work group,它下面分为若干个local work group,local work group又分为若干个执行单元。一个执行单元对应一次对Compute Shader的调用。目前我还不知道这种分为2级的设定有什么好处。

Compute Shader可以像其他Shader一样操作纹理、buffer、原子计数器等资源;它也有一些特有的内置变量(用于获取此执行单元的位置,即属于哪个local work group,是第几个)。
下面通过本文开头的ParticleSimulator的例子来学习一下如何使用Compute Shader。
Particle Simulator
设计
这个例子中,我们用Compute Shader来更新1百万个粒子的位置和速度。为了简单,我们不考虑粒子之间的碰撞问题。
首先分配2个大缓存,一个存放粒子的速度,一个存放粒子的位置。每个时刻里,Compute Shader开始运行,并且每个请求都只处理一个单一的粒子。Compute Shader从缓存中读取当前的速度和位置,计算出新的速度和位置,然后写入缓存中。
然后设置几个引力器,他们都有质量和位置。每个粒子的质量都视作1。每个粒子都受到这些引力器的作用。引力器的位置和质量用一个uniform块保存。
粒子还有生命周期,每次更新时都减少之。少到0时就重置为1,且重置其位置到原点附近。这样模拟过程就能持续进行下去。
模拟粒子的Compute Shader
此Compute Shader如下。

1 #version 430 core
2 // 引力器的位置和质量
3 layout (std140, binding = 0) uniform attractor_block
4 {
5 vec4 attractor[64]; // xyz = position, w = mass
6 };
7 // 每块中粒子的数量为128
8 layout (local_size_x = 128) in;
9 // 使用两个缓存来记录粒子的速度和质量
10 layout (rgba32f, binding = 0) uniform imageBuffer velocity_buffer;
11 layout (rgba32f, binding = 1) uniform imageBuffer position_buffer;
12 // 时间间隔
13 uniform float dt = 1.0;
14
15 void main(void)
16 {
17 // 读取当前粒子的速度和位置
18 vec4 vel = imageLoad(velocity_buffer, int(gl_GlobalInvocationID.x));
19 vec4 pos = imageLoad(position_buffer, int(gl_GlobalInvocationID.x));
20
21 int i;
22 // 更新位置和生命周期
23 pos.xyz += vel.xyz * dt;
24 pos.w -= 0.0008 * dt;
25 // 对每个引力器
26 for (i = 0; i < 4; i++)
27 {
28 // 计算受力情况并更新速度
29 vec3 dist = (attractor[i].xyz – pos.xyz);
30 vel.xyz += dt * dt * attractor[i].w * normalize(dist) / (dot(dist, dist) + 10.0);
31 }
32 // 如何粒子过期,重置它
33 if (pos.w <= 0.0)
34 {
35 pos.xyz = -pos.xyz * 0.01;
36 vel.xyz *= 0.01;
37 pos.w += 1.0f;
38 }
39 // 将新的速度和位置保存到缓存
40 imageStore(position_buffer, int(gl_GlobalInvocationID.x), pos);
41 imageStore(velocity_buffer, int(gl_GlobalInvocationID.x), vel);
42 }

初始化
创建2个缓存,把粒子的初始位置放到原点附近,生命周期在0~1之间随机。

1 protected override void DoInitialize()
2 {
3 {
4 // 创建 compute shader program
5 var computeProgram = new ShaderProgram();
6 var shaderCode = new ShaderCode(File.ReadAllText(@"Shaders\particleSimulator.comp"), ShaderType.ComputeShader);
7 var shader = shaderCode.CreateShader();
8 computeProgram.Create(shader);
9 shader.Delete();
10 this.computeProgram = computeProgram;
11 }
12 {
13 GL.GetDelegateFor<GL.glGenVertexArrays>()(1, render_vao);
14 GL.GetDelegateFor<GL.glBindVertexArray>()(render_vao[0]);
15 // 初始化粒子位置
16 GL.GetDelegateFor<GL.glGenBuffers>()(1, position_buffer);
17 GL.BindBuffer(BufferTarget.ArrayBuffer, position_buffer[0]);
18 var positions = new UnmanagedArray<vec4>(ParticleSimulatorCompute.particleCount);
19 unsafe
20 {
21 var array = (vec4*)positions.FirstElement();
22 for (int i = 0; i < ParticleSimulatorCompute.particleCount; i++)
23 {
24 array[i] = new vec4(
25 (float)(random.NextDouble() – 0.5) * 20,
26 (float)(random.NextDouble() – 0.5) * 20,
27 (float)(random.NextDouble() – 0.5) * 20,
28 (float)(random.NextDouble())
29 );
30 }
31 }
32 GL.BufferData(BufferTarget.ArrayBuffer, positions, BufferUsage.DynamicCopy);
33 positions.Dispose();
34 GL.GetDelegateFor<GL.glVertexAttribPointer>()(0, 4, GL.GL_FLOAT, false, 0, IntPtr.Zero);
35 GL.GetDelegateFor<GL.glEnableVertexAttribArray>()(0);
36 // 初始化粒子速度
37 GL.GetDelegateFor<GL.glGenBuffers>()(1, velocity_buffer);
38 GL.BindBuffer(BufferTarget.ArrayBuffer, velocity_buffer[0]);
39 var velocities = new UnmanagedArray<vec4>(ParticleSimulatorCompute.particleCount);
40 unsafe
41 {
42 var array = (vec4*)velocities.FirstElement();
43 for (int i = 0; i < ParticleSimulatorCompute.particleCount; i++)
44 {
45 array[i] = new vec4(
46 (float)(random.NextDouble() – 0.5) * 0.2f,
47 (float)(random.NextDouble() – 0.5) * 0.2f,
48 (float)(random.NextDouble() – 0.5) * 0.2f,
49 0);
50 }
51 }
52 GL.BufferData(BufferTarget.ArrayBuffer, velocities, BufferUsage.DynamicCopy);
53 velocities.Dispose();
54 // 把缓存绑定到TBO
55 GL.GenTextures(1, textureBufferPosition);
56 GL.BindTexture(GL.GL_TEXTURE_BUFFER, textureBufferPosition[0]);
57 GL.GetDelegateFor<GL.glTexBuffer>()(GL.GL_TEXTURE_BUFFER, GL.GL_RGBA32F, position_buffer[0]);
58 GL.GenTextures(1, textureBufferVelocity);
59 GL.BindTexture(GL.GL_TEXTURE_BUFFER, textureBufferVelocity[0]);
60 GL.GetDelegateFor<GL.glTexBuffer>()(GL.GL_TEXTURE_BUFFER, GL.GL_RGBA32F, velocity_buffer[0]);
61
62 // 初始化引力器
63 GL.GetDelegateFor<GL.glGenBuffers>()(1, attractor_buffer);
64 GL.BindBuffer(BufferTarget.UniformBuffer, attractor_buffer[0]);
65 GL.GetDelegateFor<GL.glBufferData>()(GL.GL_UNIFORM_BUFFER, 64 * Marshal.SizeOf(typeof(vec4)), IntPtr.Zero, GL.GL_DYNAMIC_COPY);
66 GL.GetDelegateFor<GL.glBindBufferBase>()(GL.GL_UNIFORM_BUFFER, 0, attractor_buffer[0]);
67 }
68 {
69 // 初始化渲染器
70 var visualProgram = new ShaderProgram();
71 var shaderCodes = new ShaderCode[2];
72 shaderCodes[0] = new ShaderCode(File.ReadAllText(@"Shaders\particleSimulator.vert"), ShaderType.VertexShader);
73 shaderCodes[1] = new ShaderCode(File.ReadAllText(@"Shaders\particleSimulator.frag"), ShaderType.FragmentShader);
74 var shaders = (from item in shaderCodes select item.CreateShader()).ToArray();
75 visualProgram.Create(shaders);
76 foreach (var item in shaders) { item.Delete(); }
77 this.visualProgram = visualProgram;
78 }
79 }

protected override void DoInitialize()
渲染
渲染循环
渲染过程有3个步骤,首先要更新引力器,然后更新粒子速度和位置,最后渲染出来。

1 float time = 0.0f;
2 protected override void DoRender(RenderEventArgs arg)
3 {
4 // 更新time和deltaTime
5 float deltaTime = (float)random.NextDouble() * 5;
6 time += (float)random.NextDouble() * 5;
7
8 // 更新引力器位置和质量
9 GL.BindBuffer(BufferTarget.UniformBuffer, attractor_buffer[0]);
10 IntPtr attractors = GL.MapBufferRange(BufferTarget.UniformBuffer,
11 0, 64 * Marshal.SizeOf(typeof(vec4)),
12 MapBufferRangeAccess.MapWriteBit | MapBufferRangeAccess.MapInvalidateBufferBit);
13 unsafe
14 {
15 var array = (vec4*)attractors.ToPointer();
16 for (int i = 0; i < 64; i++)
17 {
18 array[i] = new vec4(
19 (float)(Math.Sin(time * (float)(i + 4) * 7.5f * 20.0f)) * 50.0f,
20 (float)(Math.Cos(time * (float)(i + 7) * 3.9f * 20.0f)) * 50.0f,
21 (float)(Math.Sin(time * (float)(i + 3) * 5.3f * 20.0f))
22 * (float)(Math.Cos(time * (float)(i + 5) * 9.1f)) * 100.0f,
23 ParticleSimulatorCompute.attractor_masses[i]);
24 }
25 }
26
27 GL.UnmapBuffer(BufferTarget.UniformBuffer);
28 GL.BindBuffer(BufferTarget.UniformBuffer, 0);
29
30 // 激活compute shader,绑定速度和位置缓存
31 computeProgram.Bind();
32 GL.GetDelegateFor<GL.glBindImageTexture>()(0, textureBufferVelocity[0], 0, false, 0, GL.GL_READ_WRITE, GL.GL_RGBA32F);
33 GL.GetDelegateFor<GL.glBindImageTexture>()(1, textureBufferPosition[0], 0, false, 0, GL.GL_READ_WRITE, GL.GL_RGBA32F);
34 // 指定deltaTime
35 computeProgram.SetUniform("dt", deltaTime);
36 // 执行compute shader
37 GL.GetDelegateFor<GL.glDispatchCompute>()(1000000, 1, 1);
38 // 确保compute shader的计算已完成
39 GL.GetDelegateFor<GL.glMemoryBarrier>()(GL.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
40
41
42 // 渲染出粒子效果
43 visualProgram.Bind();
44 mat4 view = arg.Camera.GetViewMat4();
45 mat4 projection = arg.Camera.GetProjectionMat4();
46 visualProgram.SetUniformMatrix4("mvp", (projection * view).to_array());
47 GL.GetDelegateFor<GL.glBindVertexArray>()(render_vao[0]);
48 GL.Enable(GL.GL_BLEND);
49 GL.BlendFunc(GL.GL_ONE, GL.GL_ONE);
50 GL.DrawArrays(DrawMode.Points, 0, ParticleSimulatorCompute.particleCount);
51 GL.Disable(GL.GL_BLEND);
52 }

protected override void DoRender(RenderEventArgs arg)
粒子着色程序
渲染粒子的vertex shader很简单。

1 #version 430 core
2
3 in vec4 position;
4
5 uniform mat4 mvp;
6
7 out float intensity;
8
9 void main(void)
10 {
11 intensity = position.w;//生命周期(0~1)
12 gl_Position = mvp * vec4(position.xyz, 1.0);
13 }

下面是fragment shader。粒子有生命周期,我们就据此赋予其不同的颜色。

1 #version 430 core
2
3 layout (location = 0) out vec4 color;
4
5 in float intensity;
6
7 void main(void)
8 {
9 color = vec4(0.0f, 0.2f, 1.0f, 1.0f) * intensity + vec4(0.2f, 0.05f, 0.0f, 1.0f) * (1.0f – intensity);
10 }

万事俱备,效果就出来了。
总结
原CSharpGL的其他功能(3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。
欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL)

最后编辑:
作者:
这个作者貌似有点懒,什么都没有留下。

留下一个回复