在 OpenGL 中为地形生成平滑法线
Generating smooth normals for terrains in OpenGL
我正在实施一个使用柏林噪声生成地形的系统。这就是我生成顶点的方式:
int arrayIdx = 0;
for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {
float height0 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
height1 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
height2 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH),
height3 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 0] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height0, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 1] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 2] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 3] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height3, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 4] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 5] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 0] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 1] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 2] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 3] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 4] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 5] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
glm::vec3 normal0 = -1.0f * glm::triangleNormal(mapVertices[arrayIdx + 0], mapVertices[arrayIdx + 1], mapVertices[arrayIdx + 2]),
normal1 = +1.0f * glm::triangleNormal(mapVertices[arrayIdx + 3], mapVertices[arrayIdx + 4], mapVertices[arrayIdx + 5]);
mapNormals[arrayIdx + 0] = normal0;
mapNormals[arrayIdx + 1] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 2] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 3] = normal1;
mapNormals[arrayIdx + 4] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 5] = (normal0 + normal1) / 2.0f;
arrayIdx += 6;
}
}
不使用光照会产生这些非常平滑的结果,
唯一剩下要做的就是为三角形生成法线,这将使地形看起来平滑。
仅使用 glm::triangleNormal
会产生此结果,
如您所见,照明确实破坏了表面光滑的错觉。
我试过在三角形的碰撞顶点上使用法线的平均值,如下所示:
arrayIdx = 0;
for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {
if((x == offset.x - CHUNK_WIDTH / 2.0f && y == offset.y - CHUNK_WIDTH / 2.0f) ||
(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y == offset.y - CHUNK_WIDTH / 2.0f) ||
(x == offset.x - CHUNK_WIDTH / 2.0f && y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) ||
(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH)) {
//Special case
}
else if(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH ||
y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) {
//Special case
}
else {
glm::vec3 averageNormals = (mapNormals[arrayIdx + 3 + 0] + //This triangle
mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] + //Triangle after and this one
mapNormals[arrayIdx + 2 + 6] + //Triangle in the right
mapNormals[arrayIdx + 5 + 6] + //Triangle in the right
mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] + //Triangle after this one
mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6]) //Triangle after this one
/ 6.0f;
mapNormals[arrayIdx + 3 + 0] = averageNormals;
mapNormals[arrayIdx + 2 + 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 5 + 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] = mapNormals[arrayIdx + 3 + 0];
}
arrayIdx += 6;
}
}
产生了这个结果,
但这看起来并没有好多少。
使用法线作为片段颜色得到这个结果:
将法线渲染为线会产生此结果,这是在优化之前使用较大的三角形,以减少线的数量:
这是我的优化:
不知何故,两个法线没有设置。
这里的蓝线是平均法线,绿线是优化前的个别法线,看起来不错:
这是线框图:
可能有些法线没有设置为平均值?
如何生成平滑的法线?
逐顶点法线的计算方法如下:您必须考虑顶点也属于的每个多边形,例如:
正如您所说,问题似乎与照明有关。计算出正确的逐顶点法线后,您还应该使用正确的着色技术,在本例中应该是 Phong 或 Gouraud 着色,而不是 Flat 着色,正如您正在使用的那样。
我可以看到我请求的图像...
如果没有一些法线指向下词,现在看看地形下方。如果是,则表示交叉中的乘法顺序错误或地形几何中的绕组不同。
然后检查您的法线是否已标准化(相同大小),但快速查看对我来说还不错。
如果渲染平均法线(使用不同的颜色),它应该被其他法线包围(在它们的中间)
无论如何,这对我来说不合适:
看起来其中一条法线在反射方向上。你确定你是从正确的顶点计算法线吗?
您的几何形状看起来像三角四边形网格,所以您可能使用了错误的对角线...
我正在实施一个使用柏林噪声生成地形的系统。这就是我生成顶点的方式:
int arrayIdx = 0;
for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {
float height0 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
height1 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH),
height2 = noise->octaveNoise(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH),
height3 = noise->octaveNoise(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 0] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height0, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 1] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 2] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 3] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height3, y + 1.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 4] = glm::vec3(x + 1.0f * TRIANGLE_WIDTH, height1, y + 0.0f * TRIANGLE_WIDTH);
mapVertices[arrayIdx + 5] = glm::vec3(x + 0.0f * TRIANGLE_WIDTH, height2, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 0] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 1] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 2] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 3] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 4] = glm::vec2(x + 1.0f * TRIANGLE_WIDTH, y + 0.0f * TRIANGLE_WIDTH);
mapUVs[arrayIdx + 5] = glm::vec2(x + 0.0f * TRIANGLE_WIDTH, y + 1.0f * TRIANGLE_WIDTH);
glm::vec3 normal0 = -1.0f * glm::triangleNormal(mapVertices[arrayIdx + 0], mapVertices[arrayIdx + 1], mapVertices[arrayIdx + 2]),
normal1 = +1.0f * glm::triangleNormal(mapVertices[arrayIdx + 3], mapVertices[arrayIdx + 4], mapVertices[arrayIdx + 5]);
mapNormals[arrayIdx + 0] = normal0;
mapNormals[arrayIdx + 1] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 2] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 3] = normal1;
mapNormals[arrayIdx + 4] = (normal0 + normal1) / 2.0f;
mapNormals[arrayIdx + 5] = (normal0 + normal1) / 2.0f;
arrayIdx += 6;
}
}
不使用光照会产生这些非常平滑的结果,
唯一剩下要做的就是为三角形生成法线,这将使地形看起来平滑。
仅使用 glm::triangleNormal
会产生此结果,
如您所见,照明确实破坏了表面光滑的错觉。
我试过在三角形的碰撞顶点上使用法线的平均值,如下所示:
arrayIdx = 0;
for(float x = offset.x - CHUNK_WIDTH / 2.0f; x < float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f; x += TRIANGLE_WIDTH) {
for(float y = offset.y - CHUNK_WIDTH / 2.0f; y < float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f; y += TRIANGLE_WIDTH) {
if((x == offset.x - CHUNK_WIDTH / 2.0f && y == offset.y - CHUNK_WIDTH / 2.0f) ||
(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y == offset.y - CHUNK_WIDTH / 2.0f) ||
(x == offset.x - CHUNK_WIDTH / 2.0f && y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) ||
(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH && y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH)) {
//Special case
}
else if(x == float(CHUNK_WIDTH) + offset.x - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH ||
y == float(CHUNK_WIDTH) + offset.y - CHUNK_WIDTH / 2.0f - TRIANGLE_WIDTH) {
//Special case
}
else {
glm::vec3 averageNormals = (mapNormals[arrayIdx + 3 + 0] + //This triangle
mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] + //Triangle after and this one
mapNormals[arrayIdx + 2 + 6] + //Triangle in the right
mapNormals[arrayIdx + 5 + 6] + //Triangle in the right
mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] + //Triangle after this one
mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6]) //Triangle after this one
/ 6.0f;
mapNormals[arrayIdx + 3 + 0] = averageNormals;
mapNormals[arrayIdx + 2 + 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 5 + 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 1 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 4 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6] = mapNormals[arrayIdx + 3 + 0];
mapNormals[arrayIdx + 0 + int(CHUNK_WIDTH * (1.0f / TRIANGLE_WIDTH)) * 6 + 6] = mapNormals[arrayIdx + 3 + 0];
}
arrayIdx += 6;
}
}
产生了这个结果,
但这看起来并没有好多少。
使用法线作为片段颜色得到这个结果:
将法线渲染为线会产生此结果,这是在优化之前使用较大的三角形,以减少线的数量:
这是我的优化:
不知何故,两个法线没有设置。
这里的蓝线是平均法线,绿线是优化前的个别法线,看起来不错:
这是线框图:
可能有些法线没有设置为平均值?
如何生成平滑的法线?
逐顶点法线的计算方法如下:您必须考虑顶点也属于的每个多边形,例如:
正如您所说,问题似乎与照明有关。计算出正确的逐顶点法线后,您还应该使用正确的着色技术,在本例中应该是 Phong 或 Gouraud 着色,而不是 Flat 着色,正如您正在使用的那样。
我可以看到我请求的图像...
如果没有一些法线指向下词,现在看看地形下方。如果是,则表示交叉中的乘法顺序错误或地形几何中的绕组不同。
然后检查您的法线是否已标准化(相同大小),但快速查看对我来说还不错。
如果渲染平均法线(使用不同的颜色),它应该被其他法线包围(在它们的中间)
无论如何,这对我来说不合适:
看起来其中一条法线在反射方向上。你确定你是从正确的顶点计算法线吗?
您的几何形状看起来像三角四边形网格,所以您可能使用了错误的对角线...