C++ 从高度图生成法线贴图?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5281261/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generating a normal map from a height map?
提问by Dawson
I'm working on procedurally generating patches of dirt using randomized fractals for a video game. I've already generated a height map using the midpoint displacement algorithm and saved it to a texture. I have some ideas for how to turn that into a texture of normals, but some feedback would be much appreciated.
我正在使用随机分形为视频游戏程序生成污垢补丁。我已经使用中点置换算法生成了高度图并将其保存到纹理中。我有一些关于如何将其转换为法线纹理的想法,但非常感谢一些反馈。
My height texture is currently a 257 x 257 gray-scale image (height values are scaled for visibility purposes):
我的高度纹理目前是一个 257 x 257 的灰度图像(高度值是为了可见性而缩放的):
My thinking is that each pixel of the image represents a lattice coordinate in a 256 x 256 grid (hence, why there are 257 x 257 heights). That would mean that the normal at coordinate (i, j) is determined by the heights at (i, j), (i, j + 1), (i + 1, j), and (i + 1, j + 1) (call those A, B, C, and D, respectively).
我的想法是图像的每个像素代表一个 256 x 256 网格中的点阵坐标(因此,为什么有 257 x 257 的高度)。这意味着坐标 (i, j) 处的法线由 (i, j)、(i, j + 1)、(i + 1, j) 和 (i + 1, j + 1) 处的高度决定)(分别称为 A、B、C 和 D)。
So given the 3D coordinates of A, B, C, and D, would it make sense to:
因此,鉴于 A、B、C 和 D 的 3D 坐标,是否有意义:
- split the four into two triangles: ABC and BCD
- calculate the normals of those two faces via cross product
- split into two triangles: ACD and ABD
- calculate the normals of those two faces
- average the four normals
- 将四个分成两个三角形:ABC 和 BCD
- 通过叉积计算这两个面的法线
- 分成两个三角形:ACD 和 ABD
- 计算这两个面的法线
- 平均四个法线
...or is there a much easier method that I'm missing?
...或者有没有更简单的方法我错过了?
回答by kvark
Example GLSL code from my water surface rendering shader:
来自我的水面渲染着色器的示例 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 );
The result is a bump vector: xyz=normal, a=height
结果是一个凹凸向量:xyz=normal, a=height
回答by ybungalobill
My thinking is that each pixel of the image represents a lattice coordinate in a 256 x 256 grid (hence, why there are 257 x 257 heights). That would mean that the normal at coordinate (i, j) is determined by the heights at (i, j), (i, j + 1), (i + 1, j), and (i + 1, j + 1) (call those A, B, C, and D, respectively).
我的想法是图像的每个像素代表一个 256 x 256 网格中的点阵坐标(因此,为什么有 257 x 257 的高度)。这意味着坐标 (i, j) 处的法线由 (i, j)、(i, j + 1)、(i + 1, j) 和 (i + 1, j + 1) 处的高度决定)(分别称为 A、B、C 和 D)。
No. Each pixel of the image represents a vertex of the grid, so intuitively, from symmetry, its normal is determined by heights of neighboring pixels (i-1,j), (i+1,j), (i,j-1), (i,j+1).
不。图像的每个像素代表网格的一个顶点,所以直观上,从对称性来看,它的法线是由相邻像素的高度决定的 (i-1,j), (i+1,j), (i,j- 1), (i,j+1)。
Given a function f : ?2→ ? that describes a surface in ?3, a unit normal at (x,y) is given by
给定一个函数 f : ? 2→ ? 它描述了一个表面?3, (x,y) 处的单位法线由下式给出
v = (??f/?x, ??f/?y, 1)?and?n = v/|v|.
v = (??f/?x, ??f/?y, 1)?and?n = v/|v|。
It can be proven that the best approximation to ?f/?x by two samples is archived by:
可以证明,两个样本对 ?f/?x 的最佳近似是通过以下方式存档的:
?f/?x(x,y) = (f(x+ε,y) ? f(x?ε,y))/(2ε)
?f/?x(x,y) = (f(x+ε,y) ? f(x?ε,y))/(2ε)
To get a better approximation you need to use at least four points, thus adding a third point (i.e. (x,y)) doesn't improve the result.
要获得更好的近似值,您至少需要使用四个点,因此添加第三个点(即 (x,y))不会改善结果。
Your hightmap is a sampling of some function f on a regular grid. Taking ε=1 you get:
您的 hightmap 是常规网格上某个函数 f 的样本。取 ε=1 你得到:
2v = (f(x?1,y) ? f(x+1,y), f(x,y?1) ? f(x,y+1), 2)
2v = (f(x?1,y) ? f(x+1,y), f(x,y?1) ? f(x,y+1), 2)
回答by jozxyqk
A common method is using a Sobelfilter for a weighted/smooth derivative in each direction.
一种常见的方法是使用Sobel滤波器在每个方向上进行加权/平滑导数。
Start by sampling a 3x3 area of heights around each texel (here, [4]
is the pixel we want the normal for).
首先对每个纹素周围的 3x3 高度区域进行采样(这里[4]
是我们想要法线的像素)。
[6][7][8]
[3][4][5]
[0][1][2]
Then,
然后,
//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);
Where scale
can be adjusted to match the heightmap real world depth relative to its size.
scale
可以调整在哪里以匹配高度图相对于其大小的真实世界深度。
回答by Adrian McCarthy
If you think of each pixel as a vertex rather than a face, you can generate a simple triangular mesh.
如果将每个像素视为顶点而不是面,则可以生成简单的三角形网格。
+--+--+
|\ |\ |
| \| \|
+--+--+
|\ |\ |
| \| \|
+--+--+
Each vertex has an x and y coordinate corresponding to the x and y of the pixel in the map. The z coordinate is based on the value in the map at that location. Triangles can be generated explicitly or implicitly by their position in the grid.
每个顶点都有一个 x 和 y 坐标,对应于地图中像素的 x 和 y。z 坐标基于该位置的地图中的值。三角形可以通过它们在网格中的位置显式或隐式生成。
What you need is the normal at each vertex.
您需要的是每个顶点的法线。
A vertexnormal can be computed by taking an area-weighted average of the surfacenormals for each of the triangles that meet at that point.
甲顶点正常可以通过采取的一个区域-加权平均来计算表面在该点的法线为每个三角形的,指出了满足。
If you have a triangle with vertices v0
, v1
, v2
, then you can use a vector cross product (of two vectors that lie on two of the sides of the triangle) to compute a vector in the direction of the normal and scaled proportionally to the area of the triangle.
如果您有一个顶点为v0
, v1
,的三角形v2
,那么您可以使用矢量叉积(位于三角形两条边上的两个矢量的)来计算法线方向上的矢量,并与面积成比例地缩放三角形。
Vector3 contribution = Cross(v1 - v0, v2 - v1);
Each of your vertices that aren't on the edge will be shared by six triangles. You can loop through those triangles, summing up the contribution
s, and then normalize the vector sum.
不在边上的每个顶点都将由六个三角形共享。您可以遍历这些三角形,对contribution
s求和,然后对向量总和进行归一化。
Note:You have to compute the cross products in a consistent way to make sure the normals are all pointing in the same direction. Always pick two sides in the same order (clockwise or counterclockwise). If you mix some of them up, those contributions will be pointing in the opposite direction.
注意:您必须以一致的方式计算叉积,以确保法线都指向同一方向。始终以相同的顺序(顺时针或逆时针)选择两侧。如果您将其中的一些混合在一起,这些贡献将指向相反的方向。
For vertices on the edge, you end up with a shorter loop and a lot of special cases. It's probably easier to create a border around your grid of fake vertices and then compute the normals for the interior ones and discard the fake borders.
对于边缘上的顶点,您最终会得到更短的循环和许多特殊情况。在假顶点网格周围创建边界可能更容易,然后计算内部法线并丢弃假边界。
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;
}
If you need the normal at a particular point on a triangle (other than one of the vertices), you can interpolate by weighing the normals of the three vertices by the barycentric coordinates of your point. This is how graphics rasterizers treat the normal for shading. It allows a triangle mesh to appear like smooth, curved surface rather than a bunch of adjacent flat triangles.
如果您需要三角形上特定点的法线(顶点之一除外),您可以通过点的重心坐标对三个顶点的法线进行加权来进行插值。这就是图形光栅化器处理着色法线的方式。它允许三角形网格看起来像光滑的曲面,而不是一堆相邻的平面三角形。
Tip:For your first test, use a perfectly flat grid and make sure all of the computed normals are pointing straight up.
提示:对于您的第一次测试,请使用完全平坦的网格并确保所有计算出的法线都指向正上方。