DirectX 总结
DDS
DirectXDraw Surface file format, .dds。这是微软从DirectX7开始引进的一种文件格式,它用来存储压缩的或未压缩的纹理,该格式支持mimaps cube maps和volume maps, D3DX和许多其他的DX工具都支持这种格式,比如DirectX Texture Editor(dxtex.exe)和Texture Conversion Tool(Texconv.exe),从D3D110开始,DDS文件也支持纹理数组
DXGI
DirectX Graphics Infrastructure
转换为.x格式
MeshConvert.exe,这个tool位于Microsoft DirectX SDK \Utilities\bin\x86目录下面,可以用来转换.x文件,支持.x, .obj, .sdkmesh格式之间的相互转换,.sdkmesh是微软新的格式在DX10/DX11之后使用,用来取代.x格式,MeshConvert是一个命令行工具,使用方法如下
Usage: meshconvert <options> <input filename>
<input filename> Input mesh filename.
The input file can be of .x, .obj, or .sdkmesh format
/o <file> Optional output mesh filename
/y Overwrite existing destination file
/n Generate normals
/t Generate tangents
/tb Generate tangents and binormals
/tcount <count> Texcoord count for the output mesh
/a Output animation information
/op Vertex cache optimize the mesh before exportin
(non-functional)
/sdkmesh [default] convert to .sdkmesh binary file
/x convert to .x binary file
/xt convert to .x text file
/v Verbose
.SDKMesh is a simple custom mesh format used by new SDK samples
使用DirectX Control Panel
使用DirectX Control Panel可以方便的设置DirectX相关的东西,比如用Debug库还是Release库,是否检测内存泄漏,是否允许Shader调试等等,这个工具的位置在...\Microsoft DirectX SDK\Utilities\bin\x86,界面如下图。
使用PIX分析D3D应用程序
PIX是调试和分析D3D应用程序的,该工具位于...\Microsoft DirectX SDK\Utilities\bin\x86下,关于它的用法,DX帮助文档中有详细的说明,位于tools-DirectX Performance Tools-PIX下,工具截图
注意资源释放的顺序
有时候在释放资源的时候会出现运行时错误,这可能是由于资源之间的依赖造成的,比如下面的代码,运行时会出现一个违规访问的错误,为什么呢,因为m_SoundBuffer是依赖m_pDirectSound的,而m_pDirectSound先释放了,所以m_SoundBuffer变得不可访问了
{
m_pDirectSound->Release() ;
m_pDirectSound = NULL ;
}
if(m_SoundBuffer != NULL)
{
m_SoundBuffer->Release() ;
m_SoundBuffer = NULL ;
}
将二者释放的顺序交换一下就好了,如下。
{
m_SoundBuffer->Release() ;
m_SoundBuffer = NULL ;
}
if(m_pDirectSound != NULL)
{
m_pDirectSound->Release() ;
m_pDirectSound = NULL ;
}
注意变换的顺序
有很多图形学书籍都讲到过,先旋转再*移和先*移再旋转效果是不一样的,同理,先缩放再*移和先*移再缩放也是不一样的。
不要在Render函数中设置变换矩阵
为了渲染,一般的对象都有个Render函数,在渲染之前要做一些变换,比如移动旋转之类的,但是切记不可在Render函数中做这些变换,因为Render函数是被实时调用的,如果在这里做变换,则一个变换会被应用很多次。
D3D中顶点的顺序问题
Remember in Direct3D the vertices must be defined in clockwise order. see the picture below, the order is v0, v1, v2, v3
正确使用时间函数timeGetTime()
这个函数返回的是当前时间-单位毫秒,可是类型是DWORD,而我们一般做计算都需要float类型,转换一下,一般应该先计算差值,再做类型转换
...
DWORD currTime = timeGetTime();
float timeDelta = (currTime - lastTime) * 0.001f;
...//use timeDelta
lastTime = currTime;
而不要这样做:
...
float currTime = (float)timeGetTime();
float timeDelta = (currTime - lastTime)*0.001f;
这样是要损失精度的,因为DWORD是32为整数,而float只有24位精度,此法不可取。
让代码只执行一次
{
static bool flag = false ;
if(!flag)
{
cout << "you can not see this line twice!" << endl ;
flag = true ;
}
}
头文件包含警戒
通常我们用下面的代码来防止头文件被重复包含,这是标准的写法
#define __HEADER_H__
// code of the file
#endif //HEADER_H__
还有一种方法是,这种方法是MS特有的,不适合其他*台,所以推荐使用第一种方法
// code of the file
解决从VC6转换到VS2005/VS2008时编译和链接错误
有很多几年前编写的游戏书籍,其中所附代码大多是基于VC6的,而我们现在用的大多是VS2005/VS2008/VS2010,所以需要一次转换,而在转换中经常会遇到编译或者链接错误,解决办法如下:
编译错误:error C2440: '=' : cannot convert from 'char [14]' to 'LPCWSTR'
这是由于作者默认使用的是函数的A版本,而我们的工程使用了W版本。
解决办法一:手动将所有函数改为A版本,例如MessageBox -> MessageBoxA, 不推荐这种防范。或者将所有字符串改为宽字符串,即在字符串前加大写字母L,这个方法比较笨,但是确实好的方法。推荐使用。
解决办法二:在工程文件上点右键-Properties-Configuration Properties-C/C++-Preprocessor-Preprocessor Definitions-点击右边的省略号按钮-在弹出的对话框中将Inherit from parent or project defaults选项去掉即可。这个方法一劳永逸,可以一次性去除所有此类编译错误。但是却不是好的编程习惯,我们应该尽量使用函数的W版本。
链接错误:unresolved external symbol xxx
这种错误一般都是由于缺少lib文件导致的,只要引用正确的lib文件就可以了
解决办法:在工程文件上点右键-Properties-Configuration Properties-Linker-Input-Additional Dependencies-点击右边的小省略号按钮-将所需的lib文件加入到弹出的对话框中即可。
调试选项的设置
通过注册表也可以设置D3D的调试选项,这和使用DirectX控制面板是一样的,而且两者是相通的,改变一个,另一个也会跟着改变,如下,注册表位置:
HKLM\Software\Microsoft\Direct3D
如何求取视线
有很多时候需要用到视线,比如Billboard技术,要求被观察物体始终面对观察者,这时候就需要该物体与视线垂直,求取视线很简单,用观察点减去眼睛的位置即可
Zoom in/Zoom out效果是如何实现的?
很多图形软件都支持该效果,在浏览一个model的时候,鼠标滚轮向前滚时(mouse wheel rotate forward, away from the user),model会逐渐变大,谓之zoom in,鼠标滚轮向后滚时(mouse wheel rotate backward, toward the user),model会逐渐变小,谓之zoom out
那么这个效果是如何实现的呢?在我初学DirectX的时候,一直以为用缩放变换来实现。直到研究了DXUT的Camera类以后,才发现,其实有一个更简单的办法。通过改变Camera与Model之间的距离来实现,这与现实生活中的感觉是一样的,当我们离一个物体*的时候,感觉它很大,而当我们渐渐远离它的时候,它会变得越来越小,此其所以然也!编程的乐趣就在于灵活!
transform小结
Transform(变换)的本质就是从一个坐标系到另一个坐标系的过程
world transform
model space -> world space
view transform
world space -> view/camera space
projection transform
view/camera space -> projection space
transform engine(变换引擎) 以顶点为输入,对其进行world, view and projection transform, 然后进行剪
裁,将结果传送给rasterizer进行光栅化
//world transform通过下面的代码完成
// 将model移动至原点
D3DXMATRIXA16 matWorld ;
D3DXMatrixTranslation( &matWorld, 0.0f, 0.0f, 0.0f) ;
g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
//view transform通过下面的代码完成
D3DXVECTOR3 vEyePt( 0.0f, 0.0f,-10.0f ); //眼睛位置
D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f ); //观察中心
D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f ); //向上向量
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
projection transform通过下面的代码完成
D3DXMATRIXA16 matProj;
//视角:pi/4,即45度
//纵横比:1:1
//*剪裁*面:1.0f
//远剪裁*面:1000.0f
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 1000.0f );
g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
如何区分Point和Vector
在D3D中Point和vector都是用三维坐标表示的,比如,给定
v = [x, y, z], 如何区分这是一个点还是一个向量呢
答案是:在三维坐标下无法区分,于是在最后再加一维,形成齐次坐标
v = [x, y, z, w], 实际应用中w通常取值1
规定如下:
w = 1时,v表示点
w = 0时,v表示向量
为什么呢?
先看一下*移变换的矩阵
因为向量是没有位置属性的,向量只有长度和方向两个属性,即*移一个向量是没有意义的
所以将向量的第四维w设置为0,阻止*移。
但是点是有位置属性的,*移一个点也是有意义的,所以将点的w置为1,使之可以*移
如何从一个坐标系变换到另一个坐标系
通常这种需求都是逆向的,因为正向的D3D已经做了
比如对于一个view space中的model,如何把它变换到world space呢?(在D3D顶点处理流水线中world space是view space的前一个过程)
首先我们应该知道world space -> view space 是怎样一个过程,了解了这个过程后,应用它的逆过程即可
显然,world space -> view space是通过视图变换实现的,这个变换矩阵就是view matrix,好了,现在我们需要做的是:
1. 找出这个view矩阵
2. 求出它的逆矩阵
3. 将该逆矩阵应用到要变换的模型
下面以一个点为例,假设我们要将点p(x, y, z) 从view space变换到world space
步骤:
首先求出view matrix
D3DXMATRIX view;
Device->GetTransform(D3DTS_VIEW, &view);
再求出它的逆矩阵
D3DXMATRIX viewInverse;
D3DXMatrixInverse(&viewInverse, 0, &view);
然后将这个矩阵乘到这个点上即可
D3DXVec3TransformCoord(p, p, viewInverse);
注:D3DXVec3TransformCoord是用来变换点的(即w=1的vector)
而 D3DXVec3TransformNormal是用来变换向量的(即w=0的vector)
矩阵连乘的顺序
如果需要对同一个图形连续进行多个变换,那么可以把多个变换矩阵连乘形成一个矩阵。
但矩阵乘法不满足交换律,所以连乘的顺序很重要。
大多数文档都是以左右顺序来区分的,这是不准确的,而且不便于理解和记忆。
这里讲一个规则,比较方便
规则:在连乘式中,离被变换顶点*的矩阵对应的变换先进行。
比如,对顶点P(x, y, z, 1)进行如下变换
1. *移到(1, 1, 1)点,变换矩阵为MT
2. 绕z轴旋转30度,变换矩阵为 MR
3. 放大2倍,变换矩阵为 MS
那么正确的连成顺序是
P * MT * MR * MS (在DirectX文档里多采用这种写法)
MT离P最*,那么它是最先进行的变换,MR次之,MS再次。
很多书籍或文档里面说是按照从左到右的顺序连乘,但是别忘了顶点在公式中的位置
如果顶点在右边,那么就变成了下面的形式
MS * MR * MT * PT (在以OpenGL为基础的图形学书籍里多采用这种写法)
所以准确的说应该是,在连乘式中,先进行的变换离被变换的顶点*。
注意如果P在连乘式右边,那么应该取其转置形式,否则无法与矩阵相乘。
为什么变换矩阵是4 x 4的
因为三维的矩阵无法完成某些变换,比如*移变换对应的矩阵如下
这用三维矩阵是无法实现的,类似的还有透视投影变换,所以在D3D中使用4×4矩阵来实现变换,又一个问题来了,由于3D中的顶点都是三维的,三维的vector和四维的matrix是无法相乘的,于是在三维的顶点后面再加上一维,形成齐次坐标,如下
[x, y, z, w]
最后一维通常用w表示,而w通常取值1
这样就可以用一个1×4的vector与一个4×4matrix相乘了。
[x, y, z, w] *
d3dx9.h
这个头文件包含了几乎D3D用到的所有头文件,如果编译、链接或者运行有问题,可以尝试包含这个文件试试。
如何查看VertexShader和PixelShader的版本号
如果安装了DirectX SDK,可以使用其中的工具DirectX Caps Viewer 来查看,这个工具在DirectX的开始菜单中,也在SDK安装目录的Utilities\Bin\x86(x64)下面
打开这个工具后,依次展开结点DirectX Graphics Adapters-video card name-D3D Device Types-HAL-Caps
注意:一定要选择HAL分支,这才是你显卡真正支持的特性,而Reference目录下存储的是所有D3D特性,这是用软件模拟的,速度奇慢,只有在安装了DirectX SDK的机器上才可用,这就是HAL Device和Reference Device的区别,大家可以查看SDK帮助文件中Device Type一节来获取详细的信息
如果没有安装DirectX SDK, 可以使用Everest来查看
如何发布游戏(XNA)
看这里吧,比较详细
点积和叉积
设u和v是两个三维向量,α是它们之间的夹角
点积
u·v = ux*vx + uy*vy + uz*vz =|u|*|v|*cos(α)
乍一看,几何意义不十分明显,注意:当u和v都是单位向量时,点积就是两个向量夹角的余弦值
D3D中经常用这种方法来求夹角或者旋转角度,或者顶点法向量与入射光的夹角。
叉积
u×v = [(uyvz - uzvy), (uzvx - uxvz), (uxvy - uyvx)] =|u|*|v|*sin(α)
叉积的结果同时垂直于两个向量
叉积的结果值是以u和v为邻边的*行四边形的面积,叉积可以用来求多边形的面积
global variables are implicitly constant, enable compatibility mode to allow modification
编译Shader程序时遇到以下错误
全局变量默认是常量,貌似在Shader程序中不能修改?新版的HLSL编译器不支持
解决办法:
将D3DXCompileShaderFromFile的第六个参数改为D3DXSHADER_ENABLE_BACKWARDS_COMPATIBILITY即可
花屏
如果程序执行后,窗口出现花屏现象,那么多半是由于depth buffer没有clear导致的,比如下面这幅图就是一个花屏的例子。
解决办法,在调用Clear函数时,将depth buffer也clear一下即可。原来可能是这样调用的
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, 0xff00ff00, 1.0, 0 ); //仅仅clear render target
修改后如下
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER , 0xff00ff00, 1.0, 0 );
需要注意的是,这个Clear函数可能会失败,比如你在CreateDevice的时候并没有创建depth buffer,而在Clear函数中却要Clear depth buffer,所以请检查你的CreateDevice函数,确保设备确实有一个关联的depth buffer。
==