如何在 GLSL 中生成法线
How to generate normals in GLSL
我使用单纯形噪声创建了随机生成的地形。我的代码如下所示:
float [ ] vertices = new float [ SIZE * SIZE * 3 ];
short [ ] indices = new short [ ( SIZE - 1 ) * ( SIZE - 1 ) * 2 * 3 ];
for ( int x = 0 , index = 0 ; x < SIZE ; x ++ )
{
for ( int z = 0 ; z < SIZE ; z ++ )
{
vertices [ index ++ ] = x;
vertices [ index ++ ] = ( float ) SimplexNoise.noise ( x , z );
vertices [ index ++ ] = z;
}
}
for ( int i = 0 , index = 0 ; i < ( SIZE - 1 ) ; i ++ )
{
int offset = i * SIZE;
for ( int j = 0 ; j < ( SIZE - 1 ) ; j ++ )
{
indices [ index ++ ] = ( short ) ( j + offset );
indices [ index ++ ] = ( short ) ( j + offset + 1 );
indices [ index ++ ] = ( short ) ( j + offset + 1 + SIZE );
indices [ index ++ ] = ( short ) ( j + offset + 1 + SIZE );
indices [ index ++ ] = ( short ) ( j + offset + SIZE );
indices [ index ++ ] = ( short ) ( j + offset );
}
}
这给了我索引和顶点,但除非我使用 GL_LINES
,否则它看起来像一堆颜色。在我真正知道我在看什么之前,我无法在地形上继续前进,但我知道如何使用法线的唯一方法是将它们加载到模型中并将它们发送到着色器。我不知道如何实际生成它们。我四处搜索并阅读了很多关于获取周围面的法线并将它们归一化和其他一些东西但我不理解其中的大部分内容并且据我所知你只能访问顶点中的一个顶点一次着色器,所以我不确定您将如何处理整个三角形。如您所见,我们将不胜感激。
编辑:
我做了一些研究,现在已经计算出所有三角形的法线:
float [ ] triNormals = new float [ indices.length ];
for ( int i = 0 , index = 0 ; i < indices.length ; )
{
float x , y , z;
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p1 = new Vector3f ( x , y , z );
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p2 = new Vector3f ( x , y , z );
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p3 = new Vector3f ( x , y , z );
Vector3f u = Vector3f.subtract ( p2 , p1 );
Vector3f v = Vector3f.subtract ( p3 , p1 );
Vector3f normal = Vector3f.crossProduct ( u , v );
triNormals [ index ++ ] = normal.x;
triNormals [ index ++ ] = normal.y;
triNormals [ index ++ ] = normal.z;
}
现在我只需要知道如何使用周围三角形的法线来计算顶点的法线。
可以使用Geometry Shader
处理多顶点,计算法线。
或者,预先用CPU计算法线,并在着色器中对法线应用必要的变换。
生成 per-triangle 或面法线,它看起来平坦且具有锐利的边缘是通过归一化两条边的叉积来完成的。正如@neuo 所说,这可以在几何着色器中完成,但听起来你在追求 "smooth" 法线。
平滑法线不一定隐含在顶点几何体中,但一个不错的猜测是基于相邻三角形(的平均值或加权平均值)。除非您有邻接信息(如曲面细分着色器 (?)),否则在渲染时这在 GLSL 中是不可能的。它需要在单独的通道中完成(可能使用变换反馈)并且可能在 CPU.
上同样容易处理
在GL中,每个顶点都有一个法线*。因此,从一个等于您的顶点的新归零数组开始(或者如果您交错,则在顶点之间使用 space )。对于由索引数组 (a, b, c) 形成的每个三角形,使用叉积 (b - a) × (c - a) 找到三角形法线并将其归一化。然后将三角形法线添加到每个三角形顶点的每个顶点法线(即 a、b 和 c)。最后运行通过每一个normal和normalize。完成。
根据网格的不同,这种简单的方法可能效果很好。在三角形非常小或很瘦的情况下,相等的权重会导致奇怪的结果:
解决此问题的一种方法是按面积缩放每个三角形的法线贡献。为此,只需在将叉积结果添加到每个法线之前不要对其进行归一化。
for (int t = 0; t < numIndices / 3; ++t)
{
unsigned int t1 = dataIndices[t*3+0];
unsigned int t2 = dataIndices[t*3+1];
unsigned int t3 = dataIndices[t*3+2];
const vec3f& a = verts[t1];
const vec3f& b = verts[t2];
const vec3f& c = verts[t3];
vec3f u = b - a;
vec3f v = c - a;
vec3f n = u.cross(v);
//if (n.size() > 0.0) n.normalize(); //removes weighting based on triangle area
norms[t1] += n;
norms[t2] += n;
norms[t3] += n;
}
for (int v = 0; v < numVertices; ++v)
norms[v].normalize();
* 要获得面法线,您必须复制顶点,这就是为什么图像的每个顶点位置都使用两条红色法线绘制。您也可以在 in/out
变量上使用关键字 flat
,但这样一切都会变得平淡。
我使用单纯形噪声创建了随机生成的地形。我的代码如下所示:
float [ ] vertices = new float [ SIZE * SIZE * 3 ];
short [ ] indices = new short [ ( SIZE - 1 ) * ( SIZE - 1 ) * 2 * 3 ];
for ( int x = 0 , index = 0 ; x < SIZE ; x ++ )
{
for ( int z = 0 ; z < SIZE ; z ++ )
{
vertices [ index ++ ] = x;
vertices [ index ++ ] = ( float ) SimplexNoise.noise ( x , z );
vertices [ index ++ ] = z;
}
}
for ( int i = 0 , index = 0 ; i < ( SIZE - 1 ) ; i ++ )
{
int offset = i * SIZE;
for ( int j = 0 ; j < ( SIZE - 1 ) ; j ++ )
{
indices [ index ++ ] = ( short ) ( j + offset );
indices [ index ++ ] = ( short ) ( j + offset + 1 );
indices [ index ++ ] = ( short ) ( j + offset + 1 + SIZE );
indices [ index ++ ] = ( short ) ( j + offset + 1 + SIZE );
indices [ index ++ ] = ( short ) ( j + offset + SIZE );
indices [ index ++ ] = ( short ) ( j + offset );
}
}
这给了我索引和顶点,但除非我使用 GL_LINES
,否则它看起来像一堆颜色。在我真正知道我在看什么之前,我无法在地形上继续前进,但我知道如何使用法线的唯一方法是将它们加载到模型中并将它们发送到着色器。我不知道如何实际生成它们。我四处搜索并阅读了很多关于获取周围面的法线并将它们归一化和其他一些东西但我不理解其中的大部分内容并且据我所知你只能访问顶点中的一个顶点一次着色器,所以我不确定您将如何处理整个三角形。如您所见,我们将不胜感激。
编辑:
我做了一些研究,现在已经计算出所有三角形的法线:
float [ ] triNormals = new float [ indices.length ];
for ( int i = 0 , index = 0 ; i < indices.length ; )
{
float x , y , z;
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p1 = new Vector3f ( x , y , z );
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p2 = new Vector3f ( x , y , z );
x = vertices [ ( 3 * indices [ i ] ) ];
y = vertices [ ( 3 * indices [ i ] ) + 1 ];
z = vertices [ ( 3 * indices [ i ++ ] ) + 2 ];
Vector3f p3 = new Vector3f ( x , y , z );
Vector3f u = Vector3f.subtract ( p2 , p1 );
Vector3f v = Vector3f.subtract ( p3 , p1 );
Vector3f normal = Vector3f.crossProduct ( u , v );
triNormals [ index ++ ] = normal.x;
triNormals [ index ++ ] = normal.y;
triNormals [ index ++ ] = normal.z;
}
现在我只需要知道如何使用周围三角形的法线来计算顶点的法线。
可以使用Geometry Shader
处理多顶点,计算法线。
或者,预先用CPU计算法线,并在着色器中对法线应用必要的变换。
生成 per-triangle 或面法线,它看起来平坦且具有锐利的边缘是通过归一化两条边的叉积来完成的。正如@neuo 所说,这可以在几何着色器中完成,但听起来你在追求 "smooth" 法线。
平滑法线不一定隐含在顶点几何体中,但一个不错的猜测是基于相邻三角形(的平均值或加权平均值)。除非您有邻接信息(如曲面细分着色器 (?)),否则在渲染时这在 GLSL 中是不可能的。它需要在单独的通道中完成(可能使用变换反馈)并且可能在 CPU.
上同样容易处理在GL中,每个顶点都有一个法线*。因此,从一个等于您的顶点的新归零数组开始(或者如果您交错,则在顶点之间使用 space )。对于由索引数组 (a, b, c) 形成的每个三角形,使用叉积 (b - a) × (c - a) 找到三角形法线并将其归一化。然后将三角形法线添加到每个三角形顶点的每个顶点法线(即 a、b 和 c)。最后运行通过每一个normal和normalize。完成。
根据网格的不同,这种简单的方法可能效果很好。在三角形非常小或很瘦的情况下,相等的权重会导致奇怪的结果:
解决此问题的一种方法是按面积缩放每个三角形的法线贡献。为此,只需在将叉积结果添加到每个法线之前不要对其进行归一化。
for (int t = 0; t < numIndices / 3; ++t)
{
unsigned int t1 = dataIndices[t*3+0];
unsigned int t2 = dataIndices[t*3+1];
unsigned int t3 = dataIndices[t*3+2];
const vec3f& a = verts[t1];
const vec3f& b = verts[t2];
const vec3f& c = verts[t3];
vec3f u = b - a;
vec3f v = c - a;
vec3f n = u.cross(v);
//if (n.size() > 0.0) n.normalize(); //removes weighting based on triangle area
norms[t1] += n;
norms[t2] += n;
norms[t3] += n;
}
for (int v = 0; v < numVertices; ++v)
norms[v].normalize();
* 要获得面法线,您必须复制顶点,这就是为什么图像的每个顶点位置都使用两条红色法线绘制。您也可以在 in/out
变量上使用关键字 flat
,但这样一切都会变得平淡。