DirectX вики
Advertisement

Введение[]

Shadow map (теневые карты) - один из вариантов реализации теней. Принцип работы прост - если бы вы находились в точке света и смотрели по его направлению, то все объекты, которые бы вы видели, являлись освещенными, а та область которая находится за этими объеками была бы в тени.

Почитать теорию можно здесь (на русском) и здесь (английский). Последнюю статью рекомендую хотя бы посмотреть, так как кроме теории там представлен список разных реализаций (техник) теневых карт, что пригодится в будущем.

Реализация теневых карт[]

Для реализации данной техники мы должны выполнить следующие шаги:

Рендер сцены из позиции источника света в специальную текстуру глубины (в которой сохраняем расстояние от источника света, до поверхности объекта ). Данная текстура выглядит вот так:

Lesson 2013-07-20 17-43-31-46











Рендерим сцену во вторую текстуру, но уже из позиции камеры( наблюдателя ), сравнивая глубину пикселя( расстояние до источника ) со значением, записанным в текстуре глубин ( полученной в предыдущем этапе ). Если глубина пикселя больше, то пиксель затенен.

Прорисовываем финальную сцену, накладывая карту теней на объекты.

Код[]

Light.h[]

#pragma once

#include "D3D11_Framework.h"

using namespace D3D11Framework;

class Light
{
public:
	void SetAmbientColor(float, float, float, float);
	void SetDiffuseColor(float, float, float, float);
	void SetPosition(float, float, float);
	void SetLookAt(float, float, float);

	XMFLOAT4 GetAmbientColor();
	XMFLOAT4 GetDiffuseColor();
	XMFLOAT3 GetPosition();

	void GenerateViewMatrix();
	void GenerateProjectionMatrix(float, float);

	XMMATRIX GetViewMatrix();
	XMMATRIX GetProjectionMatrix();

private:
	XMMATRIX m_viewMatrix;
	XMMATRIX m_projectionMatrix;

	XMFLOAT4 m_ambientColor;
	XMFLOAT4 m_diffuseColor;
	XMFLOAT3 m_position;
	XMFLOAT3 m_lookAt;
};

Данный класс будет отвечать за один источник света. Кроме того в него добавлены две матрицы - видовая и проекционная. Я выше написал что сначала надо рендерить сцену из позиции света, вот для этого здесь эти матрицы. То есть видовая матрица будет находится в позиции света и смотреть по его направлению. Проекционная матрица добавлена потому что мы будем рендерить сцену в квадратную текстуру (тогда как наш экран по ширине больше чем по высоте).


Light.cpp[]

#include "Light.h"

void Light::SetAmbientColor(float red, float green, float blue, float alpha)
{
	m_ambientColor = XMFLOAT4(red, green, blue, alpha);
}

void Light::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	m_diffuseColor = XMFLOAT4(red, green, blue, alpha);
}

void Light::SetPosition(float x, float y, float z)
{
	m_position = XMFLOAT3(x, y, z);
}

void Light::SetLookAt(float x, float y, float z)
{
	m_lookAt = XMFLOAT3(x, y, z);
}

XMFLOAT4 Light::GetAmbientColor()
{
	return m_ambientColor;
}

XMFLOAT4 Light::GetDiffuseColor()
{
	return m_diffuseColor;
}

XMFLOAT3 Light::GetPosition()
{
	return m_position;
}

void Light::GenerateViewMatrix()
{
	XMVECTOR camPos = XMVectorSet(m_position.x, m_position.y, m_position.z, 0.0f);
	XMVECTOR camLookAt = XMVectorSet( m_lookAt.x, m_lookAt.y, m_lookAt.z, 0.0f );
	XMVECTOR Up = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
	
	m_viewMatrix = XMMatrixLookAtLH(camPos, camLookAt, Up);
}

void Light::GenerateProjectionMatrix(float screenNear, float screenDepth)
{
	float fieldOfView = (float)XM_PI / 2.0f;
	float screenAspect = 1.0f;
	m_projectionMatrix = XMMatrixPerspectiveFovLH(fieldOfView, screenAspect, screenNear, screenDepth);
}

XMMATRIX Light::GetViewMatrix()
{
	return m_viewMatrix;
}

XMMATRIX Light::GetProjectionMatrix()
{
	return m_projectionMatrix;
}

Обратите внимание что  screenAspect равен 1. Почему так? Помните, мы раньше делали так -  screenAspect  = width/height? и при окне размером 800 на 600 мы получали  screenAspect  = width/height=800/600=1.333

Ну а здесь у нас сцена будет рендерить в текстуру 1024 на 1024 и если их поделить, то как раз и будет 1.0.

RenderTarget.h[]

#pragma once

#include "D3D11_Framework.h"

using namespace D3D11Framework;

class MyRender;

class RenderTarget
{
public:
	RenderTarget(MyRender *render);

	bool Init(float screenNear, float screenDepth);
	void Close();

	void SetRenderTarget();
	void ClearRenderTarget(float, float, float, float);
	
	// Получаем текстуру RT в виде shader resource view
	ID3D11ShaderResourceView* GetShaderResourceView();	
	
