Monday, December 16, 2013

Sponza рэндэрлэх оролдлого 17, Deferred rendering - lighting pass

deffered rendering ийн дараагийн үе шат болох гэрэлтүүлгийн үйл явцыг програмчлахыг оролдов.

Deffered render-т гэрлийг геометр дүрс ашиглан төлөөлүүлдэг. Жишээ нь чийдэн маягийн point light төрлийн гэрлийг бөмбөрцөг дүрс ашиглана, гар чийдэн маягийн spot light төрлийн гэрлийг конус дүрс ашиглана гэх мэт, харин directional light-ийг яаж дүрсэлж болохыг одоогоор би сайн мэдэхгүй байна.

Lighting pass-д өмнөх Geometry pass-аар гаргаж авсан color(albedo), normal, depth өгөгдлүүдийг агуулсан текстуруудыг shader програмд uniform байдлаар ашиглах боломж гардаг. Эдгээрийг ашиглаж гэрлийн тооцооллыг хийнэ. Гэхдээ ямар аль хэсэгт гэрэлтүүлэг хийх гэдгийг гэрлийн геометр дүрс шийднэ. Манай тохиолдолд гэрлийн үүсгүүрийн хувьд хялбараар нь энгийн point light-ийг хэрэглэж байна. Түрүүн дурдсанчлан үүнд бөмбөрцөг дүрс хэрэг болно. Бөмбөрцөг дүрсийг ямар нэгэн 3D software ашиглаад obj форматруу хөрвүүлж хадгалж авахад тун хялбар. Жишээ нь blender ашиглаж гаргаж авсан байдал.

STE маань Assimp ашиглаж импортлож чадах тул энэ дүрсээ энгийн машин спонза модел уншиж авч байгаа юм шиг л уншиж авахад болно.

    SceneObject pointLightVolume = new SceneObject();
    modelLoader.loadSceneModel("models/sphere.obj", pointLightVolume);


Мөн гэрэлтүүлэгт хэрэглэх зорилгоор гэрлийг дүрслэх класс тодорхойлсон бөгөөд үүгээр объект үүсгэн массив хадгалсан байгаа. Энэ класст гэрлийн тусгалын хамрах радиус, хөдлөх хурд, ямар муж дотор хөдөлгөөн хийх гэх мэт шинж чанаруудыг нь тусгасан бөгөөд update хийгдэх тусмаа гэрэл санамсаргүй цэг сонгоод тэр цэгрүүгээ хөдлөнө.

#ifndef POINTLIGHT_HPP
#define POINTLIGHT_HPP

#include "AbstractLight.hpp"
#include <glm/glm.hpp>

class PointLight : public AbstractLight {
public:
    PointLight();
    PointLight(glm::vec3 position, glm::vec4 color, float radius);
    ~PointLight();
    
    glm::vec3 getPosition();
    void setPosition(glm::vec3 position);
    
    glm::vec4 getColor();
    void setColor(glm::vec4 color);
    
    float getRadius();
    void setRadius(float radius);
    
    float getSpeed();
    void setSpeed(float speed);
    
    void setMinBoundary(glm::vec3 p);
    void setMaxBoundary(glm::vec3 p);
    
    void update(float dt);
    
private:
    
    glm::vec3 position;
    glm::vec4 color;
    float radius;
    float moveSpeed;
    glm::vec3 randomCheckPoint;
    glm::vec3 minBoundaries;
    glm::vec3 maxBoundaries;
};

#endif /* POINTLIGHT_HPP */

Энэ классаар гэрэл хадгалах массивт хүссэн тоогоороо объект үүсгэн нэмж болно.
for (int i=0; i<50; i++)
        sceneLights.push_back(new PointLight());

Одоо гэрлийн тооцооллыг хийхэд хэрэг болох текстуруудыг тодорхойлох хэрэгтэй. Үүнд :
- diffuse гэрэлтүүлгийн мэдээллийг хадгалах зориулалттай emissive texture
- specular гэрэлтүүлгийн мэдээллийг хадгалах зориулалттай specular texture

