본문 바로가기

DirectX11 3D/기본 문법

<DirectX11 3D> 113 - Shadow

 

 

필요한 개념


그림자는 깊이 버퍼를 이용한다.
그림자를 그리는 방법은 게임 역사랑 거의 비슷하다고 보면 된다.
고전 게임도 그림자가 없는 게임은 없다.
캐릭터 밑에 원형 그려넣는 원형 그림자,
원을 발 밑에 그려넣는거는 프로젝션(이전시간에 배운)을 캐릭터 발밑에 그려주면 아주 간단한 얘기가 된다.

근데 요즘 게임은 캐주얼 틱하고 가벼운 게임은 원형 그림자는 쓰지 않는다.
현실감 있는 표현 방식은 투영 그림자이다.

투영 그림자란?
렌더 타깃을 이용해 검은색으로 그린 다음 지면에 렌더링 하는 방식

캐릭터들만 모아서 렌더링할때, 그릴 때 색을 캐릭터 색이 아니라, 그림자 색에 가까운 색을 렌더링하고 조명 방향에다 틀어서 붙인다.
문제가 뭐냐면 셀프 섀도잉이 생긴다.

셀프 섀도잉(자기 그림자)이란?
자신에 의해 자신의 몸이 가려진다면 자신의 몸에도 그림자가 생기는 방식이다.
(팔이나 이런것을 들어서 배를 가리면, 가린거에 의해서 그림자가 생김)

이것을 또 발전시켜서 렌더타깃이 아니라 OM에 있는 RTV, DSV
RTV로는 색상만 만들 수 있으니까(프로젝션 그림자) -> 이걸로는 자기 그림자를 그릴 수 없다.
그래서 DSV를 이용하는데, 여기서 Depth Buffer Shadow(깊이 버퍼 그림자)라는 개념이 등장한다.

깊이 버퍼 그림자(Depth Buffer Shadow)란?
현재 게임에서 사용되는 렌더링의 기반이 되는 방식
(거의 대부분 게임들이 그림자를 그릴때, 이 방식을 사용)
예를 들어 대형맵에서 그림자를 그릴때 shadow를 그릴때 CSM도 깊이버퍼 그림자를 기반으로 한다.
하드웨어를 기반으로 하는 VSM도 깊이버퍼그림자를 기반으로 한다.
현재 게임 옵션 보면 4x, 8x 나오는 그림자인데, 깊이버퍼 그림자를 몇번 더 딸것인가, 정밀하게 할것인가, 몇번 더 렌더링을 할 것인가 이런것을 결정

반드시 이해해야 좀 더 발전가능

 

그림 10-1 Shadow.cpp CalcViewProjection() 부분 수식 설명

 



깊이 버퍼 그림자 부터는 2Pass Rendering이라고 부름
1pass -> 깊이(DSV) (물론 RTV도 가능하지만, DSV를 사용) (조명의 방향으로 렌더링을 함, 조명의 방향으로 가상의 조명위치 영역(그림자가 들어갈 구역)을 만들고, Projection을 함(Orthographic) 사용, 조명의 방향으로 렌더링함)
(1Pass 렌더링 파이프라인(IA -> VS -> RS -> PS -> OM)를 지난다는 얘기)
2pass -> 렌더링 된 깊이와 현재 렌더링하고 있는 깊이를 비교해서 그림자를 드로잉
(깊이 비교, 실제 렌더링 수행)


(*포스트 이펙트는 렌더링 화면을 가져다가 2번째에 사각형화면에 텍스쳐를 바르면서 조작한거고 이거랑 개념이 다르다)


그림자를 Perspective로 사용시 그림자가 길게 늘어지는 효과가 있다.
그런데 우리는 Orthographic을 사용함


정리
2Pass rendering
- 1Pass : 조명의 방향에서 깊이를 렌더링(조명의 방향에 대한 View, Projection 생성)
- 2Pass : 깊이를 비교하면서 실제 물체들 렌더링

sPosition : 조명의 방향에서 WVP을 진행한 결과

 

 

 

 

 

 

그림 11 (1Pass 후에 결과) -> RTV로 확인가능

 

 



깊이가 그림자가 될 것이다.
1Pass 후 RTV로 보니까 가까운거(깊이가 작음)는 검은색 먼것(깊이가 큼)은 흰색으로 나온다.(그림 11 참고)
앞에 있는 애들은 0에 가까운 값, 먼 애들은 1에 가까운 값
아예 깊이가 안 닿는 곳은 그림자 판단, 깊이 판단이 안되서 0(흑백)으로 보인다.

깊이를 눈으로 확인하기 위해서는 RTV로만 가능하다.

2Pass에서 1Pass에서 나온 깊이를 가지고 비교해서 그림자를 그린다.

깊이에 의해 가려지는 부분이 바로 그림자가 그려질 부분이다.

1Pass에서는 차폐가 어디에서 이루어지는지 보는 부분이며,
2Pass에서는 차폐(빛을 받을 수 없는 곳)가 되는 부분에 그림자를 그린다.

1Pass에서는 라이트 공간의 wvp를 사용했다.
2Pass에서는 그릴때 메인카메라 구역에서 수행(메인카메라 WVP), 그러나 깊이는 라이트의 WVP에서 수행됨

 

 

그림 12-1 Z-fighting 발생


