OpenGL 可编程管线点光源

标签 opengl 3d glsl lighting

由于内置制服如gl_LightSource现在在最新版本的 OpenGL 规范中被标记为已弃用,我目前正在实现一个基本的照明系统(现在是点光源),它通过自定义统一变量接收所有灯光和 Material 信息。

我已经为点光源实现了光衰减和镜面高光,除了位置故障之外,它似乎运行良好:我手动移动光源,改变其沿 X 轴的位置。然而,光源(根据它转换在其下方方形平面上的光来判断)似乎并没有沿着 X 轴移动,而是在 X 轴和 Z 轴上对角线移动(也可能是 Y 轴,尽管它不是完全是一个定位错误)。

这是扭曲的屏幕截图(光线位于 -35, 5, 0,Suzanne 位于 0, 2, 0: This is obviously distorted :

当光线位于 0, 5, 0 时看起来不错: This looks fine

根据 OpenGL 规范,所有默认的光照计算都在眼睛坐标中进行,这就是我在这里尝试模拟的(因此将光照位置与 vMatrix 相乘)。我仅使用 View 矩阵,因为应用渲染到光线的顶点批处理的模型转换并没有真正意义。

如果重要的话,所有平面的法线都指向上方 - 0, 1, 0。

(注意:我现在解决了这个问题,感谢 msell 和 myAces!以下代码片段是更正后的版本。现在还有一个选项可以将聚光灯参数添加到灯光(d3d 样式的))

这是我在顶点着色器中使用的代码:

#version 330

uniform mat4 mvpMatrix;
uniform mat4 mvMatrix;
uniform mat4 vMatrix;

uniform mat3 normalMatrix;
uniform vec3 vLightPosition;
uniform vec3 spotDirection;
uniform bool useTexture;

uniform bool    fogEnabled;
uniform float   minFogDistance;
uniform float   maxFogDistance;

in vec4 vVertex;
in vec3 vNormal;
in vec2 vTexCoord;

smooth out vec3     vVaryingNormal;
smooth out vec3     vVaryingLightDir;
smooth out vec2     vVaryingTexCoords;
smooth out float    fogFactor;

smooth out vec4     vertPos_ec;
smooth out vec4     lightPos_ec;
smooth out vec3     spotDirection_ec;

void main() {
    // Surface normal in eye coords
    vVaryingNormal = normalMatrix * vNormal;

    vec4 vPosition4 = mvMatrix * vVertex;
    vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

    vec4 tLightPos4 = vMatrix * vec4(vLightPosition, 1.0);
    vec3 tLightPos  = tLightPos4.xyz / tLightPos4.w;

    // Diffuse light
    // Vector to light source (do NOT normalize this!)
    vVaryingLightDir = tLightPos - vPosition3;

    if(useTexture) {
        vVaryingTexCoords = vTexCoord;
    }

    lightPos_ec = vec4(tLightPos, 1.0f);
    vertPos_ec = vec4(vPosition3, 1.0f);

    // Transform the light direction (for spotlights) 
    vec4 spotDirection_ec4 = vec4(spotDirection, 1.0f);
    spotDirection_ec = spotDirection_ec4.xyz / spotDirection_ec4.w; 
    spotDirection_ec = normalMatrix * spotDirection;

    // Projected vertex
    gl_Position = mvpMatrix * vVertex;

    // Fog factor
    if(fogEnabled) {
        float len = length(gl_Position);
        fogFactor = (len - minFogDistance) / (maxFogDistance - minFogDistance);
        fogFactor = clamp(fogFactor, 0, 1);
    }   
}

这是我在片段着色器中使用的代码:

#version 330

uniform vec4 globalAmbient;

// ADS shading model
uniform vec4 lightDiffuse;
uniform vec4 lightSpecular;
uniform float lightTheta;
uniform float lightPhi;
uniform float lightExponent;

uniform int shininess;
uniform vec4 matAmbient;
uniform vec4 matDiffuse;
uniform vec4 matSpecular;

// Cubic attenuation parameters
uniform float constantAt;
uniform float linearAt;
uniform float quadraticAt;
uniform float cubicAt;

// Texture stuff
uniform bool useTexture;
uniform sampler2D colorMap;

// Fog
uniform bool    fogEnabled;
uniform vec4    fogColor;

smooth in vec3  vVaryingNormal;
smooth in vec3  vVaryingLightDir;
smooth in vec2  vVaryingTexCoords;
smooth in float fogFactor;
smooth in vec4  vertPos_ec;
smooth in vec4  lightPos_ec;
smooth in vec3  spotDirection_ec;

out vec4 vFragColor;

// Cubic attenuation function
float att(float d) {
    float den = constantAt + d * linearAt + d * d * quadraticAt + d * d * d * cubicAt;

    if(den == 0.0f) {
        return 1.0f;
    }

    return min(1.0f, 1.0f / den);
}

float computeIntensity(in vec3 nNormal, in vec3 nLightDir) {

    float intensity = max(0.0f, dot(nNormal, nLightDir));
    float cos_outer_cone = lightTheta;
    float cos_inner_cone = lightPhi;
    float cos_inner_minus_outer = cos_inner_cone - cos_outer_cone;

    // If we are a point light
    if(lightTheta > 0.0f) {
        float cos_cur = dot(normalize(spotDirection_ec), -nLightDir);
        // d3d style smooth edge
        float spotEffect = clamp((cos_cur - cos_outer_cone) / 
                                cos_inner_minus_outer, 0.0, 1.0);
        spotEffect = pow(spotEffect, lightExponent);
        intensity *= spotEffect;
    }   

    float attenuation = att( length(lightPos_ec - vertPos_ec) );
    intensity *= attenuation;

    return intensity;
}

/**
 *  Phong per-pixel lighting shading model.
 *  Implements basic texture mapping and fog.
 */
void main() {       
    vec3 ct, cf;
    vec4 texel;
    float at, af;

    if(useTexture) {
        texel = texture2D(colorMap, vVaryingTexCoords); 
    } else {
        texel = vec4(1.0f);
    }

    ct = texel.rgb;
    at = texel.a;

    vec3 nNormal = normalize(vVaryingNormal);
    vec3 nLightDir = normalize(vVaryingLightDir);

    float intensity = computeIntensity(nNormal, nLightDir); 
    cf = matAmbient.rgb * globalAmbient.rgb + intensity * lightDiffuse.rgb * matDiffuse.rgb;    
    af = matAmbient.a * globalAmbient.a + lightDiffuse.a * matDiffuse.a;

    if(intensity > 0.0f) {
        // Specular light
        //  -   added *after* the texture color is multiplied so that
        //      we get a truly shiny result
        vec3 vReflection = normalize(reflect(-nLightDir, nNormal));
        float spec = max(0.0, dot(nNormal, vReflection));
        float fSpec = pow(spec, shininess) * lightSpecular.a;
        cf += intensity * vec3(fSpec) * lightSpecular.rgb * matSpecular.rgb;
    }

    // Color modulation
    vFragColor = vec4(ct * cf, at * af);

    // Add the fog to the mix
    if(fogEnabled) {
        vFragColor = mix(vFragColor, fogColor, fogFactor);
    }
}

什么数学错误可能导致这种扭曲?

编辑1:

我已经更新了着色器代码。现在正在片段着色器中计算衰减,正如它一直应该进行的那样。不过,它目前已被禁用 - 该错误与衰减没有任何关系。当渲染光的衰减因子时(请参阅片段着色器的最后几行),衰减将被计算正确。这意味着灯光位置已正确转换为眼睛坐标,因此它不可能是错误的根源。

片段着色器的最后几行可用于一些(有点黑客但仍然富有洞察力)调试 - 似乎没有正确计算每个片段的光强度,尽管我不知道为什么。

有趣的是,这个错误仅在(非常)大的四边形(例如图像中的地板)上才明显。这在小型型号上并不明显。

编辑2:

我已将着色器代码更新为工作版本。现在一切都很好,我希望它可以帮助任何 future 的用户阅读本文,因为截至今天,我还没有看到任何 glsl 教程实现完全没有固定功能和 secret 隐式变换的灯​​光(例如 gl_LightSource[i].* 和隐式变换)到眼睛空间)。

我的代码已获得 BSD 2 条款许可和 can be found on GitHub 的许可!

最佳答案

我最近遇到了类似的问题,当使用大多边形时,光照工作有些错误。问题在于对顶点着色器中的眼睛向量进行标准化,因为插值标准化值会产生不正确的结果。

改变

vVaryingLightDir = normalize( tLightPos - vPosition3 );

vVaryingLightDir = tLightPos - vPosition3;

在你的顶点着色器中。您可以在片段着色器中保留标准化。

关于OpenGL 可编程管线点光源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14114437/

相关文章:

opengl - 为什么在使用 gluLookAt 之前我需要编写 glLoadIdentity

控制 Mandelbrot 集的放大位置

python - glsl 解析器使用 pyparsing 给出 AttributeErrors

glsl - 从顶点着色器更新信息

opengl - 如何让 texelFetchOffset 环绕?

c++ - 使用多个 Canvas 时,FTGL 纹理字体仅显示黑框

math - 由2个角度定义的3D矢量

3d - CSS3 "Curved Surface"3D 变换/透视帮助

pdf - 如何从 ABAQUS/Viewer 打印 3D pdf?

c++ - 二维角色的 move 仅在一个方向上有效