【问题标题】:Point Sprites for particle system粒子系统的点精灵
【发布时间】:2013-06-28 04:13:20
【问题描述】:

点精灵是构建粒子系统的最佳选择吗?

新版本的 OpenGL 和最新显卡的驱动程序中是否存在点精灵?还是我应该使用 vbo 和 glsl 来做?

【问题讨论】:

    标签: c++ c opengl glsl


    【解决方案1】:

    点精灵确实非常适合粒子系统。但它们与 VBO 和 GLSL 没有任何关系,这意味着它们是完全正交的特征。无论您是否使用点精灵,您总是必须使用 VBO 来上传几何体,无论它们只是点、预制精灵还是其他任何东西,并且您总是必须通过一组着色器(在现代 OpenGL当然)。

    也就是说点精灵在现代 OpenGL 中得到了很好的支持,只是不像旧的固定函数方法那样自动。不支持的是点衰减功能,可让您根据点到相机的距离缩放点的大小,您必须在顶点着色器中手动执行此操作。以同样的方式,您必须在适当的片段着色器中手动对点进行纹理化,使用特殊的输入变量gl_PointCoord(表示当前片段在整个点的 [0,1] 正方形中的位置) .例如,一个基本的点精灵管道可能是这样的:

    ...
    glPointSize(whatever);              //specify size of points in pixels
    glDrawArrays(GL_POINTS, 0, count);  //draw the points
    

    顶点着色器:

    uniform mat4 mvp;
    
    layout(location = 0) in vec4 position;
    
    void main()
    {
        gl_Position = mvp * position;
    }
    

    片段着色器:

    uniform sampler2D tex;
    
    layout(location = 0) out vec4 color;
    
    void main()
    {
        color = texture(tex, gl_PointCoord);
    }
    

    仅此而已。当然,这些着色器只是对纹理精灵进行最基本的绘制,但它们是进一步功能的起点。例如,要根据它到相机的距离来计算精灵的大小(也许是为了给它一个固定的世界空间大小),你必须glEnable(GL_PROGRAM_POINT_SIZE) 并写入顶点着色器中的特殊输出变量gl_PointSize

    uniform mat4 modelview;
    uniform mat4 projection;
    uniform vec2 screenSize;
    uniform float spriteSize;
    
    layout(location = 0) in vec4 position;
    
    void main()
    {
        vec4 eyePos = modelview * position;
        vec4 projVoxel = projection * vec4(spriteSize,spriteSize,eyePos.z,eyePos.w);
        vec2 projSize = screenSize * projVoxel.xy / projVoxel.w;
        gl_PointSize = 0.25 * (projSize.x+projSize.y);
        gl_Position = projection * eyePos;
    }
    

    这将使所有点精灵具有相同的世界空间大小(因此以像素为单位的屏幕空间大小不同)。


    但是点精灵虽然在现代 OpenGL 中仍然得到完美支持,但也有其缺点。最大的缺点之一是它们的剪裁行为。点在它们的中心坐标处被裁剪(因为裁剪是在光栅化之前完成的,因此在点被“放大”之前)。因此,如果点的中心在屏幕之外,则可能仍能到达可视区域的其余部分不会显示出来,因此最坏的情况是,一旦该点在屏幕外的一半处,它就会突然消失。然而,这只有在点精灵太大时才会引起注意(或 annyoing)。如果它们是非常小的粒子,并且每个粒子的覆盖范围不超过几个像素,那么这不会有太大问题,我仍然会将粒子系统视为点精灵的规范用例,只是不要将它们用于大型广告牌。

    但是,如果这是一个问题,那么现代 OpenGL 提供了许多其他方法来实现点精灵,除了在 CPU 上预先构建所有精灵作为单独的四边形的天真的方法。您仍然可以将它们渲染为一个充满点的缓冲区(因此它们很可能来自基于 GPU 的粒子引擎)。然后,要实际生成四边形几何体,您可以使用几何着色器,它可以让您从单个点生成四边形。首先,您只在顶点着色器中进行模型视图转换:

    uniform mat4 modelview;
    
    layout(location = 0) in vec4 position;
    
    void main()
    {
        gl_Position = modelview * position;
    }
    

    然后几何着色器完成剩下的工作。它将点位置与通用 [0,1]-quad 的 4 个角相结合,完成到剪辑空间的转换:

    const vec2 corners[4] = { 
        vec2(0.0, 1.0), vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(1.0, 0.0) };
    
    layout(points) in;
    layout(triangle_strip, max_vertices = 4) out;
    
    uniform mat4 projection;
    uniform float spriteSize;
    
    out vec2 texCoord;
    
    void main()
    {
        for(int i=0; i<4; ++i)
        {
            vec4 eyePos = gl_in[0].gl_Position;           //start with point position
            eyePos.xy += spriteSize * (corners[i] - vec2(0.5)); //add corner position
            gl_Position = projection * eyePos;             //complete transformation
            texCoord = corners[i];                         //use corner as texCoord
            EmitVertex();
        }
    }
    

    在片段着色器中,您当然可以使用自定义 texCoord 变化而不是 gl_PointCoord 进行纹理处理,因为我们不再绘制实际点。


    或者另一种可能性(也许更快,因为我记得几何着色器以速度慢着称)是使用实例化渲染。这样,您就有了一个额外的 VBO,其中包含 仅一个通用 2D 四边形(即 [0,1] 正方形)的顶点,并且您的旧 VBO 仅包含点位置。然后你要做的是多次(实例化)绘制这个单个四边形,同时从 VBO 点获取各个实例的位置:

    glVertexAttribPointer(0, ...points...);
    glVertexAttribPointer(1, ...quad...);
    glVertexAttribDivisor(0, 1);            //advance only once per instance
    ...
    glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, count);  //draw #count quads
    

    然后在顶点着色器中,您将每个点的位置与实际的角/四边形位置(也是该顶点的纹理坐标)组合起来:

    uniform mat4 modelview;
    uniform mat4 projection;
    uniform float spriteSize;
    
    layout(location = 0) in vec4 position;
    layout(location = 1) in vec2 corner;
    
    out vec2 texCoord;
    
    void main()
    {
        vec4 eyePos = modelview * position;            //transform to eye-space
        eyePos.xy += spriteSize * (corner - vec2(0.5)); //add corner position
        gl_Position = projection * eyePos;             //complete transformation
        texCoord = corner;
    }
    

    这实现了与基于几何着色器的方法相同的效果,正确裁剪的点精灵具有一致的世界空间大小。如果你真的想模仿实际点精灵的屏幕空间像素大小,你需要投入更多的计算工作。但这留作练习,与点精灵着色器的世界到屏幕转换完全相反。

    【讨论】:

    • 所以基本上,当我使用glDrawArrays(GL_POINTS, ... ) 时,我正在渲染点精灵?
    • @BRabbit27 当然,从概念上讲,点精灵只不过是一个不仅仅是像素的点。没有glDrawArrays(GL_POINT_SPRITES, ...)
    • 请注意:如果有人遇到 gl_PointCoord 一直为 0 的问题,那么在我的情况下调用 glEnable(GL_POINT_SPRITE) 会有所帮助。
    • 克里斯蒂安,谢谢你的回答。我在第一种方法中遇到的主要问题是我让用户使用箭头键平移视口,这会改变正射投影中的左/右、上/下。这会影响点大小计算中的 projSize 变量 (gl_PointSize = 0.25 * (projSize.x+projSize.y);),导致平移时点大小发生变化(这不是我们想要的)。如何预防?
    • 多么棒的帖子! 6 年后仍然有用。感谢您花时间写下所有这些!
    猜你喜欢
    • 1970-01-01
    • 2019-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-13
    • 1970-01-01
    • 2011-02-09
    • 2012-06-28
    相关资源
    最近更新 更多