DirectX11

[DX11] Point Light Shadow - Peter Panning, Shadow Acne

yeoul0714 2025. 4. 23. 04:02

1. 개요


게임 세상에서 그림자를 띄울 수 있다면 보다 더 실감나는 게임이 될것입니다.

 

오늘 알아볼내용은 바로 그림자를 구현하는 방법입니다.

 

우선 그림자를 구현하기 위해서는 Depth값이 필요합니다.

 

Depth 비교를 통해서 그림자가 지는 부분과 아닌 부분을 나누고 계산을 해서 구합니다.

 

자세한 원리는 차차 설명하도록 하겠습니다.

그림자가 없는 여울


 

2. Depth를 비교한다는 것은?


그렇다면 Depth를 비교한다는 것이 도대체 무슨 의미일까요?

 

결론부터 말하면 광원이 보고 있는 물체까지의 Depth(거리)와 광원과 물체사이의 실제 거리를 비교하는 것입니다.

 

Depth값 보다 실제 거리가 더 길다면 그림자가 지는 구간이고 같다면 그림자가 지지 않는 부분입니다.

 

이렇게 말해서는 이해가 잘 안갈것입니다.

 

Depth란것이 무엇인지 정의도 되어있지 않고 직관적으로 단번에 이해하기 어려운 부분이기 때문입니다.

 

그렇다면 Depth부터 정의하겠습니다.

 

Depth는 Depth Map으로 부터 추출되는 값입니다.

 

Depth Map은 광원의 시점에서 바라본 물체들의 깊이값(거리)를 나타내는 정보입니다.

 

아래 사진을 보면 Depth는 광원(검은 점)에서 실제 물체까지의 거리이고 

 

그림자가 지는 부분은 Depth보다 실제거리가 긴 곳입니다.

 

이렇게 사진을 보면서 생각해보면 이해가 쉬울 것입니다.

참고용 사진

 


 

3. Texture Cube 


Spot Light, Directional Light의 경우 Depth Map은 하나만 존재하면 됩니다.

 

왜냐하면 단방향으로만 빛이 나아가는 형태의 광원이기 때문입니다.

 

그러나 Point Light의 경우에는 무려 6개의 Depth Map이 필요합니다.

 

그럼 도대체 왜 6개의 Depth Map이 필요할까요?

 

그것은 바로 Point Light는 전방향으로 빛을 발산하는 광원이기 때문입니다.

 

Point Light가 물체의 상하좌우 어디에 있던간에 반대편에는 반드시 그림자가 생기는 것이 자연습니다.

 

그렇기에 Point Light의 시점에서 상, 하, 좌, 우, 앞, 뒤 이렇게 총 6방향으로 바라본 Depth Map이 필요한 것입니다.

 

우리는 여기서 TextureCube을 쓸것입니다.

hlsl에서 Point Light의 Depth값을 받는 방법으로 TextureCube라는 타입의 변수를 선언하고 있습니다. (슬롯 12번)

 

이곳에 값을 넘겨주기 위해서는 해줄 사전 작업들이 필요합니다.


 

4. Texture Cube를 만들기 위한 과정


4-1. Shadow Texture만들기 ( ID3D11Texture2D )

  D3D11_TEXTURE2D_DESC desc = {};
  desc.Width = 1024;
  desc.Height = 1024;
  desc.MipLevels = 1;
  desc.ArraySize = 6;  // 큐브맵의 6개 면
  desc.Format = DXGI_FORMAT_R32_TYPELESS;
  desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
  desc.Usage = D3D11_USAGE_DEFAULT;
  desc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;  // 중요: 큐브맵으로 지정
  desc.SampleDesc.Count = 1;
  desc.SampleDesc.Quality = 0;

  HRESULT hr = Device->CreateTexture2D(&desc, nullptr, ShadowTexture.GetAddressOf());
  if (FAILED(hr))
  {
      assert(TEXT("Shadow Texture creation failed"));
      return;
  }

 

우선 ID3D11Texture2D타입의 ShadowTexture를 만들어 주어야 합니다.

 

여기서 주목할 곳은 바로 desc.ArraySize = 6 이곳입니다.

 

상, 하, 좌, 우, 앞, 뒤 이렇게 6개의 텍스처가 저장될 공간이기 때문이기에 6으로 해주어야 합니다.

 

