GLSL / GL2.1照明:转换为眼睛空间

时间:2011-11-13 02:44:41

标签: opengl glsl shader

那么,在进行光照计算之前,它有助于将所有内容转换为眼睛空间吗?我在转换部分遇到了麻烦。我已经将法线正确转换,但是当我应用平移时(当对象不在世界坐标系的中心时),灯光仍然完全相同。

我已经确认任何C ++代码都没有问题。 我会粘贴我的着色器......

问题:我想知道我没有改变什么,以及我应该如何改造它。

顶点着色器......

const int MAXLIGHTS = 4;

uniform int lightcount;
uniform vec4 lPositions[MAXLIGHTS];

//V = transformed vertex
//N = transformed normal
//E = eye vector
//L = vector from vertex to light
varying vec3 V, N, E, L[MAXLIGHTS];

void main()
{
    int lcount = lightcount > MAXLIGHTS ? MAXLIGHTS : lightcount;

    V = vec3(gl_ModelViewMatrix * gl_Vertex);

    N = gl_NormalMatrix * gl_Normal;

    E = normalize(-V);

    for(int i = 0; i < lcount; i++)
    {
        L[i] = gl_NormalMatrix * normalize(vec3(lPositions[i] - gl_Vertex));
    }

    gl_FrontColor = gl_Color;
    gl_Position = ftransform();
}

片段着色器......

const int MAXLIGHTS = 4;

uniform int lightcount;
uniform vec4 lDiffuses[MAXLIGHTS];
uniform vec4 lAmbients[MAXLIGHTS];

varying vec3 V, N, E, L[MAXLIGHTS];
uniform bool justcolor;

void main()
{
    if(justcolor)
    {
        gl_FragColor = gl_Color;
        return;
    }
    int lcount = lightcount > MAXLIGHTS ? MAXLIGHTS : lightcount;

    vec4 ambient;
    vec4 diffuse;
    vec4 specular = vec4(0.0, 0.0, 0.0, 0.0);
    vec4 color = vec4(0.0, 0.0, 0.0, 1.0);

    vec3 H;
    float NL;
    float NH;

    for(int i = 0; i < lcount; i++)
    {
        specular = vec4(0.0, 0.0, 0.0, 0.0);

        ambient = lAmbients[i];

        NL = dot(N, L[i]);
        diffuse = lDiffuses[i] * max(NL, 0.0);

        if(NL > 0.0)
        {
            H = normalize(E + L[i]);
            NH = max(0.0, dot(N, H));
            specular = pow(NH, 40.0) * vec4(0.3, 0.3, 0.3, 1.0);
        }

        color += gl_Color * (diffuse + ambient) + specular;
    }

    gl_FragColor = color;
}

2 个答案:

答案 0 :(得分:3)

眼睛空间是场景在通过投影矩阵之前转换到的空间。这就是ftransform()方便地包装的内容(通过这个我的意思是从模型空间到眼睛空间(模型视图变换)到剪辑空间(投影变换)的完整路径)。

模型视图矩阵包含从对象本地到眼睛空间的完整转换。但是,您的灯光不会位于(每个)对象的本地空间中,而是位于世界空间中。所以我们在这里处理两个不同的转换:

  • 对象本地对象 - 这是modelview的 model 部分
  • World to Eye - 这是视图部分

因此从技术上讲,通过将分解的模型视图作为模型提供并查看均匀矩阵输入,可以在顶点着色器中变换光源和对象顶点。然后你只需要通过视图部分变换光位置,然后按模型变换对象的顶点,然后查看部分。但我建议不这样做。着色器单元的计算资源应保留给每个顶点输入具有不同结果的计算。光位置变换不会这样做。

相反,您应该将光线位置预先转换为眼睛空间,然后再将它们传递到着色器(制服)。那怎么做呢。首先,我强烈建议你摆脱旧的OpenGL矩阵操作函数(glRotate,glTranslate,glScale,...和GLU助手,如gluPerspective,......)。没有它们会变得更容易,而且它们已经从以后的OpenGL版本中删除了。

那么怎么做呢。假设你有一个矩阵库,比如GLM,但任何其他都可以工作。要渲染场景,请遵循该方案(类似Python的伪代码)

render_scene:
    projection_matrix = Matrix.Perspective(…)
    view_matrix = Matrix.LookAt(…)

    light_positions = []
    for i, light in enumerate(scene.lights):
        if i > MAX_LIGHTS:
             break
        light_positions.append( view_matrix * light.position )

    glUniform3fv(glGetUniformLocation("lPositions"), light_positions)

    for object in scene.objects:
        obj_modelview = view_matrix * object.transform
        # This is using deprecated functionality
        glMatrixMode(GL_MODELVIEW); glLoadMatrix(obj_modelview)
        object.draw()

如您所见,通过使用view_matrix将灯光“手动”转换为眼睛空间,而不是在GPU上触摸对象的顶点,而是为绘图设置着色器的参数。

答案 1 :(得分:2)

L[i] = gl_NormalMatrix * normalize(vec3(lPositions[i] - gl_Vertex));

如果lPositions位于模型空间,则此代码才有意义。这是非常不可能的。

这种方法的一般方法是在眼睛空间中传递光位置,因此无需对其进行变换。

此外,L和E完全是多余的。通过在片段着色器中计算这些结果,您将获得更准确的结果。计算非常简单和便宜,并且因为你需要在片段着色器中重新规范化它们(你没有这样做),你实际上并没有得到任何东西。

L只是眼睛空间光位置 - 眼睛空间表面位置。 E只是从眼睛到位置的方向,这是眼睛空间表面位置的归一化否定。