Skip to content

머티리얼

실제 세계에서 각 물체는 빛에 대해 서로 다른 반응을 보입니다. 예를 들어 강철 물체는 점토 꽃병보다 더 반짝이는 경우가 많고, 나무 용기는 강철 용기와 빛에 대한 반응이 다릅니다. 어떤 물체는 빛을 거의 산란시키지 않고 반사하여 작은 반사광을 만들어내는 반면, 어떤 물체는 빛을 많이 산란시켜 반사광의 반경이 더 넓어집니다. OpenGL에서 여러 종류의 물체를 시뮬레이션하려면 각 표면에 특정한 머티리얼 속성을 정의해야 합니다.

이전 장에서는 객체와 광원의 색상을 정의하여 객체의 시각적 출력을 정의했고, 주변광과 반사광의 강도 요소를 결합했습니다. 표면을 설명할 때는 주변광, 확산광, 반사광의 세 가지 조명 요소 각각에 대해 머티리얼 색상을 정의할 수 있습니다. 각 요소에 색상을 지정함으로써 표면의 색상 출력을 세밀하게 제어할 수 있습니다. 이제 이 세 가지 색상에 광택 요소를 추가하면 필요한 모든 머티리얼 속성을 갖추게 됩니다.

#version 330 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 

uniform Material material;

프래그먼트 셰이더에서 표면의 머티리얼 속성을 저장할 구조체를 생성합니다. 개별 유니폼 값으로 저장할 수도 있지만, 구조체로 저장하면 더 체계적으로 관리할 수 있습니다. 먼저 구조체의 구조를 정의한 다음, 새로 생성된 구조체를 타입으로 하는 유니폼 변수를 선언하면 됩니다.

보시다시피, 퐁 조명의 각 구성 요소에 대해 색상 벡터를 정의합니다. ambient 머티리얼 벡터는 주변광 아래에서 표면이 반사하는 색상을 정의하며, 일반적으로 표면의 색상과 동일합니다. diffuse 머티리얼 벡터는 확산광 아래에서 표면의 색상을 정의합니다. 확산광 색상은 (주변광과 마찬가지로) 원하는 표면 색상으로 설정됩니다. specular 머티리얼 벡터는 표면의 반사광 색상을 설정합니다(또는 표면별 색상을 반영할 수도 있습니다). 마지막으로, shininess(광택도)는 반사광의 산란/반경에 영향을 미칩니다.

객체의 재질을 정의하는 이 네 가지 구성 요소를 통해 실제 세계의 다양한 재질을 시뮬레이션할 수 있습니다. devernay.free.fr에서 찾을 수 있는 표에는 외부 세계에서 발견되는 실제 재질을 시뮬레이션하는 재질 속성 목록이 나와 있습니다. 다음 이미지는 이러한 실제 재질 값 중 일부가 큐브에 미치는 영향을 보여줍니다.

보시다시피, 표면의 재질 속성을 올바르게 지정하면 객체에 대한 인식이 달라지는 것을 알 수 있습니다. 효과는 분명히 눈에 띄지만, 더욱 사실적인 결과를 얻으려면 정육면체를 더 복잡한 형태로 바꿔야 합니다. 모델 로딩 장에서 더 복잡한 형태에 대해 자세히 살펴보겠습니다.

오브젝트에 적합한 재질 설정을 찾는 것은 매우 어려운 작업이며, 대부분 실험과 풍부한 경험을 필요로 합니다. 재질을 잘못 설정하면 오브젝트의 시각적 품질이 완전히 망가지는 경우도 드물지 않습니다.

셰이더에서 이러한 재질 시스템을 구현해 보겠습니다.

머티리얼 설정

프래그먼트 셰이더에서 유니폼 변수인 material 구조체를 생성했으므로, 이제 새로운 머티리얼 속성에 맞게 조명 계산 방식을 변경하려고 합니다. 모든 머티리얼 변수가 구조체에 저장되어 있으므로, 머티리얼 유니폼에서 해당 변수들에 접근할 수 있습니다.

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);
}

보시다시피 이제 필요한 곳 ​​어디에서든 머티리얼 구조체의 모든 속성에 접근할 수 있으며, 이번에는 머티리얼의 색상을 이용하여 최종 출력 색상을 계산합니다. 각 객체의 머티리얼 속성은 해당 조명 구성 요소와 곱해집니다.

애플리케이션에서 객체의 머티리얼은 적절한 유니폼 변수를 설정함으로써 지정할 수 있습니다. 하지만 GLSL에서 구조체는 유니폼 변수 설정에 있어 특별한 역할을 하지 않습니다. 구조체는 단순히 유니폼 변수들의 네임스페이스 역할을 할 뿐입니다. 구조체를 채우려면 각 유니폼 변수에 구조체 이름을 접두사로 붙여 개별적으로 설정해야 합니다.

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);

우리는 주변광과 확산광 성분을 원하는 색상으로 설정하고, 반사광 성분은 중간 밝기의 색상으로 설정합니다. 반사광이 너무 강하지 않도록 하기 위함입니다. 또한 광택도는 32로 유지합니다.

이제 애플리케이션에서 객체의 재질을 쉽게 변경할 수 있습니다. 프로그램을 실행하면 다음과 같은 결과가 나타납니다.