	XMMATRIX GetProjectionMatrix();

	void* operator new(size_t i)
	{
		return _aligned_malloc(i,16);
	}

	void operator delete(void* p)
	{
		_aligned_free(p);
	}

private:
	XMMATRIX m_projectionMatrix;
	XMMATRIX m_orthoMatrix;
	D3D11_VIEWPORT m_viewport;

	MyRender *m_render;

	ID3D11Texture2D* m_RTTexture;
	ID3D11Texture2D* m_DSTexture;
	ID3D11RenderTargetView* m_RTV;
	ID3D11ShaderResourceView* m_SRV;
	ID3D11DepthStencilView* m_DSV;
};

Данный класс будет управлять рендером в текстуру.

 RenderTarget.cpp[]

#include "RenderTarget.h"
#include "MyRender.h"

const int SHADOWMAP_WIDTH = 1024;
const int SHADOWMAP_HEIGHT = 1024;

RenderTarget::RenderTarget(MyRender *render)
{
	m_RTTexture = nullptr;
	m_RTV = nullptr;
	m_SRV = nullptr;
	m_DSTexture = nullptr;
	m_DSV = nullptr;
	m_render = render;
}

bool RenderTarget::Init(float screenNear, float screenDepth)
{		
	// Сначала мы создаем текстуру в которую будем выводить shadow map

	// создаем текстуру размером SHADOWMAP_WIDTH х SHADOWMAP_HEIGHT
	// данная текстура будет использоваться как render target (установили
	// флаг - D3D11_BIND_RENDER_TARGET)
	D3D11_TEXTURE2D_DESC textureDesc;
	ZeroMemory(&textureDesc, sizeof(textureDesc));
	textureDesc.Width = SHADOWMAP_WIDTH;
	textureDesc.Height = SHADOWMAP_HEIGHT;
	textureDesc.MipLevels = 1;
	textureDesc.ArraySize = 1;
	textureDesc.Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
	textureDesc.SampleDesc.Count = 1;
	textureDesc.Usage = D3D11_USAGE_DEFAULT;
	textureDesc.BindFlags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
	textureDesc.CPUAccessFlags = 0;
	textureDesc.MiscFlags = 0;
	if( FAILED(m_render->m_pd3dDevice->CreateTexture2D(&textureDesc, NULL, &m_RTTexture)) )
		return false;
	
	// Создаем render target view
	D3D11_RENDER_TARGET_VIEW_DESC renderTargetViewDesc;
	renderTargetViewDesc.Format = textureDesc.Format;
	renderTargetViewDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	renderTargetViewDesc.Texture2D.MipSlice = 0;
	if( FAILED(m_render->m_pd3dDevice->CreateRenderTargetView(m_RTTexture, &renderTargetViewDesc, &m_RTV)) )
		return false;

	// создаем shader resource view c ранее созданной текстуры
	D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
	shaderResourceViewDesc.Format = textureDesc.Format;
	shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
	shaderResourceViewDesc.Texture2D.MipLevels = 1;	
	if( FAILED(m_render->m_pd3dDevice->CreateShaderResourceView(m_RTTexture, &shaderResourceViewDesc, &m_SRV)) )
		return false;
	
	// Создаем Depth Stencil View

	// Создаем текстуру глубины
	D3D11_TEXTURE2D_DESC depthBufferDesc;
	ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
	depthBufferDesc.Width = SHADOWMAP_WIDTH;
	depthBufferDesc.Height = SHADOWMAP_HEIGHT;
	depthBufferDesc.MipLevels = 1;
	depthBufferDesc.ArraySize = 1;
	depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	depthBufferDesc.SampleDesc.Count = 1;
	depthBufferDesc.SampleDesc.Quality = 0;
	depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
	depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	depthBufferDesc.CPUAccessFlags = 0;
	depthBufferDesc.MiscFlags = 0;
	if( FAILED(m_render->m_pd3dDevice->CreateTexture2D(&depthBufferDesc, NULL, &m_DSTexture)) )
		return false;

	// создаем depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
	ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
	depthStencilViewDesc.Format = depthBufferDesc.Format;
	depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	depthStencilViewDesc.Texture2D.MipSlice = 0;
	if( FAILED(m_render->m_pd3dDevice->CreateDepthStencilView(m_DSTexture, &depthStencilViewDesc, &m_DSV)) )
		return false;

	// Задаем вьюпорт по размеру нашей текстуры
	m_viewport.Width = (float)SHADOWMAP_WIDTH;
	m_viewport.Height = (float)SHADOWMAP_HEIGHT;
	m_viewport.MinDepth = 0.0f;
	m_viewport.MaxDepth = 1.0f;
	m_viewport.TopLeftX = 0.0f;
	m_viewport.TopLeftY = 0.0f;

	// Создаем проекционную матрицу
	m_projectionMatrix = XMMatrixPerspectiveFovLH(((float)XM_PI / 4.0f), ((float)SHADOWMAP_WIDTH / (float)SHADOWMAP_HEIGHT), screenNear, screenDepth);
	
	return true;
}

