Home [learn-opengl] Lighting: Light casters
Post
Cancel

[learn-opengl] Lighting: Light casters

Light casters

  • 여러 유형의 빛들이 존재.
    • 오브젝트에 빛을 캐스트하는 광원을 light caster라고 한다.

Directional Light

  • 멀리 있는 광원에서 나오는 광선은 거의 서로 평행하다.
    • 광원이 무한히 멀리 있으면 모두 동일한 방향에서 온다.
    • 이를 directional light라고 부른다

light_casters_directional

  • 모든 광선들은 평행함 => 광원의 위치 정보 필요 없음
  • 대신 방향벡터가 필요.

  • light.direction 벡터를 추가.
    • 일반적으로 directional light를 광원으로부터 fragment로 향하는 방향으로 나타내는것을 선
    • 그러므로 부호를 바꾸어 반대 방향으로 설정해야함
    • 즉, 광원을 향하는 벡터
    • 추가적으로 정규화도 해줘야한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Light {
    // vec3 position; // no longer necessary when using directional lights.
    vec3 direction;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
//[...]
void main()
{
  vec3 lightDir = normalize(-light.direction);
  //[...]
}
  • lightDir벡터는 diffuse, specular 계산에 사용된다.

  • 좌표시스템의 예제인 여러 박스가 있는 씬을 사용할것이다.

    • 10개의 다른 박스의 위치를 정의했었고. 적절한 변환을 가지고 있는 model 행렬을 생성했음.
1
2
3
4
5
6
7
8
9
10
for(unsigned int i = 0; i < 10; i++)
{
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, cubePositions[i]);
    float angle = 20.0f * i;
    model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
    lightingShader.setMat4("model", model);

    glDrawArrays(GL_TRIANGLES, 0, 36);
}
  • 또한 광원의 방향을 지정해야함.
    • 지금은 광원으로부터 나오는 아래쪽 방향.
1
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);

vec4 타입으로 정의할 수 있다. 이 때 위치 벡터는 ‘w’ 요소를 1.0 으로 설정하여 변환이 올바르게 적용되도록 해야하지만, 방향 벡터는 이동변환이 영향을 미치지 않아야하기 때문에 w요소를 0.0으로 정의해야한다. (아핀변환 점과 벡터의 차이)

방향벡터들은 vec4(-0.2f, -1.0f, -0.3f, 0.0f) 과 같이 표현된다. 이는 빛의 유형을 쉽게 판단할 수 있게해준다. w 요소가 1.0이면 빛의 위치벡터이고, 0.0이면 방향벡터이므로 이를 아래와 같이 사용할 수 있다.

1
2
3
4
if(lightVector.w == 0.0) // note: be careful for floating point errors
  // do directional light calculations
else if(lightVector.w == 1.0)
  // do light calculations using the light's position (as in previous chapters)

이는 실제로 광원이 directional light인지 positional light인지 판단하기위해 OpenGL 구버전에서 사용된적이 있었음.

light_casters_directional_light

전체 코드

Point Lights

  • Directional light들은 전체 Scene을 밝히는 전반적인 빛에 사용되는 것이 좋다.

  • 일반적으로 Scene 전체에 산란되는 여러 point light들도 사용한다.

    • world의 어딘가에 주어진 위치를 갖는 광원
    • 모든 방향으로 빛을 밝힘.
    • 거리에 따라 광선은 희미
    • 전구나 횃불같음

  • 이전의 내용들이 바로 point light의 간단한 버전

    • 이전의 것은 거리 상관없이 구현했음.
    • 보통 이와 같이 구현하지 않고, 광원과 가까이에 있는 특정 영역만 밝힘.
  • 거리에 따른 공식을 사용해야한다.

    • 광원과 가까이 있는 컨테이너들과 비교해서 멀리 떨어진 컨테이너는 약간의 빛만 받기를 원한다.

Attenuation

  • 광선이 지나가는 거리에 따라 빛의 세기를 줄이는 것.
  • 다음 공식은 광원과 fragment 사이의 거리를 기반으로 하는 감쇠값을 계산한다.

    • 이 값을 나중에 빛의 세기 벡터에 곱할 것이다.
    \[\begin{equation} F_{att} = \frac{1.0}{K_c + K_l * d + K_q * d^2} \end{equation}\]
    • d: fragment에서 광원까지의 거리
    • 설정가능한 상수항 K_c, linear(1차)항인 K_l, quadratic(2차)항인 K_q
      • 상수항은 일반적으로 1.0을 유지. 최종 결과의 분모를 1보다 작게 만들지 않도록하기위해 존재. 그렇지 않으면 특정 거리에서 빛의 세기를 증폭시켜 원하는 효과를 낼 수 없다.
      • 1차항: 거리 값과 곱해짐. 1차원 방법으로 세기를 감소시킴
      • 2차항: 거리의 사분면과 곱해짐. 2차원적으로 광원의 세기를 감소. 거리가 가까울 때 이 2차항은 1차항에 비해 덜 중요해짐. 거리가 멀때는 1차항보다 중요해짐.
  • 2차항 때문에 거리가 충분히 커질 때까지 빛의 세기는 1차원적인 방법으로 빠르게 감소됨.
    • 빛이 가까운 범위내에 있을 때 상당히 밝고, 거리에따라 빠르게 어두워지며, 결국에는 점점 느린 속도로 어두워지게 되는 효과이다.
  • 다음 그래프는 이러한 감쇠효과를 보여준다.

  • 이 빛은 거리가 작을 때 높은 세기를 가지고, 거리가 커질수록 세기가 상당히 많이 줄어, 느리게 0으로 다가간다.

