DirectX9 ShadowMap例子学习笔记
本文版权归博客园 mavaL所有,如有转载请按如下方式详细标明原创作者及出处,以示尊重!!
原创作者:mavaL
原文链接:DirectX9 ShadowMap例子学习笔记
学习SDK例子真是快速加强编程能力的途径,然而虽如此,微软不仅在每个例子中展示了本次的技术重点,如
这个例子的ShadowMap,还煞费苦心把DEMO做得很好看,很复杂。不仅给我们看了固定的聚光灯光源制造的阴影,还
顺带展示了怎么模拟车前灯.看来在3D世界,模拟一切也不过是数学的问题,数学啊!T_T
这就给我们初学者者带来了:
1.学习难度直接成倍增加,面对纷繁复杂的知识点糅合在一起,我第一遍看代码时直接头晕目眩,找不到方向,
信心倍受打击。
2.这简直就是3D中的知识大餐,只要你有足够的耐心,坚定的决心和持久激昂的兴趣,以及分析归纳各个知识点,
各个击破,从而掌握的学习能力,那么学习透彻一个sample后,我想水平肯定是大大的进步啊。
关于这个ShadowMap例子,做做笔记,归纳一下知识点。
PS:总结的都是我自己思考的结果,不能保证正确,毕竟我也是一个正在学习的小菜鸟。
1.在程序开始处,手动构造了场景中所有模型的世界矩阵:D3DXMATRIXA16 g_amInitObjWorld[NUM_OBJ]。
学懂了矩阵变换,就搞得懂手动是怎么构造的了。关于矩阵的3D变换可是块难啃的骨头,得经过长期的学习实践和思考。
可参看《3D数学基础:图形与游戏开发》一书。
比如room.x这个模型的世界矩阵 3.5f, 0.0f, 0.0f, 0.0f
0.0f, 3.5f, 0.0f, 0.0f
0.0f, 0.0f, 3.5f, 0.0f
0.0f, 0.0f, 0.0f, 1.0f 是这样来的:
首先前三行代表线性变换,最后一行代表平移变换。
因为每一个行向量代表变换后的基向量,所以我们可以看出,room模型在变换到世界空间后,right,up,ahead指向还是没变。
还是符合标准左手坐标系。但是3.5则代表了缩放因子,我们看到转换后每个基向量都乘了缩放因子3.5,所以在世界空间中
room模型的大小被放大了3.5倍。这显然是为了符合场景需要而试探出来的。
最后一个行向量表示模型的原点经平移变换后在世界空间的新坐标。
plane在DEMO中飞行时我们看到其机身有点偏转,正是我们对其物体空间中的基向量进行世界变换的结果。
原来的right轴被变换成了(0.43301f, 0.25f, 0.0f, 0.0f),up轴被变换成了(-0.25f, 0.43301f, 0.0f, 0.0f)。
所以在世界空间中plane呈那种倾斜姿态。
另外,从car模型的世界矩阵中我们还能看出点端倪。我们看到其坐标原点被变换为(-14.5f, -7.1f, 0.0f,1.0f)。这也是
非常值得探讨的。因为car模型在其物体空间中是车头向-Z轴的,那么要想在DEMO中使其绕Y轴逆时针行驶的话,其初始位置必须放置在其行驶的圆的(-r,0)处。我想,当熟练掌握了这些知识点,下次自己布置场景时,就可以根据需要来手动构造模型的世界矩阵了。
2.在OnFrameRender中,这段计算当light固定在车头的灯光view矩阵值得好好研究。它说明了在数学这个强大的工具
面前,一切不过是浮云。
else
{
// Light attached to car.
mLightView = g_Obj[2].m_mWorld;
D3DXVECTOR3 vPos( mLightView._41, mLightView._42, mLightView._43 ); // Offset z by -2 so that it's closer to headlight
D3DXVECTOR4 vDir = D3DXVECTOR4( 0.0f, 0.0f, -1.0f, 1.0f ); // In object space, car is facing -Z
mLightView._41 = mLightView._42 = mLightView._43 = 0.0f; // Remove the translation
D3DXVec4Transform( &vDir, &vDir, &mLightView ); // Obtain direction in world space
vDir.w = 0.0f; // Set w 0 so that the translation part below doesn't come to play
D3DXVec4Normalize( &vDir, &vDir );
vPos.x += vDir.x * 4.0f; // Offset the center by 4 so that it's closer to the headlight
vPos.y += vDir.y * 4.0f;
vPos.z += vDir.z * 4.0f;
vDir.x += vPos.x; // vDir denotes the look-at point
vDir.y += vPos.y;
vDir.z += vPos.z;
D3DXVECTOR3 vUp( 0.0f, 1.0f, 0.0f );
D3DXMatrixLookAtLH( &mLightView, &vPos, ( D3DXVECTOR3* )&vDir, &vUp );
}
这里,我觉得
D3DXVECTOR4 vDir = D3DXVECTOR4( 0.0f, 0.0f, -1.0f, 1.0f ); // In object space, car is facing -Z
mLightView._41 = mLightView._42 = mLightView._43 = 0.0f; // Remove the translation
D3DXVec4Transform( &vDir, &vDir, &mLightView ); // Obtain direction in world space
vDir.w = 0.0f; // Set w 0 so that the translation part below doesn't come to play
这四句改成这样应该更好理解:
D3DXVECTOR4 vDir = D3DXVECTOR4( 0.0f, 0.0f, -1.0f, 0.0f ); // In object space, car is facing -Z
D3DXVec4Transform( &vDir, &vDir, &mLightView ); // Obtain direction in world space
首先,得到car的世界矩阵,注意在OnFrameRender前面的OnFrameMove中,对car的旋转已经连接到了其世界矩阵中。
然后vPos记录其在世界空间中的位置。我们最终要求得的是车前灯的位置和其指向。车灯的指向等于车头的指向,而car在
物体空间中是面向-Z轴的,即(0,0,-1,0)。所以将其变换到世界空间,就得到了在世界空间中的指向。那么车头灯的位置
也能计算出来了:通过把vPos往车头方向移动一段距离就行了。
最后求light的view矩阵: D3DXMatrixLookAtLH( &mLightView, &vPos, ( D3DXVECTOR3* )&vDir, &vUp );
3.在RenderScene中,D3DXVECTOR3 v = *g_LCamera.GetEyePt();
我们查看GetEyePt的定义是:const D3DXVECTOR3* GetEyePt() const { return (D3DXVECTOR3*)&m_mCameraWorld._41; }
这里我不懂为什么要这样定义,更直观的是这样吧:
return &D3DXVECTOR3(m_mCameraWorld._41,m_mCameraWorld._42,m_mCameraWorld._43);
还有像这种语句: *( D3DXVECTOR3* )&v4 = *g_LCamera.GetWorldAhead(); 这样绕来绕去的摆弄指针,到底是什么意思?
4.ShadowMap的生成过程。生成阴影图是每次渲染的第一步。
在OnFrameRender中,出现了几个东西:surface,render target,depth-stencil surface.
一个Device只能有一个render target,比如我们默认的render target就是swap chain中的back buffer.
因为我们的阴影图是生成在一个texture上,所以我们要把render target设定为该texture上的surface:
g_pShadowMap->GetSurfaceLevel( 0, &pShadowSurf );
pd3dDevice->SetRenderTarget( 0, pShadowSurf ); 这样我们的渲染操作就到阴影texture上了。
另外:程序为新的render target设定了新的深度-模板缓冲:
pd3dDevice->SetDepthStencilSurface( g_pDSShadow ); 这样,在渲染时,设备会自动进行深度测试,记录Z值。
因为在OnCreateDevice中,我们创建了g_pDSShadow,并设定其行为为自动处理深度模板测试:
pd3dDevice->CreateDepthStencilSurface( SHADOWMAP_SIZE,SHADOWMAP_SIZE,d3dSettings.d3d9.pp.AutoDepthStencilFormat,
D3DMULTISAMPLE_NONE,0,TRUE,&g_pDSShadow,NULL )
在RenderScene中程序设定了ShadowMap.fx中的变量,然后:g_pEffect->SetTechnique( "RenderShadow" );
我们现在可以看特效文件中产生阴影图的technique了。它是由1个pass,2个shader组成:VertShadow和PixShadow。
这2个shader都很短,应该还是很好理解的。顶点着色器中,我们用一个输出纹理坐标保存了顶点变换后的z,w值:
Depth.xy = oPos.zw;
然后在像素着色器中,程序输出最终的深度Z值到阴影图中:
Color = Depth.x / Depth.y;
这样,ShadowMap就生成了。
5.现在开始正常渲染场景了。mViewToLightProj这个矩阵很重要,通过它,原来摄像机空间的点先被转换回世界空间,再转换到光源的view空间,最后转换
到了我们要采样的纹理投影空间。
先是RenderScene这个technique,由2个shader:VertScene和PixScene组成,点就是像素着色器了。
float4 PixScene( float2 Tex : TEXCOORD0,
float4 vPos : TEXCOORD1,
float3 vNormal : TEXCOORD2,
float4 vPosLight : TEXCOORD3 ) : COLOR
{
float4 Diffuse;
// vLight is the unit vector from the light to this pixel
float3 vLight = normalize( float3( vPos - g_vLightPos ) );
// Compute diffuse from the light
if( dot( vLight, g_vLightDir ) > g_fCosTheta ) // Light must face the pixel (within Theta)
{
//下面是投影纹理映射技术(Projective Texture Mapping),即根据投影后的顶点坐标来求其纹理贴图的对应纹理坐标。
//也就是最终顶点位置和我们的深度纹理图的纹理坐标的对应关系,是这样求的:我们知道,vPosLight是顶点在光源下的
//投影坐标,首先除以其次坐标w使其范围在[-1,1]内,然后乘以1/2再加1/2,使其转换到纹理坐标范围[0,1]内。这就是投影
//纹理映射。
float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );
//我们生成的纹理贴图的y方向是向下为正,而投影空间是y向上为正。而且投影空间的y坐标0处对应纹理贴图的y坐标1处,
//想想场景投影到纹理贴图的方向吧。
ShadowTexC.y = 1.0f - ShadowTexC.y;
//这是根据纹理坐标计算texel
float2 texelpos = SMAP_SIZE * ShadowTexC;
// Determine the lerp amounts
float2 lerps = frac( texelpos );
//下面几句是所谓的啥空域滤波,就是使图像更精准的数字处理技术吧,我也不懂。
float sourcevals[4];
//在取得的texel,根据其上下左右4个相邻的texel,来取样纹理贴图的数据,与该顶点的深度值作比较。
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 );
// Light it
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;
}
我们也可以看看不采用双线性插值和空域滤波是啥效果:
float LightAmount = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON < vPosLight.z / vPosLight.w)? 0.0f: 1.0f;
另外:在ShadowMap.fx中定义的 #define SHADOW_EPSILON 0.00005f
这是个控制两个浮点数比较精度的量,参见这个帖子:
http://topic.csdn.net/u/20090818/09/d42dd7d8-9aed-4116-99b2-d227ce2cf04b.html
虽然我现在完全不能保证马上能手动实现ShadowMap特效了,但还是学到了不少东西。持之以恒吧!
DirectX9 ShadowMap例子学习笔记相关推荐
- Qt例子学习笔记 - Examples/Qt-6.2.0/charts/callout
//QChart 类提供了两种在场景坐标和系列域(由轴范围定义)之间映射的方法. //QPointF QChart::mapToPosition(const QPointF &value, Q ...
- python基础代码事例-学习笔记:python3,代码。小例子习作(2017)
http://www.cnblogs.com/qq21270/p/7634025.html 学习笔记:python3,一些基本语句(一些基础语法的代码,被挪到这里了) 日期和时间操作 http://b ...
- TensorFlow学习笔记(二):快速理解Tutorial第一个例子-MNIST机器学习入门 标签: 机器学习SoftmaxTensorFlow教程 2016-08-02 22:12 3729人阅
TensorFlow学习笔记(二):快速理解Tutorial第一个例子-MNIST机器学习入门 标签: 机器学习SoftmaxTensorFlow教程 2016-08-02 22:12 3729人阅读 ...
- Tsai笔记:GPOPS学习笔记(1)—— 高斯伪谱法的最基本优化方程求解思路(例子介绍)
Tsai笔记:GPOPS学习笔记(1)-- 高斯伪谱法的最基本优化方程求解思路(例子介绍) Tsai三步.(第一步,基本说明.第二步,结果图显示.第三步,代码展示.) 第一步,基本说明. A.问题说明 ...
- ceres快速教材及学习笔记(四)bundl adjustment,《视觉slam十四讲》第十讲ceres例子
0. 前言 本文是根据ceres官方教程内容ceres-solver官方教程链接,再结合自己理解的一个ceres快速学习笔记. 在博文ceres快速教材及学习笔记(一)hello,world!中,我们 ...
- 关于一个非寿险费率厘定例子的学习笔记
关于一个非寿险费率厘定例子的学习笔记 一.计算在当前费率水平下的已赚保费 1.1实操 1.2 理论补充 1.2.1 风险基础和风险单位 1.2.2 保费及其构成 1.2.3 数据的汇总方法 1.2.4 ...
- java学习笔记:里氏代换原则的两个例子
java学习笔记:里氏代换原则的两个例子 (根据里氏代换原则[能使用父类型的地方一定能使用子类型],抓取 ExampleA类型异常的catch块能够抓住try块中抛出的ExampleB类型的异常) p ...
- 小傻羊7.31学习笔记——AOP例子和日志的实现实例
7.31学习笔记--AOP例子和日志的实现实例 转载小栗子 https://blog.csdn.net/zhuzhezhuzhe1/article/details/80565067 切口的实现,sys ...
- 阿里云HaaS100物联网开发板学习笔记(六)做个智能灯---一个完整的开发例子
摘要:本篇文章将前期几个专题综合起来,基于阿里云HaaS100的新固件设计制作一个智能灯.这个智能灯由云平台.手机APP端和设备端组成,基本上涵盖了一个物联网小项目所需的主要步骤. 目录 1.在阿里云 ...
最新文章
- 用小神经网络和光谱仪优化关键词识别
- opc ua 服务器模拟_西门子S71500的OPC通讯组态
- 关于Android的EditText焦点问题
- server2008R2平台部署exchange2010
- android studio复选按钮样式_Ubuntu与Android开发的邂逅
- 非阻塞I/O多路复用机制
- python游戏入门书籍推荐
- ansible 并发设置_如何使用Ansible通过Prometheus设置系统监视
- sqlserver_identity
- 图解MySQL的各种 JOIN,看完不懂来找我!
- 数据结构-直接选择排序
- 【架构】需求决定架构 —— 萌Mark的架构升级之路
- MTK平台Metadata的加载(4)—Q版本后
- 企业业务架构设计方法论及实践(二)
- 红队快速打点工具(POC bomber)
- 【转】PID算法原理 一图看懂PID的三个参数
- 小傻蛋的妹妹跟随小甲鱼学习Python的第五节005
- 基于机器学习建模的 XSS 攻击防范检测
- Java面试知识点之Java基础
- 洛谷 P5027 Barracuda 题解
热门文章
- Anguar 使用interceptor拦截器设置请求头传入jwt token
- java web不用框架_初学javaweb,远离各自框架
- 森林怎么训练野人_第五人格:野人技能曝光!野猪可以骑,庄园中或将迎来新玩法!...
- ashx在web.config中如何配置_网络中,什么是半双工与全双工?它们如何配置
- 2019年开发者必读!20位阿里技术大牛们帮你列了一份经典书单!...
- 学习网页前的网页知识储备
- 诺基亚在日本测试5G网络 网速可达256MB/s
- 【吐血经验】在 windows 上安装 spark 遇到的一些坑 | 避坑指南
- mysql双主+keepalived【转】
- 几行 python 代码合成 gif / 微信表情~与恶意合成软件说再见【文末附代码】