void RenderTarget::Close()
{
	_RELEASE(m_DSV);
	_RELEASE(m_DSTexture);
	_RELEASE(m_SRV);
	_RELEASE(m_RTV);
	_RELEASE(m_RTTexture);
}

void RenderTarget::SetRenderTarget()
{
	// Биндим RTV и буфер глубины к конвееру
	m_render->m_pImmediateContext->OMSetRenderTargets(1, &m_RTV, m_DSV);
	// Устанавливаем вьюпорт
	m_render->m_pImmediateContext->RSSetViewports(1, &m_viewport);
}

void RenderTarget::ClearRenderTarget(float red, float green, float blue, float alpha)
{
	float color[4] = {red, green, blue, alpha};
	m_render->m_pImmediateContext->ClearRenderTargetView(m_RTV, color);
	m_render->m_pImmediateContext->ClearDepthStencilView(m_DSV, D3D11_CLEAR_DEPTH, 1.0f, 0);
}

ID3D11ShaderResourceView* RenderTarget::GetShaderResourceView()
{
	return m_SRV;
}

XMMATRIX RenderTarget::GetProjectionMatrix()
{
	return m_projectionMatrix;
}

SHADOWMAP_WIDTH и SHADOWMAP_HEIGHT указывают размер карты теней. Позже можете поэксперементировать с размером. Чем он меньше, тем меньше нагрузки, но при этом ухудшается качество. Например у меня, если выставить размер как 256х256, то будет 1500 FPS, но тень при этом будет "пикселявой". А при размере 1024х1024 у меня всего 300 FPS, но тень уже получше:).

DepthShader.h[]

#pragma once

#include "D3D11_Framework.h"

using namespace D3D11Framework;

class MyRender;

class DepthShader
{
public:
	DepthShader(MyRender *render);

	bool Init();
	void Close();
	void Render(int index, CXMMATRIX WVP);

private:
	MyRender *m_render;

	Shader *m_shader;
	ID3D11Buffer *m_matrixBuffer;
};

У нас будет два класса для работы с шейдерами. Данный используется при рендере глубины в текстуру.

DepthShader.cpp[]

#include "DepthShader.h"
#include "MyRender.h"

struct MatrixBufferType
{
	XMMATRIX WVP;
};

DepthShader::DepthShader(MyRender *render)
{
	m_shader = nullptr;
	m_matrixBuffer = nullptr;
	m_render = render;
}

bool DepthShader::Init()
{
	// инициализируем шейдер и входной формат
	m_shader = new Shader(m_render);
	m_shader->AddInputElementDesc("POSITION", DXGI_FORMAT_R32G32B32_FLOAT);
	if ( !m_shader->CreateShader(L"depth.vs", L"depth.ps") )
		return false;

	// создаем константный буфер
	m_matrixBuffer = Buffer::CreateConstantBuffer(m_render->m_pd3dDevice, sizeof(MatrixBufferType), false);

	return true;
}

void DepthShader::Render(int index, CXMMATRIX WVP)
{
	MatrixBufferType cb;

	cb.WVP = XMMatrixTranspose(WVP);
	m_render->m_pImmediateContext->UpdateSubresource(m_matrixBuffer, 0, NULL, &cb, 0, 0);
	m_render->m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_matrixBuffer);

	m_shader->Draw();	
	m_render->m_pImmediateContext->DrawIndexed(index, 0, 0);
}


void DepthShader::Close()
{
	_CLOSE(m_shader);
	_RELEASE(m_matrixBuffer);
}

ShadowShader.h[]

#pragma once

#include "D3D11_Framework.h"
#include "Light.h"

using namespace D3D11Framework;

class MyRender;

class ShadowShader
{

public:
	ShadowShader(MyRender *render);

	bool Init();
	void Close();
	void Render(int indexCount, CXMMATRIX worldMatrix, CXMMATRIX WVP, CXMMATRIX WVPlight, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* depthMapTexture, Light &light);
	
private:
	MyRender *m_render;
	Shader *m_shader;
	ID3D11SamplerState* m_sampleStateWrap;
	ID3D11SamplerState* m_sampleStateClamp;
	ID3D11Buffer* m_matrixBuffer;
	ID3D11Buffer* m_lightBuffer;
	ID3D11Buffer* m_lightBuffer2;
};

Второй класс для шейдера. Данный класс будет выводить конечный результат - модель с тенью.

 ShadowShader.cpp[]

 #include "ShadowShader.h"
#include "MyRender.h"

struct MatrixBufferType
{
	XMMATRIX world;
	XMMATRIX WVP;
	XMMATRIX wvplight;
};

struct LightBufferType
{
	XMFLOAT4 ambientColor;
	XMFLOAT4 diffuseColor;
};

struct LightBufferType2
{
	XMFLOAT3 lightPosition;
	float padding;
};

ShadowShader::ShadowShader(MyRender *render)
{
	m_render = render;
	m_shader = nullptr;
	m_sampleStateWrap = nullptr;
	m_sampleStateClamp = nullptr;
	m_matrixBuffer = nullptr;
	m_lightBuffer = nullptr;
	m_lightBuffer2 = nullptr;
}

