OpenGL "Multiple Lights"
[MULTIPLE LIGHTS]
1. Before
- 이번 파트에서는 지금까지 배웠던 것들을 활용해 6개의 활성화된 광원들로 완전히 비춰진 scene을 생성할 것이다
: dirextional light로 태양과 같은 빛을 시뮬레이션 하고 4개의 point light를 사용하여 scene 전체에 빛을 산란하고 flashlight도 추가할 것이다
- 하나 이상의 광원을 scene에 사용하기 위해 lighting 계산을 GLSL의 functions에 캡슐화해야한다
: GLSL의 function은 C에서의 function과 유사하다 (directional lights, point lights, spotlights 각 타입에 대한 함수를 생성한다)
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);
}
- 여러가지 light를 사용할 때의 접근방식은 아래 방식과 같이 한다 (위 코드는 이 방식의 일반적인 구조이다)
: fragment의 출력 색상을 나타내는 하나의 color vector를 가지고, 각 light를 위해 해당 fragment에 light가 기여하는 색상을 fragment 출력 색상에 더한다
: scene의 각 light는 앞서 사용한 fragment에 미치는 효과를 계산하고 최종 출력 색상에 기여하게 된다
2.Directional Light
- fragment shader에 함수를 정의한다
- directional light function은 해당 fragment에 대한 directional light의 기여도를 계산한다
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
- directional light에 필요한 변수들을 DirLight struct에 저장한다
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
- 프로토타입으로 DirLight uniform을 함수에 전달한다 (main 전에 선언)
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);
}
- function의 parameter로 주어진 vector를 directional light의 기여vector를 계산하는 데에 사용한다
: 결과적인 ambient, diffuse, specular 기여도는 단일 color vector로 return 된다
3. Point Light
- fragment shader에 함수를 정의한다
- Point light funcrtion은 해당 fragment에 대한 point light의 기여도를 계산한다
: attenuation을 사용한다
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];
- point light에 필요한 변수들을 PointLight struct에 저장한다
- scene에 배치할 point light의 개수를 GLSL에서 전처리기로 선언했다
: 선언한 NR_POINT_LIGHTS 상수를 PointLight struct의 배열을 생성하는 데에 사용했다
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
- 프로토타입으로 PointLight uniform을 함수에 전달한다 (main 전에 선언)
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);
}
- 계산에 필요한 모든 data를 parameter로 받고 fragment에 대한 특정 point light의 기여 색상을 나타내는 vec3를 return한다
4. Putting It All Together
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);
}
- 앞서 정의한 함수들을 main 안에 넣는다
- 모든 광원이 처리될 때까지 각 light type은 각자의 기여도를 최종 출력 색상에 더한다
: 최종 색상은 scene에 결합된 모든 광원들의 색상 효과를 포함하고 있다 (spotlight도 구현하여 추가할 수 있다)
lightingShader.setFloat("pointLights[0].constant", 1.0f);
- struct 배열에 대한 uniform을 설정하는 것은 단일 struct uniform을 설정하는 것과 유사하다
: uniform의 location에 대한 적절한 인덱스를 정의해야한다
- pointLights 배열의 첫 번째 PointLight struct를 인덱싱하여 내부적으로 1.0으로 설정한 constant 변수의 location을 얻고 있다
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)
};
- 4개의 point light 각각에 대한 위치 vector를 정의해야하므로 scene을 중심으로 pointt light를 분산시킨다
- 이후 pointLights 배열에서 해당 PointLight struct를 인덱싱 하고, 위치 속성을 지금 정의한 위치 중 하나로 설정한다
- point light가 4개이므로 컨테이너를 여러 개 그린 것과 마찬가지로 각 light object에 대한 서로 다른 model 행렬을 생성해 4개의 light cube를 생성한다
5. Result
- 최종 출력 화면이다 (flashlight 포함)