그림(12-1)참고
1번을 깊이를 가지고 렌더링해서 2번이 나옴, 3번이 1Pass라고 한다면(깊이 결과가 3번 처럼 들어가 있다.)
1pass 결과는 이런식으로 뒤에 깊이는 사라지고 앞에 깊이만 남는다.
2Pass는 메인카메라 영역에서 그린다. 비교는 라이트 공간의 wvp를 그대로 사용
뒤에 있는 사각형을 그릴때 메인카메라의 공간의 WVP로 그리고 깊이 비교를 라이트 공간의 VP로 한다.
원래는 자기의 깊이를 가지고 그려지겠지만, 예를 들어 첫번째 결과에서는 0.3 깊이가 제일 앞에것이 그려졌다면 2D로 전환되서 해당 픽셀의 값이 앞에것이
나오니까 0.3
그릴때는 무조건 그린다음에 가리는 거니까, 결과가 뒤에 상자가 가려져있다고 해서 안그리지는 않는다. 비교해가면서 그리지
그래서 2Pass에서 픽셀의 공간을 라이트 공간에서 가져와서 보니까, 0.7이었다. 1Pass에서는 0.3이더라, 이 부분이 그림자가 그려질 부분

 

 

그림 13-1 그림자 원리 설명


1Pass에서 나왔던 깊이는 DSV에다가 써줬을 것이고, 앞에 상자에 의해 가려져서 0.3이 들어갔다.
2pass에서는 메인 카메라영역에서 렌더링 하는데, 첫번째꺼 그리고 2번째꺼 그릴떄, 얘의 깊이는 0.7이더라 1Pass에서는 깊이가 0.3을 가지고 있더라
이 둘의 차이가 생긴다. 1Pass를 Depth 0.3이고 하고 2Pass에서는 z를 0.7이라고 했을 때 차이가 생긴다.
depth(1Pass) >= z(2Pass) 그림자가 생길 부분이 아님(ex) 0.3 >= 0.7)
depth(1Pass) < z(2Pass) 그림자가 생성될 부분(ex) 0.3 < 0.7)

1Pass WVP(L)
2Pass WVP(M), WVP(L)

1Pass에서 라이트 공간에서 따왔으니까, 깊이 비교를 2Pass에서도 라이트 공간에서 비교해줘야한다.

문제1) 결과를 보니 자글 자글 한 그림자가 생김
그려지는 이유? 조명 방향에서 1Pass가 생기고 이걸 바탕으로 비교해가면서 2Pass 그림자가 생김, 1Pass 빛 방향에 따라 그림자가 그려짐
그려나갈때 깊이가 동일 한 부분은 흰색이 나옴, 깊이가 닿지 않는 부분은 그림자가 진다.



1Pass가 라이트 공간에서의 wvp가 DSV의 텍스쳐의 2D 결과로 저장되어있던거고, 2Pass에서는 메인 카메라의 wvp를 가지고 모델을 그려가는데, 이떄 PS에서
2D 결과로 나왔던 1Pass의 결과(DSV 텍스쳐) 그거를 depth라는 이름으로 값을 가져왔고, 그려나가면서 라이트 공간에서의 wvp를 프로젝션처럼 수행했던 결과를
z로 놓고 depth가 z보다 크거나 같다면 빛이 들어갈 영역, 아니라면 그림자가 들어갈 영역

빛의 공간에서 만든 깊이를 비교해가면서 그림자를 그려나감

Z이 D보다 커서 그림자가 생김

총 문제 3가지

문제 1) Z-Fighting
1. 자글 자글 한 문제
1Pass와 2Pass를 사용할때 같은 z값을 사용해서 그래서 같은 곳을 2번 그림(Z-Fighting)
1Pass에서 그리고 2Pass에서 그리니까
그림자가 닿는 부분은 상관없다.(그림자는 d와 z의 값이 다르기 때문에)
그러나 다른 부분은 1Pass의 깊이와 2Pass의 깊이가 같아서 누가 먼저 그려질지 모름
Z-Fighting? 깊이에 의해 어느것이 먼저 그려질지 결정하는데 깊이가 같으면 누구를 먼저 그릴지 몰라 싸우는 현상

해결 방법은 간단하다.
앞에 Pass1때 시계방향을 뒤집어서 해결하면 된다.
FrontCounterClockwise_True(RasterizerState)
완전히 깔끔히 나오지만 문제가 생김

 


문제 1-1) 뒤에 부분이 Z-Fighting이 생김


1Pass는 뒤집어서 그려서 뒤에가 그려진다.
2Pass는 정상적으로 그리다보니
문제는 메인카메라 영역에서 그려지니까 뒤에가 Z-Fighting이 생김 (그림 13-2참고)

 

그림 13-2 (뒷면 Z-Fighting 발생)

 

그림 13-3 (뒷면 Z-Fighting 발생 원인)



 

 

그림 13-4

 

 


결과 13-4로 뒤에 z-Fighting이 사라진 것을 볼 수 있다.(조명을 넣으니)

잠시!
조명하고 바닥색도 제대로 안섞인다.

이렇게 하면 바닥색과 잘 섞인다.
가까운데는 어둡고 먼데는 밝다. 그래서 두개를 하면
그림자가 들어갈 부분과 색이 자연스럽게 섞인다.
그림자가 먼곳은 좀 더 옅게 나온다.(조명하고도 섞임)
깊이를 더해서 saturate로 값을 떨어트림
현실에서도 빛이 멀먼 멀수록 옅어짐
factor = saturate(factor + depth);


면접에서 그림자 문제가 뭐냐 물어보면
1. z-Fighting문제가 발생한다. 같은 공간에서 그리기때문에 발생하는 문제
-> 컬링을 뒤집으면서 자연스럽게 해결이 가능하다라고 말하면 됨

 

 




문제 2) 해상도 문제(그림자가 지저분함)