bool ShadowShader::Init()
{
	// инициализируем шейдер и входной формат
	m_shader = new Shader(m_render);
	m_shader->AddInputElementDesc("POSITION", DXGI_FORMAT_R32G32B32_FLOAT);
	m_shader->AddInputElementDesc("TEXCOORD", DXGI_FORMAT_R32G32_FLOAT);
	m_shader->AddInputElementDesc("NORMAL", DXGI_FORMAT_R32G32B32_FLOAT);
	if ( !m_shader->CreateShader(L"shadow.vs", L"shadow.ps") )
		return false;
	
	// Создаем sampler state для того чтобы 
	// установить режим адресации текстуры как WRAP
	D3D11_SAMPLER_DESC samplerDesc;
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	if( FAILED(m_render->m_pd3dDevice->CreateSamplerState(&samplerDesc, &m_sampleStateWrap)) )
		return false;

	// Создаем sampler state для того чтобы 
	// установить режим адресации текстуры как CLAMP
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;

	if( FAILED(m_render->m_pd3dDevice->CreateSamplerState(&samplerDesc, &m_sampleStateClamp)) )
		return false;
	
	// создаем константные буферы
	m_matrixBuffer = Buffer::CreateConstantBuffer(m_render->m_pd3dDevice, sizeof(MatrixBufferType), false);
	m_lightBuffer = Buffer::CreateConstantBuffer(m_render->m_pd3dDevice, sizeof(LightBufferType), false);
	m_lightBuffer2 = Buffer::CreateConstantBuffer(m_render->m_pd3dDevice, sizeof(LightBufferType2), false);
	
	return true;
}

void ShadowShader::Render(int indexCount, CXMMATRIX worldMatrix, CXMMATRIX WVP,  CXMMATRIX WVPlight, ID3D11ShaderResourceView* texture, ID3D11ShaderResourceView* depthMapTexture, Light &light)
{
	MatrixBufferType cb;
	cb.world = XMMatrixTranspose(worldMatrix);
	cb.WVP = XMMatrixTranspose(WVP);
	cb.wvplight = XMMatrixTranspose(WVPlight);
	m_render->m_pImmediateContext->UpdateSubresource(m_matrixBuffer, 0, NULL, &cb, 0, 0);
	m_render->m_pImmediateContext->VSSetConstantBuffers(0, 1, &m_matrixBuffer);

	LightBufferType2 lb2;
	lb2.lightPosition = light.GetPosition();
	lb2.padding = 0.0f;
	m_render->m_pImmediateContext->UpdateSubresource(m_lightBuffer2, 0, NULL, &lb2, 0, 0);
	m_render->m_pImmediateContext->VSSetConstantBuffers(1, 1, &m_lightBuffer2);
	
	LightBufferType lb;
	lb.ambientColor = light.GetAmbientColor();
	lb.diffuseColor = light.GetDiffuseColor();
	m_render->m_pImmediateContext->UpdateSubresource(m_lightBuffer, 0, NULL, &lb, 0, 0);
	m_render->m_pImmediateContext->PSSetConstantBuffers(0, 1, &m_lightBuffer);
		
	// Передаем в шейдер две текстуры
	m_render->m_pImmediateContext->PSSetShaderResources(0, 1, &texture);
	m_render->m_pImmediateContext->PSSetShaderResources(1, 1, &depthMapTexture);

	// Передаем в шейдер оба sampler state
	m_render->m_pImmediateContext->PSSetSamplers(0, 1, &m_sampleStateClamp);
	m_render->m_pImmediateContext->PSSetSamplers(1, 1, &m_sampleStateWrap);

	m_shader->Draw();
	m_render->m_pImmediateContext->DrawIndexed(indexCount, 0, 0);
}

void ShadowShader::Close()
{
	_CLOSE(m_shader);
	_RELEASE(m_sampleStateWrap);
	_RELEASE(m_sampleStateClamp);
	_RELEASE(m_matrixBuffer);
	_RELEASE(m_lightBuffer);
	_RELEASE(m_lightBuffer2);
}

Новым здесь является создание и передача сразу двух sampler state. Про режимы адресации текстуры можно почитать здесь. И еще момент, в вершинный шейдер мы передаем два константных буфера -  m_matrixBuffer и m_lightBuffer2. 

MyInput.h[]

#pragma once

#include "D3D11_Framework.h"
#include "MyRender.h"

using namespace D3D11Framework;

class MyInput : public InputListener
{
public:
	MyInput(MyRender *render);

	bool KeyPressed(const KeyEvent &arg);
	bool KeyReleased(const KeyEvent &arg);

private:
	MyRender *m_render;
};

В данном примере мы будем управлять камерой, поэтому нам нужен класс для управления вводом.

MyInput.cpp[]

#include "MyInput.h"

MyInput::MyInput(MyRender *render)
{
	m_render = render;
}

