着色器中的 OpenGL 法线变换

Posted

技术标签:

【中文标题】着色器中的 OpenGL 法线变换【英文标题】:OpenGL Normal transformation in shaders 【发布时间】:2013-06-26 16:00:03 【问题描述】:

我正在尝试在 LWJGL 中创建一些基本的光照着色器。一切似乎都在工作,只是当我改变相机的旋转时,灯光会发生变化。我认为这是因为当我旋转相机时,法线的旋转也被弄乱了。

这是我原来的顶点着色器:

uniform vec3 lightDir;
varying vec3 normal;

void main()
       
    normal = gl_NormalMatrix*gl_Normal;
    gl_Position = ftransform();

我的片段着色器:

varying vec3 normal;

void main()
    vec3 color = vec3(1,1,1);
    vec3 lightDir = vec3(1,1,0);
    float inten = 1;
    color = color*dot(normal, lightDir)*inten;
    gl_FragColor = vec4(color,1);


要转换我使用的相机:

public static void applyTranslations() 
    glPushAttrib(GL_TRANSFORM_BIT);
    glMatrixMode(GL_MODELVIEW);
    glRotatef(pitch, 1, 0, 0);
    glRotatef(yaw, 0, 1, 0);
    glRotatef(roll, 0, 0, 1);
    glTranslatef(-x, -y, -z);
    glPopAttrib();

我意识到这种变换相机的方法实际上可能会在相机保持静止时改变模型,这会弄乱法线,所以我尝试输入一个包含模型旋转的统一矩阵来变换法线,但是这似乎也不起作用。现在整个模型都是黑色的。 (它以前工作过,除了旋转。) 我用它来将更改传递给着色器:

Vector3f scale, rot, trans;
Matrix3f modelMatrix = new Matrix3f();
modelMatrix.setIdentity();
scale = new Vector3f(1,1,1);
rot = new Vector3f(xRot,yRot,zRot);
trans = new Vector3f(x,y,z);

Matrix4f.scale(scale, modelMatrix, modelMatrix);
Matrix4f.translate(trans, modelMatrix, modelMatrix);
Matrix4f.rotate((float) Math.toRadians(rot.z), new Vector3f(0,0,1), modelMatrix, modelMatrix);
Matrix4f.rotate((float) Math.toRadians(rot.y), new Vector3f(0,1,0), modelMatrix, modelMatrix);
Matrix4f.rotate((float) Math.toRadians(rot.x), new Vector3f(1,0,0), modelMatrix, modelMatrix);

FloatBuffer modelBuff = BufferUtils.createFloatBuffer(9);
modelMatrix.store(modelBuff);

int loc = ARBShaderObjects.glGetUniformLocationARB(e.m.shader.programID, "modelMatrix");
ARBShaderObjects.glUniformMatrix4ARB(loc, false, modelBuff);

然后我将顶点着色器更改为:

uniform vec3 lightDir;
uniform modelMatrix;
varying vec3 normal;

void main()
       
    normal = modelMatrix*gl_Normal;
    gl_Position = ftransform();

这是我应该做的创建和传递转换矩阵吗?还有另一种不使用 glRotatef() 来旋转相机的方法吗?

【问题讨论】:

【参考方案1】:

vec3 lightDir = vec3(1,1,0) 是恒定的,而 normal 在每次相机变化时都会发生变化。

为防止这种情况发生,请确保 lightDirnormal 在同一空间中。在转换normal 之前计算dot,或者将lightDir 转换为gl_NormalMatrix 然后计算dot

除了您想要实现的效果之外,片段着色器中还有一个问题:normal 未标准化。这是因为单位向量的线性插值并不总是产生单位向量。

你应该做的是:

color*dot(normalize(normal),  transform_to_same_space(lightDir))*inten;

第二部分的一些小问题:

你声明 modelMatrix 没有类型。

您不能使用 4x4 矩阵转换 vec3。您可以使用 0 填充 gl_Normal,因为它是一个向量,或者将矩阵向下转换为 float3x3

您将Matrix4f.scaleVector3f(1,1,1) 一起使用,并且转换不会影响向量,因此modelMatrix 是正交的。 但是,在一般情况下,您应该使用 modelMatrix 的转置逆转换 gl_Normal

【讨论】:

以上是关于着色器中的 OpenGL 法线变换的主要内容,如果未能解决你的问题,请参考以下文章

从片段着色器中的地形高程数据计算法线

在顶点着色器中定位顶点后如何更新法线?

OpenGL顶点着色器的NormalMatrix?

OpenGL重叠顶点属性

opengl着色器中的多边形镶嵌

现代 OpenGL 投影视图模型转换不起作用