kwan's note

[Opengl] phong lighting - 라이팅 본문

graphics VR AR/OpenGL

[Opengl] phong lighting - 라이팅

kwan's note 2022. 8. 1. 02:18
반응형

순서: Phong lighting, light sources, fragment shader 구현

 

Phong Lighting

라이팅을 위해서는 물체와 광원의 상호작용(BRDF)에 대한 정확한 계산을 필요로 하는데 이를

rasterization graphics에서 정확하게 표현하는것에는 한계가 있다. 이에 따라 여러가지 lighting 모델들이 존재하는데

그중에서 극단적으로 단순화 시키면서도 최소한의 퀄리티를 보이는 lighting model이 바로 phong 모델이다.

 

phong model은 물체를 표현하는 색상을 네가지로 나누어 계산하는데

1. ambient light

2. diffuse light

3. specular light 이다. 여기에 빛을 발산하는 경우에 더해지는

4. emissive light까지 총 4개로 나누어 계산하고 합산하여 결과를 구한다.

 

ambient light

먼저 ambient light는 공간의 다른 물체들로 부터 반사된 빛의 합을 단순화시킨 값이다.

즉, 모든 방향을 따라 물체에 비춰지는 빛이다.

 

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

위 GLSL 코드는 ambient 항을 계산하는 코드인데 여기서는 텍스쳐의 값과 빛의 ambient계수를 곱하여 결과를 낸다.

 

위 이미지는 ambient 항만 계산한 결과이다. 자세히 보면 물체가 보인다.

diffuse light

디퓨스 항은 램버트 법칙에 따라 정의되는데 이 법칙은 모든 빛을 난반사하는 lambertian surface를 가정한 경우에 빛을 계산하는 방식이다. 모든 빛을 난반사 하는 물체는 모든 방향에 같은 강도로 반사된다.

 

이러한 diffuse light는 빛의 방향과 물체의 법선에 의해 결정된다. 즉 물체 표면의 normal과 빛 벡터 사이의 각도(입사각)에 따라 결정되는 diffuse light는 빛 벡터와 법선 벡터의 내적으로 계산된다.

 

    float diff = max(dot(normal, lightDir), 0.0);

    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

다음과 같은 연산을 통해 diffuse 항이 연산된다. 내적에 max(value,0)을 취하는 이유는 마이너스 값을 갖지 않기 위함인데, 90도를 넘어가는 빛의 경우 다른 물체등으로 인해 빛이 애초에 도달하지 않으므로 diffuse 항을 계산하지 않는것이 맞기 때문이다. 즉, diffuse 항은 빛의 방향과 surface normal이 같은 방향일 때 최대값을 가지고 90도 이상일때 0을 가진다.

 

위 GLSL 코드에서 light.diffuse는 물체의 diffuse 계수를 나타낸다.

위 이미지는 diffuse 항만 계산한 결과이다. 난반사만 있을때의 결과이다.

 

specular light

specular 항은 빛의 정반사를 표현한 항이다.

정반사는 빛이 물체에 부딛힌 후 반사된 빛이 카메라(눈)으로 정반사 되는 빛을 표현한 항이다.

specular lighting (출처: 3차원 컴퓨터 그래픽스 입문)

위 그림과 같이 light source와 법선벡터 n사이의 각 theta를 이용해 반사되는 방향 r을 구하고

구한 r값과 카메라까지의 벡터 v의 내적을 기반으로 계산한다.

 

물리적으로 이상적인 정반사의 경우 로우값이 0이어야 하지만 실제 세계에서는 이상적인 정반사만 일어나지 않고 로우값이 매우 작으면 하이라이트 되는것처럼 빛이 반사되므로 r 과 로우값을 내적한것에 n제곱을 한다.

이때 n의 크기에 따라 얼마나 급속도로 값을 줄일지(얼마나 가까워야만 specular lighting을 표현할지)가 정해진다.

 

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));

 

아래는 specular 항만을 표현한 것으로 이때, specular 의 제곱 계수(코드에서 material.specular 값)은 32로 적용한 결과이다.

 

마지막으로 emissive light은 물체 자체가 빛을 발산하는 경우 이를 보여주는 항이다.

emissive value를 더하면 계산이 완료된다.

 

phong lighting의 한계중 하나는 이러한 emissive light이 다른 물체를 비추지 못한다는 것이다.

즉, 빛이 나오지만 다른 물체에 이러한 빛이 적용되지 않으므로 실제 빛이라고 인식되기 어렵다.

 

 

지금까지 간단한 형태의 phong lighting에 대해 설명하였다.

 

이제 여러가지 광원의 종류를 소개 하고자 한다.

광원의 종류에는 여러가지가 있지만 대표적으로 directional light, point light, spotlight가 있다.

 

근본적으로 이러한 광원은 다 point light으로 같지만 현실세계에서 어떻게 작동하냐에 따른 분류이고

이러한 분류로 인해 우리가 계산하는 방식이 조금씩 달라진다.

 

