D3D的绘制
一、D3D中的绘制
顶点缓存和索引缓存:IDirect3DVertexBuffer9 IDirect3DIndexBuffer
使用这两缓存而不是用数组来存储数据的原因是,缓存可以被放置在显存中,进行绘制时,使用显存中的数据将获得比使用系统内存中的数据快得多的绘制速度。
静态缓存(static buffer):一般放置在显存中,不需要进行修改,比如地形和城市建筑等。静态缓存必须在程序初始化时用几何体的数据进行填充。
动态缓存(dynamic buffer):一般放置在AGP存储区中,放置动态的内容。优点是更新速度相当快(快速的CPU写操作,方便修改),缺点是处理速度慢,因为在绘制前数据必须传输到显存中。
对显存和AGP存储区进行读操作非常慢,所以,如果您需要程序运行时读取几何数据,最好在系统内存中保留一份副本,然后在需要时对其进行读操作。
Device->CreateVertexBuffer()
Device->CreateIndexBuffer()
D3DVERTEXBUFFER_DESC
D3DINDEXBUFFER_DESC
设置绘制状态:
Device->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
还有很多其他绘制状态可供设置,每个状态都有自己的默认值。
绘制前的准备工作:
(1)指定数据流输入源
Device->SetStreamSource(StreamNum, pStreamData, OffsetInBytes, Stride);
(2)设置定点格式:
Device->SetFVF(D3DFVF_XYZ |D3DFVF_DIFFUSE | D3DFVF_TEX1);
(3)设置索引缓存:
Device->SetIndices(ib);
使用顶点缓存和索引缓存进行绘制:
Device->BeginScene();
Device->DrawPrimitive(…);
Device->DrawIndexedPrimitive(…);
Device->EndScene();
绘制Mesh:
ID3DXMesh* mesh = 0;
D3DXCreateTeapot(Device, &mesh, 0);
Device->BeginScene();
mesh->DrawSubset(0);
Device->EndScene();
mesh ->Release();
mesh = 0;
D3DX库提供了一些简单的创建几何体的方法D3DXCreate*,可以方便的创建几种常见的模型。
二、颜色
图元的颜色由构成该图元的顶点的颜色决定。 D3DCOLOR(DWORD) D3DCOLORVALUE D3DXCOLOR
FVF:D3DFVF_DIFFUSE
多边形着色(光栅化阶段):根据顶点的颜色来计算构成图元的像素的颜色。有两种着色模式:
平面着色(flat shading):由构成图元的第一个顶点的颜色决定。缺点是块状颜色、没有平滑过渡。
平滑着色(gouraud shading/smooth shading):图元中各像素的颜色值由个顶点的颜色经线性插值得到。
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
Device->SetRenderState(D3DRS_LIGHTING, false);
颜色可以看做4D向量,并做加法、减法、对应位的乘法。
三、光照
三种类型的光照:
环境光(Ambient Light);漫射光(Diffuse Light);镜面光(Specular Light)。
每个光源发出的光都有上述三种光照组成。
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Ambient;
镜面光计算量很大,D3D默认为不开启,手动开启代码为:
Device->SetRenderState(D3DRS_SPECULARENABLE, true); // 启用镜面高光
Device->SetRenderState(D3DRS_LIGHTING, true); // 开启光照,默认即是开启的
D3D支持三种光源:
点光源(Point lights),方向光(Directional lights),聚光灯(Spot lights)
对应的类定义:struct D3DLIGHT9
D3DLIGHT9 dirLight;
::ZeroMemory(&dir, sizeof(dir));
dir.Type = D3DLIGHT_DIRECTIONAL;
dir.Diffuse = d3d::WHITE;
dir.Specular = d3d::WHITE * 0.3f;
dir.Ambient = d3d::WHITE * 0.6f;
dir.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
Device->SetLight(0, &dirLight); // 注册一个光源:
Device->LightEnable(0, true); // 设置光源的开启状态:
材质:
材质用来模拟物体表面对各种颜色光的反射比例。
D3DMATERIAL9 mtrl;
mtrl.Ambient = d3d::WHITE;
mtrl.Diffuse = d3d::WHITE;
mtrl.Specular = d3d::WHITE;
mtrl.Emissive = d3d::BLACK;
mtrl.Power = 5.0f;
Device->SetMaterial(mtrl); // 设置当前材质
DrawObject();
顶点法线:
由于光照计算是对每个顶点进行的,所以每个顶点需要有局部朝向(法线)。
用面的法向量代替顶点的法向量效果会不平滑,所以更好的是取顶点共享的面的法向量的均值。
绘制时,使所有法向量规范化。
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
D3DFVF_NORMAL
四、纹理映射
纹理数据结构:LPDIRECT3DTEXTURE9
进行纹理映射的时间:光栅化时进行,也就是3D三角形已经被变换至屏幕坐标系时。
创建贴图纹理:
D3DXCreateTextureFromFile(device, file, texture);
设置当前纹理:
Device->SetTexture(0, tex);
若第二个参数设为0,表示禁用某一层纹理。
在D3D中,最多可以设置8层纹理,用一个从0开始的索引标记每层。
多重纹理(multitexturing):对多层纹理进行组合以创建一幅更细致的图像。
纹理过滤器:缩小过滤器、放大过滤器
D3D提供了3中类型的filter:
最近点采样(nearest point sampling):默认,速度快,效果差。
线性纹理过滤linear filtering):速度较快,效果相当好。
各向异性纹理过滤(anisotropic filtering):效果最好,速度最慢;可以单独设置各向异性的质量水平。
Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
多级渐进纹理Mipmap:
Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
多级渐进纹理过滤器,可以取以下三个值:
D3DTEXF_NONE:禁用Mipmap
D3DTEXF_POINT:选择最接近的一级纹理
D3DTEXF_LINEAR:选择最接近的两个纹理,然后进行线性组合,从而形成最终的颜色值。
寻址模式(address mode):
D3D定义了4种用来处理纹理坐标值超出[0, 1]区间的纹理映射模式:
D3DTADDRESS_WRAP重复
D3DTADDRESS_BORDER边界
D3DTADDRESS_CLAMP箔拉
D3DTADDRESS_MIRROR镜像
Device->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
Device->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
五、融合(blending)
将当前计算得到的像素(源像素)颜色值与先前计算所得的像素(目标像素)颜色值进行合成的做法称为融合。
blending在光栅化时进行。
Blending时的渲染顺序很重要,直接影响最终渲染的效果:首先绘制那些不需要进行融合的物体,然后将需要进行融合的物体按照相对于camera的深度值进行排序。如果已经处于观察空间中,则只需要对此时的z分量进行排序。最后自后往前依次绘制需要融合的物体。总之就是先绘制后面的再绘制前面的。
D3D中blending默认是关闭的,可以通过如下方式开启:
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
在渲染透明效果时,先开启该状态。blending的开销并不低,所以(1)只在需要的时候开启,渲染完毕再关闭之;(2)最好进行一些批处理。
融合因子:
Device->SetRenderState(D3DRS_SRCBLEND, source);
Device->SetRenderState(D3DRS_DESTBLEND, destination);
源和目标融合因子的默认值分别是D3DBLEND_SRCALPHA和D3DBLEND_INVSRCALPHA。
Alpha分量主要用于指定像素的透明度。
Alpha值的来源主要有以下两种(一定要设置):
(1) Alpha通道:如果纹理有alpha通道,则alpha值就取自该alpha通道;
// 根据diffuse color计算alpha
Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
(2) 材质:如果纹理没有alpha通道,则alpha值就取自顶点颜色。
// 根据diffuse color计算alpha
Device->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_DIFFUSE);
Device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
可以将材质中的alpha值设为0.5来获得透明顶点颜色。
在代码中经常需要切换光照是否开启D3DRS_LIGHTING、Alpha来源(贴图或是顶点颜色)、是否开启融合D3DRS_ALPHABLENDENABLE等状态。
Alpha Blending是我们可以将当前要光栅化的图元中的像素与当前后台缓存中同一位置的像素进行融合。融合因子控制融合方式。
Alpha信息来源也可以手动设置:贴图Alpha通道或是材质的漫反射分量。
六、模板缓存(Stencil buffer)
三大缓存:后台缓存(back buffer)、深度缓存(depth buffer)、模板缓存(stencil buffer)。可用Clear函数对这三类缓存进行清空操作:
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0xff000000, 1.0f, 0L);
设置深度缓存开启状态,即是否允许写入depth buffer:
Device->SetRenderState(D3DRS_ZWRITEENABLE, false);
设置模板缓存开启状态,即是否允许写入stencil buffer:
Device->SetRenderState(D3DRS_STENCILENABLE, true);
Stencil buffer和Depth buffer共享同一个表面存储区,所以二者是同时创建的,可以用D3DFORMAT类型指定Stencil/Depth分别占一个像素的多少位。
模板测试的结果决定了像素的颜色值是否要被写入到渲染目标,像素的深度值是否要被写入深度缓冲。
Stencil可用于阻止某些像素的光栅化,即动态地、有针对性地决定是否将某个像素写入后台缓存中。
查询设备是否支持模板缓存;
开启模板缓存:
Device->SetRenderState(D3DRS_STENCILENABLE, true);
模板测试(Stencil-Test):
公式: (ref & mask) ComparisonOperation (value & mask)
Ref:模板参考值,默认为0,通过D3DRS_STENCILREF设置。
Mask:模板掩码,默认为0xffffffff,通过D3DRS_STENCILMASK设置,用于屏蔽ref和mask的某些位。
CoparisonOperation:比较函数,通过D3DRS_STENCILFUNC设置。
Value:当前像素对应的模板缓存中的值。
模板测试更新:
(1)当stencil-test失败时的行为,通过D3DRS_STENCILFAIL设置
(2)当z-test失败时的行为,通过D3DRS_STENCILZFAIL设置
(3)当z-test和stencil-test都成功时的行为,通过D3DRS_STENCILPASS设置
模板写入掩码(stencil-write mask):
通过D3DRS_STENCILWRITEMASK设置,用于屏蔽写入stencil buffer的某些对应位。
Device->SetRenderState(D3DRS_STENCILENABLE, true);
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
Device->SetRenderState(D3DRS_STENCILREF, 0x1);
Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_REPLACE);
利用stencil buffer制作镜像时需要设置镜像物体的绕序:
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
可以利用blending打到disable后台缓存的效果:
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
实现只写入一次stencil buffer的方法:
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILREF, 0x0);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);
等于0则模板测试成功,成功后当前值增加,那么写一次测试的时候就不能够测试成功了。
用stencil buffer制作shadow时,先绘制地板,再禁用depth buffer,然后混合绘制阴影体。