DirectX9 ShadowMap例子学习笔记_g_aminitobjworld-程序员宅基地

技术标签: shader  测试  translation  vector  float  DirectX专题之Direct3D  object  

本文版权归博客园  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特效了,但还是学到了不少东西。持之以恒吧!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/kenkao/article/details/6667406

智能推荐

【前端做项目常用】相关插件的官网 总结_js插件官网-程序员宅基地

文章浏览阅读593次。【前端做项目常用】相关插件的官网 总结,持续更新。。。_js插件官网

MongoDB数据库优化:Mongo Database Profiler-程序员宅基地

文章浏览阅读88次。在MySQL中,慢查询日志是经常作为我们优化数据库的依据,那在MongoDB中是否有类似的功能呢?答案是肯定的,那就是Mongo Database Profiler.不仅有,而且还有一些比MySQL的Slow Query Log更详细的信息。它就是我们这篇文章的主题。  开启 Profiling 功能  有两种方式可以控制 Profiling 的开关和级别,第一种是直接在启动参数里直..._mongodb: database profiler

PyQt5桌面应用开发(10):界面布局基本支持_pyqt5 简单界面开发-程序员宅基地

文章浏览阅读858次。布局分为两种层次,高层次布局和界面布局;界面布局分为:概念布局、相对布局和绝对布局;Qt5的常用布局类有:QVBoxLayout、QHBoxLayout、QGridLayout、QFormLayout和QStackedLayout。_pyqt5 简单界面开发

php amqp 链接断开,PHP AMQP内部服务器错误-程序员宅基地

文章浏览阅读353次。我在我的Debian Squeeze服务器上使用编译和安装的AMQP连接到我的RabbitMQ服务器。我在一个类中使用下面的命令,我正在实例化:$this->_amqpConnection = new AMQPConnection($arrRmqConfig);$this->_amqpConnection->connect();// creating the amqp chann..._php容器连接amqp容器失败

Java代码实现服务器/电脑文件全局检索的技术_java 如何查询服务器文件-程序员宅基地

文章浏览阅读1.2k次。通过使用Java编程语言,我们可以轻松实现服务器文件的全局检索功能。在本文中,我们学习了如何遍历文件系统、根据文件类型和关键字进行过滤,并展示了相应的示例代码。希望本文能够帮助您理解如何使用Java实现服务器文件全局检索功能,并在日常工作中发挥作用。如果您有任何问题,请随时向我提问。_java 如何查询服务器文件

C++——继承-程序员宅基地

文章浏览阅读761次,点赞11次,收藏9次。继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

随便推点

何为大数据?大数据时代你应该知道这些_大数据是如何知道我的需求的-程序员宅基地

文章浏览阅读1.1k次。如今,业界和学术界一直在讨论一个词,那就是大数据。不管是学术圈还是IT圈,只要能谈论点儿大数据就显得很高大上。然而,大数据挖掘、大数据分析、大数据营销等等事情仅仅只是个开始,对大多数公司来说,大数据仍有很强的神秘色彩。于是,在我们还没有完全搞明白如何运用大数据进行挖掘时,各种过于神化大数据的舆论就已经不绝于耳了。当然,也有很多人直接批判大数据或大数据营销给我们造成的隐私威胁。也有很多人根本没有搞清楚什么是大数据,到底有什么价值。于是,站在客观的角度,围绕下面几个问题与大家分享有关大数据的几个观点,也扒扒_大数据是如何知道我的需求的

[C++]洛谷:数字计数 数位dp算法详解_c++数位dp-程序员宅基地

文章浏览阅读1.8k次,点赞6次,收藏14次。数字计数:数位dp算法详解,简单易懂!_c++数位dp

html语言节点树,从屌丝到架构师的飞越(JavaScript篇)-JavaScript 遍历HTML节点树-程序员宅基地

文章浏览阅读278次。一、介绍这节课呢,我们来了解的是JavaScript 遍历HTML节点树,在Dom文档查的结构中,实际上各级标签呈现树状排列。我们可以把整个html文档看成一个树形,可以通过遍历节点树的形式进行标签的选取。Dom文档中,遍历节点的节点有,文本节点,注释节点和标签节点。二、知识点介绍1、节点类型2、遍历节点树3、基于元素节点数的遍历4、parentElement ——> 返回当前元素的父元素节..._htmlnodestree

如何用Autojs写自己的卡密验证界面?实战代码_autojs卡密-程序员宅基地

文章浏览阅读2.1k次。最近有朋友问我的卡密验证界面是怎么写的,今天把源码分享出来。先上一个卡密验证界面的效果图:_autojs卡密

mysql 中char_length()与length()_char_lenth() length-程序员宅基地

文章浏览阅读1.2k次。char_length()计算字符长度length()计算字节长度_char_lenth() length

金融市场中的NLP——情感分析_bert实体否定词-程序员宅基地

文章浏览阅读3.7k次,点赞5次,收藏45次。作者|Yuki Takahashi编译|VK来源|Towards Datas Science自在ImageNet上推出AlexNet以来,计算机视觉的深度学习已成功应用于各种应用。相反,NLP在深层神经网络应用方面一直落后。许多声称使用人工智能的应用程序通常使用某种基于规则的算法和传统的机器学习,而不是使用深层神经网络。2018年,在一些NLP任务中,一种名为BERT的最先进(STOA)模型的表现超过了人类的得分。在这里,我将几个模型应用于情绪分析任务,以了解它们在我所处的金融市场中有多大用处。代_bert实体否定词

推荐文章

热门文章

相关标签