bool MyInput::KeyPressed(const KeyEvent &arg)
{
	if (arg.code == KEY_UP)
		m_render->m_key_up = true;
	else if (arg.code == KEY_DOWN)
		m_render->m_key_down = true;
	else if (arg.code == KEY_LEFT)
		m_render->m_key_left = true;
	else if (arg.code == KEY_RIGHT)
		m_render->m_key_right = true;
	else if (arg.code == KEY_A)
		m_render->m_key_a = true;
	else if (arg.code == KEY_Z)
		m_render->m_key_z = true;
	else if (arg.code == KEY_S)
		m_render->m_key_s = true;
	else if (arg.code == KEY_X)
		m_render->m_key_x = true;

	return true;
}

bool MyInput::KeyReleased(const KeyEvent &arg)
{
	if (arg.code == KEY_UP)
		m_render->m_key_up = false;
	else if (arg.code == KEY_DOWN)
		m_render->m_key_down = false;
	else if (arg.code == KEY_LEFT)
		m_render->m_key_left = false;
	else if (arg.code == KEY_RIGHT)
		m_render->m_key_right = false;
	else if (arg.code == KEY_A)
		m_render->m_key_a = false;
	else if (arg.code == KEY_Z)
		m_render->m_key_z = false;
	else if (arg.code == KEY_S)
		m_render->m_key_s = false;
	else if (arg.code == KEY_X)
		m_render->m_key_x = false;

	return true;
}

main.cpp[]

#include "MyRender.h"
#include "MyInput.h"

int main()
{
	Framework framework;

	MyRender *render = new MyRender();
	MyInput *input = new MyInput(render);

	FrameworkDesc desc;
	desc.render = render;

	framework.Init(desc);
	framework.AddInputListener(input);
	framework.Run();
	framework.Close();
	
	return 0;
}

MyRender.h[]

#pragma once

#include "D3D11_Framework.h"
#include "Light.h"
#include "DepthShader.h"
#include "RenderTarget.h"
#include "ShadowShader.h"

using namespace D3D11Framework;

class MyRender : public Render
{
public:
	MyRender();
	bool Init();
	bool Draw();
	void Close();

	void RenderSceneToTexture();
	void RenderSceneToWindow();
		
private:
	friend DepthShader;
	friend RenderTarget;
	friend ShadowShader;
	friend class MyInput;
		
	Camera m_cam;
	Light m_Light;

	RenderTarget *m_RenderTexture;
	DepthShader *m_DepthShader;
	ShadowShader *m_ShadowShader;

	D3D11_VIEWPORT m_viewport;
	
	bool m_key_up;
	bool m_key_down;
	bool m_key_left;
	bool m_key_right;
	bool m_key_a;
	bool m_key_z;
	bool m_key_s;
	bool m_key_x;
	
	// индексный и вершинный буферы для ящика и поверхности
	ID3D11Buffer *m_vb_ground; 
	ID3D11Buffer *m_ib_ground;
	ID3D11Buffer* m_vb_box;
	ID3D11Buffer* m_ib_box;

	// текстуры
	ID3D11ShaderResourceView* m_texture_ground;
	ID3D11ShaderResourceView* m_texture_box1;
	ID3D11ShaderResourceView* m_texture_box2;
	
	// позиции первого и второго ящика
	XMFLOAT3 m_posbox1;
	XMFLOAT3 m_posbox2;
};

MyRender.cpp[]

#include "MyRender.h"

struct Vertex
{
	Vertex(float x, float y, float z, float u, float v, float nx, float ny, float nz) : position(x,y,z), texture(u, v), normal(nx, ny, nz){}

	XMFLOAT3 position;
	XMFLOAT2 texture;
	XMFLOAT3 normal;
};

MyRender::MyRender()
{
	m_key_up = false;
	m_key_down = false;
	m_key_left = false;
	m_key_right = false;
	m_key_a = false;
	m_key_z = false;
	m_key_s = false;
	m_key_x = false;
}