그 외에도 중요한 부분을 보자면

 

 

1. desc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE 

이 플래그는 텍스처가 큐브맵으로 사용될 것임을 DirectX에 알려줍니다.

 

이 설정 없이는 텍스처 배열이 큐브맵으로 인식되지 않습니다.


2. desc.Format = DXGI_FORMAT_R32_TYPELESS 

TYPELESS 포맷을 사용함으로써 같은 텍스처를 DSV(Depth Stencil View)와 SRV(Shader Resource View)

 

둘 다에서 다른 형식으로 해석할 수 있게 해줍니다.

 

만약 DXGI_FORMAT_D32_FLOAT 같은 포멧을 사용하면 DSV에서 밖에 사용할 수 없게 됩니다.

 

그러나 큐브맵은 먼저 Depth Stencil View로 깊이를 기록하고 후에 Shader Resource View로 깊이를 읽어야 하기 때문에

 

모두를 범용적으로 사용할 수 있는 TYPELESS포멧이 필수적입니다.


3. desc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE 

이 텍스처가 깊이 스텐실 대상으로 사용되면서 동시에

 

셰이더에서 리소스로 읽을 수 있게 해주는 중요한 플래그 조합입니다.

 

만약 DEPTH_STENCIL만 쓴다면 이 텍스처를 통해서 DEPTH_STENCIL은 생성이 가능하지만 SRV는 생성이 불가능 합니다.

 

좋습니다 여기까지 했다면 현재 모든 것의 시작이되는 Texture2D를 생성한 것입니다.

 

 


4-2. Shadow SRV만들기 ( ID3D11ShaderResourceView)

 

 D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
 srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
 srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
 srvDesc.TextureCube.MipLevels = 1;
 srvDesc.TextureCube.MostDetailedMip = 0;

 hr = Device->CreateShaderResourceView(ShadowTexture.Get(), &srvDesc, &ShadowSRV);
 if (FAILED(hr))
 {
     assert(TEXT("Shadow SRV creation failed"));
     return;
 }

 

두번째는 Depth값을 추후에 읽기 위한 Shader Resouce View를 생성해줍니다.

 

 

1.srvDesc.Format = DXGI_FORMAT_R32_FLOAT:

앞서 텍스처는 DXGI_FORMAT_R32_TYPELESS로 생성했는데, SRV에서는 DXGI_FORMAT_R32_FLOAT로 해석합니다.


위에서 ShaderTexture를 TYPELESS로 생성했기에 가능한 부분입니다.

 

2. srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE

이 Shader Resource View가 큐브맵을 위한것임을 나타냅니다.

 

이렇게 해주어야 hlsl에서 TextureCube로 받을 수 있습니다.

 

그리고 3D로 샘플링이 가능합니다.

 


 

4-3. 6개의 Depth Stencil View만들기 (ID3D11DepthStencilView)

 

for (UINT face = 0; face < 6; ++face)
 {
     D3D11_DEPTH_STENCIL_VIEW_DESC faceDSVDesc = {};
     faceDSVDesc.Format = DXGI_FORMAT_D32_FLOAT;
     faceDSVDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
     faceDSVDesc.Texture2DArray.MipSlice = 0;
     faceDSVDesc.Texture2DArray.FirstArraySlice = face;
     faceDSVDesc.Texture2DArray.ArraySize = 1;

     ComPtr<ID3D11DepthStencilView> dsv;
     hr = Device->CreateDepthStencilView(ShadowTexture.Get(), &faceDSVDesc, &dsv);
     if (FAILED(hr))
     {
         assert(TEXT("Shadow DSV creation failed"));
         return;
     }
     ShadowDSVs.Add(dsv);
     // Viewport 설정
     D3D11_VIEWPORT viewport = {};
     viewport.TopLeftX = 0;
     viewport.TopLeftY = 0;
     viewport.Width = static_cast<FLOAT>(ShadowResolution);
     viewport.Height = static_cast<FLOAT>(ShadowResolution);
     viewport.MinDepth = 0.0f;
     viewport.MaxDepth = 1.0f;
     Viewports.Add(viewport);
 }

 

1. faceDSVDesc.Format = DXGI_FORMAT_D32_FLOAT