Choosing the right values

  • 3가지 항에대해 올바른 값을 설정해야한다.
    • 케이스마다 다르며, 경험이 필요하다.
    • 아래의 표는 특정한 반지름(거리)를 커버하는 현실적인 광원을 시뮬레이션하기 위해 가질 수 있는 항들의 값을 보여준다.
DistanceConstantLinearQuadratic
71.00.71.8
131.00.350.44
201.00.220.20
321.00.140.07
501.00.090.032
651.00.070.017
1001.00.0450.0075
1601.00.0270.0028
2001.00.0220.0019
3251.00.0140.0007
6001.00.0070.0002
32501.00.00140.000007
  • 위에서 볼수 있는 정보
    • 상수항은 항상 1.0이다.
    • 1차항, 2차항은 거리가 클수록 꽤 작은 값을 가짐

Implementing attenuation

  • 위의 항들을 이제 코드에 넣어보자
    • constant, linear, quadratic
1
2
3
4
5
6
7
8
9
10
11
struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

    float constant;
    float linear;
    float quadratic;
};
  • 그다음 값을 설정한다.
1
2
3
lightingShader.setFloat("light.constant",  1.0f);
lightingShader.setFloat("light.linear",    0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);
  • fragment shader에서 attenuation을 구현해야한다.
    • 공식을 그대로 사용하여 계산
    • 거리는 간단히 벡터 뺄셈으로 얻는다.
1
2
3
float distance    = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance +
    		    light.quadratic * (distance * distance));
  • 그런다음 이 감쇄값을 컬러에 곱하여 lighting 계산에 포함시킨다.
    • Ambient요소를 건드리지 않고, ambient lighting이 거리에 따라 어두워지지 않게 할 수 있음.
    • 하지만 한 개 이상의 광원을 사용하게된다면, ambient 컴포넌트들은 쌓이게됨.
    • 이 경우 ambient lighting에도 attenuation을 적용해야함.
    • 환경에 따라 최적의 조건으로 설정해야함.
1
2
3
ambient  *= attenuation;
diffuse  *= attenuation;
specular *= attenuation;
  • 이제 다음과 같은 결과가 나온다.

전체소스

  • Point light: lighting 계산에 적용되는 위치와 attenuation을 설정할 수 있는 광원

Spot light

  • 이는 환경의 어딘가에 위치한 광원이다.

    • 모든 방향으로 광선을 쏘지 않고, 특정 방향으로만 씀.
    • 결과적으로 spotlight 방향의 특정 반지름 내부에 있는 오브젝트만 밝아지고 나머지는 어두워짐.
  • OpenGL의 spotlight는 world-space에서의 위치, 방향, cutoff 각도로 나타낸다.

    • 각 fragment에 대해 spotlight의 cutoff방향 사이(원뿔 내부)에 있는지 계산한다.
    • 그리고 사이에 있으면 밝힌다.(아래 그림 참고)

    • LightDir: frag에서 광원까지의 방향 벡터
    • SpotDir: spotlight가 겨누고 있는 방향 벡터
    • Phi ϕ:: spotlight의 반지름 지정하는 cutoff각도, 이 각 외부에 있는 모든것들은 빛을 받지 못함.
    • Theta θ: LightDir 벡터와 SpotDir 벡터 사이의 각도. spotlight의 내부에 있기 때문에 이는 Phi 값보다 작아야함.
  • 계산에서 먼저, LightDir벡터와 SpotDir벡터를 내적하여 이를 cutoff 각 과 비교해야한다.

Flash Light

  • Flashlight는 viewer의 위치에 있고, 플레이어의 관점을 향해 똑바로 겨누고 있는 spotlight이다.

    • 위치와 방향이 플레이어의 위치와 방향에 따라 계속해서 업데이트된다.
  • 이를 위해 필요한 값들은 spotlight의 위치 벡터(빛 방향 계산용), 방향 벡터, cutoff 각도이다.

1
2
3
4
5
6
struct Light {
    vec3  position;
    vec3  direction;
    float cutOff;
    //...
};
  • 그다음 코드에서 값을 넘겨주자