bool MyRender::Init()
{	
	// создаем вьюпорт окна. Вообще это неверное решение, так как данный 
	// вьюпорт мы создавали во фреймворке. Но так как там нет метода для его получения, мы создаем его еще раз
	m_viewport.Width = (float)m_width;
	m_viewport.Height = (float)m_height;
	m_viewport.MinDepth = 0.0f;
	m_viewport.MaxDepth = 1.0f;
	m_viewport.TopLeftX = 0.0f;
	m_viewport.TopLeftY = 0.0f;
	m_pImmediateContext->RSSetViewports(1, &m_viewport);
	
	// настраиваем камеру
	m_cam.SetPos(0.0f, 2.0f, -10.0f);

	// позиции ящиков
	m_posbox1 = XMFLOAT3(-1.0f, 2.0f, 0.0f);
	m_posbox2 = XMFLOAT3(2.0f, 2.0f, 0.0f);
	
	// настраиваем свет
	m_Light.SetAmbientColor(0.15f, 0.15f, 0.15f, 1.0f);
	m_Light.SetDiffuseColor(1.0f, 1.0f, 1.0f, 1.0f);
	m_Light.SetLookAt(0.0f, 0.0f, 0.0f);
	m_Light.GenerateProjectionMatrix(1.0f, 100.0f);

	m_RenderTexture = new RenderTarget(this);
	if( !m_RenderTexture->Init(1.0f, 100.0f) )
		return false;
	m_DepthShader = new DepthShader(this);
	if( !m_DepthShader->Init() )
		return false;
	m_ShadowShader = new ShadowShader(this);
	if(!m_ShadowShader->Init())
		return false;

	// геометрия (plane)
	Vertex vert1[] =
	{
		Vertex(-5.0f, 0.0f, 5.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f),
		Vertex(5.0f, 0.0f, 5.0f, 1.0f, 0.0f,0.0f, 1.0f, 0.0f),
		Vertex(-5.0f, 0.0f, -5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f),
		Vertex(-5.0f, 0.0f, -5.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f),
		Vertex(5.0f, 0.0f, 5.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f),
		Vertex(5.0f, 0.0f, -5.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f)
	};
	unsigned long indices1[6] = 
	{
		0,1,2,
		3,4,5
	};

	// создаем вершинный и индексный буферы
	m_vb_ground = Buffer::CreateVertexBuffer(m_pd3dDevice, sizeof(Vertex)*6, false, &vert1);
	m_ib_ground = Buffer::CreateIndexBuffer(m_pd3dDevice, sizeof(unsigned long)*6, false, &indices1);
	// грузим текстуру для поверхности
	D3DX11CreateShaderResourceViewFromFile(m_pd3dDevice, L"stone01.jpg", NULL, NULL, &m_texture_ground, NULL);

	// геометрия (box)
	Vertex vert2[] =
	{
		Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f),
		Vertex(-1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f),
		Vertex( 1.0f,  1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, -1.0f),
		Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, -1.0f),

		Vertex(-1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f),
		Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f),
		Vertex( 1.0f,  1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f),
		Vertex(-1.0f,  1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f),

		Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f),
		Vertex(-1.0f, 1.0f,  1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f),
		Vertex( 1.0f, 1.0f,  1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f),
		Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f),

		Vertex(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, -1.0f, 0.0f),
		Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 0.0f),
		Vertex( 1.0f, -1.0f,  1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f),
		Vertex(-1.0f, -1.0f,  1.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f),

		Vertex(-1.0f, -1.0f,  1.0f, 0.0f, 1.0f, -1.0f, 0.0f, 0.0f),
		Vertex(-1.0f,  1.0f,  1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f),
		Vertex(-1.0f,  1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f),
		Vertex(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f),

		Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f),
		Vertex( 1.0f,  1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f),
		Vertex( 1.0f,  1.0f,  1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f),
		Vertex( 1.0f, -1.0f,  1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f),
	};

	unsigned long indices2[] =
	{
		0,  1,  2,
		0,  2,  3,

		4,  5,  6,
		4,  6,  7,

		8,  9, 10,
		8, 10, 11,

		12, 13, 14,
		12, 14, 15,

		16, 17, 18,
		16, 18, 19,

		20, 21, 22,
		20, 22, 23
	};

	m_vb_box = Buffer::CreateVertexBuffer(m_pd3dDevice, sizeof( Vertex )*24, false, vert2);
	m_ib_box = Buffer::CreateIndexBuffer(m_pd3dDevice, sizeof(unsigned long)*36, false, indices2);
	// грузим текстуры для кубов
	D3DX11CreateShaderResourceViewFromFile(m_pd3dDevice, L"wall01.jpg", NULL, NULL, &m_texture_box1, NULL);
	D3DX11CreateShaderResourceViewFromFile(m_pd3dDevice, L"ice.jpg", NULL, NULL, &m_texture_box2, NULL);

	return true;
}

Создаем геометри, грузим шейдеры и т.д.

bool MyRender::Draw()
{	
	static float lightPositionX = -5.0f;
	static bool reverse = false;
	// изменяем позицию света по X
	if (!reverse)
		lightPositionX += 0.05f;
	else
		lightPositionX -= 0.05f;
	if(lightPositionX > 5.0f)
		reverse = true;
	else if(lightPositionX < -5.0f)
		reverse = false;
	m_Light.SetPosition(lightPositionX, 8.0f, -5.0f);
	// Генерируем видовую матрицу для света
	m_Light.GenerateViewMatrix();

	// управление камерой
	m_cam.MoveForward(m_key_up);
	m_cam.MoveBackward(m_key_down);
	m_cam.RotateLeft(m_key_left);
	m_cam.RotateRight(m_key_right);
	m_cam.MoveUpward(m_key_a);
	m_cam.MoveDownward(m_key_z);
	m_cam.LookUpward(m_key_s);
	m_cam.LookDownward(m_key_x);

	m_cam.Render();
	
	// рендерим сцену в текстуру
	RenderSceneToTexture();

	// рендерим сцену на экран
	RenderSceneToWindow();
		
	return true;
}

Вначале мы обновляем кадр (позицию света и камеры). Затем мы производим рендер в текстуру. В итоге мы получим текстуру глубины (текстуру в которой записан не цвет, а значение глубины). После чего выводим все уже на экран.

