如何从Perlin梯度噪声的2D梯度计算3D表面法向量
GLSL梯度噪声生成地形高度图的3D法向量计算问题
需求背景
我在GLSL中实现了梯度噪声函数,用于生成地形高度图。需要获取高度图每个像素的3D法向量,以此计算来自“太阳”的方向光。法向量会与地形高度一同存储在纹理中,平坦表面的法向量为vec3(0, 0, -1);(z轴方向)。
梯度噪声实现代码
梯度本身是2D向量,目前先使用单八度噪声测试,每个像素的噪声和梯度生成代码如下:
vec3 random_pcg3d(uvec3 v) { v = v * 1664525u + 1013904223u; v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y; v ^= v >> 16u; v.x += v.y*v.z; v.y += v.z*v.x; v.z += v.x*v.y; return vec3(v) * (1.0/float(0xffffffffu)); } vec2 random_gradient(uvec3 p) { vec3 uv = random_pcg3d(p); float r = sqrt(uv[0]); float phi = 2.0 * M_PI * uv[1]; return vec2(r * cos(phi), r * sin(phi)); } vec3 gradient_noise(vec2 pos, float gridSize) { vec2 gridPos = pos * gridSize; uvec2 i = uvec2(gridPos); vec2 f = fract(gridPos); vec2 g11 = random_gradient(uvec3(i.x, i.y, 1)); vec2 g12 = random_gradient(uvec3(i.x + 1u, i.y, 1)); vec2 g21 = random_gradient(uvec3(i.x, i.y + 1u, 1)); vec2 g22 = random_gradient(uvec3(i.x + 1u, i.y + 1u, 1)); float d11 = dot(g11, f); float d12 = dot(g12, f - vec2(1.0, 0.0)); float d21 = dot(g21, f - vec2(0.0, 1.0)); float d22 = dot(g22, f - vec2(1.0, 1.0)); f = smoothstep(0.0, 1.0, f); vec2 gradient_lerp_x0 = mix(g11, g12, f.x); vec2 gradient_lerp_x1 = mix(g21, g22, f.x); vec2 gradient = mix(gradient_lerp_x0, gradient_lerp_x1, f.y); float q1 = mix(d11, d12, f.x); float q2 = mix(d21, d22, f.x); float noise = mix(q1, q2, f.y); return vec3(noise, gradient.x, gradient.y); }
这段代码是对一个未包含梯度输出的YouTube教程修改而来,我已查证资料,确认梯度可以像噪声值一样进行插值。
问题核心
我确定现有梯度数据足够用来精确计算3D表面法向量。目前通过采样像素附近两点、仅用噪声数据估计梯度的方法,得到了匹配度更高、光照效果更合理的法向量结果。但尝试直接用生成噪声时得到的梯度计算法向量,结果都与估计值相差甚远,对应的光照效果也和地形高度/颜色细节不匹配。
两次法向量计算尝试代码
以下是两次从2D梯度计算3D法向量的尝试,第一次包含几个注释掉的3D梯度方向计算尝试,问题可能出在此处:
vec3 calculate_surface_normal(float height, vec2 gradient) { vec2 tangent_xy = normalize(gradient); // vec2 tangent_xy = vec2(1.0) / gradient; float tangent_z = length(gradient); // float tangent_z = 1; // float tangent_z = sqrt(1.0 - dot(tangent_xy, tangent_xy)); vec3 gradient_forward = normalize(vec3(tangent_xy, tangent_z)); vec3 gradient_right = vec3(rotate_by_phase(normalize(gradient), 0.25), 0); vec3 gradient_up = cross(gradient_right, gradient_forward); return gradient_up; } vec3 calculate_surface_normal(float height, vec2 gradient) { // Compute the partial derivatives using finite differencing float dz_dx = gradient.x; float dz_dy = gradient.y; // Compute the surface normal using cross product vec3 tangent = normalize(vec3(1.0, 0.0, dz_dx)); vec3 bitangent = normalize(vec3(0.0, 1.0, dz_dy)); vec3 normal = normalize(cross(bitangent, tangent)); // Negate z as up is simply -z normal.z *= -1.f; return normal; }
结果对比
可通过图片对比验证,图片从左到右、从上到下依次为:
- 噪声值对应的高度图
- 从噪声生成时的梯度计算出的法向量(法向量轴映射为RGB)
- 通过对比附近区域高度值估计出的法向量(法向量轴映射为RGB)
- 根据高度着色并使用估计法向量计算光照的地形(高于水位时)
第一次法向量计算结果:
第二次法向量计算结果:
两次计算结果都与估计值差异很大,用梯度转法向量函数算出的光照效果和地形细节不匹配,而估计值的效果更合理。
内容的提问来源于stack exchange,提问作者Iron Attorney
相关产品推荐
相关产品推荐