Эдгээрийг мөн шинээр гэрэлтүүлгэнд зориулсан frame buffer object үүсгээд attachment байдлаар ашиглан гэрэлтүүлгийн үр дүнг гаргаж авна.

    glGenTextures(1, &emissiveTexture);
    glBindTexture(GL_TEXTURE_2D, emissiveTexture);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    glGenTextures(1, &specularTexture);
    glBindTexture(GL_TEXTURE_2D, specularTexture);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, windowWidth, windowHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

    glGenFramebuffers(1, &lightingFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, lightingFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, emissiveTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, specularTexture, 0);

    glDrawBuffers(2, buffers);

Харин одоо гэрэлтүүлгийн shader ажиллуулах командуудыг үндсэн програмаас дуудаж ажиллуулах хэрэгтэй. Үүний тулд бүх гэрлээр давтаад гэрэлтүүлэг хийх зориулалттай shader програмыг дуудаж ажиллуулан үр дүнг emissiveTexture, specularTexture хоёр дээр хадгалж авна. Гэрэлтүүлгийн shader маань uniform хувьсагчидаараа albedo, normal, depth текстуруудыг хэрэглэнэ. Ингэснээр гэрэлтүүлэгийн алгоритм ажиллуулахад шаардлагатай гадаргуун мэдээллүүдээр shader програм хангагдаж өгч байна гэсэн үг. Shader маань гэрлийг төлөөлөх бөмбөрцөг дүрсийн оройнуудыг vertex shader-тээ оролтын утга болгон авч хэрэглэнэ. Ийм болохоор зөвхөн тэр бөмбөрцөгийн хамрах хүрээн дэх fragment-үүд дээр гэрэлтүүлэг ажиллана.
    glBindFramebuffer(GL_FRAMEBUFFER, lightingFBO);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glBlendFunc(GL_ONE, GL_ONE);
    for (AbstractLight* light : sceneLights) {
        glm::mat4 lightModelMatrix = glm::mat4(1.0);
        lightModelMatrix = glm::translate(lightModelMatrix, light->getPosition());
        lightModelMatrix = glm::scale(lightModelMatrix, glm::vec3(light->getRadius(), light->getRadius(), light->getRadius()));
        pointLightVolume->setModelMatrix(lightModelMatrix);
        float distance = glm::length(light->getPosition() - camera->getPosition());
        if (distance < light->getRadius()) {
            glCullFace(GL_FRONT);
        } else {
            glCullFace(GL_BACK);
        }
        glUseProgram(lightingProgramID);
        glm::mat4 inverseProjectionMatrix = glm::inverse(projectionMatrix);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, colourTexture);
        glUniform1i(glGetUniformLocation(lightingProgramID, "colour_texture"), 0);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, normalTexture);
        glUniform1i(glGetUniformLocation(lightingProgramID, "normal_texture"), 1);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, depthTexture);
        glUniform1i(glGetUniformLocation(lightingProgramID, "depth_texture"), 2);

        glUniform2f(glGetUniformLocation(lightingProgramID, "pixelSize"), 1.0f / windowWidth, 1.0f / windowHeight);
        glUniform1i(glGetUniformLocation(lightingProgramID, "lightRadius"), light->getRadius());
        glUniform3f(glGetUniformLocation(lightingProgramID, "lightPos"), light->getPosition().x, light->getPosition().y, light->getPosition().z);
        glUniform4f(glGetUniformLocation(lightingProgramID, "lightColor"), light->getColor().x, light->getColor().y, light->getColor().z, light->getColor().w);
        glUniform3f(glGetUniformLocation(lightingProgramID, "cameraPos"), camera->getPosition().x, camera->getPosition().y, camera->getPosition().z);
        glUniformMatrix4fv(glGetUniformLocation(lightingProgramID, "inverseProjection"), 1, GL_FALSE, glm::value_ptr(inverseProjectionMatrix));
        glUniform2f(glGetUniformLocation(lightingProgramID, "screen_dimension"), windowWidth, windowHeight);
        glm::mat4 modelMatrix = pointLightVolume->getModelMatrix();
        mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
        normalMatrix = glm::inverseTranspose(glm::mat3(viewMatrix * modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(lightingProgramID, "mvpMatrix"), 1, GL_FALSE, glm::value_ptr(mvpMatrix));
        glUniformMatrix4fv(glGetUniformLocation(lightingProgramID, "modelMatrix"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(lightingProgramID, "viewMatrix"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
        glUniformMatrix3fv(glGetUniformLocation(lightingProgramID, "normalMatrix"), 1, GL_FALSE, glm::value_ptr(normalMatrix));
        pointLightVolume->render(lightingProgramID);
        glUseProgram(0);
    }
    glCullFace(GL_BACK);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glClearColor(0.0f, 0.5f, 1.0f, 0.0f);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);


Vertex Shader
#version 330
layout(location=0) in vec4 in_position; // model-space
layout(location=1) in vec3 in_normal;   // model-space
layout(location=2) in vec2 in_texcoord;
layout(location=3) in vec3 in_tangent;
layout(location=4) in vec3 in_bitangent;

out vec2 vTexcoord;
out vec3 vPosition; // view-space
out vec3 vNormal;   // view-space
out vec3 vEyeDir;   // view-space
out vec4 vLightPosition;

uniform mat4 mvpMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;

uniform vec3 lightPos;

void main(void)
{
    gl_Position = mvpMatrix * in_position;
    
    vTexcoord = in_texcoord;
    
    vPosition = (viewMatrix * modelMatrix * in_position).xyz;
    
    vNormal = (normalMatrix * in_normal).xyz;
    
    vec3 tbnNormal    = normalize(vNormal);
    vec3 tbnTangent   = normalize(normalMatrix * in_tangent);
    //vec3 tbnBitangent = normalize(normalMatrix * in_bitangent);
    vec3 tbnBitangent  = normalize(cross(tbnNormal, tbnTangent));
    mat3 tbnMatrix = transpose(mat3(tbnTangent, tbnBitangent, tbnNormal));
    
    vec3 posInEyespace = (viewMatrix * modelMatrix * in_position).xyz;
    vEyeDir = vec3(0,0,0) - posInEyespace;

    vLightPosition = viewMatrix * vec4(lightPos, 0.0);

}


Fragment Shader
#version 330

uniform float     material_shininess; 
uniform vec3      material_diffuse_color;
uniform vec3      material_ambient_color;
uniform vec3      material_specular_color;
uniform vec3      meterial_emissive_color;

uniform sampler2D colour_texture;
uniform sampler2D normal_texture;
uniform sampler2D depth_texture;

uniform vec2 pixelSize;
uniform vec4 lightColor;
uniform float lightRadius;
uniform mat4 inverseProjection;
uniform vec3 cameraPos;
uniform vec2 screen_dimension;

in vec2 vTexcoord;
in vec3 vPosition; // view-space
in vec3 vNormal;   // view-space
in vec3 vEyeDir;   // view-space
in vec4 vLightPosition; // light position in view-space

out vec4 out_color1; // emissive
out vec4 out_color2; // specular

void main(void)
{
    vec2 texCoord = vec2(gl_FragCoord.x, gl_FragCoord.y)/screen_dimension;
    
    float depth = texture2D(depth_texture, texCoord);
    
    vec4 screenPosition = vec4(
        texCoord,
        depth,
        1.0
    )*2.0 - 1.0;
    vec4 positionInView = inverseProjection * screenPosition;
    positionInView /= positionInView.w; // surface position in view-space
    
    vec3 normal = normalize(texture2D(normal_texture, texCoord).xyz*2.0-1.0); // normal in view-space

    float distance = length(vec3(vLightPosition.xyz - positionInView.xyz));

    float attenuation = 0.0;    
    float constant = 1.0;
    float linear   = 0.0014;
    float quadric  = 0.000007;
    attenuation = 1.0 / (constant + linear*distance + quadric*distance*distance);
    if (attenuation < 0.2)
        discard;
    
    vec3 incidentDir = normalize(vLightPosition.xyz - positionInView.xyz);
    vec3 viewDir = normalize(vEyeDir);
    vec3 halfDir = normalize(incidentDir + viewDir);
    
    float lambert = clamp(dot(incidentDir, normal), 0.0, 1.0);
    float rFactor = clamp(dot(halfDir, normal), 0.0, 1.0);
    float sFactor = pow(rFactor, 33.0);

    out_color1 = vec4(lightColor.xyz * lambert * attenuation, 1.0);
    out_color2 = vec4(lightColor.xyz * sFactor * attenuation * 0.33, 1.0);
    
}

Гэрлийн байрлал болон гэрлийн гадаргуу хоорондох зай нь гэрлийн бөмбөрцөгийн радиусаас хэтэрч болохгүй үүнийг attenuation ашиглан шийднэ. lambert болон specular утгуудаа олоод out_color1, out_color2 хувьсагчдад гэрлийн өнгө attenuation утгуудаар үржээд хадгална. lambert specular олох үйлдлүүд нь өмнөх постууд дээр ашигласан blinn-phong той аргатай ижилхэн хэрэгжүүлэгдэнэ.
Одоо нийтдээ albedo, normal, depth, emissive, specular гэсэн тусдаа 5-н ширхэг үр дүнг текстур дээр хадгалжээ. Тэгвэл эдгээр текстуруудын albedo, emissive, specular гэсэн текстуруудыг screen space quad дээр ажилладаг shader програм хэрэглэн үндсэн framebuffer object-рүү рэндэрлэх хэрэгтэй ингэж байж бид эцсийн үр дүнг харах боломжтой болно. Үндсэн FBO -ийн дугаар маань 0 байдаг тул 0 дугаартай FBO-г bind хийгээд screen space quad-аа зурахад болно.
albedo текстурыг ambient гэрэлтүүлэгт
albedo болон emissive хоёр текстуруудыг diffuse гэрэлтүүлэгт
specular текстурыг specular гэрэлтүүлгийн утгуудад хэрэглэнэ.
Ингээд үндсэн програм дээр зурахдаа

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(0, 0, windowWidth, windowHeight);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    glUseProgram(combineProgramID);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, colourTexture);
    glUniform1i(glGetUniformLocation(combineProgramID, "diffuseTexture"), 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, emissiveTexture);
    glUniform1i(glGetUniformLocation(combineProgramID, "emissiveTexture"), 1);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, specularTexture);
    glUniform1i(glGetUniformLocation(combineProgramID, "specularTexture"), 2);
    
    glUniform2f(glGetUniformLocation(combineProgramID, "screen_dimension"), windowWidth, windowHeight);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, quadBufferID);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*) 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glDisableVertexAttribArray(0);
    glUseProgram(0);


