최초 그림자 매핑 문제


9

셰이더를 사용하여 OpenGL에서 처음으로 기본 그림자 매핑을 구현했으며 몇 가지 문제에 직면하고 있습니다. 아래에서 렌더링 된 장면의 예를 볼 수 있습니다.

여기에 이미지 설명을 입력하십시오

내가 따르는 섀도 매핑의 과정은 라이트 렌더링 관점에서 뷰 매트릭스를 사용하고 일반 렌더링에 사용되는 투영 및 모델 매트릭스를 사용하여 장면을 프레임 버퍼로 렌더링하는 것입니다.

두 번째 단계에서는 위의 MVP 매트릭스를 조명 시점에서 정점 셰이더로 전송하여 위치를 조명 공간으로 변환합니다. 프래그먼트 셰이더는 원근 분할을 수행하고 위치를 텍스처 좌표로 변경합니다.

여기 내 버텍스 쉐이더가 있습니다.


#version 150 core

uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
uniform mat4 MVPMatrix;
uniform mat4 lightMVP;

uniform float scale;

in vec3 in_Position;
in vec3 in_Normal;
in vec2 in_TexCoord;

smooth out vec3 pass_Normal;
smooth out vec3 pass_Position;
smooth out vec2 TexCoord;
smooth out vec4 lightspace_Position;

void main(void){
    pass_Normal = NormalMatrix * in_Normal; 
    pass_Position = (ModelViewMatrix * vec4(scale * in_Position, 1.0)).xyz;
    lightspace_Position = lightMVP * vec4(scale * in_Position, 1.0);
    TexCoord = in_TexCoord;

    gl_Position = MVPMatrix * vec4(scale * in_Position, 1.0);
}

그리고 내 조각 쉐이더


#version 150 core

struct Light{
    vec3 direction;
};

uniform Light light;
uniform sampler2D inSampler;
uniform sampler2D inShadowMap;

smooth in vec3 pass_Normal;
smooth in vec3 pass_Position;
smooth in vec2 TexCoord;
smooth in vec4 lightspace_Position;

out vec4 out_Color;


float CalcShadowFactor(vec4 lightspace_Position){

    vec3 ProjectionCoords = lightspace_Position.xyz / lightspace_Position.w;

    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjectionCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjectionCoords.y + 0.5;

    float Depth = texture(inShadowMap, UVCoords).x;
    if(Depth < (ProjectionCoords.z + 0.001)) return 0.5;
    else return 1.0;
}

void main(void){

    vec3 Normal = normalize(pass_Normal);
    vec3 light_Direction = -normalize(light.direction);
    vec3 camera_Direction = normalize(-pass_Position);
    vec3 half_vector = normalize(camera_Direction + light_Direction);

    float diffuse = max(0.2, dot(Normal, light_Direction));
    vec3 temp_Color = diffuse * vec3(1.0);

    float specular = max( 0.0, dot( Normal, half_vector) );

    float shadowFactor = CalcShadowFactor(lightspace_Position);

    if(diffuse != 0 && shadowFactor > 0.5){
        float fspecular = pow(specular, 128.0);
        temp_Color += fspecular;
    }

    out_Color = vec4(shadowFactor * texture(inSampler, TexCoord).xyz * temp_Color, 1.0);
}

그림에서 볼 수 있듯이 문제 중 하나는 자체 그림자입니다. 상자에는 자체 그림자가 있습니다. 내가 시도한 것은 다각형 오프셋 (즉, glEnable (POLYGON_OFFSET_FILL), glPolygonOffset (GLfloat, GLfloat))을 활성화하는 것이지만 많이 변경되지 않았습니다. 프래그먼트 셰이더에서 볼 수 있듯이 정적 오프셋 값은 0.001이지만 빛의 거리에 따라 값을 변경하여 더 바람직한 효과를 얻어야하므로 매우 유용하지 않습니다. 또한 프레임 버퍼로 렌더링 할 때 전면 컬링을 사용해 보았습니다.

다른 문제는 라이트의 뷰 프러스 텀 외부의 픽셀이 음영 처리된다는 것입니다. 그림자를 드리울 수있는 유일한 물체는 상자입니다. 더 적절한 투영과 뷰 매트릭스를 선택해야하지만 그 방법을 잘 모르겠습니다. 직교 투영법을 선택해야하는 일반적인 관행은 무엇입니까?

인터넷 검색에서 나는 이러한 문제가 사소한 것이 아니라는 것을 이해합니다. 누구든지 이러한 문제에 대한 솔루션을 쉽게 구현할 수 있습니까? 추가 정보를 제공해 주시겠습니까?

내 코드에 대한 자세한 정보가 필요하면 문의하십시오.

다음 은 상자 클로즈업의 그림자 매핑 유무에 대한 비교입니다. 셀프 섀도 잉이 더 잘 보입니다.


문제를 자세히 설명해야하는 것 같습니다. 당신은 당신의 문제에 대해 한 문장을 가지고 있으며 다음 문장은 그에 대한 해결책을 제안합니다. 문제가 무엇인지, 문제를 해결하기 위해 이미 수행 한 작업을 정확하게 알려주십시오 .
MichaelHouse

내 게시물을 수정했습니다. 지금 더 나아지기를 바랍니다.
Grieverheart 2016 년

답변:


6

조명에서 뒷면을 향한 다각형을 그림자 처리해서는 안됩니다.

조각 셰이더를 아래 코드로 변경했습니다.

float CalcShadowFactor(vec4 lightspace_Position)
{

    vec3 ProjectionCoords = lightspace_Position.xyz / lightspace_Position.w;

    vec2 UVCoords;
    UVCoords.x = 0.5 * ProjectionCoords.x + 0.5;
    UVCoords.y = 0.5 * ProjectionCoords.y + 0.5;

    float Depth = texture(inShadowMap, UVCoords).x;
    if(Depth < (ProjectionCoords.z + 0.001)) return 0.5;
    else return 1.0;
}

