본문 바로가기

Game/Graphics

Learn OpenGL - Lighting : Multiple lights

link : https://learnopengl.com/Lighting/Multiple-lights


Multiple lights


이전 튜토리얼에서 우리는 OpenGL에서 조명에 대해 많은 것을 배웠습니다.


우리는 Phong 음영, 재료, 조명 맵 및 다양한 유형의 조명 캐스터에 대해 배웠다.


이 튜토리얼에서는 6개의 활성 광원으로 완전히 조명된 장면을 만들어 이전에 습득한


모든 지식을 결합합니다. 우리는 방향 광원으로 태양과 같은 빛을 시뮬레이션 하려고 한다.


장면 전체에 흩어져있는 4점의 조명과 손전등을 추가할 것이다.



 장면에서 광원을 두 개 이상 사용하려면 조명 계산을 GLSL 함수로 캡슐화해야한다.


그 이유는 각각의 조명 유형이 서로 다른 계산을 필요로 하는 다중 조명을 사용해 조명 계산을


수행하고자 할 때 코드가 빠르게 불쾌해지기 때문이다. 주 기능에서만 이러한 모든 계산을 수행하면


코드가 빨리 이해하기 어려워진다.



 GLSL의 함수는 C 함수와 같다. 우리는 함수 이름, 리턴 타입을 가지고 있으며, 아직 함수가 main 함수 전에


선언되지 않았다면 코드 파일의 맨 위에 프로토 타입을 선언해야한다. 방향 유형 조명, 점 광원 및 스포트라이트와


같이 각 조명 유형에 대해 다른 기능을 생성한다.



 장면에서 여러 광원을 사용하는 경우 일반적으로 접근 방식은 다음과 같다. 조각의 출력 색상을 나타내는


단일 색상 벡터가 있다. 각 조명에 대해 조각의 빛 기여 색상이 조각의 출력 색상 벡터에 추가된다.


따라서 장면의 각 라이트는 앞서 언급한 조각에 대한 개별적인 영향을 계산하고, 최종 출력 색상에 기여한다.


일반적인 구조는 다음과 같다:

out vec4 FragColor;
  
void main()
{
  // define an output color value
  vec3 output = vec3(0.0);
  // add the directional light's contribution to the output
  output += someFunctionToCalculateDirectionalLight();
  // do the same for all point lights
  for(int i = 0; i < nr_of_point_lights; i++)
  	output += someFunctionToCalculatePointLight();
  // and add others lights as well (like spotlights)
  output += someFunctionToCalculateSpotLight();
  
  FragColor = vec4(output, 1.0);
}  

 실제 코드는 구현마다 다를 수 있지만 일반적인 구조는 동일하게 유지된다. 우리는 광원당 영향을 계산하고,


결과 컬러를 출력 컬러 벡터에 추가하는 몇 가지 함수를 정의한다. 예를 들어 2개의 광원이 조각에 가까운 경우,


그 결합된 기여는 단 하나의 광원에 의해 조각이 조명되는 것보다 더 밝은 조각을 초래할 것이다.







Directional light


 우리가 하고자 하는 것은 조각 그림자 쉐이더에서 Directional Light가 상응하는 조각에 미치는 영향을 계산하는


함수를 정의하는 것이다. 몇 가지 매개 변수를 사용하고 계산된 Directional Light 색을 반환하는 함수이다.



 먼저 Directional Light에 대해 최소한으로 필요한 변수를 설정해야한다.


DirLight라는 구조체에 변수를 저장하고, 이를 uniform으로 정의할 수 있다. 필요한 변수는 이전 튜토리얼에서 익숙해야한다:

struct DirLight {
    vec3 direction;
  
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};  
uniform DirLight dirLight;

다음 DirLight 유니폼을 다음 프로토 타입이 있는 함수로 전달할 수 있다:

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); 

C 및 C++과 마찬가지로 함수를 호출하려면 함수가 호출자의 행 번호 앞에 정의되어야한다. 이 경우 main 함수 아래에 함수를 정의해 이 요구 사항이 충족되지 않도록하는 것이 좋다. 따라서 우리는 C에서와 같이 함수의 프로토타입을 main 함수 위에 선언한다.

 이 함수는 DirLight 구조체와 계산이 필요한 다른 두 벡터가 필요함을 알 수 있다. 이전 튜토리얼을 성공적으로 완료했다면