void MyRender::RenderSceneToTexture()
{
	XMMATRIX WVP;
	unsigned int stride = sizeof(Vertex); 
	unsigned int offset = 0;	
	
	//Указываем что нужно рендерить в текстуру
	m_RenderTexture->SetRenderTarget();
	// Очищаем ее
	m_RenderTexture->ClearRenderTarget(0.0f, 0.0f, 0.0f, 1.0f);
	
	// Выводим первый ящик
	XMMATRIX wldMatrix = XMMatrixTranslation(m_posbox1.x, m_posbox1.y, m_posbox1.z);
	WVP = wldMatrix* m_Light.GetViewMatrix()* m_Light.GetProjectionMatrix();
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_box, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_box, DXGI_FORMAT_R32_UINT, 0);
	m_DepthShader->Render(36, WVP);

	// Выводим второй ящик
	wldMatrix = XMMatrixTranslation(m_posbox2.x, m_posbox2.y, m_posbox2.z);	
	WVP = wldMatrix* m_Light.GetViewMatrix()* m_Light.GetProjectionMatrix();
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_box, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_box, DXGI_FORMAT_R32_UINT, 0);
	m_DepthShader->Render(36, WVP);
		
	// Выводим поверхность
	wldMatrix = XMMatrixTranslation(0.0f, 1.0f, 0.0f);
	WVP = wldMatrix* m_Light.GetViewMatrix()* m_Light.GetProjectionMatrix();
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_ground, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_ground, DXGI_FORMAT_R32_UINT, 0);
	m_DepthShader->Render(6, WVP);
}

Выводим всю геометрию в текстуру. При этом используем класс  m_DepthShader для того чтобы использовать шейдеры depth.vs и depth.ps.

 void MyRender::RenderSceneToWindow()
{
	// Сбрасываем render target (теперь снова будет рисовать на экран)
	m_pImmediateContext->OMSetRenderTargets(1, &m_pRenderTargetView, m_pDepthStencilView);
	// Сбрасываем вьюпорт
	m_pImmediateContext->RSSetViewports(1, &m_viewport);

	XMMATRIX camView = m_cam.GetViewMatrix();
	XMMATRIX lightViewMatrix = m_Light.GetViewMatrix();
	XMMATRIX lightProjectionMatrix = m_Light.GetProjectionMatrix();

	unsigned int stride = sizeof(Vertex); 
	unsigned int offset = 0;
	XMMATRIX wvp;
	XMMATRIX wvplight;
	XMMATRIX wldMatrix;

	// Выводим первый куб
	wldMatrix = XMMatrixTranslation(m_posbox1.x, m_posbox1.y, m_posbox1.z);
	wvp = wldMatrix * camView * m_Projection;
	wvplight = wldMatrix * lightViewMatrix * lightProjectionMatrix;
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_box, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_box, DXGI_FORMAT_R32_UINT, 0);
	m_ShadowShader->Render(36, wldMatrix, wvp, wvplight, m_texture_box1, m_RenderTexture->GetShaderResourceView(), m_Light);

	// Выводим второй куб
	wldMatrix = XMMatrixTranslation(m_posbox2.x, m_posbox2.y, m_posbox2.z);
	wvp = wldMatrix * camView * m_Projection;
	wvplight = wldMatrix * lightViewMatrix * lightProjectionMatrix;
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_box, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_box, DXGI_FORMAT_R32_UINT, 0);
	m_ShadowShader->Render(36, wldMatrix, wvp, wvplight, m_texture_box2, m_RenderTexture->GetShaderResourceView(), m_Light);

	// выводим поверхность
	wldMatrix = XMMatrixTranslation(0.0f, 1.0f, 0.0f);
	wvp = wldMatrix * camView * m_Projection;
	wvplight = wldMatrix * lightViewMatrix * lightProjectionMatrix;
	m_pImmediateContext->IASetVertexBuffers(0, 1, &m_vb_ground, &stride, &offset);
	m_pImmediateContext->IASetIndexBuffer(m_ib_ground, DXGI_FORMAT_R32_UINT, 0);
	m_ShadowShader->Render(6, wldMatrix, wvp, wvplight, m_texture_ground, m_RenderTexture->GetShaderResourceView(), m_Light);

}

Снова выводим всю геометрию, но теперь на экран и используем класс  m_ShadowShader (шейдеры shadow.vs и shadow.ps).

void MyRender::Close()
{
	_CLOSE(m_RenderTexture);
	_CLOSE(m_DepthShader);
	_CLOSE(m_ShadowShader);

	_RELEASE(m_vb_ground);
	_RELEASE(m_ib_ground);
	_RELEASE(m_vb_box);
	_RELEASE(m_ib_box);
	_RELEASE(m_texture_ground);
	_RELEASE(m_texture_box1);
	_RELEASE(m_texture_box2);
}


 Шейдеры[]

depth.vs[]

cbuffer MatrixBuffer
{
	matrix WVP;
};

struct VertexInputType
{
	float4 position : POSITION;
};

