很宝贵的资料,总结的相当不错。目前原创作者未知,如有知情者肯请告知,感激不尽~

 

一.前言
这个教程主要面对DirectX9.0的初学者,文中代码说明部分以DirectX9.0c SDK(August2006)中的ShadowMap Sample 为例进行讲解。如果没有D3D矢量运算基础,HLSL,或是对D3D流程不熟悉的朋友推荐《 3D游戏程序设计入门》(翁云兵翻译)这本电子文档,图书推荐《Visual C++/DirectX9 3D游戏开发导引》(叶至军)作为入门读物,另外directx9_c的帮助文件永远是开发人员的参考手册。
二.介绍
阴影贴图(Shadow Map)是Williams在1978年提出的,可以说是所有阴影处理中最简单的方法,图1-1展示了使用这一方法的渲染效果.


图1-1:图中为一立方体,在一聚光灯照射下地面呈现的阴影效果

三.原理
在阴影贴图算法中,每个光源都相当于一个独立的摄像机,都有个一个独立的阴影贴图。
(比如说场景需要两盏灯光,那么就需要建立三个摄像机,一号二号摄像机的位置和朝向与两个灯光相同,三号摄像机为真正渲染成最终画面的摄像机。)
这里的阴影贴图(IDirect3DTexture9)不同于我们以往使用的D3DFMT_A8R8G8B8类型的彩色贴图,而是D3DFMT_R32F即每点存储32位灰度的格式。此外每个阴影贴图还要使用深度模板(DepthStencilSurface)。

图2-2展示了一个局外视角观察的场景中摄像机,物体,灯光的全境。


图2-2:摄像机坐标系,世界坐标系,灯光坐标系。

立方体下的阴影可以用如下方法描述:
1.首先以灯光为视点,渲染出一幅带深度缓冲的平面阴影贴图(Shadow Map),图1-3展示的是以灯光为视点我们我看到的平面阴影贴图。


图1-3:以灯光为摄像机位置,我们所看到的图像。

此平面图就是一张32位灰度经过深度模版处理过的阴影贴图,灰度代表以灯光视点出发,穿过投影面这点,形成的射线经过的所有场景中的点中最近的那个点地深度值,把该值通过某种映射变为灰度值,并且该条射线所有穿过的点灰度值都被设定为了这个灰度。
可以看到在阴影贴图中我们是看不到任何阴影的,这符合我们的常识,图中的颜色灰度记录了从灯光视角点出发一条射线上所有顶点的Z深度值信息(我们把Z深度转换为灰度颜色渲染成图,这些灰度我们成为“深度相对灰度”),那些被遮挡的顶点恰恰是产生阴影的点。(有关Z深度解释请查看前面提供的参考书)。

2.下面我们的任务就是要在正常坐标系中找出这些被遮挡的点,然后把它们渲染成黑色。就完成了我门全部的工作。我们发现这些在灯光摄像机下的被遮挡点何其它点在正常摄像机下的区别是:把正常摄像机坐标系的点变换到灯光坐标系,然后投影变换,得到了和图1-3相同视角的图.


图1-4:没有阴影贴图的正常摄像机视角
那些被遮挡点代表深度相对灰度与阴影贴图的代表灰度的颜色相比是大的,而其它亮区的点经过正常摄像机到灯光摄像机变换,灯光摄像机投影变换这两次坐标系变换后,深度相对灰度的颜色与阴影贴图的灰度的颜色相比是相同的。

图1-5显示了这一比较渲染过程,A,B,C三点都是正常摄像机可以看到的点,但在灯光摄像机下被投影成了一条线上灰度相同为A:Zlight=2,B:Zlight=2,C:Zlight=2 (均为C点的深度相对灰度)的点,并被制成阴影贴图。我们把正常摄像机下的点变换到灯坐标系A,B,C,投影化,仍然保留了原来的深度相对灰度A:Zcamera=7,B:Zcamera=6,C:Zcamera=2 ,用原来的深度相对灰度和阴影图同样点的灰度作比较就可以得出该点是否处在阴影中的结论了。


3.我们就可以用Shader把这些点单独处理成阴影。
四.代码及注释
我们主要分析DirectX9.0c SDK(August2006)中的ShadowMap例子。该源文件在
\Microsoft DirectX SDK (August 2006)\Samples\C++\Direct3D\ShadowMap中

