Directx11教程三十一之ShadowMap(阴影贴图)之平行光成影

这一节教程跟 D3D11教程三十一之ShadowMap(阴影贴图)之点光源成影  那节教程差不多,那节教程是关于点光源下利用ShadowMap形成阴影的,这一节教程是关于平行光下利用ShadowMap形成阴影的。先看看这节教程的结构吧:

 

 

 

一,正交投影矩阵(OrthographicMatrix)

据我们所知在当顶点处于相机空间时,接下来使用的投影矩阵有两种:透视投影矩阵(PerspectiveProjectionMatrix)和正交投影矩阵(OrthoProjectionMatrix)。

 

透视投影矩阵我们这里就不推导了,这里我们就推到OrthographicMatrix的原理,很多同学说这节教程不是关于平行光的ShadowMap成影?大家先别急,先慢慢往下看。

 

这里我们在此放出D3D11的空间变换的渲染流水线图,跟D3D11教程二十五之DepthBuffer(深度缓存) 那里给出的3D流水线图差不多

 

这里我还是放出两张3D渲染流水线流程图吧:

 

第一张:

 

 

 

 

 

 

 

 

第二张,

 

 

 

 

 

 

 

 

看第一张为D3D11的使用透视投影矩阵的3D渲染流水线图,第二张为D3D11的使用正交投影矩阵的3D渲染流水线图,两张图很大的区别可能就是,

 

当一个顶点处于相机空间时,如果乘以透视投影矩阵,则变换到齐次裁剪空间,假设顶点(X,Y,Z,W)在齐次裁剪空间,  -W=<X<=W, -W=<Y<=W,  0=<Z<=W, 要经过透视除法才能变换到NDC空间;假设顶点(X,Y,Z,W)在NDC空间,则-1=<X<=1, -1=<Y<=1,  0=<Z<=1 ,W=1。

而一个顶点处于相机空间时,如果乘以正交投影矩阵,则直接变换到NDC空间。

 

那么正交投影矩阵是怎么推导出来的呢?先来看看下面的图:

 

我们都知道透视投影矩阵是建立起一个视锥体,而正交投影矩阵建立起的为长方体,如上面图所示。

 

假设在相机空间(ViewCoordinateSpace)中, 长方体的宽度为w,高度为h,近裁剪面NearPlane为Z=n,远裁剪面为Z=f,  

P点为(X,Y,Z,1.0) P点为这个长方体内的一个顶点,则可知 -w/2=<X<=w/2,     -h/2=<Y<=h/2,  n=<Z<=f,   P点在近裁剪面的投影为(X,Y)

我上面提到过,在相机空间的顶点乘以正交矩阵后直接变换到NDC坐标,(假设顶点(X,Y,Z,W)在NDC空间,则-1=<X<=1, -1=<Y<=1,  0=<Z<=1 ,W=1)

即由相机空间的

 

 

变换到NDC空间的

 

那么怎么转换呢?先看看-w/2=<X<=w/2,     -h/2=<Y<=h/2,

-w/2*(2/w)=<X*(2/w)<=w/2*(2/w),即  -1=<X*(2/w)<=1

同理有  -h/2*(2/h)=<Y*(2/h)<=h/2*(2/h),即 -1=<Y*(2/h)<=1

这样X和Y的转换就解决了,还剩下最终的Z转换,怎么把Z从范围[n,f]转为[0,1]呢,我们可以设置方程求解,假设有两个系数a和b,当P点在FarPlane时,即Z=f时,转换为1,当P点在NearPlane时,Z=n时,转换为0。

 

方程式如下面所示:

    

    

求出的解为:

 

 

 

则P点坐标的Z值从相机空间转到NDC空间的函数为:

 

 

 

综上所述:P点(X,Y,Z,1.0f)在相机空间转换到NDC空间的变换函数为:

 

变为矩阵乘的形式:

 

 

其实透视投影矩阵也是这样推导过来的,只不过复杂些。

 

我们的XNAMATH库已经给出了生成正交投影矩阵的函数:

 

 

