Notice
Recent Posts
Recent Comments
Link
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Archives
Today
Total
관리 메뉴

게임공장

[Learn OpenGL 번역] 3-1. 조명 - 색 본문

OpenGL

[Learn OpenGL 번역] 3-1. 조명 - 색

짱승_ 2018. 7. 15. 19:57

조명/색

  이전 강좌에서 OpenGL 에서 색을 가지고 어떻게 동작하는지 간단히 언급했었습니다. 하지만 지금까지는 오직 면의 색만 다루었습니다. 여기에서 우리는 색이란 무엇인지 광범위하게 다룰 것이고 다가올 조명 강좌를 위해 scene을 생성해보도록 하겠습니다.


  실제 세계에서는 자신만의 색을 가지고 있는 임의의 알려진 색상 값을 실제로 취할 수 있습니다. 디지털 세계에서는 (무한한)실제 색들을 (제한된)디지털 값에 매핑해야합니다. 그러므로 모든 실세계의 색들을 디지털로 표현할 수 없습니다. 하지만 우리는 알아차리지 못할 정도로 다른 수 많은 색들을 표현할 수 있습니다. 디지털 세계에서 색은 red, green, blue 요소로 나타내어집니다. 각 요소들은 흔히 RGB로 축약됩니다. 단 이 3가지의 값을 조합하여 거의 모든 색을 표현할 수 있습니다. 예를 들어 coral 색을 얻기위해 다음과 같은 컬러 벡터를 정의할 수 있습니다.


glm::vec3 coral(1.0f, 0.5f, 0.31f);   

  실생활에서 보는 컬러는 실제로 사물이 가지고 있는 컬러가 아니라 사물에 반사(reflected)된 컬러입니다. 이 흡수되지 않고 반사된 컬러들이 우리가 그들을 인식하면 컬러입니다. 예를 들어 태양의 빛은 흰색 빛으로 인식되어집니다. 이 흰색 빛은 여러가지 다른 컬러들이 조합된 결과입니다(아래에서 보시다시피). 그래서 만약 파란색 장난감에 흰색 빛을 비춘다면 파란색을 제외하고 모든 흰색 컬러의 세부 컬러들을 흡수합니다. 이 장난감이 파란색 값을 흡수하지 않기 때문에 반사되어 우리의 눈에 들어오게됩니다. 따라서 이 장난감은 파란색으로 보이게 됩니다. 다음 이미지는 coral 색을 가지 장난감이 여러 컬러들을 반사하는 것을 보여주는 그림입니다.



  흰색 태양빛이 실제로 모든 컬러들의 집합이고 사물은 그 컬러들 중 큰 일부를 흡수하게 된다는 것을 알 수 있습니다. 오직 반사된 컬러들만이 이 사물의 컬러를 나타내고 이 컬러들의 조합이 우리가 인식하는 것입니다(이 경우에는 coral 컬러).


  이 컬러 반사의 규칙은 그래픽 세계에서도 적용됩니다. 우리가 OpenGL에 광원을 정의할 때 우리는 이광원에 컬러를 설정할 수 있습니다. 이전 단락에서 우리는 흰색을 가지고 있었습니다. 그래서 우리는 광원에 흰색 컬러를 줄 것입니다. 그런 다음 광원의 컬러 값과 오브젝트의 컬러 값을 곱하면 결과로 나온 컬러는 오브젝트에 반사된 컬러입니다(따라서 우리에게 인식되는 컬러). 다시 우리의 장난감(이번에는 coral 값을 가진)을 생각해보고 디지털 세계에서 우리가 어떻게 장난감을 인식할 수 있는 컬러를 계산하는지 살펴봅시다. 두 개의 컬러 벡터를 요소마다 곱하여 최종 컬러 벡터를 얻습니다.


glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);

  장난감의 컬러가 장난감이 가지고 있는 고유한 컬러 값에 따라 흰색 빛의 큰 부분을 흡수(absorb)하고 여러 red, green, blue 값을 반사시키는 것을 볼 수 있습니다. 이 것이 실생활에서 컬러를 나타내는 방법입니다. 따라서 우리는 오브젝트의 컬러를 광원으로 부터 반사된 각 컬러 요소들의 양으로 정의할 수 있습니다. 이제 우리가 녹색 빛을 사용한다면 어떻게 될까요?


glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);

  보시다시피 이 장난감은 red, blue 빛을 가지고 있지 않습니다. 그리고 또한 빛의 green 값의 반을 흡수하고 반을 반사했습니다. 그러면 우리가 인식하는 이 장난감의 컬러는 어두운 녹색이 되는 것입니다. 녹색 빛을 사용하면 오직 녹색 컬러 요소만 반사되어 인식되는 것을 볼 수 있습니다. 빨간색과 파란색은 인식되지 않는 것이죠. 결과적으로 coral 오브젝트는 갑자기 짙은 녹색의 오브젝트가 되었습니다. 어두운 올리브 녹색 빛을 사용하여 예제를 한번 더 봅시다.


glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);

  보시다시피 다른 색의 빛을 사용하여 예상치 못한 컬러들을 얻을 수 있습니다. 독특한 컬러를 얻는 것은 어렵지 않습니다.


이제 컬러에 대한 것은 충분하고 실험해볼 수 있는 scene을 만들어 봅시다.

빛이 있는 Scene

  다가올 강좌에서 우리는 광범위하게 컬러를 사용하여 실세계 조명을 시뮬레이션함으로써 흥미로운 효과들을 만들어 볼 것입니다. 이제 광원을 사용할 것이기 때문에 우리는 이들을 볼 수 있는 오브젝트로 나타내야 합니다. 그리고 조명을 시뮬레이션하기 위해 최소한 하나 이상의 오브젝트를 추가할 것입니다.


  제일 먼저 필요한 것은 빛을 만든 오브젝트이고 우리는 이전 강좌의 악명 높은 컨테이너를 사용할 것입니다. 또한 우리는 광원이 3D scene의 어디에 위치하고 있는지 보여줄 조명 오브젝트가 필요합니다. 간단히 이 광원을 정육면체로 표현할 것입니다(이미 가지고 있는 vertex 데이터).


  그래서 vertex buffer object를 채우고 vertex attribute pointer를 설정할건데 이 것들은 이제 여러분에게 쉬울 것이기 때문에 자세한 설명은 넘어가도록 하겠습니다. 여러분이 이것들에 대해 아직도 어려움을 느낀면 이전 강좌를 복습하여 연습하는 것을 권장합니다.


  그래서 우리가 실제로 필요한 첫 번째는 컨테이너를 그릴 vertex shader입니다. 컨테이너의 vertex 위치는 동일하게 남겨두므로(이번에는 텍스처 좌표는 필요 없습니다) 코드에는 새로운 것이 없습니다. 우리는 마지막 강좌에서 불필요한 것들을 뺀 버전의 vertex shader를 사용할 것입니다.


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

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

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

  vertex 데이터와 attribute pointer들을 새로운 vertex shader에 맞도록 수정하세요(여러분이 텍스처 데이터와 attribute pointer 활성화를 유지하고 싶다면 지금 당장은 사용하지 않을 것이지만 나쁘지 않은 생각인 것 같습니다) .


  램프 큐브(정육면체)를 만들기 위해 특별히 이 램프를 위한 새로운 VAO를 생성해야 합니다. 이 램프를 동일한 VAO로 생성하고 간단히 modle 행렬을 수정해도 가능하지만 다가올 강좌에서 우리는 컨테이너 오브젝트의 vertex 데이터와 attribute pointer를 꽤 자주 수정할 것이므로 우리는 이 수정이 램프 오브젝트에 영향을 끼치지 않도록 해야합니다(우리는 오직 램프의 vertex 위치들에 대해서만 고려합니다). 따라서 우리는 새로운 VAO를 만듭니다.


unsigned int lightVAO;
glGenVertexArrays(1, &lightVAO);
glBindVertexArray(lightVAO);
// VBO를 바인딩 바인딩하기만 하면 됩니다. 컨테이너의 VBO 데이터는 이미 정확한 데이터를 가지고 있습니다.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// vertex attribute를 설정합니다(램프를 위한 위치 데이터만).
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

  이 코드는 비교적 간단합니다. 이제 우리는 컨테이너와 램프 큐브를 생성하였습니다. 하지만 하나 남은게 있는데 그것은 fragment shader입니다.


#version 330 core
out vec4 FragColor;
  
uniform vec3 objectColor;
uniform vec3 lightColor;