这里简单提一下DXUT的结构:
DXUT入口函数是WinMain()

WinMain()依次调用:
DXUTSetCursorSettings();//设定鼠标指针
DXUTInit();//初始化DXUT管理结构
DXUTCreateWindow();//创建窗口
设定消息处理函数为用户函数MsgProc()
MsgProc()再把消息传递KeyboardProc(),MouseProc()
DXUTCreateDevice()//创建D3D设备接口,
其中调用用户函数OnCreateDevice()OnResetDevice()
DXUTMainLoop();//主循环
掉用OnFrameMove(),OnFrameRender()实现在处理运动,和渲染
DXUTGetExitCode()//销毁接口
OnLostDevice(),OnDestroyDevice()

在MainLoop()之前我们都可以加入我们自己的初始化函数,比如InitializeDialogs()。
DXUTSetCallbackDeviceCreated( OnCreateDevice );
DXUTSetCallbackDeviceReset( OnResetDevice );
DXUTSetCallbackDeviceLost( OnLostDevice );
DXUTSetCallbackDeviceDestroyed( OnDestroyDevice );
DXUTSetCallbackMsgProc( MsgProc );
DXUTSetCallbackKeyboard( KeyboardProc );
DXUTSetCallbackMouse( MouseProc );
DXUTSetCallbackFrameRender( OnFrameRender );
DXUTSetCallbackFrameMove( OnFrameMove );
是为了让DUXT在该调用的时候调用我们自己写的函数,比如OnCreateDevice()

IsDeviceAcceptable()是测试硬件支持的用户函数,
ModifyDeviceSettings()是更改设备调用的用户函数,之后调用OnResetDevice()

我们的主要工作将集中在:
1.Main()里的初始化
2. OnCreateDevice()OnResetDevice()
3.OnFrameMove(),OnFrameRender()
其中OnFrameRender()调用RenderScene()来辅助完成渲染。

ShadowMap.cpp文件

1。初始化:
首先定义ShadowMap贴图的大小为512*512
#define SHADOWMAP_SIZE 512

然后设定正常摄像机的位置
D3DXVECTOR3 vFromPt = D3DXVECTOR3( 0.0f, 5.0f, -18.0f );
D3DXVECTOR3 vLookatPt = D3DXVECTOR3( 0.0f, -1.0f, 0.0f );
g_VCamera.SetViewParams( &vFromPt, &vLookatPt );

设定灯光摄像机的位置
vFromPt = D3DXVECTOR3( 0.0f, 0.0f, -12.0f );
vLookatPt = D3DXVECTOR3( 0.0f, -2.0f, 1.0f );
g_LCamera.SetViewParams( &vFromPt, &vLookatPt );

设定灯光的锥体张开角度大小
g_fLightFov = D3DX_PI / 2.0f;

灯光漫反射和位置
g_Light.Diffuse.r = 1.0f;
g_Light.Diffuse.g = 1.0f;
g_Light.Diffuse.b = 1.0f;
g_Light.Diffuse.a = 1.0f;
g_Light.Position = D3DXVECTOR3( -8.0f, -8.0f, 0.0f );
g_Light.Direction = D3DXVECTOR3( 1.0f, -1.0f, 0.0f );
D3DXVec3Normalize( (D3DXVECTOR3*)&g_Light.Direction, (D3DXVECTOR3*)&g_Light.Direction );
g_Light.Range = 10.0f;
g_Light.Theta = g_fLightFov / 2.0f;
g_Light.Phi = g_fLightFov / 2.0f;

OnCreateDevice()& OnResetDevice( )
创建效果接口
D3DXCreateEffectFromFile()

创建顶点声明
CreateVertexDeclaration()

加载网格模型并把模型顶点格式变为顶点声明格式
g_Obj[i].m_Mesh.Create()
g_Obj[i].m_Mesh.SetVertexDecl()

设定正常摄像机和灯光摄像机的投影矩阵
g_VCamera.SetProjParams( D3DX_PI/4, fAspectRatio, 0.1f, 100.0f );
g_LCamera.SetProjParams( D3DX_PI/4, fAspectRatio, 0.1f, 100.0f );

