这节教程我们将介绍一种生成Shadow(阴影)的主流技术,程序结构如下:
读懂此节教程你应该先懂得的技术: (1)D3D11如何求得DepthBuffer(深度缓存值),D3D11教程二十五之DepthBuffer(深度缓存)
(2)D3D11如何使用RTT技术(渲染到纹理技术) ,D3D11教程十四之RenderToTexture(RTT技术)
(3)D3D11如何使用ProjectiveTexturing(投影纹理技术),D3D11教程三十之ProjectiveTexturing(投影纹理)
同学们读此教程前,务必得搞清楚上面三项技术,否则这节教程学的将有些吃力,我敢说,把上面三个教程的技术看懂并亲自实现一遍,那么你对D3D11渲染管线的掌握已经是入门了。
事先说明我们这节教程是针对聚光灯生成阴影的,在强调一遍,我们这节教程是针对聚光灯成阴影的,利用平行光生成阴影的ShadowMap教程以后我再给出。
#pragma once
#ifndef _RENDER_MODEL_TO_TEXTURE_H
#define _RENDER_MODEL_TO_TEXTURE_H
#include
#include
#include
#include"Macro.h"
class RenderModelToTextureClass
{
private:
ID3D11ShaderResourceView* mShaderResourceView; //Shader资源视图
ID3D11Texture2D* mDepthStencilTexture;
ID3D11DepthStencilView* mDepthStencilView;
D3D11_VIEWPORT mViewPort; //视口
public:
RenderModelToTextureClass();
RenderModelToTextureClass(const RenderModelToTextureClass&other);
~RenderModelToTextureClass();
bool Initialize(ID3D11Device* d3dDevice,int TextureWidth,int TexureHeight);
void ShutDown();
void SetRenderTarget(ID3D11DeviceContext* deviceContext);
void ClearRenderTarget(ID3D11DeviceContext* deviceContext, float red, float green, float blue, float alpha);
ID3D11ShaderResourceView* GetShaderResourceView();
};
#endif // !_RENDER_3D_MODEL_TO_TEXTURE_H
#include"RenderModelToTexure.h"
RenderModelToTextureClass::RenderModelToTextureClass()
{
mShaderResourceView = NULL;
mDepthStencilTexture = NULL;
mDepthStencilView = NULL;
}
RenderModelToTextureClass::RenderModelToTextureClass(const RenderModelToTextureClass&other)
{
}
RenderModelToTextureClass::~RenderModelToTextureClass()
{
}
bool RenderModelToTextureClass::Initialize(ID3D11Device* d3dDevice, int TextureWidth, int TexureHeight)
{
//第一,填充深度视图的2D纹理形容结构体,并创建2D渲染纹理
D3D11_TEXTURE2D_DESC depthBufferDesc;
ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc));
depthBufferDesc.Width = TextureWidth;
depthBufferDesc.Height = TexureHeight;
depthBufferDesc.MipLevels = 1;
depthBufferDesc.ArraySize = 1;
depthBufferDesc.Format = DXGI_FORMAT_R24G8_TYPELESS; //24位是为了深度缓存,8位是为了模板缓存
depthBufferDesc.SampleDesc.Count = 1;
depthBufferDesc.SampleDesc.Quality = 0;
depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE; //注意深度缓存(纹理)的绑定标志
depthBufferDesc.CPUAccessFlags = 0;
depthBufferDesc.MiscFlags = 0;
HR(d3dDevice->CreateTexture2D(&depthBufferDesc, NULL, &mDepthStencilTexture));
//第二,填充深度缓存视图形容结构体,并创建深度缓存视图
D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc;
ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc));
depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
depthStencilViewDesc.Texture2D.MipSlice = 0;
HR(d3dDevice->CreateDepthStencilView(mDepthStencilTexture, &depthStencilViewDesc, &mDepthStencilView));
//第三,填充着色器资源视图形容体,并进行创建着色器资源视图,注意这是用深度缓存(纹理)来创建的,而不是渲染目标缓存(纹理)创建的
D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
shaderResourceViewDesc.Format = DXGI_FORMAT_R24_UNORM_X8_TYPELESS; //此时因为是仅仅进行深度写,而不是颜色写,所以此时Shader资源格式跟深度缓存是一样的
shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
shaderResourceViewDesc.Texture2D.MipLevels = depthBufferDesc.MipLevels;
HR(d3dDevice->CreateShaderResourceView(mDepthStencilTexture, &shaderResourceViewDesc, &mShaderResourceView));
//第四,设置视口的属性
mViewPort.Width = (float)TextureWidth;
mViewPort.Height = (float)TexureHeight;
mViewPort.MinDepth = 0.0f;
mViewPort.MaxDepth = 1.0f;
mViewPort.TopLeftX = 0.0f;
mViewPort.TopLeftY = 0.0f;
return true;
}
void RenderModelToTextureClass::ShutDown()
{
ReleaseCOM(mDepthStencilTexture);
ReleaseCOM(mDepthStencilView);
ReleaseCOM(mShaderResourceView);
}
//让此时所有图形渲染到这个目前渲染的位置
void RenderModelToTextureClass::SetRenderTarget(ID3D11DeviceContext* deviceContext)
{
ID3D11RenderTargetView* renderTarget[1] = { 0 };
//绑定渲染目标视图和深度模板视图到输出渲染管线
deviceContext->OMSetRenderTargets(1,renderTarget, mDepthStencilView);
//设置视口
deviceContext->RSSetViewports(1, &mViewPort);
}
//不用清除背后缓存,因为不需要进颜色写(ColorWrite),仅仅进行深度写
void RenderModelToTextureClass::ClearRenderTarget(ID3D11DeviceContext* deviceContext, float red, float green, float blue, float alpha)
{
//设置清除缓存为的颜色
float color[4];
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;
//清除深度缓存和模板缓存
deviceContext->ClearDepthStencilView(mDepthStencilView, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
}
// 将“被渲染模型到纹理的纹理”作为ShaderResourceView资源返回,这个资源将会跟其它的ShaderResourceView资源一样被送入Shader里计算.
ID3D11ShaderResourceView* RenderModelToTextureClass::GetShaderResourceView()
{
return mShaderResourceView;
}
//让此时所有图形渲染到这个类的深度缓存
void RenderModelToTextureClass::SetRenderTarget(ID3D11DeviceContext* deviceContext)
{
ID3D11RenderTargetView* renderTarget[1] = { 0 };
//绑定渲染目标视图和深度模板视图到输出渲染管线
deviceContext->OMSetRenderTargets(1,renderTarget, mDepthStencilView);
//设置视口
deviceContext->RSSetViewports(1, &mViewPort);
}
Texture2D BaseTexture:register(t0); //基础纹理
Texture2D ShadowMap:register(t1); //投影纹理
SamplerState WrapSampleType:register(s0); //采样方式
SamplerState ClampSampleType:register(s1); //采样方式
//VertexShader
cbuffer CBMatrix:register(b0)
{
matrix World;
matrix View;
matrix Proj;
matrix WorldInvTranspose;
matrix ProjectorView;
matrix ProjectorProj;
};
cbuffer CBLight:register(b1)
{
float4 DiffuseColor;
float4 AmbientColor;
float3 PointLightPos;
float pad; //填充系数
}
struct VertexIn
{
float3 Pos:POSITION;
float2 Tex:TEXCOORD0; //多重纹理可以用其它数字
float3 Normal:NORMAL;
};
struct VertexOut
{
float4 Pos:SV_POSITION;
float4 ProjPos:POSITION; //基于点光源投影在齐次裁剪空间的坐标
float2 Tex:TEXCOORD0;
float3 W_Normal:NORMAL; //世界空间的法线
float3 Pos_W:NORMAL1; //物体在世界空间的顶点坐标
};
VertexOut VS(VertexIn ina)
{
VertexOut outa;
//将坐标变换到观察相机下的齐次裁剪空间
outa.Pos = mul(float4(ina.Pos,1.0f), World);
outa.Pos = mul(outa.Pos, View);
outa.Pos = mul(outa.Pos, Proj);
//将顶点法向量由局部坐标变换到世界坐标
outa.W_Normal = mul(ina.Normal, (float3x3)WorldInvTranspose); //此事世界逆转置矩阵的第四行本来就没啥用
//对世界空间的顶点法向量进行规格化
outa.W_Normal = normalize(outa.W_Normal);
//获取纹理坐标
outa.Tex= ina.Tex;
//将坐标变换到投影相机下的齐次裁剪空间
outa.ProjPos= mul(float4(ina.Pos, 1.0f), World);
outa.ProjPos = mul(outa.ProjPos, ProjectorView);
outa.ProjPos = mul(outa.ProjPos, ProjectorProj);
//获取物体在世界空间下的坐标
outa.Pos_W= (float3)mul(float4(ina.Pos, 1.0f), World);
return outa;
}
float4 PS(VertexOut outa) : SV_Target
{
float4 TexColor; //采集基础纹理颜色
float ShadowMapDepth; //a,g,b存储的都是深度
float DiffuseFactor;
float4 DiffuseLight;
float2 ShadowTex; //阴影纹理坐标
float4 color = {0.0f,0.0f,0.0f,0.0f}; //最终输出的颜色
float Depth;
float bias;
//设置偏斜量
bias = 0.001f;
//第一,获取基础纹理的采样颜色
TexColor = BaseTexture.Sample(WrapSampleType, outa.Tex);
//第二,不管有没有遮挡,都应该具备环境光,注意环境光不生成阴影,这里仅仅是漫反射光生成阴影
color = AmbientColor;
//第三,求出相应顶点坐标对应在ShdowMap上的深度值
//获取投影相机下的投影纹理空间的坐标值[0.0,1.0] u=0.5*x+0.5; v=-0.5*y+0.5; -w<=x<=w -w<=y<=w
ShadowTex.x = (outa.ProjPos.x / outa.ProjPos.w)*0.5f + 0.5f;
ShadowTex.y = (outa.ProjPos.y / outa.ProjPos.w)*(-0.5f) + 0.5f;
//第四,由于3D模型可能超出投影相机下的视截体,其投影纹理可能不在[0.0,1.0],所以得进行判定这个3D物体投影的部分是否在视截体内(没SV_POSITION签名 显卡不会进行裁剪)
if (saturate(ShadowTex.x) == ShadowTex.x&&saturate(ShadowTex.y) == ShadowTex.y)
{
//求出顶点纹理坐标对应的深度值
ShadowMapDepth = ShadowMap.Sample(ClampSampleType, ShadowTex).r;
//求出顶点坐标相应的深度值(点光源到渲染点的深度值)
Depth = outa.ProjPos.z / outa.ProjPos.w;
//减去阴影偏斜量
ShadowMapDepth = ShadowMapDepth + bias;
//如果不被遮挡,则物体具备漫反射光
if (ShadowMapDepth >= Depth)
{
//求出漫反射光的的方向
float3 DiffuseDir = outa.Pos_W - PointLightPos;
//求出点光源到像素的距离
float distance = length(DiffuseDir);
//求出衰减因子
float atten1 = 0.5f;
float atten2 = 0.1f;
float atten3 = 0.0f;
float LightIntensity = 1.0f / (atten1 + atten2*distance + atten3*distance*distance);
//求漫反射光的反光向
float3 InvseDiffuseDir = -DiffuseDir;
//求出漫反射因子[0.0,1.0]
DiffuseFactor = saturate(dot(InvseDiffuseDir,outa.W_Normal));
//求出漫射光
DiffuseLight = DiffuseFactor*DiffuseColor*LightIntensity;
//颜色加上漫反射光
color += DiffuseLight;
color = saturate(color);
}
}
color = color*TexColor;
return color;
}