Multiple lights
이번에는 6개의 광원들을 생성한다.
- directional light로 태양과 같은 빛을 시뮬레이션
- 4개의 point light를 사용하여 scene 전체에 빛을 산란
- flashlight 추가
하나 이상의 광원을 scene에 사용하기 위해, lighting 계산을 GLSL의 함수로 작성할 것이다.
- main에 다 계산하면 코드 읽기 불편하다.
- glsl의 함수는 C와 비슷(프로토타입, 리턴, 이름 등)
여러가지 light를 사용할 때 접근 방식은 일반적으로 다음과 같이 한다.
- fragment의 출력 칼라를 나타내는 하나의 컬러 벡터.
- 각 light들을 위해 이 fragment에 light가 기여하는 컬러를 이 fragment의 출력 컬러에 더함.
- scene의 각 light는 fragment에 미치는 효과를 계산하고 최종 출력 컬러에 기여하게됨.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
실제 코드는 구현에 따라 다르지만 일반적인 구조는 동일함
- 광원에 대한 효과를 계산하는 여러가지 함수들을 정의하고, 그 결과 컬러를 출력 컬러 벡터에 더한다.
Directional light
fragment shader에 함수를 정의하는 것
- 이 함수는 해당 fragment에 대한 directional light의 기여도를 계산한다.
- 몇 가지의 파라미터를 받고 계산된
directional lighting
를 리턴함
먼저 필요한 변수들을 설정
DirLight
구조체에 필요한 변수들을 담고, uniform으로 선언
1
2
3
4
5
6
7
8
struct DirLight {
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
uniform DirLight dirLight;
- 그런다음
dirLight
를 함수에 넘겨준다.
1
2
vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir);
C언어처럼 프로토타입을 main 함수 위에 두고 main 밑에 함수 작성 가능
이 함수는
DirLight
타입과 두개의 벡터를 필요로한다.- 이 함수의 내용은 다음과 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
- 이전에 작성한 코드를 복사한것.
- 하나의 컬러 벡터로 리턴
Point light
- 마찬가지로 attenuation과 함께 함수로 정의
1
2
3
4
5
6
7
8
9
10
11
12
13
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에서 전처리기로 선언한 것을 볼 수 있다.
프로토 타입은 다음과 같다.
- 이 함수는 필요한 모든 데이터를 파라미터로 받고, fragment에 대한 특정한
point light
의 기여 컬러를 나타내는vec3
을 리턴한다.
- 이 함수는 필요한 모든 데이터를 파라미터로 받고, fragment에 대한 특정한
1
vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir);
- 아래와 같은 함수를 생성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
}
- 이 함수를 반복문으로 호출하여 여러개의 point light를 계산할 수 있다.
Putting it all together
- 이제 다음과 같이 계산할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
여기서 함수안에 많은 중복된 계산들이 있다.(reflect vecto, specular, diffuse, sampling the material textures). 이런 코드들이 최적화할 수 있는 부분이다.
모든 광원이 처리될 때까지 각 light 타입은 그들의 기여도를 최종 출력 컬러에 더한다.
- 최종 컬러는 scene의 모든 광원들의 컬러 효과를 포함하고 있다.
point light uniform 은 배열이기 때문에 여기서 잠깐 설명한다.
- 하나의 struct uniform을 설정하는 것과 비슷하다.
1
lightingShader.setFloat("pointLights[0].constant", 1.0f);
- 하지만 이렇게 하면 코드가 길어지니 추상화할 수 있지만, 결국에는 모든 light들의 uniform을 설정해야함.
또한 point light들의 위치 벡터를 정의해서 scene에 배치할 수 있다.
1
2
3
4
5
6
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개의 light, flashlight는 플레이어 시점을 기준으로 보이는것을 볼 수 있다.