iPad GLSL。在片段着色器中,如何获取表面(而不是顶点)法线?

4

在片段着色器中是否有可能访问表面法线 - 与片段所在平面相关联的法线?或者这可以在顶点着色器中完成吗?

当我们进入着色器管道时,所有关于相关几何形状的信息是否都丢失了,还是有一些巧妙的方法可以在顶点或片段着色器中恢复这些信息?

提前致谢。

祝好,
道格
Twitter: @dugla

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起)。 例如,如果您绘制三角形,则可以在几何着色器中获得所有三个点。 三个点定义了一个平面,可以从中计算出法向量。 只需注意点的排列是顺时针还是逆时针。

三角形的法向量是由三角形的角点定义的两个向量的归一化叉积。 以下是逆时针三角形的示例:

顶点着色器

#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;
}

Geometry shader

#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()
{
    // ...
}

3
您可以通过使用“varying”(在较新的OpenGL中只是in/out)变量来从顶点法线插值得到每个像素的法线。但不要忘记归一化这个法线!插值法线的长度不能再是1了。这些法线在锐利的边缘上也会产生不良结果。
如果您想使用更高分辨率的自定义法线,常用技术是法线贴图。您可以为对象创建带有烘焙法线的纹理。然后,您可以使用纹理查找在片段纹理中访问法线。

好的观点!我忘了在片段着色器中提到需要重新规范化法线。+1 - Incredulous Monk

0

如果你将顶点法线传递到片段着色器中作为“varying”,那么你将得到一个插值的片段法线。

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

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

  1. 将组成三角形的三个点称为P1、P2和P3。
  2. 计算V1,从P1到P2的向量。
  3. 计算V2,从P1到P3的向量。
  4. 计算V1和V2的叉积。

这将给出三角形平面的法线。 V2应该在V1的“左侧”,否则您的法线将指向“内部”而不是“外部”。有关详细信息,请参见维基百科关于叉积的文章

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

我能想到的唯一可能有用的方法是使用几何着色器,因为它可以获取三角形的所有三个顶点。但是我没有使用过几何着色器。

2
我要找的是插值所在三角形的法线。每个细分曲面的三角形都位于一个平面上。我需要的是该平面的法线。 - dugla
这在我的情况下不起作用,因为我有共享顶点(顶点数组)。如果我一次向GPU发送三角形,我可以简单地将平面系数附加到每个顶点上(冗余,我知道),然后就可以了。但是对于一个顶点数组,着色器(无论是顶点还是片段)如何区分共享一个顶点的三角形?平面方程不同,并且我不知道如何向着色器发出信号以使用哪些平面方程系数。 - dugla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接