가장먼저 point light는 지금까지 우리가 계산한 phong light의 계산 방식을 그대로 적용한다고 보면 된다. 즉, 한 점에서 모든 방향으로 발산하는 빛으로 전구등이 포함된다.

 

하지만 이전에 작성한 phong lighting의 코드를 그대로 사용한다면 눈앞에 있는 물체와 무수히 멀리 떨어진 물체에 동일하게 비출 것이다. 이러한 문제를 해결하고자 감쇠를 적용하는데 이는 광선이 지나가는 거리에 따라 빛의 세기를 줄이는 것을 말한다. 이러한 감쇠는 다음과 같은 형태로 적용한다.

 

Fatt=1.0Kc+Kl∗d+Kq∗d2

 

이는 절대적인 값은 아니고 근사한 값으로 상수, 1차 2차항 까지만 나타낸다. 즉, 테일러 전개를 통한 근사식으로 생각할 수 있을것 같다.

 

 

point light 다음으로 directional light는 무한히 멀리 떨어진 point light이다. 태양은 무수히 멀리 떨어져 있지는 않지만 지면에 평행하게(거의 평행하게) 도달하므로 이러한 특성을 고려한 light로 볼 수 있다.

    //directional sunlight
    vec3 lightDir = normalize(-light.direction);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // 결과들을 결합
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    return (ambient + diffuse + specular);

light direction을 구하는 과정을 보면 point light에서는 light의 위치와 fragment 의 위치 차이를 통해 light direction을 구하는 반면, directional light에서는 이 벡터를 uniform으로 입력받는다.

 

마지막으로 spot light는 캐릭터에게 비추는 spotlight이나 유저가 지닌 랜턴등을 표현할 수 있는 원뿔 형태의 빛이다.

즉, 특정 각 이전에만 빛을 비추는 값으로 단순히 이 각도 이상이면 버리는 방식으로 진행할 수 있다.

만약 spotlight이 부드러운 외곽선을 가지도록 하고 싶다면, 파이값에 따라서 점차 감쇠하도록 작성할 수 도 있다.

float theta     = dot(lightDir, normalize(-light.direction));
float epsilon   = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);

위에서 구한 intensity를 곱하는 방법으로 부드러운 외곽선을 만들 수 있다.

 

 

이렇게 구현한 fragment shader 코드는 다음과 같다.

#version 460 core
out vec4 FragColor;

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    //sampler2D emission;

    float shininess;
};

struct DirLight {
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct PointLight {
    vec3 position;

    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

struct SpotLight {
    vec3 position;
    vec3 direction;
    float cutOff;
    float outerCutOff;

    float linear;
    float quadratic;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};



#define NR_POINT_LIGHTS 4  

in vec3 FragPos;
in vec3 Normal;
in vec2 TexCoords;

uniform vec3 viewPos;
uniform DirLight dirLight;
uniform PointLight pointLights[NR_POINT_LIGHTS];
uniform SpotLight spotLight;
uniform Material material;

//function decl
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir);

void main()
{
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);
    //directional light
    vec3 sumLight = CalcDirLight(dirLight, norm, viewDir);
    //Point lights
    for (int i = 0; i < NR_POINT_LIGHTS; i++)
        sumLight += CalcPointLight(pointLights[i], norm, FragPos, viewDir);

    sumLight += CalcSpotLight(spotLight, norm, FragPos, viewDir);
    
    //emission light
    //sumLight += texture(material.emission, TexCoords).rgb;

    FragColor = vec4(sumLight, 1.0);
}

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
    //directional sunlight
    vec3 lightDir = normalize(-light.direction);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // 결과들을 결합
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    return (ambient + diffuse + specular);
}

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (1.0 + light.linear * distance +
        light.quadratic * (distance * distance));
    // 결과들을 결합
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    ambient *= attenuation;
    diffuse *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
}

// calculates the color when using a spot light.
vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
    vec3 lightDir = normalize(light.position - fragPos);
    // diffuse shading
    float diff = max(dot(normal, lightDir), 0.0);
    // specular shading
    vec3 reflectDir = reflect(-lightDir, normal);
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    // attenuation
    float distance = length(light.position - fragPos);
    float attenuation = 1.0 / (1.0 + light.linear * distance + light.quadratic * (distance * distance));
    // spotlight intensity
    float theta = dot(lightDir, normalize(-light.direction));
    float epsilon = light.cutOff - light.outerCutOff;
    float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
    // combine results
    vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
    ambient *= attenuation * intensity;
    diffuse *= attenuation * intensity;
    specular *= attenuation * intensity;
    return (ambient + diffuse + specular);
}

 

전체 소스코드는 깃헙에 공개

 

https://github.com/kwanyun/LightOpengl

 

GitHub - kwanyun/LightOpengl

Contribute to kwanyun/LightOpengl development by creating an account on GitHub.

github.com

 

반응형

'graphics VR AR > OpenGL' 카테고리의 다른 글

opengl - 유니폼 (uniform)  (0) 2022.05.05
Opengl - VAO VBO 컴퓨터 그래픽스  (1) 2022.04.28
openGL  (0) 2022.03.30