그림 14-1 기존 방식

 

그림 13-5 (Shadow의 크기를 늘림)


가장 간단한 방법은 shadow의 크기를 늘려주면 된다.(그림 13-5)
그런데 그러면 프레임이 박살난다.(내 기준 1000프레임 -> 300프레임)
그래서 간단히 해상도를 올리는 것으로 해상도 문제가 해결되지 않는다.(해결책이 못됨)

해상도 문제를 해결하기 위해 PCF방식이라는 게 있다.

PCF(Percentage Closer Filtering)방식
깊이를 코드로 비교하는 방식이아니라 하드웨어에 맡기는 방식
깊이를 비교한다음에 

 

 

그림 14-2 (PCF 방식 설명)

 



PCF말고도 VSM(Various Shadow Mapping : 얘도 하드웨어에 맡김)이라는 방식도 있다.

PCF의 샘플러를 만들어야 하는데 복잡해서 C에서 만들고 Shader로 넘기는 방식으로 한다. 

 

그림 14-3 (PCF 적용)


결과를 보면(그림 14-3)
선형보간이 되어서 외곽선이 어느정도 정리가 일어남
외곽선이 엷어지면서 부드러워지기는 하나 PCF만으로는 좋은 퀄리티의 그림자를 그리기 힘듬

나중에 MSAA를 다룰줄 알게 되면 Anisotropy쓴다.

PCF + Blur를 사용하면 상당부분 해결이 된다.
Blur가 옮겨다니며 주변 픽셀에 대해 평균을 구해서 뿌얘보임

결과 14-4 (PCF + Blur)

 

결과를 보면(그림 14-4)

확 부드러워짐


대형 맵을 그릴때, 단계 별로 해상도를 떨어뜨려 그림(케스케이트 그림자?)


3. 피터 패닝 문제

 

해결 방법 2가지

1) Bias 값 조정을 통한 간격 조정(완전히 해결하지 못함)

2) 디자이너가 물체를 크고 두껍게 그려야 함

 

지금은 경계면을 정확히 떨어뜨려서 계산, 그래서 경계면이 어색하지 않게 나옴
근데 하다보면 경계면이 어색하게 뜨는 경우가 생김
디자이너 임의로 약간 수정해야할 때가 있다.

 

그림 14-5 (피터 패닝 발생)


현재는 간격이 거의 발생하지 않고 있으나 실제로는 문제가 발생할 소지가 있다.
모델과 그림자 간의 간격이 발생하는 문제를 피터 패닝이라고 한다.(그림 14-5)
- Bias로 해결은 조금 가능하지만 이 방식도 힘들다고 봐야한다.
문제의 해결은 모델을 적당히 두껍게 그리는 방식으로 해결한다.

깊이가 정확히 계산이 되지 않았거나, 깊이값이 비교하기에는 너무 작은 값일때 발생, 이런거는 디자이너에게 맡겨야 한다.
그러나 임의로 가능하다.

 

그림 14-6 (Bias 값 간격 조정을 이용한 피터 패닝 방지)



float Bias = -0.0006f;을 주었었다. 미세한 값을
이걸 이용해서 간격을 조정할 수는 있다.
결과를 보면 그림자와 물체의 간격이 다르게 된다. (그림 14-6 참고)
뜨는 현상을 없애기 위해(피터 패닝) 땡겨서 물체 안에 넣을 수도 있지만,
완전히 해결되지는 않는다.
다른 두꺼운 물체는 상관없는데 물체가 얇다면 간격이 생긴다.
깊이에 대한 오차거나, 얇은 판같은거는 보이는 경우가 많다.(피터패닝)

피터패닝 : 실제 물체와 그림자간의 떠서 보이는 간격을 말한다.
가려줄 수 있도록 디자이너가 물체를 크고 두껍게 그리는 방법 밖에 없다.
bias로는 일부 보정만 가능
float z = position.z - ShadowBias;

정리
그림자 문제 3가지
Z-Fighting - Culling을 뒤집어 해결
해상도 문제 - PCF로 해결
피터 패닝 문제 - 디자이너가 모델을 적당히 두껍게 디자인

 

 

 

 

 

Shadow.h


더보기
#pragma once

class Shadow
{
public:	
	Shadow(Shader* shader, Vector3& position, float radius, UINT width = 1024, UINT height = 1024);
	~Shadow();

	void PreRender();

	ID3D11ShaderResourceView* SRV() { return renderTarget->SRV(); }

private:
	void CalcViewProjection();

private:
	struct Desc
	{
		Matrix View; // 라이팅의 위치와 방향
		Matrix Projection; // 라이팅에서의 보여줄 영역

		Vector2 MapSize; // 그림자맵의 사이즈(퀄리티 올릴때 사용)
		float Bias = -0.0006f; // 그림자 해결하기 위해 사용

		UINT Quality = 0; // 그림자를 처음 그리고 나면 퀄리티가 좋지 않음(올려가는 방법)
	} desc;

private:
	Shader* shader;
	UINT width, height; // 그림자맵의 해상도를 결정할, 깊이의 크기도 결정

	Vector3 position; // 조명을 어느 지점을 비출것인가
	float radius; // 조명 구역(그림자가 들어갈 구역의 반경이다. 반경을 내적하는 직육면체 만듬(그림자가 들어갈 영역이됨))

	RenderTarget* renderTarget; // 없어도 되지만, 어떻게 나오는 지 봐야해서
	DepthStencil* depthStencil;
	Viewport* viewport;
	// 1 : 1 : 1 맞추는게 편하다.