void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}

  이 fragment shader는 오브젝트 컬러와 조명 컬러를 unifrom 변수로 받습니다. 여기에서 우리는 이 강좌의 처음에 설명했던 대로 조명 컬러와 오브젝트 컬러를 곱합니다. 이 shader는 이해하기 쉬울 것입니다. 오브젝트의 컬러를 마지막 섹션의 coral 컬러로 설정하고 조명은 흰색으로 설정합니다.


// 먼저 해당 shader program을 'use'해야 한다는 것을 잊지마세요(uniform을 설정하기 위해).
lightingShader.use();
lightingShader.setVec3("objectColor", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("lightColor",  1.0f, 1.0f, 1.0f);

  남은 하나는 vertex, fragment shader를 수정하기 시작할 때 램프 큐브는 우리가 원하지 않아도 같이 변할 것이라는 사실입니다. 우리는 램프 오브젝트의 컬러가 다음 강좌에서의 조명 계산에 영향을 받지 않도록 하고 싶습니다. 오히려 램프를 나머지 것들과 격리시켜놓아야 합니다. 우리는 램프가 다른 컬러 수정에 영향을 받지 않는 끊임없이 밝은 컬러를 가지고 있게 하고 싶습니다(이는 램프가 진짜 광원처럼 보이게 해줍니다).


  이를 수행하기 위해 우리는 실제로 램프를 그리기 위해 사용될 shader의 두 번째 세트가 필요합니다. 이렇게 하면 어떠한 수정에도 안전합니다. vertex shader는 현재 vertex shader와 동일하므로 간단히 소스 코드를 램프의 vertex shader에 붙여넣으면 됩니다. 램프의 fragment shader는 수정할 수 없는 흰색 컬러를 정의해서 램프의 컬러를 밝게 유지하도록 합니다.


#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // 4 개의 모든 벡터의 값을 1.0으로 설정합니다.
}

  오브젝트를 그리기 원할 때 방금 정의한 lighting shader를 이용하여 컨테이너 오브젝트(또는 가능한 많은 다른 오브젝트들)를 그리고 램프 shader를 사용하여 램프를 그립니다. 강좌를 진행하는 동안 우리는 천천히 현실적인 결과로 다가가기 위해 점차 lighting shader를 수정해 나갈 것입니다.


  램프 큐브의 주 목적은 빛이 어디로부터 오는지 보여주기 위함입니다. 우리는 일반적으로 광원의 위치를 scene의 어딘가에 정의합니다. 하지만 이 것은 단순히 위치를 나타내고 비주얼에 관해서 아무런 의미를 갖지 않습니다. 실제 램프를 보여주기 위해 광원과 동일한 위치에 램프 큐브를 그립니다. lamp shader를 사용하여 램프 오브젝트를 그림으로써 수행될 수 있고 이 램프 큐브는 scene의 조명 상황에 상관없이 항상 흰색으로 유지됩니다.


  그래서 world space 좌표에서 광원의 위치를 나타내는 vec3 타입의 전역 변수를 선언해봅시다.


glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

  그런 다음 우리는 이 램프 큐브를 그리기 전에 광원의 위치로 이동시킵니다. 또한 약간 축소시켜 너무 많이 차지하지 않게 합니다.


model = glm::mat4();
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f)); 

  램프를 위한 최종 드로잉 코드는 다음과 같이 보일 것입니다.


lampShader.use();
// model, view, projection 행렬 unifrom을 설정하세요.
...
// 램프 오브젝트를 그립니다.
glBindVertexArray(lightVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);			

  모든 코드 조각들을 적절한 위치에 삽입하면 조명을 실험하기 위한 OpenGL 응용 프로그램은 적절히 구성될 것입니다. 모든 것이 컴파일 되면 다음과 같이 보일 것입니다.



  지금 당장은 보여줄게 별로 없습니다. 하지만 다가올 강좌에서는 더 흥미로워 질 것을 약속드립니다.

  모든 코드 조각들을 어디에 삽입해야 하는지에 대해 어려움을 느낀다면 여기에서 확인해보시고 코드를 한줄한줄 잘 살펴보세요.


  이제 우리는 컬러에 대한 지식을 약간 가지고 있고 좀 섹시한 조명을 위한 기본적인 scene을 만들었습니다. 이제 진짜 매직을 시작할 다음 강좌를 시작할 수 있습니다.



출처 : https://learnopengl.com, Joey de Vries

반응형