원래 텍스처는 R32_TYPELESS였지만, DSV에서는 D32_FLOAT로 해석합니다.

 

이는 깊이 버퍼로 사용하기 위한 포맷으로, 깊이 테스트에 최적화되어 있습니다.

 

2. faceDSVDesc.Texture2DArray.FirstArraySlice = face

각 DSV가 큐브맵의 어떤 면을 가리킬지 지정합니다.

 

0부터 5까지의 값으로 6개 면을 구분합니다(+X, -X, +Y, -Y, +Z, -Z 순).

 

3. faceDSVDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY

2D 텍스처 배열 형식으로 뷰를 생성합니다.

 

큐브맵은 내부적으로 6개의 2D 텍스처 배열로 처리됩니다.

 

4. ShadowDSVs.Add(dsv) 및 Viewports.Add(viewport)

생성된 DSV를 TArray에 전부 넣어둡니다.

후에 TArray에 접근해서 DSV를 사용할 수 있습니다.

 

 

 


 

5. DSV에 Depth값 draw하기


좋습니다 이제 필요한 모든 소스들을 만들어 주었습니다.

 

이제 우리가 만든 소스에 필요한 Depth값을 저장하는 작업을 해보도록 하겠습니다.

 

아래 코드는 6회 반복문을 돌며 각 DSV에 Depth를 그리는 과정입니다.