1
2
3
lightingShader.setVec3("light.position",  camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff",   glm::cos(glm::radians(12.5f)));
  • Cutoff 값에서 cos 값으로 넘겨준것을 주의해야한다.

    • LightDirspotDir 벡터의 내적을 계산, 내적은 각이 아닌 cos 값을 반환하므로.
    • cos값끼리 직접 비교하기 위함.

      역연산을 통해 각을 구하는것은 비싼 연산이다. 그래서 cos값으로 전달함.

  • 남은 것은 theta θ값을 계산하고, 이를 cutoff 값과 비교하여 spotlight의 내부에 있는지 판단한다.

1
2
3
4
5
6
7
8
9
10
11
12
float theta = dot(lightDir, normalize(-light.direction));

if(theta > light.cutOff)
{
  // do lighting calculations
}
else
{
  // else, use ambient light so scene isn't completely dark outside the spotlight.
  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);
}
  • 먼저 lightDir 벡터와 부호를 바꾼 direction 벡터의 내적을 계산한다.

    • 부호 바꾼이유: 광원을 향하는 벡터가 필요하기 때문.
    • 주의: 관련 벡터들이 정규화되어 있어야한다.

    cosine 값으로 비교하므로 theta가 cutoff값보다 커야한다. 위에서 설정한 각도가 12.5 이므로 0~12.5 의 범위 안에 들어와야한다. 아래 코사인 그래프를 보면, 이는 0.9979 와 1.0 내부에 들어와야 하기 때문.

  • 이제 다음과 같은 결과가 나온다.

전체 코드

  • spotlight의 외곽선을 좀 더 부드럽게 해야한다.
    • spotlight는 외곽선에서 점차적으로 빛을 감소시킨다.

Smooth/Soft edges

  • 부드러운 외곽선을 가진 spotlight를 생성하기 위해, inner 원뿔과 outer 원뿔을 가지도록 해야한다.

    • inner: 앞서 정의한 원뿔
    • outer: 좀 더 큰 원뿔로, 점점 빛이 어두워지는 원뿔
  • outer 원뿔 생성

    • spotlight의 방향 벡터와 외부 원뿔의 벡터(반지름)사이의 각에 대한 cosine 값을 정의하면된다.
  • fragment가 inner과 outer 사이에 있으면 빛의 세기 값을 0.0 ~ 1.0 사이로 계산한다.

    • inner에 있으면 빛의 세기는 1.0
    • outer 밖에 있으면, 0.0
  • 다음 공식을 사용하여 빛의 세기를 계산할 수 있다.

    • ϵ (epsilon): inner (ϕ)원뿔과 outer (γ) 원뿔의 차이 (ϵ=ϕ−γ)
    • 최종 결과인 I 값은 현재 fragment의 spotlight 빛의 세기이다.
\[\begin{equation} I = \frac{\theta - \gamma}{\epsilon} \end{equation}%\]
  • 이 공식에 관한 표는 다음과 같다.
θθ in degreesϕ (inner cutoff)ϕ in degreesγ (outer cutoff)γ in degreesϵI
0.87300.91250.82350.91 - 0.82 = 0.090.87 - 0.82 / 0.09 = 0.56
0.9260.91250.82350.91 - 0.82 = 0.090.9 - 0.82 / 0.09 = 0.89
0.97140.91250.82350.91 - 0.82 = 0.090.97 - 0.82 / 0.09 = 1.67
0.83340.91250.82350.91 - 0.82 = 0.090.83 - 0.82 / 0.09 = 0.11
0.64500.91250.82350.91 - 0.82 = 0.090.64 - 0.82 / 0.09 = -2.0
0.966150.997812.50.95317.50.9978 - 0.953 = 0.04480.966 - 0.953 / 0.0448 = 0.29
  • 위 식에서 볼 수 있듯이, inner cosine outer cosine 사이를 theta 값을 기반으로 보간하고 있다.

  • 이제 우리는 빛의 세기 값을 가지고 있지만, 이 값은 spotlight outer에 있으면 음수 값을 가지고 inner에 있을 때 1.0보다 큰 값을 가지게 된다.

    • 이 값을 적절하게 고정시킨다면 fragment shader에 if문을 제거할 수 있다.
    • 그후, 계산된 세기 값을 light 컴포넌트들에 곱해야함.
1
2
3
4
5
6
7
8
float theta     = dot(lightDir, normalize(-light.direction));
float epsilon   = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0);
...
// we'll leave ambient unaffected so we always have a little light.
diffuse  *= intensity;
specular *= intensity;
...
  • 여기에서는 보여주지는 않았지만, outerCutOff 값을 추가하고 uniform 값을 설정하고,inner cutoff를 12.5, outer cutoff을 17.5로 설정했다.

전체 코드

  • 이러한 flashlight/spotlight 타입의 램프는 공포게임에 사용하기 좋다.
    • directional light와 point light를 조합하면 환하게 만들 수있다.

출처

Light-casters

Light-casters

This post is licensed under CC BY 4.0 by the author.

[learn-opengl] Lighting: Lighting maps

[shader] 노트