이 함수의 내용은 놀랄 일이 아니다:

vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir)
{
    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);
    // 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));
    return (ambient + diffuse + specular);
}  

 우리는 기본적으로 이전 튜토리얼의 코드를 복사한 다음 함수 인수로 주어진 벡터를 사용해 방향 광원의 기여 벡터를


계산했다. 결과로 생성되는 ambient, diffuse 및 specular 효과는 단일 색상 벡터로 반환된다.







Point light


 Directional light와 마찬가지로 Point Light가 감쇠를 포함하여 주어진 조각에 미치는 영향을 계산하는 함수를


정의하고자한다. Directional light와 마찬가지로 Point Light에 필요한 모든 변수를 지정하는 구조체를 정의하려고 한다:

struct PointLight {    
    vec3 position;
    
    float constant;
    float linear;
    float quadratic;  

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};  
#define NR_POINT_LIGHTS 4  
uniform PointLight pointLights[NR_POINT_LIGHTS];

 보시다시피, GLSL에서 pre-processor 지시문을 사용해 장면에서 원하는 점 광원 수를 정의했다.


그런 다음 이 NR_POINT_LIGHTS 상수를 사용해 PointLight 구조체의 배열을 만든다.


GLSL의 배열은 C 배열과 같으며 두 개의 대괄호를 사용해 만들 수 있다. 지금 우리는 4개의 PointLight 구조체를


가지고 데이터를 채운다.

우리는 또한 모든 다른 조명 유형에 필요한 모든 변수를 포함하고 각 함수에 대해 해당 구조체를 사용하고 단순히 필요하지 않은 변수를 무시하는 하나의 큰 구조체를 정의할 수 있다. 그러나 필자는 개인적으로 현재의 접근법을 좀 더 직관적으로 생각하고, 코드의 몇 줄을 추가하면 모든 조명 유형에 모든 변수가 필요하지 않기 때문에 메모리를 절약할 수 있다.

 Point Light 기능의 프로토 타입은 다음과 같다:

vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);  

 이 함수는 필요한 모든 데이터를 인수로 사용하고, 이 specific light point가 조각에 미치는 색상 기여도를 나타내는 vec3를


반환한다. 다시 말하지만 지능적인 복사 및 붙여넣기는 다음과 같은 기능을 한다:

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 / (light.constant + light.linear * distance + 
  			     light.quadratic * (distance * distance));    
    // 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;
    diffuse  *= attenuation;
    specular *= attenuation;
    return (ambient + diffuse + specular);
} 

 이와 같은 함수에서 이 기능을 추상화하면 불쾌한 중복 코드를 사용하지 않고도 다중 점 광원의 조명을 쉽게


계산할 수 있다는 이점이 있다. main 함수에서 우리는 간단히 각 점 광원에 대해 CalcPointLight를


호출하는 점 광원 배열을 반복하는 루프를 만든다.





Putting it all together


 Directional light와 Point light를 위한 함수 정의를 했으므로 main 함수에 모두 넣을 수 있다.

void main()
{
    // properties
    vec3 norm = normalize(Normal);
    vec3 viewDir = normalize(viewPos - FragPos);

    // phase 1: Directional lighting
    vec3 result = CalcDirLight(dirLight, norm, viewDir);
    // phase 2: Point lights
    for(int i = 0; i < NR_POINT_LIGHTS; i++)
        result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);    
    // phase 3: Spot light
    //result += CalcSpotLight(spotLight, norm, FragPos, viewDir);    
    
    FragColor = vec4(result, 1.0);
}

 각 광원 유형은 모든 광원이 처리 될 때까지 결과 출력 색상에 기여도를 더한다.


결과 색상에는 장면의 모든 광원의 색상 효과가 결합되어 있다. 원하는 경우 스포트라이트를


구현하고 출력 색상에도 효과를 추가할 수 있다. CalcSpotLight 함수는 독자를 위한 연습으로 남겨둔다.



 Directional light 구조체의 유니폼 설정은 너무 익숙하지 않아야하지만 point light 유니폼이 이제


pointlight 구조체의 배열이므로 point light의 균일한 값을 설정하는 방법이 궁금할 수 있다.