ViewWidth为上面说的长方体的宽度,ViewHeight为上面说的长方体的高度,NearZ为相机空间中近裁剪面的Z值,FarZ为相机空间中远裁剪面的Z值.

 

 

二,平行光投影。

上面说了这么久,就是为了说明平行光投影。我在D3D11教程三十一之ShadowMap(阴影贴图)之点光源成影解释过在点光源物体投射成影可以视锥体表示,可能聪明的逆已经想到,平行光成影可以用长方体来制作ShadowMap,如下面图所示:

 

 

 

上面是关于YZ平面的一个截图,利用OrthoMatrix的长方体来形成ShadowMap的过程,模拟平行光成影,所以这节教程跟D3D11教程三十一之ShadowMap(阴影贴图)之点光源成影  不同的地方就是上个教程使用的是透视投影矩阵,而这个教程使用的是正交投影矩阵。再次给出PointClass的代码:

 

 

PointLightClass.h

#pragma once
#ifndef _POINT_LIGHT_CLASS_H
#define _POINT_LIGHT_CLASS_H


#include<Windows.h>
#include<xnamath.h>






//跟CameraClass的用法差不多
class PointLightClass
{
private:
<span style="white-space:pre">	</span>XMFLOAT4 mDiffuseColor;  //漫反射光强度
<span style="white-space:pre">	</span>XMFLOAT4 mAmbientColor; //发出的环境光
<span style="white-space:pre">	</span>XMFLOAT3 mPointLightPosition; //光源位置
<span style="white-space:pre">	</span>XMFLOAT3 mLookAt; //投影相机看到的位置
<span style="white-space:pre">	</span>XMFLOAT3 mParallelLightDirection; //平行光方向
<span style="white-space:pre">	</span>XMMATRIX mProjectorViewMatirx, mProjectorProjMatrix, mProjectorOrthoMatrix; //投影相机的相机变换矩阵和投影矩阵


public:
<span style="white-space:pre">	</span>PointLightClass();
<span style="white-space:pre">	</span>PointLightClass(const PointLightClass&other);
<span style="white-space:pre">	</span>~PointLightClass();


public:
<span style="white-space:pre">	</span>//Set函数
<span style="white-space:pre">	</span>void SetDiffuseColor(float red,float green,float blue,float alpha);
<span style="white-space:pre">	</span>void SetAmbientColor(float red, float green, float blue, float alpha);
<span style="white-space:pre">	</span>void SetPointLightPostion(float x,float y,float z);
<span style="white-space:pre">	</span>void SetLookAt(float x, float y, float z);
<span style="white-space:pre">	</span>void SetParallelLightDirection(float x, float y, float z);


<span style="white-space:pre">	</span>//Get函数
<span style="white-space:pre">	</span>XMVECTOR GetDiffuseColor();
<span style="white-space:pre">	</span>XMVECTOR GetPointLightPosition();
<span style="white-space:pre">	</span>XMVECTOR GetAmbientColor();
<span style="white-space:pre">	</span>XMVECTOR GetParallelLightDirection();
<span style="white-space:pre">	</span>XMMATRIX GetViewMatrix();
<span style="white-space:pre">	</span>XMMATRIX GetProjMatrix();
<span style="white-space:pre">	</span>XMMATRIX GetOrthoMatrix();




<span style="white-space:pre">	</span>//生成相机矩阵和投影矩阵
<span style="white-space:pre">	</span>void GenerateViewMatrix();
<span style="white-space:pre">	</span>void GenerateProjMatrix(float FiledOfView,float ScreenAspectRatio,float NearPlane,float FarPlane);
<span style="white-space:pre">	</span>void GenerateOrthoMatrix(float ScreenWidth, float ScreenHeight, float NearPlane, float FarPlane);
};
#endif // !_LIGHT_CLASS

 

PointLightClass.cpp

 

</pre><pre name="code" class="cpp">#include"PointLightClass.h"


PointLightClass::PointLightClass()
{

}

PointLightClass::PointLightClass(const PointLightClass&other)
{

}

