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 語句來重新對映紋理座標。我們可以將其移到幾何著色器中嗎?
- 兩個輸入頂點的順序重要嗎?