CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator

我还没有用过Compute Shader,所以现在把红宝书里的例子拿来了,加入CSharpGL中。

如下图所示。

CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator

或者看视频演示。

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

CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator

 

下载

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级的设定有什么好处。

CSharpGL(23)用ComputeShader实现一个简单的ParticleSimulator

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()

相关文章:

  • 2022-12-23
  • 2022-02-08
  • 2021-12-04
  • 2021-11-19
  • 2021-12-16
猜你喜欢
  • 2022-01-13
  • 2021-12-05
  • 2021-10-15
  • 2021-11-23
  • 2021-06-19
相关资源
相似解决方案