PointLightClass::~PointLightClass()
{

}



//Get函数 
XMVECTOR PointLightClass::GetDiffuseColor()
{
	return XMLoadFloat4(&mDiffuseColor);
}



XMVECTOR PointLightClass::GetAmbientColor()
{
	return XMLoadFloat4(&mAmbientColor);
}


XMVECTOR PointLightClass::GetPointLightPosition()
{
	return XMLoadFloat3(&mPointLightPosition);
}


XMVECTOR PointLightClass::GetParallelLightDirection()
{
	return  XMLoadFloat3(&mParallelLightDirection);
}

XMMATRIX PointLightClass::GetViewMatrix()
{
	return mProjectorViewMatirx;
}

XMMATRIX PointLightClass::GetProjMatrix()
{
	return mProjectorProjMatrix;
}


XMMATRIX PointLightClass::GetOrthoMatrix()
{
	return mProjectorOrthoMatrix;
}






//Set函数
void PointLightClass::SetDiffuseColor(float red, float green, float blue, float alpha)
{
	mDiffuseColor = XMFLOAT4(red, green, blue, alpha);
}


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

void PointLightClass::SetPointLightPostion(float x, float y, float z)
{
	mPointLightPosition = XMFLOAT3(x, y, z);
	
}

void PointLightClass::SetLookAt(float x, float y, float z)
{
	mLookAt = XMFLOAT3(x, y, z);
}


void PointLightClass::SetParallelLightDirection(float x, float y, float z)
{
	mParallelLightDirection = XMFLOAT3(x, y, z);
}





//Generate函数
void PointLightClass::GenerateViewMatrix()
{
	XMVECTOR EyePostion = XMLoadFloat3(&mPointLightPosition);
	XMVECTOR LookAt = XMLoadFloat3(&mLookAt);
	XMVECTOR Up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	mProjectorViewMatirx = XMMatrixLookAtLH(EyePostion, LookAt, Up);
}



//近平面不变的条件下,远平面只影响远处看到物体的范围
void PointLightClass::GenerateProjMatrix(float FiledOfView, float ScreenAspectRatio, float NearPlane, float FarPlane)
{
	mProjectorProjMatrix = XMMatrixPerspectiveFovLH(FiledOfView,ScreenAspectRatio,NearPlane,FarPlane);
}

//生成正交投影矩阵
void PointLightClass::GenerateOrthoMatrix(float ScreenWidth, float ScreenHeight, float NearPlane, float FarPlane)
{
	mProjectorOrthoMatrix = XMMatrixOrthographicLH(ScreenWidth, ScreenHeight, NearPlane, FarPlane);
}





 

注意这节教程PointLight的位置模拟的不是点光源的位置,而是太阳的位置,太阳发出的是平行光,太阳的位置和观察目标的位置(LookAt)决定了场景中物体成影的方向,而太阳发出的平行光的方向(mLookAt-mPointLightPostion)决定了光照的细节.

 

 

 

 

下面放出我的Shader代码:

 

DrawShadowShader.fx

 

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;   -1<=x<=1  -1<=y<=1  
	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;
}


最后可能你也注意到了,Shader代码里 那段有关于转换坐标变为纹理坐标的代码跟D3D11教程三十一之ShadowMap(阴影贴图)之点光源成影是不一样,为什么不除以W呢?我们在上面说过,顶点在相机空间乘以正交投影矩阵后,是直接变换到NDC坐标的,顶点(X,Y,Z,W)在NDC空间满足的条件 -1=<X<=1, -1=<Y<=1,  0=<Z<=1 ,W=1,因此不用除以W了,也就是不用在进行透视除法了,当然你闲得蛋疼除以W也没事,因为在NDC空间中,w=1.0f, 只不过从运行效率的角度上这是没必要的。

 

 

放出程序运行图:

 

 

  平行光的

 

 

 

 

上一节教程点光源的

 

 

源码链接

3D渲染引擎Git源码

 https://github.com/2047241149/SDEngine

简单版本

http://download.csdn.net/detail/qq_29523119/9674771

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值