从高度图生成法线贴图?

Daw*_*son 34 c++ opengl graphics

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

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

在此输入图像描述

我的想法是,图像的每个像素表示在256×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. 平均四个法线

......还是有一种我更容易丢失的方法?

kva*_*ark 42

我的水面渲染着色器的示例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 );
Run Code Online (Sandbox Code Playgroud)

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

  • 出于好奇,根据您的经验,从准备好的法线贴图计算凹凸贴图还是从片段着色器中的高度图动态获取法线是否更有效?如果这种方式会更快,我不会感到惊讶,因为您读取的纹理数据比使用法线贴图少 4 倍,而且您甚至不需要切线或副法线作为插值或顶点属性。另一方面,这种方法在片段阶段的 ALU 和 SF 上更重...... (2认同)

ybu*_*ill 17

我的想法是,图像的每个像素表示在256×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)


joz*_*yqk 13

常见的方法是使用Sobel滤波器在每个方向上进行加权/平滑导数.

首先对每个纹素周围的3x3高度区域进行采样(这里[4]是我们想要的正常像素).

[6][7][8]
[3][4][5]
[0][1][2]
Run Code Online (Sandbox Code Playgroud)

然后,

//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);
Run Code Online (Sandbox Code Playgroud)

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

  • @Richard 尝试将结果从 [-1, 1] 范围缩放到 [0, 1],即“n * 0.5 + 0.5”。 (2认同)

Adr*_*thy 9

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

+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+
Run Code Online (Sandbox Code Playgroud)

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

你需要的是每个顶点的法线.

顶点正常可以通过采取的面积加权平均来计算表面法线的每个在该点满足三角形.

如果你有顶点的三角形v0,v1,v2,则可以使用(即位于在两个三角形的边中的两个矢量的)的矢量叉积来计算在正常的方向的矢量和按比例缩放到的区域三角形.

Vector3 contribution = Cross(v1 - v0, v2 - v1);
Run Code Online (Sandbox Code Playgroud)

不在边缘的每个顶点将由六个三角形共享.您可以循环遍历这些三角形,对contributions 进行求和,然后对矢量和进行归一化.

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

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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