	ConstantBuffer* buffer; // 그림자 정보를 넘길애
	ID3DX11EffectConstantBuffer* sBuffer;
	ID3DX11EffectShaderResourceVariable* sShadowMap; // 2pass에다 깊이 줄 변수

	// PCF 방식 사용을 위해서 필요
	ID3D11SamplerState* pcfSampler;
	ID3DX11EffectSamplerVariable* sPcfSampler;
};

 

 

 

 

 

 

 

Shadow.cpp


더보기
#include "Framework.h"
#include "Shadow.h"

Shadow::Shadow(Shader* shader, Vector3& position, float radius, UINT width, UINT height)
	: shader(shader), position(position), radius(radius), width(width), height(height)
{
	// 그림자 깊이를 렌더링하기 위해 거(즉, 해상도를 결정하고 있다.)
	renderTarget = new RenderTarget(width, height);
	depthStencil = new DepthStencil(width, height);
	viewport = new Viewport((float)width, (float)height);

	// ShadowMapSize : 그림자 맵의 해상도
	// 한 픽셀 크기 알아내려고 Shader로 줌
	desc.MapSize = Vector2((float)width, (float)height);

	buffer = new ConstantBuffer(&desc, sizeof(Desc));
	sBuffer = shader->AsConstantBuffer("CB_Shadow");
	// 렌더링하고 나면 2pass에게 넘겨줄 그림자 맵
	sShadowMap = shader->AsSRV("ShadowMap");

	//Create Sampler State
	{
		D3D11_SAMPLER_DESC desc;
		ZeroMemory(&desc, sizeof(D3D11_SAMPLER_DESC));
		// Comparison? 샘플링 시 깊이 비교를 위한 필터 옵션
		// 그림자 깊이 비교를 위해
		// MIN_MAG_MIP_LINEAR : 축소, 확대, 민맵핑 선형으로
		// POINT로 세팅시 격자로 나온다.(그림자의 퀄리티가 더 떨어짐)
		// 포인트는 보간이라는게 없다.
		desc.Filter = D3D11_FILTER_COMPARISON_MIN_MAG_MIP_LINEAR;
		// ComparisonFunc : 비교 방식
		// Less_Equl이란? 작거나 같은 깊이들을 통과시키기 위한 옵션
		desc.ComparisonFunc = D3D11_COMPARISON_LESS_EQUAL;
		// 아무거나 상관 x
		desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
		desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
		desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
		// msaa와 관련된
		desc.MaxAnisotropy = 1;
		// LOD 최대 간격 = float의 최대값
		desc.MaxLOD = FLT_MAX;

		Check(D3D::GetDevice()->CreateSamplerState(&desc, &pcfSampler));
		sPcfSampler = shader->AsSampler("ShadowSampler");
	}
}

Shadow::~Shadow()
{
	SafeDelete(renderTarget);
	SafeDelete(depthStencil);
	SafeDelete(viewport);

	SafeDelete(buffer);
}

void Shadow::PreRender()
{
	ImGui::InputInt("Quality", (int*)&desc.Quality);
	desc.Quality %= 3;

	//1Pass
	ImGui::SliderFloat3("Light Direction", Context::Get()->Direction(), -1, +1);
	ImGui::SliderFloat("Bias", &desc.Bias, -0.0001f, +0.01f, "%.4f");

	// 이때 깊이가 들어감
	renderTarget->PreRender(depthStencil);
	viewport->RSSetViewport();


	// 라이트용 뷰 프로젝션을 계산해서 만든다.(매 프레임마다)
	CalcViewProjection();

	buffer->Render();
	sBuffer->SetConstantBuffer(buffer->Buffer());

	// 1Pass의 결과 밀어줌
	sShadowMap->SetResource(depthStencil->SRV());

	// 여러개 넣을때 슬롯번호 준다.
	sPcfSampler->SetSampler(0, pcfSampler);
}

// 그림자가 그려질 공간을 계산
void Shadow::CalcViewProjection()
{
	// 그림자 그릴 주 라이트에서의 그릴 뷰, 그림자를 그릴 공간에 대해 그릴 프로젝션
	// 우선 그림자 방향에서의 뷰를 만들기 위해 Up, direction, foward를 정의한다.

	Vector3 up = Vector3(0, 1, 0);
	// 주 빛의 방향
	Vector3 direction = Context::Get()->Direction();
	// 조명 방향에서 반지름 만큼 늘린다.
	// 조명이 들어와야 하니까 -로 뒤집는다.
	Vector3 position = direction * radius * -2.0f;

	// 뷰 만듬(2: 눈의 위치, 3: (받아논) 원점 위치, 4: 위 방향)
	// 뷰 공간을 만들어줌
	D3DXMatrixLookAtLH(&desc.View, &position, &this->position, &up);

	// 뷰 공간으로 원점을 이동시킴
	Vector3 origin;
	// origin은 원점으로 부터 뷰 공간 이동시킨 값이다.
	D3DXVec3TransformCoord(&origin, &this->position, &desc.View);


	// 비출 프로젝션 영역을 만듬
	// 왼쪽 면
	float l = origin.x - radius;
	float b = origin.y - radius;
	float n = origin.z - radius;

	float r = origin.x + radius;
	float t = origin.y + radius;
	float f = origin.z + radius;

	// 얘는 크기만으로 공간을 정의(얘는 공간의 반이 중심점이됨)
	// r - l은 크기가 됨
	D3DXMatrixOrthoLH(&desc.Projection, r - l, t - b, n, f);

	// offcenterLH(중심점을 정하는 것이다.)
}

 

 

 