创建默认纹理
CreateTexture(&g_pTexDef)

把灯光的漫反射,张角传递给Shader
SetVector( "g_vLightDiffuse", (D3DXVECTOR4 *)&g_Light.Diffuse ) );
SetFloat( "g_fCosTheta", cosf( g_Light.Theta ) ) );

创建ShadowMap纹理
V_RETURN( pd3dDevice->CreateTexture( SHADOWMAP_SIZE, SHADOWMAP_SIZE,
1, D3DUSAGE_RENDERTARGET,
D3DFMT_R32F,
D3DPOOL_DEFAULT,
&g_pShadowMap,
NULL ) );

创建深度模版
V_RETURN( pd3dDevice->CreateDepthStencilSurface( SHADOWMAP_SIZE,
SHADOWMAP_SIZE,
D3DFMT_D24X8,
D3DMULTISAMPLE_NONE,
0,
TRUE,
&g_pDSShadow,
NULL ) );
设定ShadowMap的投影矩阵
D3DXMatrixPerspectiveFovLH( &g_mShadowProj, g_fLightFov, 1, 0.01f, 100.0f);

OnFrameMove()
更新模型位置,更新摄像机位置

OnFrameRender()
这是程序的重头戏

首先判断灯光是否为自用移动状态,为了便于说明,以下我们仅以自由灯光为例
if( g_bFreeLight )

把灯光摄像机变换矩阵赋予mLightView
mLightView = *g_LCamera.GetViewMatrix();

然后把设备下的旧的RenderTarget,和DepthStencilSurface保存起来,并把新的RenderTarget设为ShadowMap纹理下的表面,把DepthStencilSurface设为我们要返回的目标,目的是把图像渲染到ShadowMap纹理中,用g_pDSShadow保存深度信息,流程见图1-6

然后调用RenderScene( pd3dDevice, true, fElapsedTime, &mLightView, &g_mShadowProj )
开始渲染ShadowMap纹理和深度模版

这里我们把原程序改一下以便理解
渲染ShadowMap时,可以不进行SetVector( "g_vLightPos", &v4 )和SetVector( "g_vLightDir", &v4 )的操作。(SetVector( "g_vLightPos", &v4 )和SetVector( "g_vLightDir", &v4 )是第二次渲染正常摄像机时候需要传读的参数)
直接进行SetTechnique( "RenderShadow" )然后以灯光摄像机为视点开始渲染深度

下面进入RenderShadow 的Shader代码段

顶点处理
void VertShadow( float4 Pos : POSITION,//位置
float3 Normal : NORMAL,//法线
out float4 oPos : POSITION,//输出位置
out float2 Depth : TEXCOORD0 )//深度
{
oPos = mul( Pos, g_mWorldView );把顶点从世界坐标变到灯光摄像机坐标系
oPos = mul( oPos, g_mProj );//进行投影变换
Depth.xy = oPos.zw;//深度为投影变换后的ZW分量
}

像素处理
void PixShadow( float2 Depth : TEXCOORD0,//输入深度
out float4 Color : COLOR )//输出颜色灰度
{
Color = Depth.x / Depth.y;//把深度两个分量相除,得到颜色灰度,这就是我们说的深度到灰度的映射关系
}

下面我们分析一下代码在做什么

假定输入顶点经过灯光摄像机坐标系变化后的位置为(x,y,z),

下面进行投影变换

该灰度被渲染到了ShadowMap纹理中,然后我们取出旧的RenderTarget面,和深度模版面,渲染正常摄像机的场景
在调用RenderScene( pd3dDevice, false, fElapsedTime, pmView, g_VCamera.GetProjMatrix() )前我们多了一个矩阵mViewToLightProj,这个矩阵目的是把正常摄像机坐标系中的点变换到世界坐标系下,在变换到灯光摄像机坐标系下,在做投影变换。

图中a点是最下方黑点的正常摄像机坐标系下的向量,通过世界变换矩阵变到了b向量,再通过灯光摄像机矩阵变到了c,c在通过投影矩阵变到了灯光摄像机的投影空间。这个投影空间的深度灰度要比阴影贴图的深度灰度大,因为c的长度大于黑点在阴影贴图中的深度d, 所以那个黑点是位于阴影区域的。

