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


    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(@"ShadersparticleSimulator.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(@"ShadersparticleSimulator.vert"), ShaderType.VertexShader);
    73         shaderCodes[1] = new ShaderCode(File.ReadAllText(@"ShadersparticleSimulator.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 }

    万事俱备,效果就出来了。

    2016-05-17

    尝试新的粒子运动方式,效果如图。

    所需的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 
     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     vec4 vel = imageLoad(velocity_buffer, int(gl_GlobalInvocationID.x));
    18     vec4 pos = imageLoad(position_buffer, int(gl_GlobalInvocationID.x));
    19 
    20     int i;
    21     float factor = 0.05f;
    22     pos.xyz += vel.xyz * dt;
    23     pos.w -= factor * 0.025f * dt;
    24 
    25     vel.xyz += vec3(0, factor * -0.02f, 0);
    26 
    27     if (pos.w <= 0.0)
    28     {
    29         pos.xyz = -pos.xyz * 0.01;
    30         vel.x = factor * sin(gl_GlobalInvocationID.x);
    31         vel.y = factor * 3f;
    32         vel.z = factor * cos(gl_GlobalInvocationID.x);
    33         pos.w += 1.0f;
    34     }
    35 
    36     imageStore(position_buffer, int(gl_GlobalInvocationID.x), pos);
    37     imageStore(velocity_buffer, int(gl_GlobalInvocationID.x), vel);
    38 }
    39  
    fountain effect

    总结

    原CSharpGL的其他功能(3ds解析器、TTF2Bmp、CSSL等),我将逐步加入新CSharpGL。

    欢迎对OpenGL有兴趣的同学关注(https://github.com/bitzhuwei/CSharpGL

  • 相关阅读:
    头脑王者 艺术,电影,体育,时尚,动漫
    头脑王者 音乐
    头脑王者 地理
    头脑王者 历史,军事
    头脑王者 文学
    Jenkins project
    sql server drop login failed
    devenv.exe 编译Solution
    Jenkins
    头脑王者
  • 原文地址:https://www.cnblogs.com/bitzhuwei/p/CSharpGL-23-a-particle-simulator-from-Compute-Shader.html
Copyright © 2020-2023  润新知