4

是否可以从片段着色器中访问表面法线(与片段平面相关的法线)?或者这可以在顶点着色器中完成?

当我们进入着色器管道时,是否会丢失所有相关几何图形的知识,或者是否有一些聪明的方法可以在片段着色器的顶点中恢复该信息?

提前致谢。

干杯,
道格
推特:@dugla

4

3 回答 3

4

表面法线向量可以通过片段着色器中的视图空间位置的偏导来近似计算。偏导数可以通过函数dFdxdFdy得到。为此需要 OpenGL es 3.0 或OES_standard_derivatives扩展:

in vec3 view_position;

void main()
{
    vec3 normalvector = cross(dFdx(view_position), dFdy(view_position));
    nv = normalize(normalvector * sign(normalvector.z));

    .....
}

一般来说,可以在几何着色器中计算表面的法线向量(从 OpenGL ES 3.2 开始)。例如,如果您绘制三角形,您将在几何着色器中获得所有三个点。三个点定义了一个平面,从中可以计算法向量。如果这些点是顺时针或逆时针排列的,你只需要小心。

三角形的法线向量是由三角形的角点定义的 2 个向量的归​​一化叉积。请参阅以下针对逆时针三角形的示例:

顶点着色器

#version 400

layout (location = 0) in vec3 inPos;

out vec3 vertPos;

uniform mat4 u_projectionMat44;
uniform mat4 u_modelViewMat44;

void main()
{
    vec4 viewPos = u_modelViewMat44 * vec4( inPos, 1.0 );
    vertPos = viewPos.xyz;
    gl_Position = u_projectionMat44 * viewPos;
}

几何着色器

#version 400

layout( triangles ) in;
layout( triangle_strip, max_vertices = 3 ) out;

in vec3 vertPos[];

out vec3 geoPos;
out vec3 geoNV;

void main()
{
    vec3 leg1 = vertPos[1] - vertPos[0];
    vec3 leg2 = vertPos[2] - vertPos[0];
    geoNV = normalize( cross( leg1, leg2 ) ); 

    geoPos = vertPos[0];
    EmitVertex();
    geoPos = vertPos[1];
    EmitVertex();
    geoPos = vertPos[2];
    EmitVertex();
    EndPrimitive();
}

片段着色器

#version 400

in vec3 geoPos;
in vec3 geoNV;

void main()
{
    // ...
}

当然,您也可以在镶嵌着色器中计算法线向量(从 OpenGL ES 3.2 开始)。但这仅在您出于其他原因已经需要曲面细分着色器并另外计算人脸的法线向量时才有意义:

顶点着色器

顶点着色器与上面相同。

镶嵌控制着色器

#version 400

layout( vertices=3 ) out;

in  vec3 vertPos[];
out vec3 tctrlPos[];

void main()
{
    tctrlPos[gl_InvocationID] = vertPos[gl_InvocationID];

    if ( gl_InvocationID == 0 )
    {
        gl_TessLevelOuter[0] =  ;
        gl_TessLevelOuter[1] =  ;
        gl_TessLevelOuter[2] =  ;
        gl_TessLevelInner[0] =  ;
    }
}

镶嵌评估着色器

#version 400

layout(triangles, ccw) in;

in vec3 tctrlPos[];

out vec3 tevalPos;
out vec3 tevalNV;

void main()
{
  vec3 leg1 = tctrlPos[1] - tctrlPos[0];
  vec3 leg2 = tctrlPos[2] - tctrlPos[0];
  tevalNV = normalize( cross( leg1, leg2 ) ); 

  tevalPos = tctrlPos[0] * gl_TessCoord.x + tctrlPos[1] * gl_TessCoord.y + tctrlPos[2] * gl_TessCoord.z;
}

碎片着色器

#version 400

in vec3 tevalPos;
in vec3 tevalNV;

void main()
{
    // ...
}
于 2017-07-02T17:36:06.023 回答
3

您可以通过使用“变化”(在较新的 OpenGL 中它只是输入/输出)变量来获得从顶点法线插值的每像素法线。但是不要忘记将这个正常化!插值法线的长度不能再为 1。这些法线也会在锐利的边缘上产生不好的结果。

如果您想使用具有更高分辨率的自定义法线,常用的技术是法线贴图。您可以为对象创建一个带有烘焙法线的纹理。然后,您可以使用纹理查找访问片段纹理中的法线。

于 2010-04-27T12:54:33.177 回答
0

如果您将顶点法线以“变化”的方式传递给片段着色器,那么您将获得插值片段法线。

编辑:您必须在应用程序中计算法线,并将它们作为三角形每个顶点的属性传递到着色器中。

计算三角形法线的常用方法是使用叉积。

  1. 调用构成三角形 P1、P2 和 P3 的三个点。
  2. 计算 V1,即从 P1 到 P2 的向量。
  3. 计算 V2,即从 P1 到 P3 的向量。
  4. 计算 V1 和 V2 的叉积。

这将为您提供三角形平面的法线。V2 应该在 V1 的“左侧”,否则您的法线将指向“in”而不是“out”。有关详细信息,请参阅有关交叉产品的 Wikipedia 文章

进一步编辑:对,我现在明白你的问题了。是的,确实,对于共享顶点,每个顶点实际上不能有多个法线。

我能想到的唯一另一件事是几何着色器可能会有所帮助,因为它通过了三角形的所有三个顶点。不过我对他们没有任何经验。

于 2010-04-09T02:06:52.347 回答