void FShadowRenderPass::RenderPointLightShadowMap(UPointLightComponent* PointLight, FShadowResource* ShadowResource, FGraphicsDevice& Graphics)
{
    // 각 면마다 렌더링
    for (int faceIndex = 0; faceIndex < 6; ++faceIndex)
    {
        D3D11_VIEWPORT vp = ShadowResource->GetViewport();
        Graphics.DeviceContext->RSSetViewports(1, &vp);

        // 현재 면의 DSV 가져오기
        ID3D11DepthStencilView* CurrentFaceDSV = ShadowResource->GetDSV(faceIndex);
        Graphics.DeviceContext->ClearDepthStencilView(CurrentFaceDSV, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

        ID3D11ShaderResourceView* nullSRV = nullptr;
        Graphics.DeviceContext->PSSetShaderResources(0, 1, &nullSRV);
        Graphics.DeviceContext->OMSetRenderTargets(0, nullptr, CurrentFaceDSV);

        FMatrix CubeView = PointLight->GetViewMatrixForFace(faceIndex);
        FMatrix CubeProj= PointLight->GetProjectionMatrix();
        // 현재 면에 대한 뷰와 프로젝션 매트릭스로 렌더링
       RenderStaticMesh(CubeView, CubeProj);
    }
}

 

코드를 보면 CubeView와 CubeProj를 가져오고 있는데 이는 StaticMesh를 Point Light의 시점에서 그리기 위한 행렬입니다.

 

그리고 OMSetRenderTargets를 통해 현재 가져온 DSV에 그려주기로 설정해주고 있습니다.

 

첫번째 인자가 0인 이유는 오직 깊이 버퍼만 사용하기 위해서입니다.

 

아래와 같은 방식으로 face값에 따라서 다른 Forward 와 Up을 받고 View Matrix를 만들어주고 있습니다.

FMatrix UPointLightComponent::GetViewMatrixForFace(int faceIndex) const
{
    FVector Up, Forward;

    switch (faceIndex)
    {
    case 0: // +X 방향
        Forward = FVector(1.0f, 0.0f, 0.0f);
        Up = FVector(0.0f, 1.0f, 0.0f);
        break;
    case 1: // -X 방향
        Forward = FVector(-1.0f, 0.0f, 0.0f);
        Up = FVector(0.0f, 1.0f, 0.0f);
        break;
    case 2: // +Y 방향
        Forward = FVector(0.0f, 1.0f, 0.0f);
        Up = FVector(0.0f, 0.0f, -1.0f);
        break;
    case 3: // -Y 방향
        Forward = FVector(0.0f, -1.0f, 0.0f);
        Up = FVector(0.0f, 0.0f, 1.0f);
        break;
    case 4: // +Z 방향
        Forward = FVector(0.0f, 0.0f, 1.0f);
        Up = FVector(0.0f, 1.0f, 0.0f);
        break;
    case 5: // -Z 방향
        Forward = FVector(0.0f, 0.0f, -1.0f);
        Up = FVector(0.0f, 1.0f, 0.0f);
        break;
    }
    return JungleMath::CreateViewMatrix(
        GetComponentLocation(),
        Forward + GetComponentLocation(),
        Up
    );
}

 

그리고 projection 행렬같은 경우에 farplane은 point light의 radius입니다. (빛이 닿는 범위까지만 depth 생성)

 

이렇게 설정하고 Draw Call을 하게 되면 Point Light시점에서 본 Depth가 DSV에 저장됩니다.

 

그리고 최종적으론 SRV를 쉐이더에 넘겨주게 됩니다.

 


 

5.5. 5번 하면서 들은 의문


분명 6개의 DSV에 각각 그려줬는데 

 

어떻게 SRV에 그 내용이 다 저장이 되어있는지 궁금했습니다.

 

어떤식으로 연결되어 있는것인지 의문이 들어서 관련 내용을 학습하게 되었습니다.

 

이 부분을 이해하기 위해서는 D11의 용어에 대한 개념이 잡혀있어야 합니다.

 

메모리 리소스

ShadowTexture(ID3D11Texture2D)는 GPU 메모리에 할당된 실제 데이터 공간입니다.

이 메모리 공간은 6개의 2D 텍스처를 배열로 포함하고 있습니다.

 

뷰(View)

DSV와 SRV는 이 메모리 공간을 바라보는 창과 같습니다.

뷰는 실제 데이터를 포함하지 않고, 기존 데이터를 어떻게 해석하고 접근할지를 정의합니다.

 

즉 우리는 DSV를 통해서 Texture2D에 접근해서 6면에 해당하는 Depth값을 기록하고

 

SRV를 통해서 Shader에서 Texture2D의 각 면에 접근해서 Depth값을 Sampling하는 것 입니다.

 

 


 

6. Shader에서 그림자 연산


이렇게 Depth값을 DSV를 통해서 전부 기록을 했습니다.

 

이제 SRV를 쉐이더로 넘겨서 SRV를 통해 Depth 값을 쉐이더에서 읽어서 그림자를 계산해주어야 합니다.

 

여러개의 광원이 한 물체에 대해 여러가지의 그림자를 만들어줄 가능성이 있기에 

 

하나의 PointLight는 다른 모든 PointLight들의 SRV를 가지고 있습니다.

 

이를 배열로 Shader에 넘겨주어서 여러개의 광원에서 생기는 그림자를 처리합니다.

 

 

아래는 shadow를 계산하는 hlsl코드입니다.

 

worldPos : 픽셀의 월드 position

 

worldNormal : 픽셀의 월드 normal

 

mapIndex : 몇번째 CubeMap쓸것인지

 

우선 LightDirection (광원에서 점으로 향하는 벡터)를 구합니다.

 

그 후에 그 벡터를 이용해서 해당 벡터의 큐브의 어떤 면을 향하고 있는지 알아냅니다.

 

 

float CalculatePointLightShadow(float3 worldPos,float3 worldNormal, FPointLight PointLight,int mapIndex)
{
    float4 WoldPos4 = float4(worldPos, 1.0f);
    
    float3 LightDirection = normalize(worldPos - PointLight.Position);
    
    float3 LightDirectionAbs = abs(LightDirection); // 절대값 각 축 크기 구함.
    int face;
    if (LightDirectionAbs.x >= LightDirectionAbs.y && LightDirectionAbs.x >= LightDirectionAbs.z) 
        face = LightDirection.x > 0 ? 0 : 1; // +X, -X
    else if (LightDirectionAbs.y >= LightDirectionAbs.z)
        face = LightDirection.y > 0 ? 2 : 3; // +Y, -Y
    else
        face = LightDirection.z > 0 ? 4 : 5; // +Z, -Z
    
    float4 LightViewPos = mul(WoldPos4, PointLight.View[face]); // 월드 → 라이트(큐브 face) 공간
    float4 clipPos = mul(LightViewPos,PointLight.Proj); // 라이트 공간 -> Clip space

    //FIXME : bias 적용
    // NDC 깊이 (0~1) 추출
    //float refDepth = clipPos.z / clipPos.w - bias;
    float bias = max(0.001 * (1.0 - dot(worldNormal, LightDirection)), 0.0001);
    float refDepth = clipPos.z / clipPos.w - bias;


    float shadow = PointLightShadowMap[mapIndex].SampleCmp(
        CompareSampler,
        LightDirection, // dir.xyzw: (방향벡터, 큐브맵 array 인덱스)
        refDepth
    );

   return shadow;
}

 

그 후엔 우리가 현재 보는 점의 위치를 빛의 시점에서 얼마나 떨어진기 구해야합니다.

 

그래야만 depth map에서 뽑은 depth 값과 비교가 가능합니다.

 

정리하자면 아래와 같습니다.

  • 그림자 맵 생성 단계 (DSV에 그리는 단계):
    • 이 단계에서 라이트의 view/projection 행렬을 사용해 장면을 렌더링합니다.
    • 결과물로 깊이 맵(그림자 맵)이 생성되며, 여기에는 광원에서 본 각 픽셀까지의 가장 가까운 거리가 저장됩니다.
    • 이 과정은 이미 완료되어 그림자 맵이 생성된 상태입니다.
  • 그림자 계산 단계 (SRV에서 샘플링하는 단계):
    • 현재 코드가 실행되는 단계입니다.
    • 이제는 카메라 시점에서 장면을 렌더링하면서, 각 픽셀이 그림자 안에 있는지 확인해야 합니다.
    • 그러기 위해서는 현재 처리 중인 각 픽셀을 다시 라이트 시점의 좌표로 변환해야 합니다.

결론적으로 각 Pointlight의 view와 projectnio을 곱하는 이유는 해당 light에서 본 픽셀의 거리가 필요하기 때문입니다.

 

그렇게 뽑아낸 clipPos.z의 값을 clipPos.w로 나눠서 정규화시켜줍니다.

 

이는 원근 나눗셈을 통해 [0,1] 범위의 정규화된 깊이 값을 얻기 위한 과정입니다.

 

그 후엔 SampleCmp를 이용해서 CompareSampler를 사용해서 깊이 값 비교를 해줍니다.

 

LightDirection을 통해서 자동으로 적절한 Depth값을 가져오게 됩니다.

 

큐브맵은 방향벡터를 이용해서 값을 샘플링합니다.

 

그렇게 하면 그림자 부분은 shadow값이 0, 빛이 드는 부분은 1로 나오게 됩니다.

 

이 값을 기존 빛에 곱해주면 그림자 연산이 완료됩니다.


 

7. Bias, Peterpanning, Shadow Acne


7-1. Shadow Acne

그림자를 계산하는 shader 코드를 보면 bias라는 값이 있습니다.

 

이부분은 상당히 중요한 값이고 그림자를 계산할때 발생하는 다양한 문제를 해결할 수 있습니다.

 

아래 사진은 bias를 0으로 했을때 발생하는 Shadow Acne라는 현상입니다.

Shadow Acne

 

그렇다면 이러한 현상은 왜 생길까요?

 

 

1. 그림자 맵의 이산화 (discreate)

그림자 맵은 제한된 해상도의 텍스처에(1024x1024) 깊이 정보를 저장합니다.

이 과정에서 정보 손실이 발생합니다.

 

2. 표면 기울기 

빛에 대해 비스듬히 기울어진 표면의 경우, 하나의 그림자 맵 텍셀이 실제 표면의 넓은 영역에 영향을 줍니다.

더 자세히 말하자면 1024x1024의 shadow texture로는 월드의 스태틱 매시들의 depth값을 세밀하게 표현하는 것이 불가합니다.

그런데 오브젝트가 기울어진경우 한 텍셀이 많은 영역을 담당하게 되어서 정확도가 떨어지게 됩니다.

반대로 오브젝트가 빛의 방향과 수직인경우 한 텍셀이 최소의 영역만 담당하게 되어서 정확도가 올라가게 됩니다.

떨어진 정확도에 대비하기 위해서 정확도가 낮을수록 더 큰 bias가 필요하게 됩니다.

 

3. 부동소수점 오차 

깊이 계산과 비교 과정에서 발생하는 작은 부동소수점 오차도 문제를 악화시킵니다.

 

 

결론적으로 이러한 문제들을 해결하기 위하여 bias라는 값을 넣어주어야 합니다.

 

float bias = max(0.001 * (1.0 - dot(worldNormal, LightDirection)), 0.0001);

 

0도일때 가장작고 90도일때 가장 커지게 됩니다.

 

즉 비스듬할수록 더 큰 bias값이 필요한것에 대한 대응입니다.

 

max로 감싸준 이유는 0도여서 bias가 0이 나오면 오차에 대한 대응이 안되기에 0.00001의 작은 bias를 주는 식으로 되어있습니다.

 

7-2. Peter Panning

Peter Panning

 

Peter Panning이란 현상은 그림자가 실제 객체에서 분리되어 떠 있는 것처럼 보이는 현상입니다.

 

실제 캐릭터 피터팬의 이름에서 유례되었습니다.

 

이러한 문제는 보통 bias가 너무 커지면 발생합니다. 

 

그렇다고 또 너무 작아지면 Shadow Acne가 생기게 됩니다.

 

그래서 Peter Panning이 발생하지 않으면서 Shadow Acne도 발생하지 않은 최적의 bias를 찾는것이 아주 중요합니다.

 

특히 천이나 나뭇잎같은 얇은 객체에서 Peter Panning은 더 두드러지게 나타납니다.

 

만약 bias가 객체의 두께보다 크면 그림자가 완전히 분리됩니다.

 

이렇게 bias를 적절히 조절하는 것 이외에도 Peter Panning을 해결하는 다른 방법이 있습니다.

  • 백페이스 셰도잉(Backface Shadowing): 그림자 맵 생성 시 객체 뒷면만 렌더링
  • VSM(Variance Shadow Maps)이나 PCSS(Percentage Closer Soft Shadows) 같은 고급 기법
  • 두께 기반 바이어스: 얇은 물체에 더 작은 바이어스 적용

8. Compare Sampler에 대해


그림자를 구현하면서 새롭게 나온 개념인 Compare Sampler에 대해 설명하도록 하겠습니다.

 

 

이곳을 보면 CamparisonFunc를 찾아볼 수 있습니다.

 

여기서 원하는 함수를 정할 수 있습니다.

 

현재는 LESS로 되어있는데 이는 현재 픽셀의 depth가 조명에서 본 depth보다 작을 때(less)일때 1을 반환해줍니다.

 

즉 현재 픽셀의 depth가 더 작으면 그림자가 지지 않는 곳이라는 것이죠

 

이렇게 Sampler를 설정해주면 자동으로 깊이를 비교해줍니다.

    ZeroMemory(&samplerDesc, sizeof(samplerDesc));
    samplerDesc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_POINT; // 비교 필터링 모드
    samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
    samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
    samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
    samplerDesc.ComparisonFunc = D3D11_COMPARISON_LESS; // 그림자 맵 비교에 적합한 LESS
    samplerDesc.BorderColor[0] = 1.0f;
    samplerDesc.BorderColor[1] = 1.0f;
    samplerDesc.BorderColor[2] = 1.0f;
    samplerDesc.BorderColor[3] = 1.0f;
    samplerDesc.MinLOD = 0;
    samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;
    GraphicDevice->Device->CreateSamplerState(&samplerDesc, &SamplerStates[static_cast<uint32>(ESamplerType::ComparisonSampler)]);

 

 

 

그림자 완성!

 

 

 

그림자 테두리 부드럽게 하는 기법

https://yeoul0714.tistory.com/41

 

[DX11] PCF (Percentage Closer Filtering)

1. 개요우리가 지난번 만들었던 그림자에는 한가지 문제점이 있습니다.https://yeoul0714.tistory.com/37 [DX11] Point Light Shadow - Peter Panning, Shadow Acne1. 개요게임 세상에서 그림자를 띄울 수 있다면 보다 더

yeoul0714.tistory.com

 

'DirectX11' 카테고리의 다른 글

[DX11] PCF (Percentage Closer Filtering)  (0) 2025.04.24
[DX11] Renderdoc 사용법 CubeMap 디버깅  (0) 2025.04.23
[DX11] Normal Map  (0) 2025.04.16
[DX11] Light Culling  (0) 2025.04.12
[DX11] Point Light 구현하기 (내적, 정사영)  (0) 2025.04.09