이것은 이전에 논의한 것이 아니다.



 다행스럽게도 우리에게는 너무 복잡하지 않다. 구조체 배열의 유니폼을 설정하려면 단일 구조체의 


유니폼 설정하는 것처럼 작동한다. 이번에는 유니폼의 위치를 쿼리할 때 적절한 인덱스를 정의해야한다:

lightingShader.setFloat("pointLights[0].constant", 1.0f);

 여기에서는 pointLights 배열의 첫번째 PointLight 구조체를 인덱싱하고 상수 변수의 위치를 검색한다.


불행히도 우리는 4점의 조명에 대해 모든 유니폼을 수동으로 설정해야한다는 것을 의미한다.


이는 포인트 라이트에 대해 28회의 통일된 호출을 수행하기 때문에 조금 지루하다.


유니폼을 설정하는 포인트 라이트 클래스를 정의함으로써 약간의 추상화를 시도할 수는 있지만


결국에는 모든 라이트의 균일한 값을 이 방식으로 설정해야한다.



 우리는 또한 각 점등에 대한 위치 벡터를 정의해야하므로 장면 주위로 약간 확산시켜야한다는 것을


잊지말아라. 우리는 pointlight의 위치를 포함하는 다른 glm::vec3 배열을 정의할 것이다:

glm::vec3 pointLightPositions[] = {
	glm::vec3( 0.7f,  0.2f,  2.0f),
	glm::vec3( 2.3f, -3.3f, -4.0f),
	glm::vec3(-4.0f,  2.0f, -12.0f),
	glm::vec3( 0.0f,  0.0f, -3.0f)
};  

 그런 다음 pointLights 배열에서 해당 PointLight 구조체를 인덱싱하고 방금 정의한 위치 중 하나로


position 속성을 설정한다. 또한, 이제는 1 대신에 4개의 라이트 큐브를 그려야한다.


우리가 컨테이너에서 했던 것처럼 라이트 오브젝트 각각에 대해 다른 모델 매트릭스를 작성하기만 하면 된다.



 손전등을 사용한다면 모든 결합된 조명의 결과는 다음과 같다:



 하늘의 어딘가에 태양과 같은 지구상의 빛이 있는것처럼 보일 수 있다. 장면 전체에 4개의 불빛이 흩어져 있고,


플레이어의 관점에서 손전등을 볼 수 있다. 꽤 깔끔한거 같다.



 이미지는 모든 이전 튜토리얼에서 사용한 기본 광원 속성으로 설정된 모든 광원을 보여준다.


그러나 이 값으로 놀아보면 꽤 재미있는 결과를 얻을 수 있다. 아티스트 및 레벨 편집자는 대개 대형 편집기에서


이러한 조명 변수를 모두 조정해 조명이 환경과 일치하는지 확인한다. 방금 작성한 간단한 조명 환경을


사용하면 조명을 조정해 흥미로운 영상을 만들 수 있다:



 또한, 조명을 더 잘 반영하도록 선명한 색상을 변경했다. 조명 매개 변수 중 일부를 조정하면 완전히 다른 분위기를


만들 수 있음을 알 수 있다.



 지금까지는 OpenGL에서 조명에 대해 잘 이해하고 있어야한다. 지금까지의 지식으로 우리는 이미 재밌고


시각적으로 풍부한 환경과 분위기를 조성할 수 있다. 모든 다른 값으로 놀아보고 자신만의 분위기를 만들어 보아라.





** ** ** ** ** ** ** ** ** **


wow 짧은 챕터여서 다행이었다. 라이팅은 눈에 바로바로 결과물이 나와서 제일 재밌다.


내일은 마지막 알고리즘 강의날! 빨리 끝내고 광주 내려가고 싶다. 광주 내려가면 공모전 하나 끝내고... 공부에 더 집중해야겠다.

'Game > Graphics' 카테고리의 다른 글

Learn OpenGL - Model Loading : Assimp  (0) 2018.08.24
LearnOpenGL - Glossary  (0) 2018.08.24
Learn OpenGL - Lighting : Light casters  (0) 2018.08.21
Learn OpenGL - Lighting : Lighting maps  (0) 2018.08.17
Learn OpenGL - Lighting : Materials  (0) 2018.08.16