GLSL - 计算表面法线

4

我有一个简单的顶点着色器,用GLSL编写,并想知道是否有人能够帮助我计算表面法线。我正在“升级”一个平面,因此当前的光照模型看起来很奇怪。这是我的当前代码:

varying vec4 oColor;
varying vec3 oEyeNormal;
varying vec4 oEyePosition;

uniform float Amplitude;     // Amplitude of sine wave
uniform float Phase;         // Phase of sine wave
uniform float Frequency;     // Frequency of sine wave

varying float sinValue;

void main()
{
    vec4 thisPos = gl_Vertex;

    thisPos.z = sin( ( thisPos.x + Phase ) * Frequency) * Amplitude;

    // Transform normal and position to eye space (for fragment shader)
    oEyeNormal    = normalize( vec3( gl_NormalMatrix * gl_Normal ) );
    oEyePosition  = gl_ModelViewMatrix * thisPos;       

    // Transform vertex to clip space for fragment shader
    gl_Position   = gl_ModelViewProjectionMatrix * thisPos;

    sinValue = thisPos.z;
}

有没有人有什么想法?

2个回答

7
好的,让我们从微分几何的角度来看待这个问题。您有一个具有参数s和t的参数化曲面:
X(s,t) = ( s, t, A*sin((s+P)*F) )

所以,我们首先计算这个表面的切线,即基于两个参数的偏导数:

Xs(s,t) = ( 1, 0, A*F*cos((s+P)*F) )
Xt(s,t) = ( 0, 1, 0 )

那么我们只需要计算它们的叉积即可得到法向量:

N = Xs x Xt = ( -A*F*cos((s+P)*F), 0, 1 )

为了完全通过分析计算出你的正常值,实际上不需要使用gl_Normal属性:

float angle = (thisPos.x + Phase) * Frequency;
thisPos.z = sin(angle) * Amplitude;
vec3 normal = normalize(vec3(-Amplitude*Frequency*cos(angle), 0.0, 1.0));

// Transform normal and position to eye space (for fragment shader)
oEyeNormal    = normalize( gl_NormalMatrix * normal );
normal 的归一化可能不是必要的(因为我们无论如何都会对变换后的法线进行归一化),但我现在不确定非归一化的法线在存在非均匀缩放时是否会正确工作。当然,如果您希望法线指向负z方向,则需要对其取反。
在空间中沿着曲面的方法并非必要。我们也可以仅考虑x-z平面内的正弦曲线,因为法线的y部分总是为零,只有z依赖于x。因此,我们只需取曲线z=A*sin((x+P)*F)的切线,其斜率为z的导数,即x-z向量(1,A*F*cos((x+P)*F)),则其法线就是(-A*F*cos((x+P)*F), 1)(交换坐标并取反一个),即法线的x和z(未归一化)。好了,没有3D矢量和偏导数,但结果是相同的。

非常感谢!我一会儿就试试,等我启动到Win7分区。我在光照和GLSL方面遇到了很大的问题! - Josh

0
此外,你应该调整你的性能:
oEyeNormal = normalize(vec3(gl_NormalMatrix * gl_Normal));
  1. 由于gl_NormalMatrix是一个3x3矩阵,所以不需要将其转换为vec3。
  2. 在顶点着色器中,不需要对传入的法线进行归一化,因为您没有进行任何基于长度的计算。一些来源称应用程序始终应该对传入的法线进行归一化,以便在顶点着色器中根本不需要这样做。但由于这超出了着色器开发人员的控制范围,因此在计算基于顶点的光照(高洛德)时仍然会对它们进行归一化。

  • 嗯,强制类型转换是不必要的,但也不会影响性能,因为从vec3到vec3的转换应该是一个无操作(GLSL编译器不可能在这里进行复制)。但你说得对,这完全是不必要的。
- Christian Rau
1
这是完全错误的。他没有对传入的法线进行归一化(这确实不是一个好主意,因为应用程序当然应该提供归一化的法线)。但是他对变换后的法线进行了归一化(在乘以gl_NormalMatrix之后),这是必要的,以考虑任何缩放和剪切变换。当然,您知道在表面上插值未归一化的法线会导致错误的每个片段法线。因此,在最后,这里没有太多性能可以调整。 - Christian Rau
最后它甚至没有回答实际问题,可能更适合作为评论。 - Christian Rau
@ChristianRau 我的回答更适合作为评论,因为它与主题无关,我同意这一点。但是你应该仔细再次阅读我的回答。我说除非你在顶点着色器中使用它进行特殊计算(例如在顶点着色器中进行光照),否则没有必要在你的顶点着色器中对其进行归一化! - djmj
2
是的,在顶点着色器中不需要对输入法线进行归一化。但是,OP 也没有这样做。他所做的是对变换后的法线(在乘以 gl_NormalMatrix 后)进行归一化,这是必要的,因为你需要将一个正确归一化的法线放入 varying 中,以便为片段获得正确插值的法线。请仔细再次阅读我的评论。 - Christian Rau

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