OpenGL/공부

[OpenGL] Phong Reflection Model - 구현

ciel45 2024. 6. 19. 23:26

Vertex Shader, Fragment Shader 외 c++ 코딩은 그냥 적당한 값을 정하고 쉐이더로 계속 쏴주는 반복적이고 단순한 코딩이라서, 그 과정은 생략하려고 한다.

 

Vertex Shader:

#version 330                          
                                      
layout (location = 0) in vec3 a_pos;
layout (location = 1) in vec2 a_texcoord;
layout (location = 2) in vec3 a_normal;
                                      
out vec2 v_texcoord;
out vec3 v_normal_wc; // World Space 기준 normal
out vec3 v_position_wc; // World Space 기준 Position

uniform mat4 u_PVM;
uniform mat4 u_modelMat;
uniform mat3 u_normalMat;
                                      
void main()                          
{                                     
    gl_Position = u_PVM * vec4(a_pos, 1.f);          
    
    v_texcoord = vec2(a_texcoord.x, 1 - a_texcoord.y);

    v_normal_wc = u_normalMat * a_normal;
    v_position_wc = (u_modelMat * vec4(a_pos, 1.f)).xyz;
}

 

라이팅을 위해서는 World Space 기준 normal과 position을 가지고 있을 필요가 있었다.

Object Space 기준 position을 Word Space 기준 position으로 바꿔주기 위해서는 Model Matrix를 곱해주면 되지만,

 

normal을 그렇게 변환하기 위해서는 Normal Matrix가 필요하다. (uniform scaling만 할 경우에는 Model Matrix로도 가능하기는 함)

자세한 내용은 https://ciel45.tistory.com/102

 

[OpenGL] Normal Transformation (Normal Matrix)

학교 컴퓨터 그래픽스 수업에서는 적당히 언급만 하고 넘어갔던 부분이라서, 개인적으로 공부해보았다.  Phong Reflection Model을 통한 쉐이딩을 위해서는 물체의 표면의 normal을 구해야 한다.이 때

ciel45.tistory.com

여기서는 C++ 코드에서 위 글처럼 normal matrix를 따로 만들어 쉐이더로 쏴주었다.

//main.cpp 중 일부
glm::mat3 normalMat = (glm::mat3)transpose(inverse(model));

 

 

이제 Fragment Shader에서 상당히 할 것이 많다.

 

Fragment Shader:

#version 330                         
                                     
in vec2 v_texcoord;
in vec3 v_normal_wc;
in vec3 v_position_wc;
                                     
out vec4 color;             

struct DirectionalLight
{   
    vec3 color; // 빛의 컬러
    float ambientIntensity; //ambient 강도
    vec3 direction; // 빛의 방향
    float diffuseIntensity; // diffuse 강도
};

struct Material
{
    float specularIntensity; // specular 강도
    float shininess; // phong lighting 수식의 alpha에 해당
};

uniform sampler2D u_texture;
uniform DirectionalLight u_directionalLight;
uniform Material u_material;

uniform vec3 u_eyePosition; // 카메라의 포지션 (World Space)
                                      
void main()                          
{ 
	// 빛의 ambient 컬러
    vec4 ambientColor = vec4(u_directionalLight.color, 1.f) * u_directionalLight.ambientIntensity;
	
    // diffuseFactor : 빛이 표면을 얼마나 똑바로 때리는지
    float diffuseFactor = max(dot(normalize(v_normal_wc), normalize(u_directionalLight.direction)), 0.f);
    // 빛의 diffuse 컬러
    vec4 diffuseColor = vec4(u_directionalLight.color, 1.f) * u_directionalLight.diffuseIntensity * diffuseFactor;

	// 빛의 specularColor. 기본 값은 없음으로
    vec4 specularColor = vec4(0,0,0,0);

	// diffuseFactor가 유효했을 경우에만
    if(diffuseFactor > 0.f)
    {
    	// 표면->카메라 벡터
        vec3 fragToEye = normalize(u_eyePosition - v_position_wc);
        // 빛과 표면 간의 정반사각 벡터
        vec3 reflected = normalize(reflect(u_directionalLight.direction, normalize(v_normal_wc)));

		// specularFactor : 카메라가 얼마나 정반사각에 가까운지
        float specularFactor = dot(fragToEye, reflected);
        if(specularFactor > 0.f)
        {
            specularFactor = pow(specularFactor, u_material.shininess); // shininess 제곱
            specularColor = vec4(u_directionalLight.color * u_material.specularIntensity * specularFactor, 1.f);
        }
    }

    color = texture(u_texture, v_texcoord); // 물체의 기본 컬러
    color *= (ambientColor + diffuseColor + specularColor); // 빛의 컬러와 곱해줌
}

 

중간에 if(diffuseFactor > 0.f)가 들어간 이유는, diffuse가 없는데 specular가 있다는건 불가능하기 때문이다.

 

최종 문장이 Phong Reflection Model - 개요에서 다뤘던 수식과 괴리감이 좀 있다.

몇 가지 단순화한 게 있기 때문이다.

 

수식에서는 물체의 기본 컬러에 해당하는 K를 Ka, Kd, Ks로 ambient, diffuse, specular 각각 따로 두지만,

여기서는 모두 texture(u_texture, v_texcoord)로 통일하였다.

 

또한 빛의 컬러에 해당하는 i의 경우,

  • i_a  -> u_directionalLight.color, 1.f) * u_directionalLight.ambientIntensity
  • i_d -> (u_directionalLight.color, 1.f) * u_directionalLight.diffuseIntensity
  • i_s -> (u_directionalLight.color * u_material.specularIntensity, 1.f)

이렇게 굳이 하나하나 rgb를 정해주지 않고 한 가지 color에서 intensity만 다르게 해주었다.

 

또한

dot(Lm, N)이 쉐이더의 diffuseFactor이고,

dot(Rm, V)가 쉐이더의 specularFactor이다.

 

 

이렇게 함으로써 원래 위의 식의 형태가,

최종 컬러 = (물체의 컬러 * 빛의 ambient컬러) + (물체의 컬러 * 빛의 diffuse 컬러) + (물체의컬러 * 빛의 specular 컬러)
(여기서의 ~~컬러는 ~~Factor까지 곱해진 값)

 

 

이던 것을 쉐이더 코드(마지막 2줄) 에서는,

최종 컬러 = (물체의 컬러) * (빛의 ambient 컬러 + 빛의 diffuse 컬러 + 빛의 specular 컬러)

 

로 표현할 수 있었던 것이다.

 

 

 

테스트:

 

Light와 Material의 세팅은 다음과 같이 해서 쉐이더에 쏴주었다.

mainLight = Light(1.0f, 1.0f, 1.0f, // r, g, b
		0.1f, // ambient Intensity
        0.0f, 0.0f, -1.0f, // Direction (x, y, z)
        0.3f); // diffuse Intensity
        


shinyMaterial = Material(1.f, 32.f); // (specular Intensity, shininess)
dullMaterial = Material(0.3f, 4.f); // (specular Intensity, shininess)

 

 

 

 

shinyMaterial은 봇치에게,

dullMaterial은 니지카에게 적용되었다.

 

버텍스 구조를 한번 바꿨는데, 캐릭터가 여러 면에 걸쳐 그려져 못생겨보이게 되었다..