Chr*_*ica 96
点精灵确实非常适合粒子系统.但它们与VBO和GLSL没有任何关系,这意味着它们是完全正交的特征.无论你是否使用点精灵,你总是必须使用VBO来上传几何体,无论是点,预制精灵还是其他什么,你总是需要通过一组着色器来放置这个几何体(在现代的OpenGL中)当然).
可以说现在的OpenGL非常支持点精灵,而不是像旧的固定功能方法那样自动支持精灵.不支持的是点衰减功能,您可以根据它与相机的距离来缩放点的大小,您必须在顶点着色器中手动执行此操作.同样地,您必须在适当的片段着色器中手动对点进行纹理处理,使用特殊的输入变量gl_PointCoord(表示当前片段在整个点的[0,1] -square中的位置).例如,基本点精灵管道可以这样看:
...
glPointSize(whatever); //specify size of points in pixels
glDrawArrays(GL_POINTS, 0, count); //draw the points
Run Code Online (Sandbox Code Playgroud)
顶点着色器:
uniform mat4 mvp;
layout(location = 0) in vec4 position;
void main()
{
gl_Position = mvp * position;
}
Run Code Online (Sandbox Code Playgroud)
片段着色器:
uniform sampler2D tex;
layout(location = 0) out vec4 color;
void main()
{
color = texture(tex, gl_PointCoord);
}
Run Code Online (Sandbox Code Playgroud)
就这样.当然,这些着色器只是进行最基本的纹理精灵绘制,但它是进一步功能的起点.例如,根据它与相机的距离来计算精灵的大小(可能是为了给它一个固定的世界空间大小),你必须在顶点着色器中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;
}
Run Code Online (Sandbox Code Playgroud)
这将使所有点精灵具有相同的世界空间大小(因此以像素为单位的屏幕空间大小不同).
但是在现代OpenGL中仍然得到完美支持的点精灵有其缺点.其中一个最大的缺点是它们的削波行为.点被剪切在它们的中心坐标处(因为剪切在光栅化之前完成,因此在点被"放大"之前).因此,如果该点的中心位于屏幕之外,则其余部分可能仍然可以进入观察区域,因此最坏的情况是,当该点离屏幕一半时,它将突然消失.然而,如果点精灵太大,这只是值得注意的(或者是一种不可思议的).如果它们是非常小的粒子,无论如何都不会覆盖几个像素,那么这不会是一个大问题,我仍然会认为粒子系统是点精灵的规范用例,只是不要
但是,如果这是一个问题,那么现代OpenGL提供了许多其他方法来实现点精灵,除了在CPU上预先构建所有精灵作为单个四边形的天真方式.您仍然可以将它们渲染为一个充满点的缓冲区(因此它们很可能来自基于GPU的粒子引擎).要实际生成四边形几何体,您可以使用几何体着色器,它允许您从单个点生成四边形.首先,您只在顶点着色器中进行模型视图转换:
uniform mat4 modelview;
layout(location = 0) in vec4 position;
void main()
{
gl_Position = modelview * position;
}
Run Code Online (Sandbox Code Playgroud)
然后几何着色器完成其余的工作.它将点位置与通用[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();
}
}
Run Code Online (Sandbox Code Playgroud)
在片段着色器中,您当然会使用自定义texCoord变体而不是gl_PointCoord纹理,因为我们不再绘制实际点.
或者另一种可能性(也许更快,因为我记得几何着色器因慢速而闻名)将使用实例化渲染.这样你就可以得到一个额外的VBO,其中只包含一个通用2D四边形的顶点(即[0,1] -square)和刚刚包含点位置的旧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
Run Code Online (Sandbox Code Playgroud)
然后在顶点着色器中,您可以使用实际的角点/四边形位置(也是该顶点的纹理坐标)组合每点位置:
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;
}
Run Code Online (Sandbox Code Playgroud)
这与基于几何着色器的方法相同,具有一致的世界空间大小的适当剪切的点精灵.如果你真的想模仿实际点精灵的屏幕空间像素大小,你需要增加一些计算量.但这仍然是一个练习,并且将成为从点精灵着色器进行世界到屏幕转换的反对者.