뭔가 좀 어색해 보이는데요?

빛의 특성

물체가 너무 밝습니다. 물체가 너무 밝은 이유는 주변광, 확산광, 반사광 색상이 모든 광원에서 최대 강도로 반사되기 때문입니다. 광원은 또한 주변광, 확산광, 반사광 성분에 대해 각각 다른 강도를 가지고 있습니다. 이전 장에서는 주변광과 반사광의 강도를 강도 값으로 변경하여 이 문제를 해결했습니다. 이번에는 각 조명 성분에 대한 강도 벡터를 지정하여 비슷한 작업을 수행하려고 합니다. lightColor를 vec3(1.0)으로 시각화한다면 코드는 다음과 같습니다.

vec3 ambient  = vec3(1.0) * material.ambient;
vec3 diffuse  = vec3(1.0) * (diff * material.diffuse);
vec3 specular = vec3(1.0) * (spec * material.specular); 

따라서 객체의 각 재질 속성은 광원의 각 구성 요소에 대해 최대 강도로 반환됩니다. 이러한 vec3(1.0) 값은 각 광원에 대해 개별적으로 영향을 줄 수도 있으며, 일반적으로 이것이 우리가 원하는 것입니다. 현재 객체의 주변광 구성 요소가 큐브의 색상에 완전히 영향을 미치고 있습니다. 주변광 구성 요소는 최종 색상에 그렇게 큰 영향을 미치지 않아야 하므로 광원의 주변광 강도를 더 낮은 값으로 설정하여 주변광 색상을 제한할 수 있습니다.

vec3 ambient = vec3(0.1) * material.ambient;  

우리는 같은 방식으로 광원의 확산광과 반사광 강도를 조절할 수 있습니다. 이는 이전 장에서 했던 것과 매우 유사합니다. 즉, 각 조명 구성 요소에 개별적으로 영향을 줄 수 있는 몇 가지 조명 속성을 이미 만들었다고 할 수 있습니다. 이제 조명 속성을 위한 재질 구조와 유사한 것을 만들고자 합니다.

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;  

광원은 주변광, 확산광, 반사광 성분에 대해 서로 다른 강도를 가집니다. 주변광은 주변 색상이 너무 두드러지지 않도록 일반적으로 낮은 강도로 설정됩니다. 광원의 확산광 성분은 일반적으로 원하는 색상(대개 밝은 흰색)으로 설정됩니다. 반사광 성분은 일반적으로 최대 강도로 빛나도록 vec3(1.0)으로 유지됩니다. 또한 구조체에 광원의 위치 벡터를 추가했음을 참고하십시오.

머티리얼 유니폼과 마찬가지로 프래그먼트 셰이더도 업데이트해야 합니다.

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); // 산란광은 약간 어둡게
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); 

이제 빛이 물체의 재질에 미치는 영향을 조절했으므로 이전 장의 결과물과 매우 유사한 시각적 결과를 얻을 수 있습니다. 하지만 이번에는 조명과 물체의 재질을 완벽하게 제어할 수 있게 되었습니다.

현재로서는 사물의 시각적 요소를 바꾸는 것은 비교적 쉽습니다. 하지만 좀 더 다채롭게 만들어 볼까요!

다양한 빛 색상

지금까지는 빛의 색상을 사용하여 흰색에서 회색, 검은색까지 다양한 색상을 선택함으로써 각 구성 요소의 강도만 조절하고, 물체의 실제 색상에는 영향을 주지 않았습니다. 이제 빛의 속성에 쉽게 접근할 수 있게 되었으므로 시간에 따라 빛의 색상을 변경하여 매우 흥미로운 효과를 만들어낼 수 있습니다. 프래그먼트 셰이더에 모든 설정이 완료되어 있으므로 빛의 색상을 변경하는 것은 간단하며, 즉시 독특한 효과를 만들어낼 수 있습니다. (아래 이미지를 클릭해 확인하세요.)

보시다시피, 빛의 색깔은 물체의 색상 표현에 큰 영향을 미칩니다. 빛의 색깔은 물체가 반사할 수 있는 색깔에 직접적인 영향을 주기 때문에(색깔 편에서 배웠듯이) 시각적 표현에 상당한 영향을 미칩니다.

sin과 glfwGetTime을 통해 빛의 주변 색상과 확산 색상을 변경하면 시간에 따라 빛의 색상을 쉽게 바꿀 수 있습니다.

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);

다양한 조명 및 재질 값을 적용해 보고 시각적 결과에 어떤 영향을 미치는지 실험해 보세요. 애플리케이션의 소스 코드는 여기에서 확인할 수 있습니다.

연습 문제

  • 조명 색상을 변경하면 조명 큐브 객체의 색상도 변경되도록 만들 수 있나요?
  • 이 장의 시작 부분에서 본 것처럼 각각의 재질을 정의하여 실제 세계의 물체를 시뮬레이션할 수 있습니까? 표의 주변광 값은 확산광 값과 동일하지 않습니다. 광 강도를 고려하지 않았기 때문입니다. 값을 올바르게 설정하려면 모든 광 강도를 vec3(1.0)으로 설정해야 동일한 출력을 얻을 수 있습니다. (청록색 플라스틱 용기의 해결 방법)

Comments