RenderScene():
设置SetTechnique( "RenderScene" )
正常摄像机的Shader代码段
顶点处理
void VertScene( float4 iPos : POSITION,
float3 iNormal : NORMAL,
float2 iTex : TEXCOORD0,
out float4 oPos : POSITION,
out float2 Tex : TEXCOORD0,
out float4 vPos : TEXCOORD1,
out float3 vNormal : TEXCOORD2,
out float4 vPosLight : TEXCOORD3 )
{vPos = mul( iPos, g_mWorldView );
oPos = mul( vPos, g_mProj );//输出投影变换后的顶点
vNormal = mul( iNormal, (float3x3)g_mWorldView );//对法线进行摄像机坐标系变换
Tex = iTex; //纹理坐标复制
vPosLight = mul( vPos, g_mViewToLightProj );//把正常摄像机坐标系的顶点变到灯光投影面中
}
float4 PixScene( float2 Tex : TEXCOORD0,//坐标
float4 vPos : TEXCOORD1,//顶点输出位置
float3 vNormal : TEXCOORD2,//顶点法线
float4 vPosLight : TEXCOORD3 ) : COLOR//灯光摄像机的投影
{float4 Diffuse;
float3 vLight = normalize( float3( vPos - g_vLightPos ) );取得灯到点的向量

//如果这个向量在灯光主轴上的投影大于一个值则说明这个点超出了照射范围
//应该给处理为暗点
if( dot( vLight, g_vLightDir ) > g_fCosTheta ) // Light must face the pixel (within Theta)

//以下是投影空间到纹理空间的变换
{float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );
ShadowTexC.y = 1.0f - ShadowTexC.y;
float2 texelpos = SMAP_SIZE * ShadowTexC;

//以下分了四种情况进行灰度的判断。并进行了空域滤波
float2 lerps = frac( texelpos );
float sourcevals[4];
sourcevals[0] = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[1] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[2] = (tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
sourcevals[3] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),
lerp( sourcevals[2], sourcevals[3], lerps.x ),
lerps.y );

//如果是纯阴影的话我们可以知道LightAmount的值是0,如果是灯光照射面的话//LightAmount的值是1,如果是影的边缘的话,LightAmount值介于1和0之间。
Diffuse = ( saturate( dot( -vLight, normalize( vNormal ) ) ) * LightAmount * ( 1 - g_vLightAmbient ) + g_vLightAmbient )
* g_vMaterial;

//如果在灯照范围之外的点进行下面处理
} else
{
Diffuse = g_vLightAmbient * g_vMaterial;
}

//返回这个像素点在纹理贴图的像素颜色,并乘了一个代表明暗的漫反射系数代表光照
return tex2D( g_samScene, Tex ) * Diffuse;
}

说明:
如果不考虑空域滤波,我们可以让LightAmount =tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;

代码的意思是,tex2D( g_samShadow, ShadowTexC )-----阴影贴图上的该像素点的深度相应灰度。
vPosLight.z / vPosLight.w-----正常摄像机变到灯光投影后Z,W分量相除结果。
想一下这个变换公式:

就会发现我们对vPosLight.z / vPosLight.w进行了同样的深度灰度映射,只不过我们没有再渲染纹理。这样两个经过同样处理的值可以进行比较了。比较的结果就是该点是否处于阴影区域的判据。
然后我们让阴影区域中的象素点漫反射乘0渲染,在灯光区域中的象素点漫反射乘1渲染。

到此为止ShadowMap 的思想和核心代码已经讲述完毕了。希望大家多研究研究代码还是有好处的,越学会觉得自己知识越贫乏,这就对了.