ShadowDemo.h


더보기
#pragma once
#include "Systems/IExecute.h"

class ShadowDemo : public IExecute
{
public:
	virtual void Initialize() override;
	virtual void Ready() override {}
	virtual void Destroy() override {}
	virtual void Update() override;
	virtual void PreRender() override;
	virtual void Render() override;
	virtual void PostRender() override;
	virtual void ResizeScreen() override {}

private:
	void Mesh();
	void Airplane();
	void Kachujin();
	void KachujinCollider();
	void KachujinWeapon();
	void PointLighting();
	void SpotLighting();

	void Pass(UINT mesh, UINT model, UINT anim);


private:
	Shader* shader;

	Shadow* shadow;
	Render2D* render2D;

	CubeSky* sky;

	Material* floor;
	Material* stone;
	Material* brick;
	Material* wall;

	MeshRender* cube;
	MeshRender* cylinder;
	MeshRender* sphere;
	MeshRender* grid;

	ModelRender* airplane = NULL;


	ModelAnimator* kachujin = NULL;
	Transform* colliderInitTransforms;
	ColliderObject** colliders;

	ModelRender* weapon = NULL;
	Transform* weaponInitTransform;

	vector<MeshRender*> meshes;
	vector<ModelRender*> models;
	vector<ModelAnimator*> animators;
};

 

 

 

 

 

 

 

ShadowDemo.cpp


더보기
#include "stdafx.h"
#include "ShadowDemo.h"

void ShadowDemo::Initialize()
{
	Context::Get()->GetCamera()->RotationDegree(20, 0, 0);
	Context::Get()->GetCamera()->Position(1, 36, -85);

	shader = new Shader(L"113_Shadow.fxo");

	// 1, 1로 줄 시 정비율로 됨
	// projector = new Projector(shader, L"Environment/MagicCircle.png", 1, 1);
	
	UINT size = 1024; //* 16

	// 그림자를 비출 원점(0, 0, 0), 내부적으로 곱하니깐 사이즈가 130이됨
	shadow = new Shadow(shader, Vector3(0, 0, 0), 65, size, size);

	render2D = new Render2D();
	//render2D->GetTransform()->Position(D3D::Width() * 0.5f, D3D::Height() * 0.5f, 0);
	//render2D->GetTransform()->Scale(D3D::Width(), D3D::Height(), 1);
	// shadow에 있는 SRV
	
	render2D->GetTransform()->Position(150, D3D::Height() - 150, 0);
	render2D->GetTransform()->Scale(300, 300, 1);
	
	// 실제 그림자를 렌더링하는 것은 DSV의 SRV이다.
	// 현재는 깊이를 보기 위해 RTV의 SRV를 사용함
	render2D->SRV(shadow->SRV());

	sky = new CubeSky(L"Environment/GrassCube1024.dds");

	Mesh();
	Airplane();

	Kachujin();
	KachujinCollider();
	KachujinWeapon();

	PointLighting();
	SpotLighting();
}

void ShadowDemo::Update()
{
	sky->Update();

	cube->Update();
	grid->Update();
	cylinder->Update();
	sphere->Update();

	airplane->Update();
	kachujin->Update();

	Matrix worlds[MAX_MODEL_TRANSFORMS];
	for (UINT i = 0; i < kachujin->GetTransformCount(); i++)
	{
		kachujin->GetAttachTransform(i, worlds);

		weapon->GetTransform(i)->World(weaponInitTransform->World() * worlds[40]);
	}

	weapon->UpdateTransforms();
	weapon->Update();


	render2D->Update();
}

void ShadowDemo::PreRender()
{
	// 1Pass 그림
	shadow->PreRender();

	Pass(0, 1, 2);

	wall->Render();
	sphere->Render();

	brick->Render();
	cylinder->Render();

	stone->Render();
	cube->Render();

	floor->Render();
	grid->Render();

	airplane->Render();

	kachujin->Render();
	weapon->Render();
}

void ShadowDemo::Render()
{

	// sky는 따로 그림자 주지 않음
	sky->Pass(0);
	sky->Render();

	// 2Pass 렌더링
	Pass(3, 4, 5);

	wall->Render();
	sphere->Render();

	brick->Render();
	cylinder->Render();

	stone->Render();
	cube->Render();

	floor->Render();
	grid->Render();

	airplane->Render();

	kachujin->Render();
	weapon->Render();
}

void ShadowDemo::PostRender()
{
	render2D->Render();
}


