OpenGL Basic Lighting 조명 [Learn OpenGL 쉬운 번역]

728x90
반응형
※ 본 포스팅은 learnopengl.com을 번역 및 가감한 포스팅이며, learnopengl에서는 번역 작업 참여를 적극 장려하고 있습니다!
아래 링크에서 원문을 확인할 수 있습니다.

 

Basic Lighting

https://learnopengl.com/Lighting/Basic-Lighting

조명 연산은 실제 물리적인 연산과 동일한 과정을 거치는게 불가능합니다.

그래서 컴퓨터 그래픽스에서는 이를 비슷하게 따라하는 단순화 모델을 이용합니다.

 

그래도 웬만큼 물리학을 염두하고 개발된 모델을 이용하는데요, phong lighting model이 그렇습니다. 

phong 모델은 위 그림과 같이 ambient, diffuse, specular의 세 요소가 합쳐서 생성되는 모델입니다.

 

각각 주변광, 분산광, 반사광으로 해석하면 되는데.

ambient light는 어두운 곳이 100% 어두운 것은 아니고 어디선가 반사된 빛을 받아 희미하게 밝은 빛을 표현하는데, 이를 ambient 상수를 이용해서 일정한 밝기를 줍니다.

 

diffuse light는 빛이 직접 물체에 비치는 것을 나타냅니다. 빛의 세기가 세면 더 밝아집니다.

specular light는 광택이 있는 물체의 경우 빛의 하이라이트 부분이 생기는데 이를 표현합니다. specular light의 경우는 물체보단 빛의 색상에 영향을 많이 받습니다.

 

Ambient lighting

실제로 빛은 정말 많은 곳에 퍼져있습니다. 지구에서 빛이 완전히 없는 곳은 거의 없죠. 여러 광원으로 빛이 흩어져있고 산란하고 반사되면서 광원에 직접 향한 방향이 아니더라도 약한 빛은 항상 도달합니다. 이렇게 공간에 전역으로 퍼진 빛을 global illumination 이라고 하고 이러한 알고리즘을 완벽하게 계산하는 것은 너무 비용이 큽니다.

 

그래서 단순한 모델인 ambient light를 이용해서 빛 상수 하나를 빛이 객체에서 직접 닿지 않는 부분에 적용하여 산란된 빛이 도달한 것처럼 보이게 만듭니다.

 

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

방법은 생각보다 단순한데요. shader 에서 light color에 ambient 인자를 곱하고,

이렇게 구한 ambient값을 object color와 곱하여 객체의 셰이더에서 fragment color로 사용합니다.

 

프로그램을 실행해보면 이제 첫 번째 조명인 ambient가 적용되어서 어둡지만 살짝 물체의 색이 보입니다. 앞에서 shader를 서로 분리했기 때문에 광원 큐브에는 영향이 없죠.

Diffuse lighting

위의 ambient light는 빛이라고 보기에 많이 어두운 느낌입니다. diffuse light를 사용하면 비로소 시각적인 효과를 많이 보는데요. diffuse lighting은 광원에서 나오는 빛과 가까울 수록 밝에 빛나는 계산이 됩니다. 

그림을 보면 하나의 프래그먼트를 향해서 광원에서 빛이 나가고 있습니다. 이게 각도를 알기 위해서 법선 벡처를 사용하는데, 그림에서 노란색 벡터입니다. 각도를 구할 때 dor product 내적을 사용하면 됩니다.

 

https://learnopengl.com/Lighting/Basic-Lighting

diffuse light 계산을 위해서 빛이 물체에 닿는 각도와 수직 법선 벡터를 알아야합니다. 각도는 cos으로 계산되므로 0에 가까워질 수록 값이 1이 되어, 수직이 되면 빛을 가장 많이 받습니다.

반대로 각도가 커지면 빛이 비스듬하게 닿으므로 희미해지겠죠. 

 

빛과 수직 벡터의 각도를 내적계산하여 각도를 구하게 될 때, normalization(정규화)을 해야합니다. 그렇지 않으면 cos에서 나올 수 있는 값보다 더 큰 값이 나올거에요.

이렇게 빛의 각도와 표면의 각도의 따라서 빛을 받아 색의 밝기가 변하게 됩니다. 

 

정리하면 diffuse lighting을 위해서는 다음 두 개가 필요합니다. 

  • 법선: 빛이 닿는 점의 표면과 수직인 벡터
  • 빛의 광선 벡터 : 광원에서 빛이 닿는 점까지의 벡터. 빛의 위치 벡터를 프래그먼트 위치 벡터에서 빼면 구할 수 있습니다.

Normal vectors

노말벡터 즉 정점벡터라고도 하는데, 이건 어떻게 구할까요? graphic API에는 내장되어있겠지만 우리는 알 필요가 있습니다. 우선 정점 자체는 표면이 없기 때문에 주변 정점을 사용하여 정점의 표면을 파악함으로써 법선 벡터를 얻습니다.

 

법선 벡터는 원래 외적을 이용해서 구하지만 복잡하지 않은 큐브와 같은 모양은 수동으로 법선 벡터를 vertex 데이터에 추가할 수 있습니다. 기본적인 모양인 큐브를 이용해서 이를 알아봅시다. 

 

 

위의 큐브 vertices 코드를 사용하세요.

먼저 큐브의 vertex shader를 업데이트합니다. 

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
...

cube의 각 정점에 법선 벡터를 추가하고 shader를 업데이트했습니다. 이제 정점 속성 포인터도 업데이트 해줍니다.

조명의 영향을 받는 큐브는 방금 추가한 정점 배열을 사용하면 되는데, 광원. 즉 램프 셰이더는 추가된 법선 벡터를 사용하지 않습니다. 그래서 램프 셰이더나 속성 구성은 업데이트 하지 않지만, 그래도 새로 정점 배열이 추가되었으니 정점 속성 포인터는 수정해야합니다. 

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

각 정점의 첫 3개의 float만 사용하고 이후 3개는 무시하도록 하기 위해 stride는 6 * sizeof(float)으로 설정합니다.

 

램프 셰이더가 정점 데이터를 전부는 사용하지 않는 것 같아보여도, 정점 데이터는 이미 컨테이너 객체에서 GPU 메모리에 저장을 해놓았습니다. 그래서 GPU 메모리에 데이터를 새로 저장하지 않아도 됩니다. 램프를 다시 정의하려고 VBO를 할당하는 것보다는 이게 더 효율적인 방법입니다. 조명 계산은 frament shader에서 수행되기 때문에 vertex 에서 fragment shader로 법선 벡터를 전달합시다.

out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = aNormal;
}

 

이제 fragment shader에 입력 변수를 선언합니다.

in vec3 Normal;

 

다음 포스팅에서 diffuse color lighting을 추가하도록 합시다.

728x90
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유