[Learn OpenGL 번역] 3-3. 조명 - Materials
Materials
조명/Materials
실제 세꼐에서 사물들은 빛에 각자 다르게 반응합니다. 예를 들어 철로 된 사물은 점토로 만들어진 꽃병보다 반짝이고 나무로된 컨테이너는 철로 된 컨테이너가 빛에 반응하는 것과 반대로 빛에 반응하지 않습니다. 또한 specular 하이라이트에도 각자 다르게 반응합니다. 어떠한 사물들은 많은 양을 퍼지게 하지않아 결과적으로 작은 하이라이트를 만들고 다른 어떠한 사물들은 빛의 많이 퍼지게 함으로써 큰 반지름을 가진 하이라이트를 만듭니다. OpenGL 에서 여러가지 유형의 오브젝트들을 시뮬레이션해보기 위해 각 오브젝트에
이 전의 강좌에서 우리는 오브젝트의 시각적인 출력을 정의하기 위해 오브젝트 컬러와 빛 컬러를 지정하였습니다. 오브젝트를 묘사할 때 우리는 각 3가지의 lighting 컴포넌트(ambient, diffuse, specular)에 material을 정의할 수 있습니다. 각 컴포넌트에의 컬러를 지정함으로써 객체의 컬러 출력을 세부적으로 제어할 수 있습니다. 이 3가지의 컬러에 shininess 컴포넌트를 추가하면 우리가 필요한 모든 material 속성을 가지게 됩니다.
#version 330 core
struct Material {
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
uniform Material material;
Fragment shader에서 오브젝트의 material 속성들을 저장하기 위해 struct
를 생성합니다. 이들을 각각의 uniform 값으로 저장할 수 있지만 struct로 저장하는 것이 좀 더 체계적입니다. 먼저 struct의 레이아웃을 정의하고 새로 생성된 struct를 타입으로 사용하여 uniform 변수를 선언합니다.
보시다시피 Phong lighting 각 컴포넌트들에 대한 컬러 벡터를 정의합니다. ambient material 벡터는 오브젝트가 ambient lighting에 대해 어떤 컬러를 반사할 것인지 정의합니다. 이 것을 일반적으로 오브젝트의 컬러와 동일합니다. diffuse material 벡터는 diffuse lighting에 대한 컬러를 정의합니다. diffuse 컬러는 (ambient lighting과 마찬가지로) 원하는 오브젝트의 컬러로 설정됩니다. specular material 벡터는 오브젝트가 specular light에 영향을 받아 생성되는 컬러를 설정합니다. 마지막으로 shininess는 specular 하이라이트의 퍼짐의 정도/반지름에 영향을 줍니다.
오브젝트의 material을 정의하는이 4개의 컴포넌트들과 함께 우리는 많은 실세계의 material(재질)들을 시뮬레이션할 수 있습니다. devernay.free.fr에 있는 테이블은 실제 material을 시뮬레이션해볼 수 있는 여러가지 material 속성들을 보여줍니다. 다음 이미지는 이러한 실세계의 material들이 우리 큐브에서 어떠한 효과를 내는지 보여줍니다.
보시다시피 오브젝트의 material 속성들을 정확하게 지정함으로써 오브젝트에 대한 인식을 바꿉니다. 이 효과들을 분명히 알아챌 수 있습니다. 하지만 가장 현실적인 결과를 위해서는 결국 우리는 큐브보다 더 복잡한 도형이 필요합니다. 나중에 올 섹션에서 우리는 좀더 복잡한 도형들에 대해서 다루도록 하겠습니다.
오브젝에 대해 올바른 material을 얻는 것은 어렵습니다. 많은 경험을 요구하기 때문입니다. 그래서 무적절한 material로 인해 오브젝트의 시각적 품질을 완벽히 파괴하는 행위는 흔한일입니다.
shader에서 이러한 material 시스템을 구현해봅시다.
Material 세팅
우리는 fragment shader에 unifrom material struct를 생성했으므로 새로운 material 속성을 사용하여 lighting 계산을 바꿀 수 있습니다. 모든 material 변수들이 struct 안에 저장되어 있기 때문에 material uniform으로 그들에게 접근할 수 있습니다.
void main()
{
// ambient
vec3 ambient = lightColor * material.ambient;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightColor * (diff * material.diffuse);
// specular
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
vec3 specular = lightColor * (spec * material.specular);
vec3 result = ambient + diffuse + specular;
FragColor = vec4(result, 1.0);
}
이제 필요할때마다 material struct의 모든 속성에 접근한다는 것을 볼 수 있을 것입니다. 그리고 이번에는 material의 컬러의 도움을 받아 최종 출력을 계산합니다. 오브젝트의 각각의 material 속성들은 각각의 lighting 컴포넌트에 곱해집니다.
적절한 uniform을 설정하여 응용 프로그램에서 오브젝트의 material을 설정할 수 있습니다. 하지만 GLSL에서 struct는 uniform을 설정할 때 특별하지 않습니다. struct는 uniform 변수의 캡슐화를 해줄뿐으므로 struct를 채우기 위해서는 각각의 uniform 변수를 설정해주어야 합니다. 이번에는 struct 이름으로 집두사를 붙인 uniform 변수를 사용합니다.
lightingShader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
lightingShader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
lightingShader.setFloat("material.shininess", 32.0f);
우리는 ambient, diffuse 컴포넌트에 오브젝트가 가지기를 원하는 컬러를 설정하고 오브젝트의 specular 컴포넌트를 중간 밝기의 컬러로 설정했습니다. 또한 shininess를 32
로 유지시켜 놓습니다. 우리는 이제 응용 프로그램에서 오브젝트의 material에 쉽게 영향을 줄 수 있습니다.
프로그램을 실행하면 다음과 같이 보일 것입니다.
그래도 올바르게 보이지 않나요?
빛의 속성
오브젝트가 너무 밝습니다. 이 오브젝트가 너무 밝은 이유는 ambient, diffuse, specular 컬러들이 모든 광원으로부터 완전하게 반사되기 때문입니다. 또한 광원은 ambient, diffuse, specular 컴포넌트마다 각각 세기가 다릅니다. 이전의 강좌에서 우리는 ambien 세기와 specular 세기를 strength 값을 통해 다르게 함으로써 이를 해결했었습니다. 우리는 이와 비슷하게 하지만 이번에는 각 lighting 컴포넌트의 세기 벡터를 지정합니다. lightColor를 vec3
로 시각화하면 코드는 다음과 같습니다.
vec3 ambient = vec3(1.0) * material.ambient;
vec3 diffuse = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular);
그래서 각 오브젝트의 material 속성들은 각 light 컴포넌트들의 가장 센 값을 리턴하게 됩니다. 이 vec3(1.0)
값들은 광원에 대해 각각 영향을 받을 수 있고 이는 일반적으로 우리가 원하는 것입니다. 지금 당장 오브젝트의 ambient 컴포넌트는 완전히 큐브의 컬러에 영향을 미치고 있습니다. 하지만 ambient 컴포넌트는 최종 컬러에 이렇게 많은 영향을 줄 필요는 없으므로 빛의 ambient 세기의 값을 낮추어 ambient 컬러를 제한할 수 있습니다.
vec3 ambient = vec3(0.1) * material.ambient;
같은 방법으로 광원의 diffuse, specular 세기에 영향을 끼칠 수 있습니다. 이는 우리가 이전 강좌에서 했던 것과 거의 비슷합니다. 각 lighting 컴포넌트에 영향을 주는 light 속성들 이미 만들었다고 말할 수 있습니다. 우리는 빛의 속성을 위해 material struct와 비슷한 것을 만들어야 합니다.
struct Light {
vec3 position;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform Light light;
광원은 ambient, diffuse, specular 빛에 대해 다른 세기를 가지고 있습니다. ambient 빛은 일반적으로 작은 세기로 설정됩니다. ambient 컬러가 너무 많이 차지하지 않기를 원하기 때문입니다. 광원의 diffuse 컴포넌트는 일반적으로 우리가 원하는 정확한 컬러로 설정합니다. 대부부 밝은 흰색입니다. specular 컴포넌트는 일반적으로 최대한의 세기로 빛나는 vec3(1.0)
값으로 유지됩니다. 또한 빛의 위치 벡터를 struct에 추가한다는 것을 알아두세요.
material uniform과 마찬가지로 fragment shader를 수정해야 합니다.
vec3 ambient = light.ambient * material.ambient;
vec3 diffuse = light.diffuse * (diff * material.diffuse);
vec3 specular = light.specular * (spec * material.specular);
그런 다음 우리는 응용 프로그램에서 빛의 세기를 설정합니다.
lightingShader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // Scene에 맞는 어두운 빛
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
이제 우리는 빛이 어떻게 모든 오브젝트의 material에 영향을 미치는지 조절하여 이전의 강좌에서 보다 좋은 시각적인 출력을 얻어냈습니다. 하지만 이번에는 lighting과 오브젝트의 material에 대해 모든 것을 관리했습니다.
오브젝트의 관점에서 보여지는 것을 수정한다는 것은 지금은 비교적 쉽습니다. 좀 더 어려운 것을 해봅시다!
다른 빛 컬러
지금까지는 오브젝트의 실제 컬러에 영향을 주 것이 아니라 빛의 컬러를 흰색에서 회색 검정색 사이의 컬러를 선택하여 각각의 컴포넌트들의 세기를 다르게할 뿐이었습니다. 이제 우리는 빛의 속성에 쉽게 접근할 수 있기 때문에 그들의 컬러를 시간이 지남에 따라 수정할 수 있어 굉장히 흥미로운 효과를 얻을 수 있습니다. 모든 것이 fragment shader에 세팅되어 있기 때문에 빛의 컬러를 수정하는 것은 쉽고 즉각적으로 펑키한 효과를 만들 수 있습니다.
보시다시피 다른 빛의 컬러들이 오브젝트의 출력 컬러의 크게 영향을 미치고 있습니다. 빛의 컬러가 오브젝트가 반사할 수 있는 컬러에 대해 직접적으로 영향을 주므로 시각적인 출력에서 중요한 영향을 가지게 됩니다.
우리는
glm::vec3 lightColor;
lightColor.x = sin(glfwGetTime () * 2.0f);
lightColor.y = sin(glfwGetTime () * 0.7f);
lightColor.z = sin(glfwGetTime () * 1.3f);
glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // 영향을 감소시킵니다.
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // 낮은 영향
lightingShader.setVec3("light.ambient", ambientColor);
lightingShader.setVec3("light.diffuse", diffuseColor);
여러가지 lighting과 material 값들을 사용하여 실험해보세요. 그리고 그들이 시각적인 출력에 어떠한 영향을 미치는지 확인해보세요. 여기에서 응용 프로그램의 소스 코드를 확인할 수 있습니다.
연습
- 우리가 이 강좌의 초반에 보았던 것처럼 오브젝트의 material을 정의하여 실세계 오브젝트를 시뮬레이션할 수 있습니까? 표의 ambient 값은 diffuse 값과 다르다는 것을 알아두세요. 이들은 빛의 세기를 고려하지 않았습니다. 정확하게 그들의 값을 설정하기 위해 여러분은 모든 빛의 세기를
vec3(1.0)
로 설정하여 같은 결과를 얻을 수 있습니다: 해답 cyan plastic 컨테이너의 경우