void ShadowDemo::Mesh()
{
	//Create Material
	{
		floor = new Material(shader);
		floor->DiffuseMap("Floor.png");
		floor->Specular(1, 1, 1, 20);
		floor->SpecularMap("Floor_Specular.png");
		floor->NormalMap("Floor_Normal.png");

		stone = new Material(shader);
		stone->DiffuseMap("Stones.png");
		stone->Specular(1, 1, 1, 20);
		stone->SpecularMap("Stones_Specular.png");
		stone->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		stone->NormalMap("Stones_Normal.png");


		brick = new Material(shader);
		brick->DiffuseMap("Bricks.png");
		brick->Specular(1, 0.3f, 0.3f, 20);
		brick->SpecularMap("Bricks_Specular.png");
		brick->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		brick->NormalMap("Bricks_Normal.png");

		wall = new Material(shader);
		wall->DiffuseMap("Wall.png");
		wall->Specular(1, 1, 1, 20);
		wall->SpecularMap("Wall_Specular.png");
		wall->Emissive(1.0f, 1.0f, 1.0f, 0.3f);
		wall->NormalMap("Wall_Normal.png");
	}

	//Create Mesh
	{
		Transform* transform = NULL;

		cube = new MeshRender(shader, new MeshCube());
		transform = cube->AddTransform();
		transform->Position(0, 5, 0);
		transform->Scale(20, 10, 20);

		grid = new MeshRender(shader, new MeshGrid(5, 5));
		transform = grid->AddTransform();
		transform->Position(0, 0, 0);
		transform->Scale(12, 1, 12);

		cylinder = new MeshRender(shader, new MeshCylinder(0.5f, 3.0f, 20, 20));
		sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20));
		for (UINT i = 0; i < 5; i++)
		{
			transform = cylinder->AddTransform();
			transform->Position(-30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = cylinder->AddTransform();
			transform->Position(30, 6, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);


			transform = sphere->AddTransform();
			transform->Position(-30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);

			transform = sphere->AddTransform();
			transform->Position(30, 15.5f, -15.0f + (float)i * 15.0f);
			transform->Scale(5, 5, 5);
		}

	}

	sphere->UpdateTransforms();
	cylinder->UpdateTransforms();
	cube->UpdateTransforms();
	grid->UpdateTransforms();

	meshes.push_back(sphere);
	meshes.push_back(cylinder);
	meshes.push_back(cube);
	meshes.push_back(grid);
}

void ShadowDemo::Airplane()
{
	airplane = new ModelRender(shader);
	airplane->ReadMesh(L"B787/Airplane");
	airplane->ReadMaterial(L"B787/Airplane");

	Transform* transform = airplane->AddTransform();
	transform->Position(2.0f, 9.91f, 2.0f);
	transform->Scale(0.004f, 0.004f, 0.004f);
	airplane->UpdateTransforms();

	models.push_back(airplane);
}

void ShadowDemo::Kachujin()
{
	kachujin = new ModelAnimator(shader);
	kachujin->ReadMesh(L"Kachujin/Mesh");
	kachujin->ReadMaterial(L"Kachujin/Mesh");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Idle");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Walk");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Run");
	kachujin->ReadClip(L"Kachujin/Sword And Shield Slash");
	kachujin->ReadClip(L"Kachujin/Salsa Dancing");


	Transform* transform = NULL;

	transform = kachujin->AddTransform();
	transform->Position(0, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(0, 0, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(1, 1, 1.0f);

	transform = kachujin->AddTransform();
	transform->Position(-30, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(2, 2, 0.75f);

	transform = kachujin->AddTransform();
	transform->Position(15, 0, -30);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayBlendMode(3, 0, 1, 2);
	kachujin->SetBlendAlpha(3, 1.5f);

	transform = kachujin->AddTransform();
	transform->Position(30, 0, -32.5f);
	transform->Scale(0.075f, 0.075f, 0.075f);
	kachujin->PlayTweenMode(4, 4, 0.75f);

	kachujin->UpdateTransforms();

	animators.push_back(kachujin);
}

void ShadowDemo::KachujinCollider()
{
	UINT count = kachujin->GetTransformCount();
	colliders = new  ColliderObject * [count];

	colliderInitTransforms = new Transform();
	colliderInitTransforms->Position(-2.9f, 1.45f, -50.0f);
	colliderInitTransforms->Scale(5, 5, 75);

	for (UINT i = 0; i < count; i++)
	{
		colliders[i] = new ColliderObject();

		//colliders[i]->Init = new Transform();
		//colliders[i]->Init->Position(0, 0, 0);
		//colliders[i]->Init->Scale(10, 30, 10);

		colliders[i]->Transform = new Transform();
		//colliders[i]->Collider = new Collider(colliders[i]->Transform, colliders[i]->Init);
		colliders[i]->Collider = new Collider(colliders[i]->Transform, colliderInitTransforms);
	}
}

void ShadowDemo::KachujinWeapon()
{
	weapon = new ModelRender(shader);
	weapon->ReadMesh(L"Weapon/Sword");
	weapon->ReadMaterial(L"Weapon/Sword");

	UINT count = kachujin->GetTransformCount();
	for (UINT i = 0; i < count; i++)
		weapon->AddTransform();

	weapon->UpdateTransforms();
	models.push_back(weapon);


	weaponInitTransform = new Transform();
	weaponInitTransform->Position(-2.9f, 1.45f, -6.45f);
	weaponInitTransform->Scale(0.5f, 0.5f, 0.75f);
	weaponInitTransform->Rotation(0, 0, 1);
}

void ShadowDemo::PointLighting()
{
	PointLight light;
	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
		Color(0.0f, 0.0f, 1.0f, 1.0f), //Diffuse
		Color(0.0f, 0.0f, 0.7f, 1.0f), //Specular
		Color(0.0f, 0.0f, 0.7f, 1.0f), //Emissive
		Vector3(-30, 10, -30), 5.0f, 0.9f // 위치, 범위, 강도
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f),
		Color(1.0f, 0.0f, 0.0f, 1.0f),
		Color(0.6f, 0.2f, 0.0f, 1.0f),
		Color(0.6f, 0.3f, 0.0f, 1.0f),
		Vector3(15, 10, -30), 10.0f, 0.3f
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f), //Ambient
		Color(0.0f, 1.0f, 0.0f, 1.0f), //Diffuse
		Color(0.0f, 0.7f, 0.0f, 1.0f), //Specular
		Color(0.0f, 0.7f, 0.0f, 1.0f), //Emissive
		Vector3(-5, 1, -17.5f), 5.0f, 0.9f
	};
	Lighting::Get()->AddPointLight(light);

	light =
	{
		Color(0.0f, 0.0f, 0.0f, 1.0f),
		Color(0.0f, 0.0f, 1.0f, 1.0f),
		Color(0.0f, 0.0f, 0.7f, 1.0f),
		Color(0.0f, 0.0f, 0.7f, 1.0f),
		Vector3(-10, 1, -17.5f), 5.0f, 0.9f
	};
	Lighting::Get()->AddPointLight(light);
}

