Введение[]
Shadow map (теневые карты) - один из вариантов реализации теней. Принцип работы прост - если бы вы находились в точке света и смотрели по его направлению, то все объекты, которые бы вы видели, являлись освещенными, а та область которая находится за этими объеками была бы в тени.
Почитать теорию можно здесь (на русском) и здесь (английский). Последнюю статью рекомендую хотя бы посмотреть, так как кроме теории там представлен список разных реализаций (техник) теневых карт, что пригодится в будущем.
Реализация теневых карт[]
Для реализации данной техники мы должны выполнить следующие шаги:
Рендер сцены из позиции источника света в специальную текстуру глубины (в которой сохраняем расстояние от источника света, до поверхности объекта ). Данная текстура выглядит вот так:
Рендерим сцену во вторую текстуру, но уже из позиции камеры( наблюдателя ), сравнивая глубину пикселя( расстояние до источника ) со значением, записанным в текстуре глубин ( полученной в предыдущем этапе ). Если глубина пикселя больше, то пиксель затенен.
Прорисовываем финальную сцену, накладывая карту теней на объекты.
Код[]
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; }