从高度图生成法线贴图?

时间:2011-03-12 07:26:05

标签: c++ opengl graphics

我正在使用随机分形为视频游戏在程序上生成一些污垢。我已经使用中点位移算法生成了高度图并将其保存到纹理中。我有一些关于如何将其变成法线纹理的想法,但是一些反馈将非常受欢迎。

我的高度纹理目前是257 x 257灰度图像(高度值是为了可见度而缩放):

enter image description here

我的想法是图像的每个像素代表256 x 256网格中的网格坐标(因此,为什么有257 x 257高度)。这意味着坐标(i,j)处的法线由(i,j),(i,j + 1),(i + 1,j)和(i + 1,j + 1)处的高度确定。 )(分别称为A,B,C和D)。

因此,考虑到A,B,C和D的3D坐标,它是否有意义:

  1. 将四个分成两个三角形:ABC和BCD
  2. 通过叉积计算这两个面的法线
  3. 分为两个三角形:ACD和ABD
  4. 计算这两个面的法线
  5. 平均四个法线
  6. ...还是有一种我更容易丢弃的方法?

4 个答案:

答案 0 :(得分:41)

来自我的水面渲染着色器的示例GLSL代码:

#version 130
uniform sampler2D unit_wave
noperspective in vec2 tex_coord;
const vec2 size = vec2(2.0,0.0);
const ivec3 off = ivec3(-1,0,1);

    vec4 wave = texture(unit_wave, tex_coord);
    float s11 = wave.x;
    float s01 = textureOffset(unit_wave, tex_coord, off.xy).x;
    float s21 = textureOffset(unit_wave, tex_coord, off.zy).x;
    float s10 = textureOffset(unit_wave, tex_coord, off.yx).x;
    float s12 = textureOffset(unit_wave, tex_coord, off.yz).x;
    vec3 va = normalize(vec3(size.xy,s21-s01));
    vec3 vb = normalize(vec3(size.yx,s12-s10));
    vec4 bump = vec4( cross(va,vb), s11 );

结果是凹凸矢量:xyz = normal,a = height

答案 1 :(得分:13)

  

我的想法是图像的每个像素代表256 x 256网格中的网格坐标(因此,为什么有257 x 257高度)。这意味着坐标(i,j)处的法线由(i,j),(i,j + 1),(i + 1,j)和(i + 1,j + 1)处的高度确定。 )(分别称为A,B,C和D)。

没有。图像的每个像素表示网格的顶点,因此直观地,从对称性来看,其法线由相邻像素(i-1,j),(i + 1,j),(i,j-1)的高度确定。 ,(i,j + 1)。

给定函数f:ℝ 2 →ℝ描述ℝ 3 中的表面,(x,y)处的单位法则由

给出

v =( - ∂f/∂x,-∂f/∂y,1)和n = v / | v |。

可以证明,两个样本对∂f/∂x的最佳近似值归档为:

∂f/∂x(x,y)=(f(x +ε,y) - f(x-ε,y))/(2ε)

要获得更好的近似值,您需要使用至少四个点,因此添加第三个点(即(x,y))不会改善结果。

您的hightmap是常规网格上某些函数f的示例。取ε= 1,你得到:

2v =(f(x-1,y) - f(x + 1,y),f(x,y-1) - f(x,y + 1),2)

答案 2 :(得分:10)

一种常见的方法是使用Sobel滤镜在每个方向上进行加权/平滑导数。

首先对每个纹素周围的3x3高度区域进行采样(此处[4]是我们想要法线的像素)。

[6][7][8]
[3][4][5]
[0][1][2]

然后,

//float s[9] contains above samples
vec3 n;
n.x = scale * -(s[2]-s[0]+2*(s[5]-s[3])+s[8]-s[6]);
n.y = scale * -(s[6]-s[0]+2*(s[7]-s[1])+s[8]-s[2]);
n.z = 1.0;
n = normalize(n);

可以调整scale以匹配相对于其大小的高度图真实世界深度。

答案 3 :(得分:9)

如果您将每个像素视为顶点而不是面,则可以生成简单的三角形网格。

+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+

每个顶点的x和y坐标对应于地图中像素的x和y。 z坐标基于该位置的地图中的值。三角形可以通过它们在网格中的位置显式或隐式生成。

您需要的是每个顶点的正常

顶点法线可以通过对在该点遇到的每个三角形的 surface 法线的面积加权平均值进行计算。

如果你有一个顶点为v0v1v2的三角形,那么你可以使用一个矢量叉积(两个矢量位于三角形的两边) )计算法线方向的矢量,并按比例缩放三角形区域。

Vector3 contribution = Cross(v1 - v0, v2 - v1);

不在边缘的每个顶点将由六个三角形共享。你可以循环遍历这些三角形,总结contribution s,然后规范化矢量和。

注意:您必须以一致的方式计算交叉积,以确保法线指向同一方向。始终以相同的顺序(顺时针或逆时针)选择两侧。如果你把它们中的一些混合在一起,那些贡献将指向相反的方向。

对于边缘上的顶点,最终会出现一个较短的循环和许多特殊情况。围绕伪顶点网格创建边框可能更容易,然后计算内部顶点的法线并丢弃假边框。

for each interior vertex V {
  Vector3 sum(0.0, 0.0, 0.0);
  for each of the six triangles T that share V {
    const Vector3 side1 = T.v1 - T.v0;
    const Vector3 side2 = T.v2 - T.v1;
    const Vector3 contribution = Cross(side1, side2);
    sum += contribution;
  }
  sum.Normalize();
  V.normal = sum;
}

如果需要三角形上某个特定点(不是顶点之一)的法线,可以通过点的重心坐标称量三个顶点的法线进行插值。这就是图形光栅化器如何处理着色的法线。它允许三角形网格看起来像光滑的曲面,而不是一堆相邻的扁平三角形。

提示:对于第一次测试,请使用完全平坦的网格,并确保所有计算的法线都指向正确。