눈팅하는 게임개발자 블로그
OpenGL Lighting 3-6 Multiple Lights 본문
원문 사이트
learnopengl.com/Lighting/Multiple-lights
번역 사이트
heinleinsgame.tistory.com/20?category=757483
Multiple Lights
이전까지 라이팅에 대한 꽤 많은 것들을 살펴보았다.
Phong shading, materials, lighting maps 등에 대해 배웠고 light caster의 여러 유형(디렉션, 포인트, 하이라이트)
에 대해서도 배웠다.
이번에는 지금까지 배웠던 지식들로 6개의 활성화된 광원들로 완전히 비춰진 scene을 생성한다.
directional light로 태양과 같은 빛을 시뮬레이션하고 4개의 point light를 사용하여 scene 전체에 빛을 산란하고
flashlight도 추가한다.
하나 이상의 광원을 scene에 사용하기 위해 lighting 계산을 GLSL의 functions에 캡슐화해야 한다.
이렇게 하는 이유는 여러가지 빛을 요구된 다른 계산법들로 lighting 계산한다면 끔찍한 코드가 생기기 때문이다.
만약 해당 연산을 main함수 안에서 모두 수행한다면 이해하기 힘든 코드가 작성되버린다.
GLSL의 Functions는 C에서의 함수들과 비슷하다. 함수의 이름과 리턴 타입을 가지고 있고 main 함수 전에 선언되어
있지 않다면 코드 파일의 최상위에 프로토타입을 선언해야 한다.
각 light의 타입에 대한 함수를 따로 만들 것이다.(Direction, point, spotlights)
여러가지 light를 사용할 때 접근 방식은 일반적으로 다음과 같다.
기본적으로 fragment의 출력 컬러를 나타내는 하나의 컬러 벡터를 가진다.
각 light들을 위해 이 fragment에 light가 기여하는 컬러를 이 fragment의 출력 컬러에 더한다.
그리고 scene의 각 light는 앞서 언급한 fragment에 미치는 효과를 계산하고 최종 출력 컬러에 기여하게 된다.
일반적인 구조는 다음과 같다.
out vec4 FragColor;
void main()
{
// 출력 컬러 값을 정의
vec3 output = vec3(0.0);
// Directional light의 기여를 출력에 더합니다.
output += someFunctionToCalculateDirectionalLight();
// 모든 point light들에도 동일하게 수행합니다.
for(int i = 0; i < nr_of_point_lights; i++)
output += someFunctionToCalculatePointLight();
// 다른 light들도 더합니다(spotlight 같은 것들)
output += someFunctionToCalculateSpotLight();
FragColor = vec4(output, 1.0);
}
실제 코드는 구현에 따라 다를 수 있지만 일반적인 구조는 동일하다.
광원에 대한 효과를 계산하는 여러가지 함수들을 정의하고 그 결과 컬러를 출력 컬러 벡터에 더한다.
예를 들어 2개의 광원이 fragment와 가까이에 있다면 그들이 혼합된 기여도는 하나의 광원이 fragment를 비추는 것보다 좀 더 밝게 비춘다.
Directional Light
기본적으로 해야할 일은 fragment shader에 함수를 정의하는 것이다.
이 함수는 해당 fragment에 대한 directional light의 기여도를 계산한다.
몇 가지의 파라미터를 받고 계산된 directional lighting 컬러를 리턴한다.
먼저 directional light에 최소한으로 필요한 변수들을 설정한다.
DirLight라고 불리는 struct에 변수들을 저장하고 이를 uniform으로 선언한다.
필요한 변수들은 이전에 사용했던 것들과 크게 다르지 않다.
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
그 다음 dirLight uniform을 다음과 같이 함수에 넘겨준다.
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
해당 함수를 호출하기 위해서는 main함수의 위쪽에 해당 함수가 선언되어 있어야 한다.
현재 이 함수를 main 함수 밑에 정의하려고 하기 때문에 선언부를 미리 작성한다.
이 함수는 DirLight struct와 2개의 다른 벡터들을 매개변수로 받는 것을 알 수 있다.
Directional light의 계산에 필요한 벡터들이다.
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);
// 결과들을 결합
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);
}
기본적으로 이전의 코드를 복사하여 함수의 파라미터로 주어진 벡터들을
directional light의 기여 벡터를 계산하는 데에 사용한다.
결과 ambient, diffuse, specular 기여도는 하나의 컬러 벡터로 리턴된다.
Point Light
Directional Light와 마찬가지로 주어진 fragment에 대한 point light가 가지는 기여도를 계산할 함수를 정의해야 한다.
attenuation도 마찬가지이다.
directional light와 마찬가지로 point light에 필요한 모든 변수들을 지정하는 struct를 정의한다.
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];
현재 scene에 배치할 point light의 개수를 GLSL에서 전처리기로 선언한 것을 알 수 있다.
그 다음 이 NR_POINT_LIGHTS 상수를 PointLight struct의 배열을 생성하는 데 사용한다.
GLSL에서의 배열은 C 배열과 비슷하다.
2개의 대괄호를 사용하여 배열을 생성할 수 있다.
당장 4개의 PointLight struct를 저장할 수 있다.
Point light 함수의 포맷은 다음과 같다.
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
이 함수는 필요한 모든 데이터를 파라미터로 받고 fragment에 대한 특정한 point light의 기여 컬러를
나타내는 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));
// 결과들을 결합
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);
}
이 기능을 이처럼 함수에 추상화하는 것은 코드 중복을 회피하고 여러 개의 point light들을 쉽게 계산할 수 있다는
장점을 가진다.
main 함수에서는 간단히 각각의 point light에 대해 CalcPointLight함수를 호출하도록 반복문을 만들 수 있다.
Putting it all together
이제 directional light와 point light 함수를 정의하였고 이들을 main 함수에 모두 같이 넣을 수 있다.
void main()
{
// 속성들
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - FragPos);
// 1 단계: Directional lighting
vec3 result = CalcDirLight(dirLight, norm, viewDir);
// 2 단계: Point lights
for(int i = 0; i < NR_POINT_LIGHTS; i++)
result += CalcPointLight(pointLights[i], norm, FragPos, viewDir);
// 3 단계: Spot light
//result += CalcSpotLight(spotLight, norm, FragPos, viewDir);
FragColor = vec4(result, 1.0);
}
모든 광원이 처리될 때까지 각 light 타입은 그들의 기여도를 최종 출력 컬러에 더한다.
최종 컬러는 scene에 결합된 모든 광원들의 컬러 효과를 포함하고 있다.
원한다면 spotlight도 구현하여 출력 컬러에 효과를 추가할 수도 있다.
Directional light를 위한 uniform을 설정하는 것은 이전과 크게 다르지 않다.
하지만 point light들의 uniform 값을 어떻게 설정해야 하는 지에 대해 알아야 한다.
point light uniform은 이제 struct의 배열 값으로서 존재하기 때문이다.
다행히도 이는 그렇게 복잡하지 않다. struct의 배열에 대한 uniform을 설정하는 것은 하나의 struct uniform을 설정하는
것과 비슷하다. 이번에는 uniform의 location에 대한 적절한 인덱스를 정의해야 한다는 것만 다를 뿐이다.
lightingShader.setFloat("pointLights[0].constant", 1.0f);
여기에서 pointLights 배열의 첫 번째 PointLight struct를 인덱싱하여 constant 변수의 location을 얻고 있다.
이는 불행하게도 4개의 각각의 light에 대한 모든 uniform을 일일히 설정해주어야 한다는 것을 의미한다.
이는 28번의 uniform 호출을 발생시킨다. 이를 피하기 위해
uniform을 설정하는 point light 클래스를 정의함으로써 추상화를 시도해 볼 수 있다.
하지만 결국에는 이 방법에서도 모든 light들의 uniform을 설정해야 한다.
또한 point light들의 위치 벡터를 정의해야 scene 주위에 그들을 배치할 수 있다는 것을 잊지 않아야 한다.
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 struct를 인덱싱하여 position 속성을 방금 정의한 위치들 중 하나로 설정한다.
또한 하나가 아닌 4개의 light 큐브를 그려야 한다. 컨테이너들을 여러 개 그린 것과 마찬가지로
간단히 각 light 오브젝트에 대한 서로 다른 model 행렬을 생성한다.
flashlight까지 사용한 모든 light의 결합의 결과는 다음과 같다.
'공부한거 > OpenGL' 카테고리의 다른 글
OpenGL Model Loading 4-2 Mesh (0) | 2020.12.07 |
---|---|
OpenGL Model Loading 4-1 Assimp (0) | 2020.12.07 |
OpenGL Advanced 5-6 Cubemaps (0) | 2020.11.26 |
OpenGL Lighting 3-5 Light casters (0) | 2020.11.04 |
OpenGL Lighting 3-4 Lighting maps (0) | 2020.10.22 |