void main(void)
{

    vec3 Normal = normalize(pass_Normal);
    vec3 light_Direction = -normalize(light.direction);
    vec3 camera_Direction = normalize(-pass_Position);
    vec3 half_vector = normalize(camera_Direction + light_Direction);

    float fndotl = dot(Normal, light_Direction);
    float shadowFactor = CalcShadowFactor(lightspace_Position);
    float diffuse = max(0.0, fndotl) * shadowFactor + 0.2;
    vec3 temp_Color = vec3(diffuse);

    float specular = max( 0.0, dot( Normal, half_vector) );

    if(shadowFactor > 0.9){
        float fspecular = pow(specular, 128.0);
        temp_Color += fspecular;
    }

    out_Color = vec4(shadowFactor * texture(inSampler, TexCoord).xyz * temp_Color, 1.0);
}

8

셀프 섀도 잉에 관해서는, 당신이 무엇을 말하는지 잘 모르겠습니다. 스크린 샷의 상자에 "그림자 여드름"이 보이지 않습니다. 당신이 빛을 마주 보는 얼굴이 어둡다는 것을 의미한다면, 물론 그들은 맞습니다! :) 측면이 약간 이상하게 보이고 대각선을 반으로 밝게하고 반으로 어둡게 만듭니다. 조명에 N dot L을 사용하고 있고 좋은 법선 (즉, 평활 법선이 아닌이 큐브의 단단한 법선)이 발생하지 않아야합니다.

다각형 오프셋을 사용하거나 그림자 맵에 뒷면을 그리는 것이 그림자 여드름에 대한 표준 솔루션입니다.

투영 매트릭스는 빛을 카메라로 생각해야합니다. 포인트 라이트 인 경우 투시 카메라가되고 지향성 라이트 인 경우 직교 카메라와 같습니다. 빛의 시야, 종횡비 등을 사용하여 카메라와 동일한 방식으로 투영 매트릭스를 구성합니다.

픽셀 쉐이더가 라이트의 뷰 프러스 텀에서 픽셀 만 그리도록 제한하려면, 섀도 맵 UV를 계산 한 후에는 0에서 1 사이인지 아닌지 확인하십시오 discard(또는 라이트의 색상을 0으로 설정). 또 다른 방법은 그림자 맵 텍스처를 "경계로 고정"모드로 설정하고 경계 색상을 0으로 설정하여 그림자 맵 외부의 모든 것이 완전히 그림자되도록합니다.


다음은 박스의 근접 촬영과 그림자 매핑 링크 유무에 따른 비교입니다 . 셀프 섀도 잉이 여기에서 더 잘 보입니다. 포인트 라이트에 원근 카메라를 사용하고 방향에 직교를 사용하는 이유를 설명해 주시겠습니까?
Grieverheart

좋아요, 그 장면에서 그림자 여드름처럼 보입니다. 앞면이 아닌 그림자 맵으로 뒷면을 그리면 문제가 해결됩니다. 나는 당신이 그것을 시도했다는 것을 알고 있지만, 뭔가 잘못되었을 수 있습니다. 그냥 glCullFace(GL_FRONT). 점 대 방향에 관해서는 광선에 관한 것입니다. 카메라 광선은 투시 카메라의 한 지점으로 수렴하고 광선은 점 조명의 한 지점으로 수렴합니다. 카메라 광선은 직교 카메라의 경우 평행이고 광선은 방향성 광선의 경우 평행입니다.
Nathan Reed 2018 년

알겠습니다. 여기 와 페이스 컬링해야만 비교이다. 바닥에있는 것은 얼굴 컬링이 가능합니다. 자체 그림자 문제에 영향을 미치지 않는 라이트 뷰 매트릭스에 직교 투영법을 사용하려고합니다. 어쩌면 내 클립 범위가 너무 큽니까 (예 : 0.1 ~ 100)?
Grieverheart

그것들은 똑같아 보입니다. 나머지 장면에서 뒷면 컬링을 사용하고 있습니까? 당신은 했습니까 glEnable(GL_CULL_FACE)?
Nathan Reed 2018 년

그들은 정확히 동일하지 않습니다. 상자를 자세히 살펴보십시오. 위의 그림에서 그림자 선은 볼록한 반면 아래쪽의 선은 오목합니다.
Grieverheart

3

라이트 뷰 외부의 픽셀은 라이트 깊이 텍스처 외부에서 샘플링되므로 어둡습니다.

투영 행렬로 정점을 변환하면 조각의 클립 좌표 [lightspace_Position]가 표시됩니다. 그런 다음이를 텍스처 좌표 [UVCoords]로 변환합니다. 그러나 프래그먼트의 클립 공간 좌표는 여전히 -1.0에서 1.0 범위를 벗어날 수 있으며 (화면에서 벗어남) 따라서 변환 된 텍스처 좌표는 텍스처의 0.0에서 1.0 범위를 벗어날 수 있습니다.

이 시도:

if(UVCoords.x >= 0.0 && UVCoords.x <= 1.0 && UVCoords.y >= 0.0 && UVCoords.y <= 1.0 && (Depth < (ProjectionCoords.z + 0.001)))
    return 0.5;

바이어스 행렬을 [lightMVP]에 곱하고 프래그먼트 셰이더에서 좌표 변환을 수행하지 않아도됩니다.


바이어스 매트릭스는 일반적으로 사람들이가는 방식입니다. 프래그먼트 셰이더에서 분기 코드보다 훨씬 빠릅니다.
이안 영
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.