Vertex Shader
#version 330
layout(location=0) in vec3 in_position;

out vec2 vCoord;

void main(void)
{
    gl_Position = vec4(in_position, 1.0);
    vCoord = (in_position.xy+vec2(1,1))/2.0;
}

Fragment Shader
uniform vec2 screen_dimension;

in vec2 vCoord;

out vec4 out_color;

void main(void)
{
    vec2 texCoord = vec2(gl_FragCoord.x, gl_FragCoord.y)/screen_dimension;
    vec3 diffuse = texture(diffuseTexture, texCoord).xyz;
    vec3 light   = texture(emissiveTexture, texCoord).xyz;
    vec3 specular = texture(specularTexture, texCoord).xyz;
    
    out_color.xyz = diffuse * 0.2; // ambient
    out_color.xyz += diffuse * light; // lambert
    out_color.xyz += specular; // specular
    out_color.a = 1.0;
}

Ингээд энэ сүүлийн нэгтгэл хийж гаргаж авсан үр дүн маань энэ

Гэхдээ даанч хүсэн хүлээсэн үр дүн маань биш байлаа :(

Програм бичигдэж байхад авсан зурагнууд

энэ зураг дээр бөмбөрцөг дүрс ашиглан гэрэлтүүлэг хийхэд бэлгэх үе

зөвхөн diffuse гэрэлтүүлэг хэрэглэсэн байдал

blinn-phong ийн томъёогоор олсон зөвхөн specular утгууд

ambient+diffuse+specular зүгээр л туршилтын журмаар хооронд нь нэмчихсэн үр дүн

харин энд үндсэн зөв томъёо болох ambient + diffuse*emissive + specular үр дүн

энэ ч бас өмнөхтэйгээ ижилхэн харин гэрэл нь олон
За одоогоор нэг иймэрхүү. Хүсэн хүлээсэн үр дүндээ хараахан хүрсэнгүй. Яагаад гэвэл point гэрэлтүүлэг хийх үед цаад гэрэл хүрч очих ёсгүй гадаргуунууд дээр гэрэлтүүлгийн алгоритм ажиллаад байгаа явдал юм. Үүнийг шийдчихвэл жинхэнэ deffered rendering хэрэгжүүлэлт хийгдэх болно. Дараагийн постоор шийдчихээд энэ талаараа бичнээ.