OpenGL 编程/Glescraft 7
我们已经看到,通过仅绘制可见的立方体面,甚至通过合并相邻面,我们可以极大地减少体素世界中要绘制的顶点数。我们可以通过使用几何着色器进一步减少发送到 GPU 的数据量。这个想法是将体素面的最紧凑表示发送到几何着色器,并让它生成六个顶点(用于组成一个面的两个三角形)以及我们可能需要的任何其他数据。我们可以用一个顶点来表示整个体素,但这将意味着几何着色器不知道要渲染哪些边,因此它将渲染所有六个边,无论它们是否被遮挡。绘制被遮挡面的可能性是,GPU 的花费时间比通过这种几何着色器节省的处理顶点的时间要多。一个更好的方法是为每个面发送两个顶点(下图中的 A 和 C)到顶点着色器,这两个顶点来自该面的两个对角线。这样我们也可以表示合并的面。知道面是矩形并位于 x、y 或 z 平面中,我们可以重建另外两个角(B 和 D),并且从四个角我们可以创建一个带状三角形(BAC 和 ACD)。也可以通过这种方式重建面的法线(使用 AC 和 AB 线的叉积),我们可以将其用于光照计算。
以前,我们必须在面的所有六个顶点中传递相同的纹理坐标。使用几何着色器,着色器可以同时访问两个输入顶点,因此我们只需要在一个顶点中传递纹理坐标。着色器可以将其复制到所有六个输出顶点。这也意味着我们可以将第二个输入顶点的 w 坐标用于其他目的,例如强度信息。
在使用几何着色器之前,我们可以使用 GLEW 检查它是否实际受您的 GPU 支持。
if(!GLEW_EXT_geometry_shader4) {
fprintf(stderr, "No support for geometry shaders found\n");
exit(1);
}
我们编译和链接几何着色器的方式与顶点和片段着色器相同,只是我们需要告诉 OpenGL 几何着色器期望的输入类型和它生成的输出类型。在我们的例子中,它期望 LINES 作为输入,并产生 TRIANGLE_STRIPS 作为输出。执行方式如下
GLuint vs, fs, gs;
if ((vs = create_shader("glescraft.v.glsl", GL_VERTEX_SHADER)) == 0) return 0;
if ((gs = create_shader("glescraft.g.glsl", GL_GEOMETRY_SHADER_EXT)) == 0) return 0;
if ((fs = create_shader("glescraft.f.glsl", GL_FRAGMENT_SHADER)) == 0) return 0;
GLuint program = glCreateProgram();
glAttachShader(program, vs);
glAttachShader(program, fs);
glAttachShader(program, gs);
glProgramParameteriEXT(program, GL_GEOMETRY_INPUT_TYPE_EXT, GL_LINES);
glProgramParameteriEXT(program, GL_GEOMETRY_OUTPUT_TYPE_EXT, GL_TRIANGLE_STRIP);
glLinkProgram(program);
当我们绘制时,我们只需像绘制 GL_LINES 一样,GPU 会处理其余的工作。
以前,在我们的 update() 函数中,我们必须生成六个顶点,如下所示(对于从负 x 方向观看的面)
// Same block as previous one? Extend it.
if(vis && z != 0 && blk[x][y][z] == blk[x][y][z - 1]) {
vertex[i - 5] = byte4(x, y, z + 1, side);
vertex[i - 2] = byte4(x, y, z + 1, side);
vertex[i - 1] = byte4(x, y + 1, z + 1, side);
merged++;
// Otherwise, add a new quad.
} else {
vertex[i++] = byte4(x, y, z, side);
vertex[i++] = byte4(x, y, z + 1, side);
vertex[i++] = byte4(x, y + 1, z, side);
vertex[i++] = byte4(x, y + 1, z, side);
vertex[i++] = byte4(x, y, z + 1, side);
vertex[i++] = byte4(x, y + 1, z + 1, side);
}
我们可以简单地修改代码段以生成我们几何着色器的两个顶点
// Same block as previous one? Extend it.
if(vis && z != 0 && blk[x][y][z] == blk[x][y][z - 1]) {
vertex[i - 2].y = y + 1;
vertex[i - 1].z = z + 1;
merged++;
// Otherwise, add a new quad.
} else {
vertex[i++] = byte4(x, y + 1, z, side);
vertex[i++] = byte4(x, y, z + 1, intensity);
}
注意我们如何在第二个顶点中传递强度信息。
几何着色器如下所示
#version 120
#extension GL_EXT_geometry_shader4 : enable
varying out vec4 texcoord;
varying out vec3 normal;
varying out float intensity;
uniform mat4 mvp;
const vec3 sundir = normalize(vec3(0.5, 1, 0.25));
const float ambient = 0.5;
void main(void) {
// Two input vertices will be the first and last vertex of the quad
vec4 a = gl_PositionIn[0];
vec4 d = gl_PositionIn[1];
// Save intensity information from second input vertex
intensity = d.w / 127.0;
d.w = a.w;
// Calculate the middle two vertices of the quad
vec4 b = a;
vec4 c = a;
if(a.y == d.y) { // y same
c.z = d.z;
b.x = d.x;
} else { // x or z same
b.y = d.y;
c.xz = d.xz;
}
// Calculate surface normal
normal = normalize(cross(a.xyz - b.xyz, b.xyz - c.xyz));
// Surface intensity depends on angle of solar light
// This is the same for all the fragments, so we do the calculation in the geometry shader
intensity *= ambient + (1 - ambient) * clamp(dot(normal, sundir), 0, 1);
// Emit the vertices of the quad
texcoord = a; gl_Position = mvp * vec4(a.xyz, 1); EmitVertex();
texcoord = b; gl_Position = mvp * vec4(b.xyz, 1); EmitVertex();
texcoord = c; gl_Position = mvp * vec4(c.xyz, 1); EmitVertex();
texcoord = d; gl_Position = mvp * vec4(d.xyz, 1); EmitVertex();
EndPrimitive();
}
顶点着色器除了传递几何着色器计算的顶点之外,别无他法
#version 120
attribute vec4 coord;
void main(void) {
gl_Position = coord;
}
片段着色器如下所示
#version 120
varying vec4 texcoord;
varying vec3 normal;
varying float intensity;
uniform sampler3D texture;
const vec4 fogcolor = vec4(0.6, 0.8, 1.0, 1.0);
const float fogdensity = .00003;
void main(void) {
vec4 color;
// Look at normal to see how to map texture coordinates
if(normal.y != 0) {
color = texture3D(texture, vec3(texcoord.x, texcoord.z, (texcoord.w + 0.5) / 16.0));
} else {
color = texture3D(texture, vec3(texcoord.x + texcoord.z, -texcoord.y, (texcoord.w + 0.5) / 16.0));
}
// Very cheap "transparency": don't draw pixels with a low alpha value
if(color.a < 0.4)
discard;
// Attenuate
color *= intensity;
// Calculate strength of fog
float z = gl_FragCoord.z / gl_FragCoord.w;
float fog = clamp(exp(-fogdensity * z * z), 0.2, 1);
// Final color is a mix of the actual color and the fog color
gl_FragColor = mix(fogcolor, color, fog);
}
- 尝试不同的方法将强度分配给体素。
- 片段着色器仍然包含一个 if 语句来重新映射纹理坐标。我们可以将其移到几何着色器中吗?
- 两个输入顶点的顺序重要吗?