OpenGL Study

OpenGL "Multiple Lights"

lvneux 2023. 1. 24. 22:29

[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 포함)