본문 바로가기

Game/Graphics

Learn OpenGL - Advanced Lighting : Shadow Mapping (2)

link : https://learnopengl.com/Advanced-Lighting/Shadows/Shadow-Mapping


Rendering shadows


 적절하게 생성된 깊이 맵을 사용해 실제 그림자를 생성 할 수 있다. 조각이 그림자에 있는지를 확인하는 코드는 조각 쉐이더에서 (분명히)


실행되지만, 우리는 정점 쉐이더에서 light-space 변환을 수행한다:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;

void main()
{    
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
    vs_out.TexCoords = aTexCoords;
    vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

 여기서 새로 추가 된 것은 FragPosLightSpace라는 추가 출력 벡터이다. 우리는 동일한 lightSpaceMatrix를 가져와서 세계 공간 정점 위치를


밝은 공간으로 변환한다. 정점 쉐이더는 일반적으로 변환된 world-space 정점 위치 vs_out.FragPos와 변환된 light-space vs_out.FragPosLightSpace를


조각 쉐이더에 전달한다.



 장면 렌더링에 사용할 조각 쉐이더는 Blinn-Phong 조명 모델을 사용한다. 조각 쉐이더 내에서 우리는 조각이 그림자에 있을 때 1.0이거나


그림자에 없을 때 0.0 인 그림자 값을 계산한다. 결과로 생성되는 확산 및 반사 색상에 이 그림자 구성 요소가 곱해진다. 빛의 산란으로 인해


그림자가 거의 완전히 어두워지지 않기 때문에 그림자 곱셈에서 주변 색상을 벗어나지 않는다.

#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} fs_in;

uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

float ShadowCalculation(vec4 fragPosLightSpace)
{
    [...]
}

void main()
{           
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(1.0);
    // ambient
    vec3 ambient = 0.15 * color;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);  
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;    
    // calculate shadow
    float shadow = ShadowCalculation(fs_in.FragPosLightSpace);       
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;    
    
    FragColor = vec4(lighting, 1.0);
}

  조각 쉐이더는 고급 조명 튜토리얼에서 사용한 것의 사본이지만 그림자 계산이 추가되었다. 우리는 대부분의 그림자 작업을 수행하는


ShadowCalculation 함수를 선언했다. 조각 쉐이더의 종단에서 확산 및 반사 기여에 그림자 성분의 역행렬을 곱해 조각이 그림자에 없는


양을 늘린다. 이 조각 쉐이더는 첫 번째 렌더 패스에서 생성된 깊이 맵과 light-space 조각 위치를 추가 입력으로 사용한다.



 조각이 그림자에 있는지 여부를 확인하기 위해 수행해야 할 첫 번째 작업은 클립 영역의 밝은 공간 조각 위치를 표준화된 장치 좌표로


변환하는 것이다. 정점 쉐이더의 gl_position에 클립 공간 정점 위치를 출력 할 때, OpenGL은 자동으로 원근법 나누기를 한다. x,y,z 구성 요소를


벡터의 w 구성 요소로 나눠서 [-w,w] 범위의 클립 공간 좌표를 [-1,1]로 변환한다. 클립 공간 FragPosLightSpace가 gl_Position을 통해 조각 쉐이더에


전달되지 않기 때문에 우리는 이 관점을 스스로 분리해야한다:

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // perform perspective divide
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    [...]
}

 이것은 [-1,1]의 범위에서 조각의 light-space 위치를 반환한다.

직교 투영 행렬을 사용할 때 정점의 w 구성 요소는 그대로 유지되므로 이 단계는 실제로 의미가 없다. 그러나 원근 투영을 사용할 때 이 선을 유지하면 두 투영 행렬 모두에서 사용할 수 있다.

 깊이 맵의 깊이는 [0,1]의 범위에 있고 우리는 또한 깊이 맵에서 샘플링하기 위해 projCoords를 사용해 NDC 좌표를 [0,1] 범위로 변환한다:
projCoords = projCoords * 0.5 + 0.5; 
 이 투영된 좌표를 사용하면 projCoords의 결과 [0,1] 좌표가 첫 번째 렌더 패스에서 변환된 NDC 좌표와 직접 일치하므로 깊이 맵을 샘플링 할 수 있다.

이것은 우리에게 빛의 관점에서 가장 가까운 깊이를 준다:
float closestDepth = texture(shadowMap, projCoords.xy).r;   
 조각의 현재 깊이를 얻으려면 광원의 관점에서 조각의 깊이와 동일한 투영된 벡터의 z 좌표를 검색하면된다.
float currentDepth = projCoords.z;  
 실제 비교는 단순히 currentDepth가 nearestDepth보다 높은지 여부를 확인하고, 그렇다면 조각이 그림자에 있는지 여부를 확인하는 것이다.
float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;  
 전체 ShadowCalculation 함수는 다음과 같다:
float ShadowCalculation(vec4 fragPosLightSpace)
{
    // perform perspective divide
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // transform to [0,1] range
    projCoords = projCoords * 0.5 + 0.5;
    // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords)
    float closestDepth = texture(shadowMap, projCoords.xy).r; 
    // get depth of current fragment from light's perspective
    float currentDepth = projCoords.z;
    // check whether current frag pos is in shadow
    float shadow = currentDepth > closestDepth  ? 1.0 : 0.0;

    return shadow;
}  
 이 쉐이더를 활성화하고 적절한 텍스처를 바인딩하고 두 번째 렌더 패스에서 기본 투영 및 뷰 행렬을 활성화하면 아래 이미지와

비슷한 결과가 나타난다:

Shadow mapped images, without improvements.

 당신이 일을 제대로 했다면 바닥과 큐브에 그림자를 보아야한다. (인공물이 많다)