我的高度纹理目前是一个257x257的灰度图像(高度值被缩放以用于可视化):
![enter image description here](https://istack.dev59.com/bPuyw.webp)
因此,基于A、B、C和D的3D坐标,是否有意义:
1. 将四个点分成两个三角形:ABC和BCD 2. 通过叉积计算这两个面的法线 3. 将其分成两个三角形:ACD和ABD 4. 计算这两个面的法线 5. 对这四个法线取平均
还是我遗漏了更简单的方法?
这是我水面渲染着色器中的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=法线,a=高度。
`vec3 va = normalize(vec3(size.x, s21-s01, size.y));
vec3 vb = normalize(vec3(size.y, s12-s10, -size.x));`
虽然交换Y和Z并不是什么大问题,但我认为有趣的是,我不得不减去s21-s01
而不是示例中所示的s21-s11
。我还不得不否定vb
中的size.x
。 - sinisterchipmunkva
和vb
必须使用2.0, 0.0
进行计算。为什么我们需要恰好是数字2
和0
,从而得到向量(2, 0, s21-s01)
和(0, 2, s12-s10)
。有人能用数学的方式解释一下吗? - j00hi我认为图像的每个像素表示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)的高度确定。
给定一个描述实数3中曲面的函数f:ℝ2 → ℝ,(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))不会改善结果。
您的高度图是正则网格上某个函数f的采样。取ε=1,您会得到:
2v = (f(x−1,y) − f(x+1,y), f(x,y−1) − f(x,y+1), 2)
将其转化为代码的形式如下:
// sample the height map:
float fx0 = f(x-1,y), fx1 = f(x+1,y);
float fy0 = f(x,y-1), fy1 = f(x,y+1);
// the spacing of the grid in same units as the height map
float eps = ... ;
// plug into the formulae above:
vec3 n = normalize(vec3((fx0 - fx1)/(2*eps), (fy0 - fy1)/(2*eps), 1));
eps
是什么? - Mike 'Pomax' Kamermanseps
,然后顶点沿着Z轴根据高度图的要求进行位移。这通常是在简单的平坦世界中(如OP中)的常用方法。 - Yakov Galka一种常见的方法是使用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
可以调整以匹配高度图在其大小相对于实际世界深度方面的实际深度。
const float d = 2.0
并将其替换为计算中的2,然后它就完美地工作了。 - Ashn * 0.5 + 0.5
。 - jozxyqk+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+
Vector3 contribution = Cross(v1 - v0, v2 - v1);
每个不在边缘上的顶点将被六个三角形共享。您可以循环遍历这些三角形,总结各自的contribution
,然后规范化向量和。
注意: 您必须以一致的方式计算叉乘,以确保法线都指向同一个方向。始终按相同顺序(顺时针或逆时针)选择两个侧面。如果混淆其中一些,则这些贡献将指向相反的方向。
对于边缘上的顶点,您最终得到一个较短的循环和许多特殊情况。创建围绕虚拟顶点网格的边界,然后计算内部顶点的法线并丢弃虚拟边界可能更容易。
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;
}
如果您需要在三角形的特定点(除了顶点之一)处的法线,您可以通过按照该点的重心坐标加权三个顶点的法线来进行插值。这就是图形光栅化器用于着色的法线处理方式。它使得三角网格看起来像平滑的曲面,而不是一堆相邻的平面三角形。
提示: 对于您的第一个测试,请使用完全平坦的网格,并确保所有计算出的法线都朝上。