void ShadowDemo::SpotLighting()
{
	SpotLight light;
	light =
	{
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Color(0.7f, 1.0f, 0.0f, 1.0f),
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Color(0.3f, 1.0f, 0.0f, 1.0f),
		Vector3(-15, 20, -30), 25.0f,
		Vector3(0, -1, 0), 30.0f, 0.4f
	};
	Lighting::Get()->AddSpotLight(light);

	light =
	{
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Color(1.0f, 0.2f, 0.9f, 1.0f),
		Vector3(0, 20, -30), 30.0f,
		Vector3(0, -1, 0), 40.0f, 0.55f
	};
	Lighting::Get()->AddSpotLight(light);
}

void ShadowDemo::Pass(UINT mesh, UINT model, UINT anim)
{
	for (MeshRender* temp : meshes)
		temp->Pass(mesh);

	for (ModelRender* temp : models)
		temp->Pass(model);

	for (ModelAnimator* temp : animators)
		temp->Pass(anim);
}

 

 

 

 

 

 

 

113_Shadow.fx


더보기
#include "00_Global.fx"
#include "00_Light.fx"
#include "00_Render.fx"

// 2Pass 깊이를 비교하면서 그림자를 그림
// 1Pass의 결과 ShadowMap을 가져다가 현재 z하고 비교하면서 그려나감(그림자가 생김)
float4 PS(MeshOutput input) : SV_Target
{
    // PS_AllLight을 해서 조명 처리가 됨
    float4 color = PS_AllLight(input);
    
    // sPosition : 라이트 방향에서 WVP가 적용된 놈
    float4 position = input.sPosition;
    
    //프로젝션 하듯이 이미지 2D 공간에서 한다.
    // ShadowMap이라는 애로 1Pass 결과가 들어가있다.
    // 그래서 2Pass에서도 마찬가지로 이미지공간(UV)에서 비교해야한다.
    // NDC 공간으로 만들어준다.
    position.xyz /= position.w;
    
    // NDC 영역에 안들어오면 그림자를 계산할 부분이 아니다.(라이트 공간의 VP공간인데
    // VP이 NDC를 넘어섰다면 그리지 않음)
    // 라이트 부분이 안닿는 부분은 깔끔히 값이 그대로 나온다.(그래야 나중에 텍스쳐랑 합쳐도 이상한 값이 안들어감)
    [flatten]
    if (position.x < -1.0f || position.x > +1.0f ||
        position.y < -1.0f || position.y > +1.0f ||
        position.z < +0.0f || position.z > +1.0f)
    {
        return color;
    }
    
    
    
    // UV 공간으로 만들어 준다.
    position.x = position.x * 0.5f + 0.5f;
    position.y = -position.y * 0.5f + 0.5f;
    
    
    // ShadowMap을 불러옴
    float depth = 0; // 1Pass는 depth이다.
    // ShadowBias은 피터패닝문제로 해결을 위해 값을 뺴준다.
    float z = position.z - ShadowBias; // 2Pass는 z라고 표현
    float factor = 0; // 결과
    
    
    if(ShadowQuality == 0) // 그냥
    {
        // Sapmling도 0 ~ 1까지니까, NDC공간해서 -> UV 공간해준것
        // z도 마찬가지로 이미지 공간이 된다.
        // 1Pass의 결과는 xy에 들어가 있다.
        // r을 써야함, DSV 깊이를 렌더링 할때 포맷이 R32여서 Red채널을 써야함
        depth = ShadowMap.Sample(LinearSampler, position.xy).r;
    
        // 현재 우리가 그리는 z(깊이)와 비교함
        // depth가 z보다 크거나 같다면 빛이 들어가지 않을 영역
        // depth >= z가 참이면 float으로 바뀌어서 1이 들어감, 아니라면 그림자가 생겨야 하는 구간이라면 0이됨
        // 이 값을 그대로 색한테 곱해줄 것이다.
        factor = (float) (depth >= z);
    }
    
    
    else if (ShadowQuality == 1) // PCF
    {
        // 시스템에게 줄때(PCF) 방식을 할때는 거꾸로 준다.
        // 작거나 같은것만 그림자를 지게해라
        
        // 뒤집어서 depth에 z깊이를 넣는다.        
        depth = position.z;
        // SampleBias : 오차를 보정하면서 샘플링
        // SampleCmp : 민맵핑 있게 샘플링
        // SampleCmpLevelZero : 민맵핑 없이 샘플링
        // 깊이 비교
        // depth : 깊이 비교
        // 결국 샘플링된 값을 depth(깊이)와 비교
        // 부드럽게 만들어서 factor에 넘겨줌
        factor = ShadowMap.SampleCmpLevelZero(ShadowSampler, position.xy, depth).r;
    }
   
    else if (ShadowQuality == 2) //PCF + Blur
    {
        depth = position.z;
        
        // ShadowMapSize : 그림자 맵의 해상도
        float2 size = 1.0f / ShadowMapSize;
        float2 offsets[] =
        {
            float2(-size.x, -size.y), float2(0.0f, -size.y), float2(+size.x, -size.y),
            float2(-size.x, 0.0f), float2(0.0f, 0.0f), float2(+size.x, 0.0f),
            float2(-size.x, +size.y), float2(0.0f, +size.y), float2(+size.x, +size.y),
        };
        
        
        float2 uv = 0;
        float sum = 0;
        
        [unroll(9)]
        for (int i = 0; i < 9; i++)
        {
            uv = position.xy + offsets[i];
            // 포지션을 주변 픽셀하고 옮겨다니면서, 9개의 주변 픽셀에 대한 평균을 수행함
            // 블러링 방식으로 그림자 퀄리티 향상 -> 주변 픽셀에 대한 평균을 구해서 뿌연 효과가 나옴
            sum += ShadowMap.SampleCmpLevelZero(ShadowSampler, uv, depth).r;
        }
        
        factor = sum / 9.0f;
    }
    
    
    // 이렇게 하면 바닥색과 잘 섞인다.
    // 가까운데는 어둡고 먼데는 밝다. 그래서 두개를 하면
    // 그림자가 들어갈 부분과 색이 자연스럽게 섞인다.
    // 그림자가 먼곳은 좀 더 옅게 나온다.
    factor = saturate(factor + depth);
    
    return color * factor;
}