struct PixelInputType
{
	float4 position : SV_POSITION;
	float4 depthPosition : TEXTURE0;
};

PixelInputType VS(VertexInputType input)
{
	PixelInputType output;
	
	input.position.w = 1.0f;
	output.position = mul(input.position, WVP);
	// Пишем позицию в depthPosition
	output.depthPosition = output.position;
	
	return output;
}

depth.ps[]

struct PixelInputType
{
	float4 position : SV_POSITION;
	float4 depthPosition : TEXTURE0;
};

float4 PS(PixelInputType input) : SV_TARGET
{
	// Получаем значение глубины пикселя деля Z пикселя глубины на гомогенную координату W
	float depthValue = input.depthPosition.z / input.depthPosition.w;

	return float4(depthValue, depthValue, depthValue, 1.0f);
}

Напомню, данный шейдер нужен для рендера значения глубины в текстуру, поэтому здесь нет цвета.

По поводу деления  input.depthPosition.z / input.depthPosition.w:

http://www.gamedev.ru/code/articles/ShadowMapGLSL?page=2 

Делением на w мы переводим z координату из однородных в декартовы, результатом данной 
операции будет число в диапазоне[-1..1].

shadow.vs[]

cbuffer MatrixBuffer
{
	matrix world;
	matrix wvp;
	matrix wvplight;
};

cbuffer LightBuffer2
{
	float3 lightPosition;
	float padding;
};

struct VertexInputType
{
	float4 position : POSITION;
	float2 tex : TEXCOORD0;
	float3 normal : NORMAL;
};

struct PixelInputType
{
	float4 position : SV_POSITION;
	float2 tex : TEXCOORD0;
	float3 normal : NORMAL;
	float4 lightViewPosition : TEXCOORD1;
	float3 lightPos : TEXCOORD2;
};

PixelInputType VS(VertexInputType input)
{
	PixelInputType output;
        
	input.position.w = 1.0f;

	output.position = mul(input.position, wvp); 
	output.lightViewPosition = mul(input.position, wvplight);

	output.tex = input.tex;
    
	output.normal = mul(input.normal, (float3x3)world);
	output.normal = normalize(output.normal);

	// Вычисление позиции вершины в мире
	float4 worldPosition = mul(input.position, world);

	// Определение позиции света на основе позиции света и позиции вершины в мире
	output.lightPos = lightPosition.xyz - worldPosition.xyz;
	output.lightPos = normalize(output.lightPos);

	return output;
}

shadow.ps[]

Texture2D shaderTexture : register(t0);
Texture2D depthMapTexture : register(t1);

SamplerState SampleTypeClamp : register(s0);
SamplerState SampleTypeWrap  : register(s1);

cbuffer LightBuffer
{
	float4 ambientColor;
	float4 diffuseColor;
};

struct PixelInputType
{
	float4 position : SV_POSITION;
	float2 tex : TEXCOORD0;
	float3 normal : NORMAL;
	float4 lightViewPosition : TEXCOORD1;
	float3 lightPos : TEXCOORD2;
};

float4 PS(PixelInputType input) : SV_TARGET
{
	float2 projectTexCoord;
	float depthValue;
	float lightDepthValue;
    float lightIntensity;
	float4 textureColor;

	// Установка значения смещения используемого для устранения проблем точности с плавающей запятой
	float bias = 0.001f;

	float4 color = ambientColor;

	// Вычисление координат проецирования текстуры
	projectTexCoord.x =  input.lightViewPosition.x / input.lightViewPosition.w / 2.0f + 0.5f;
	projectTexCoord.y = -input.lightViewPosition.y / input.lightViewPosition.w / 2.0f + 0.5f;

	// Находится ли спроецированные координаты в пределах 0 и 1. Если да, то пиксель находится в видимости света
	if((saturate(projectTexCoord.x) == projectTexCoord.x) && (saturate(projectTexCoord.y) == projectTexCoord.y))
	{
		depthValue = depthMapTexture.Sample(SampleTypeClamp, projectTexCoord).r;

		// Вычисление глубины света
		lightDepthValue = input.lightViewPosition.z / input.lightViewPosition.w;

		// Вычитание смещения из lightDepthValue
		lightDepthValue = lightDepthValue - bias;

		// Сравнение глубины теневой карты и глубины света, для определения того, освещен или затенен пиксель
		// Если свет перед объектом, то пиксель освещен; если нет, то пиксель затенен и объект бросает тень за ним
		if(lightDepthValue < depthValue)
		{
		    // Вычисление количества света в пикселе
			lightIntensity = saturate(dot(input.normal, input.lightPos));

		    if(lightIntensity > 0.0f)
			{
				// Определение заключительного рассеяного (diffuse) света на основе рассеяного цвети и интенсивности света
				color += (diffuseColor * lightIntensity);
				color = saturate(color);
			}
		}
	}

	textureColor = shaderTexture.Sample(SampleTypeWrap, input.tex);

	return color * textureColor;
}

Итог[]

Скачать

Lesson 2013-07-20 20-32-35-01
Advertisement