Shadow Map在DirectX9.0 SDK Sample 的实现方法相关推荐

  1. 安装 VS 2015 Update 2 + Windows SDK Tools 1.3.1 + Windows SDK 10586.212 后提示找不到 10586.0 SDK 问题的解决方法...

    将 Visual Studio 2015 升级到 Update 2,并安装 Windows SDK Tools 1.3.1 和 Windows SDK 10586.212 后,有可能造成原本已安装的 ...

  2. [工作积累] shadow map问题汇总

    1.基本问题和相关 Common Techniques to Improve Shadow Depth Maps: https://msdn.microsoft.com/en-us/library/w ...

  3. WPF 与Surface 2.0 SDK 亲密接触 - ScatterView 篇

    以前的博文我曾向大家介绍过利用WPF 4 开发具有多点触屏功能的应用程序,可参考<Multi-Touch 开发资源汇总>.在那些文章中无论是简单的拖拽,还是复杂的旋转.缩放效果(下文简称M ...

  4. 联级阴影贴图CSM(Cascaded shadow map)原理与实现

    联级阴影贴图CSM(Cascaded shadow map)原理与实现 CSM是利用分层的ShadowMap技术,实现大场景的阴影算法.示意图如下图: 我们通过给眼视锥分片,为每个分片生成一个相同分辨 ...

  5. Shadow Map阴影贴图技术之探 【转】

    这两天勉勉强强把一个shadowmap的demo做出来了.参考资料多,苦头可不少.Shadow Map技术是目前与Shadow Volume技术并行的传统阴影渲染技术,而且在游戏领域可谓占很大优势.本 ...

  6. Shadow Map 原理和改进 【转】

    http://blog.csdn.net/ronintao/article/details/51649664 参考 1.Common Techniques to Improve Shadow Dept ...

  7. Cascaded Shadow Map(CSM)中的一些问题

    Cascaded Shadow Map(CSM)是目前引擎中主流的阴影技术,效率与效果均不错.它与传统的单张Shadow Map的区别主要在于将视锥体进行了层次的分解,每一层单独计算相关的SM,这样在 ...

  8. Vulkan Cascade Shadow Map的故事

    你只需做你自己,做你想做的事,不要沦为人海中的沧海一粟. --<沉默的多数派> 序 最近几周,看了看知乎上关于阴影的文章,虽然我记得大二的时候好像也看过那本<Unity Shader ...

  9. Unity Shader - Custom SSSM(Screen Space Shadow Map) 自定义屏幕空间阴影图

    文章目录 思路 实践 获取光源空间ShadowMap[A] 获取屏幕空间的深度图[B] 获取SSSM(Screen Space Shadow Map) 绘制一个全屏的Quad[C] 输出SSSM RT ...

最新文章

  1. 华为鸿蒙编程:如何显示网络图片
  2. 面试官问你“有什么问题问我吗?”,你该如何回答?
  3. leetcode-Excel Sheet Column Title
  4. 设计模式——中介者模式
  5. 计算机表格收入水平怎么算,怎么用excel计算工资所得税
  6. BackGroundWorker用法
  7. 某个JAVA类断点无效_解决eclipse中断点调试不起作用的问题
  8. RAID入门一页通,最全的RAID技术、原理图解
  9. 有了漏洞扫描器,如何用好?一点不成熟的小总结
  10. C#常见委托のdelegate定义,Func,Action,Predicate总结
  11. xci转化nsp_Switch游戏XCI转NSP的教程+工具下载
  12. java redis令牌桶_Redis令牌桶算法在限速中的应用
  13. 方差分析 球形检验_方差分析的前提,与检验,以及球形检验
  14. centos ipv6 网卡_centOS添加ipv6支持(仅限已分配ipv6地址和网关)
  15. Fibonacci费氏数列
  16. matlab科研绘图模板,直接奉上源代码!
  17. 思科c系列服务器cimc密码,UCS C系列服务器故障排除提示.PDF
  18. 软件开发工作量及费用量化评估方法在金融行业的应用
  19. WatchGuard Firebox X50硬件防火墙
  20. zcu102_1_PS端LED开关

热门文章

  1. 自动化测试如何保持登录状态_自动化测试po模式是什么?自动化测试po分层如何实现?-附详细源码...
  2. 力扣——删除有序数组中的重复项
  3. 实验2-2-2 计算摄氏温度 (10 分)
  4. mybatis使用char类型字段查询oracle数据库时结果查询不到的问题
  5. type=xhr的500错误
  6. 拉勾数据岗位和热门编程语言现状分析
  7. 深入浅出TensorFlow(七)TensorFlow计算加速
  8. Scala,一门「特立独行」的语言!
  9. 【李宏毅2020 ML/DL】P80 Generative Adversarial Network | Feature Extraction
  10. 大学计算机在线阅读,大学计算机