technique11 T0
{
    //1Pass - Depth Rendering
    P_RS_VP(P0, FrontCounterClockwise_True, VS_Depth_Mesh, PS_Depth)
    P_RS_VP(P1, FrontCounterClockwise_True, VS_Depth_Model, PS_Depth)
    P_RS_VP(P2, FrontCounterClockwise_True, VS_Depth_Animation, PS_Depth)

    P_VP(P3, VS_Mesh, PS)
    P_VP(P4, VS_Model, PS)
    P_VP(P5, VS_Animation, PS)
}

 

 

 

 

00_Light.fx 추가된 내용


 

더보기
...

////////////////////////////////////////////////////////////////////////////

Texture2D ShadowMap; // 깊이 맵이 들어옴

//SamplerState 
//Comparison을 위한 SamplerState는 SamplerComparisonState를 사용해야 한다.
SamplerComparisonState ShadowSampler;

cbuffer CB_Shadow
{
    matrix ShadowView;
    matrix ShadowProjection;
    
    float2 ShadowMapSize;
    float ShadowBias;
    
    uint ShadowQuality;
};

 

 

 

00_Render.fx 추가된 내용


더보기
...
////////////////////////////////////////////////////////////////////////////

// sPosition 추가됨

#define VS_GENERATE \
output.oPosition = input.Position.xyz; \
\
output.Position = WorldPosition(input.Position); \
output.wPosition = output.Position.xyz; \
output.Position = ViewProjection(output.Position); \
output.wvpPosition = output.Position; \
output.wvpPosition_Sub = output.Position; \
\
output.sPosition = WorldPosition(input.Position); \
output.sPosition = mul(output.sPosition, ShadowView); \
output.sPosition = mul(output.sPosition, ShadowProjection); \
\
output.Normal = WorldNormal(input.Normal); \
output.Tangent = WorldTangent(input.Tangent); \
\
output.Uv = input.Uv; \
output.Color = input.Color;

////////////////////////////////////////////////////////////////////////////
// VS의 깊이용 렌더링을 해서 리턴을 해주는 애
struct DepthOutput
{
    float4 Position : SV_Position;
    float4 sPosition : Position1; // sPosition이란? 라이팅 방향에서의 WVP 변환한 값을 저장할 값
    
};

// RTV에 테스트용으로 써주기 위해(실제로 DSV는 PS를 거치지 않고 바로 OM으로 간다.)
float4 PS_Depth(DepthOutput input) :SV_Target
{
    // 깊이를 NDC로 만들기 위해 Z값을 w로 나눠서 사용한다.
    float depth = input.Position.z / input.Position.w;
    
    // 가까운 건 검은색, 먼건 흰색이 나온다.
    return float4(depth, depth, depth, 1.0f);
}

// 여기는 라이트 방향에서의 viewprojection이 들어감
#define VS_DEPTH_GENERATE \
output.Position = WorldPosition(input.Position);\
output.Position = mul(output.Position, ShadowView);\
output.Position = mul(output.Position, ShadowProjection);\
\
output.sPosition = output.Position;

////////////////////////////////////////////////////////////////////////////

DepthOutput VS_Depth_Mesh(VertexMesh input)
{
    DepthOutput output;
    
    SetMeshWorld(World, input);
    VS_DEPTH_GENERATE
    
    
    return output;
}

DepthOutput VS_Depth_Model(VertexModel input)
{
    DepthOutput output;
    
    SetModelWorld(World, input);
    VS_DEPTH_GENERATE
    
    
    return output;
}


DepthOutput VS_Depth_Animation(VertexModel input)
{
    DepthOutput output;
    
    if (BlendFrames[input.InstanceID].Mode == 0)
        SetTweenWorld(World, input);
    else
        SetBlendWorld(World, input);
    
    VS_DEPTH_GENERATE
    
    return output;
}

 

 

 

00_Global.fx 추가된 내용


더보기
// 모든 정점 셰이더가 이 방법으로 리턴함
struct MeshOutput
{
    float4 Position : SV_Position0;
    float3 oPosition : Position1;
    float3 wPosition : Position2;    
    float4 wvpPosition : Position3;
    float4 wvpPosition_Sub : Position4; 
    
    float4 sPosition : Position5; // Light방향에서의 WVP -> 추가됨
    
    float3 Normal : Normal;
    float3 Tangent : Tangent;
    float2 Uv : Uv;
    float4 Color : Color;
};