Программируемые OpenGL точечные светильники конвейера

Поскольку встроенные юниформы, такие как gl_LightSource, теперь помечены как устаревшие в последних версиях спецификации OpenGL, в настоящее время я реализую базовую систему освещения (сейчас точечные источники света), которая получает всю информацию о свете и материалах через пользовательские юниформ-переменные.

Я реализовал затухание света и зеркальные блики для точечного источника света, и, кажется, он работает хорошо, за исключением сбоя положения: я вручную перемещаю источник света, изменяя его положение по оси X. Однако источник света (судя по свету, который он отбрасывает на квадратную плоскость под ним) движется не по оси X, а скорее по диагонали, по обеим осям X и Z (возможно, и по оси Y, хотя это не так). полностью ошибка позиционирования).

Вот скриншот того, как выглядит искажение (свет на -35, 5, 0, Сюзанна на 0, 2, 0: Это явно искажено:

Это выглядит нормально, когда свет находится на 0, 5, 0: Это выглядит нормально

Согласно спецификации OpenGL, все расчеты освещения по умолчанию происходят в координатах глаза, что я и пытаюсь эмулировать здесь (отсюда и умножение положения источника света на vMatrix). Я использую только матрицу вида, так как применение преобразования модели пакета вершин, визуализируемого к источнику света, на самом деле не имеет смысла.

Если это имеет значение, все нормали плоскости направлены вверх — 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:

Я обновил код шейдера до рабочей версии. Теперь все хорошо, и я надеюсь, что это поможет любому будущему пользователю, читающему это, поскольку на сегодняшний день я еще не видел ни одного учебника по gsll, который реализует источники света без абсолютно фиксированной функциональности и секретных неявных преобразований (таких как gl_LightSource[i].* и неявные преобразования для глаза космос).

Мой код находится под лицензией BSD с двумя пунктами, и можно найти на GitHub!


person Andrei Bârsan    schedule 01.01.2013    source источник
comment
Может быть, это потому, что вы делаете молнию на основе вершин, качество которой зависит от количества вершин. Сколько у вас вершин? Равномерно ли они распределены? Я бы предложил затенение Фонга в шейдере фрагментов. Также не уверен, что вам нужно это сделать: vec3 vPosition3 = vPosition4.xyz / vPosition4.w;   -  person Michael IV    schedule 02.01.2013
comment
Спасибо за ваш отзыв, но это освещение фонга — все вычисления света выполняются во фрагментном шейдере. Плоскость пола — это всего лишь один четырехугольник — если бы это было вершинное освещение, никакая конкретная форма света не была бы видна в проекции на пол.   -  person Andrei Bârsan    schedule 02.01.2013


Ответы (2)


Недавно у меня была похожая проблема, когда освещение работало несколько неправильно при использовании больших полигонов. Проблема заключалась в нормализации вектора глаза в вершинном шейдере, так как интерполяция нормализованных значений дает неправильные результаты.

Изменять

vVaryingLightDir = normalize( tLightPos - vPosition3 );

to

vVaryingLightDir = tLightPos - vPosition3;

в вашем вершинном шейдере. Вы можете сохранить нормализацию во фрагментном шейдере.

person msell    schedule 03.01.2013
comment
Спасибо! Теперь это работает отлично! Но это должно означать, что примеры шейдеров в OpenGL SuperBible 5th ed. не правы. :( (стр. 275 - они выполняют нормализацию в вершинном шейдере) Но математически ваше объяснение теперь имеет большой смысл - нет смысла делать эту нормализацию в вершинном шейдере! Еще раз спасибо! - person Andrei Bârsan; 03.01.2013

Просто потому, что я заметил:

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

вы просто исключаете однородную координату здесь, не разделяя ее сначала. Это вызовет некоторые проблемы.

person myAces    schedule 03.01.2013