OpenGPU Forum (开源图形处理器论坛)'s Archiver

ic.expert 发表于 2008-6-11 07:19

Direct3D Graphics pipeline

[url=http://blog.csdn.net/weili_2007/archive/2007/11/23/1899228.aspx][color=#810081]Modeling(建模)[/color][/url]

   前一章讲解的主要是怎样向direct3D 描述场景。场景里面的物体都是由很多组primitives组成的,primitive包括point, line, triangles。 由于三角形也是一个面,所以一个三角形就可以模拟出一个平滑的表面。一个顶点除了位置信息,面法线外,还有很多额外的信息绑定到这个顶点,如顶点颜色值,纹理坐标值,顶点blend weight,任意的shader数据等等。 这些数据是通过一组数据流传递给Device,每一个流都对应一个顶点buffer。

   Direct3D 提供了两种方法来描述一个物体的形状,可以用参数把面的精细度定义成真实面的一组三角形。设备一次只描述场景中的一个primitive 。



建模场景
   所有的3D 渲染都在在场景里面完成的,BeginScene标志着一个场景的开始,紧接着是图形渲染,EndScene标志着场景渲染的结束。设备则是被创建以后就可以设置它的属性和状态,他们不受场景的影响。StretchRect 不是一个渲染方法,能被应用在场景之外。



可视性
   "Z buffer"是一个判断可视性的算法,它工作在Render Target的每个像素上,而不是每个模型上。 当一个模型被光栅化之后,每一个像素就会有一个Z 值,来表示这个像素离照相机的距离。这个算法也不是完美的,虽然对于不透明的像素还能处理的很好,但是如处理那些果透明的或者半透明的像素,它就有缺陷了。幸运的是,我们可以先画出所有不透明的物体,然后把透明的物体从后到前排序,然后用painter算法画它们。 这个方法并不完美,它确实能减少artifacts,但是仍然使用了快速Z buffer硬件。

   depth/stencil buffer 存放着像素离照相机的距离。 一个depth/stencil buffer是通过设备的D3DPRESENT_PARAMETERS来创建的,或者显示的调用CreateDepthStencilSurface。GetDepthStencilSurface可以取得设备的depth/stencil surface。为了利用Z buffer来解决可视性问题,设置RS Z enable 为 D3DZB_TRUE, RS Z Write Enable为True 和 RS Z Func 为D3DCMP_LESS。

   如果 D3DCAPS9::RasterCaps 的 D3DPRASTERCAPS_ZBUFFERLESSHSR被设置,它意味着用另外一种方法做可视性判断,可视性判断是与硬件相关,这种情况下render targe的depth/stencil surface 为空,而且设置RS Z enable为 D3DZB_TRUE.



Render Targets

   当光栅处理结束后,像素流就产生了。它的目的地就是设备的render target。 Swap chain上的所有的back buffer 都是合理的render targets,但是render target并不仅仅限制于back buffer的surface。当设备被创建或者reset的时候,render target就是默认swap chain的back buffer 0。当调用present的时候,render target就跳到下一个back buffer,这样当present 返回以后,render target又回到back buffer 0。

   可以把render target设置一个表面,这样就允许设备直接渲染到一张图片上。非swap chain 上的render target surface 可以通过调用函数CreateRenderTarget。

   SetRenderTarget ,GetRenderTarget, Clear 方法用于对render target surface进行操作。



Primitive Type

   一般包括D3DPT_POINTLIST, D3DPT_LINELIST, D3DPT_LINESTRIP,D3DPT_TRIANGLELIST,D3DPT_TRIANGLESTRIP, D3DPT_TRIANGLEFAN。

顶点数据

   场景数据由定义物体形状和外表的数据组成。我们已经看到形状可以由primitive和顶点坐标确定。用于固定流水线的数据要么是FVF格式的数据,要么是顶点shader声明格式的数据。顶点shader程序可以在每个顶点上使用任意的数据。每个与顶点相关的数据都被称作是顶点的component。利用FVF格式的数据,所有的数据必须在一个单一的流里面。但是顶点shader声明可以把这些components分成多个流。

   除了选择固定功能处理和可编程顶点处理外,应用程序还可以把数据直接传递给设备,而不进行任何的顶点处理。



灵活顶点格式(FVF)

   顶点FVF是一个DWORD,它包含一个或者多个描述在内存里面顶点组件,如:D3DFVF_XYZ, D3DFVF_XYZRHW, D3DFVF_XYZB1,等等。

   位置相关的FVF包括D3DFVF_XYZ, D3DFVF_XYZRHW,D3DFVF_XYZB1, D3DFVF_XYZB3,D3DFVF_XYZB4 和 D3DFVF_XYZB5。B值是它的blending weight。

   D3DFVF_PSIZE 与点精灵有关,描述了点精灵的大小。

   D3DFVF_TEXn标记纹理坐标的数目,D3DFVF_TEXCOORDSIZEN 纹理坐标的维度。

   GetFVF和SetFVF用于FV格式的管理F。



顶点声明

   使用顶点声明,可以将顶点数据分成多个流,每个流都包含一个或多个顶点组件数据。顶点声明可以被用于固定功能处理也可以用于可编程处理。

   顶点声明是由一组D3DVERTEXELEMENT9结构的数组创建的。数组里面的每个元素代表顶点里面一个component。元素在数组里面的顺序会影响流里面数据的分布。但是,顶点组件顺序可以存放在内存里面的任意位置。

typedef struct _D3DVERTEXELEMENT9

{

   WORD Stream; //

   WORD Offset;

   BYTE   Type; //数据的类型,如果D3DECLTYPE_FLOAT3,理论上所有的数据在被传入到顶点处理之前都要是4值的向量,如果不是,则会在y,z补0,w补1。

   BYTE Method; // 用于顶点的tessellation。

    BYTE Usage; // 如: D3DECLUSAGE_POSITION。

    BYTE UsageIndex; //用于有同一个Usage的索引,如哪个texture stage。

}

   顶点声明的接口是IDirect3DVertexDeclaration9,它有两个只读的方法,GetDeclaration和GetDevice。

   当固定功能流水线使用顶点声明时,每个顶点组件必须被映射到一个特定的usage。你能使用多个stream,不过这个流必须遵循FVF顺序和顶点类型。

   注意:顶点声明总是存放在系统内存,所以当设备重启之后它不需要恢复。



Vertex Buffer

   Vertex buffer 资源用于存储应用程序的顶点数据。Direct3D 的接口是IDirect3DVertexBuffer9。

interface IDirect3DVertexBuffer9 : IUnknown

{

HRESULT GetDesc(D3DVERTEXBUFFER_DESC* value);

HRESULT Lock(UINT offset, UINT size, BYTE** data , DWORD flags);

HRESULT Unlock();

}

   创建一个包含size 个字节的vertex buffer,可以通函数CreateVertexBuffer();

   HRESULT CreateVertexBuffer(UINT size, DWORD usage, D3DPOOL pool, DWORD fvf,IDirect3DVertexBuffer9 ** reuslt, Handle * unused);

   如果fvf参数为0 ,它将创建一个non-FVF vertex buffer,其内容将使用顶点shader声明。 unused必须是NULL。 usage 包括D3DUSAGE_DONOTCLIP, D3DUSAGE_DYNAMIC, D3DUSAGE_NPATCHES,D3DUSAGE_POINTS, D3DUSAGE_RTPATCHES, D3DUSAGE_SOFTWAREPROCESSING, D3DUSAGE_WRITEONLY.

   D3DUSAGE_DONOTCLIP指这些顶点不需要clipping; D3DUSAGE_NPATCHES, D3DUSAGE_POINTS, D3DUSAGE_RTPATCHES 指这个顶点buffer将分别被用于画 N-Patches, 点 sprite,higher order suface patches;D3DUSAGE_SOFTWAREPROCESSING 指顶点用于软件处理;D3DUSAGE_WRITEONLY指这个buffer不能读取,只能写入;D3DUSAGE_DYNAMIC指应用程序可能会改变里面的内容,如果不指定这个标记,顶点buffer将是静态的。

    如果D3DCAPS9::DevCaps的D3DDEVCAPS_TLVVERTEXSYSTEMMEMORY 或者 D3DDEVCAPS_TLVVERTEXVIDEOMEMORY 被设置,设备能够使用video 或者system内存中的而且包含被转换顶点的vertex buffer。通常static vertex buffer是分配在设备内存中的,然而动态顶点buffer则是在系统内存中或者AGP内存,可以被CPU直接访问。动态顶点buffer必须要在系统内存池中分配,或者在默认内存池中分配。

    IDirect3DVertexBuffer9::GetDesc将会返回一个D3DVERTEXBUFFER_DESC的结构。

typedef struct _D3DVERTEXBUFFER_DESC

{

   D3DFORMAT Format; //对于vertex buffer,将永远是D3DFMT_VERTEXDATA;

   D3DRESOURCETYPE Type;

   DWORD Usage;

   D3DPOOL Pool;

   UINT   Size;

   DWORD FVF;

}

   IDirect3DVertexBuffer::Lock提供对顶点数据的直接访问。对buffer 数据的访问必须与它的参数flags相一致。 flags 包括D3DLOCK_DISCARD,D3DLOCK_NOOVERWRITE, D3DLOCK_NOSYSLOCK, D3DLOCK_READONLY, D3DLOCK_DISCARD指应用程序不关心以前的内容,以前的内容将会被抛弃;D3DLOCK_NOOVERWRITE 指应用程序不能重写里面的数据;D3DLOCK_DISCARD和D3DLOCK_NOOVERWRITE只能用于动态顶点buffer。对于动态几何的场景,不正确lock将对render的性能影响很大。



被索引的Primitives

    我们已经知道在描述一个形状的时候有很多重复的顶点,如果我们为每个顶点分配一个序号,并且把所有不同的顶点按照序号缓存起来,这样当我们制作一个primitive的时候,我们只需要提供primitive 顶点序号即可。这样我们节省了很大存储空间。

   Direct3D通过IDirect3DIndexBuffer9 接口来管理index buffer。这个接口的方法与IDirect3DVertexBuffer类似,这里就不做详细讲解了。



顶点Shader

   设备的顶点shader属性是在固定功能顶点处理和可编程顶点处理之中选择。这些属性就是顶点shader的接口指针,它是通过SetVertexShader和GetVertexShader来管理。设置这个接口指针为空,表示选择固定功能的处理,合理的接口指针,它将是意味着可编程的顶点处理。

HRESULT GetVertexShader(IDirect3DVertexShader9 ** value);

HRESULT GetVertexShader(IDirect3DVertexShader9 * value);

可以通过CreateVertexShader创建一个顶点shader,

HRESULT CreateVertexShader(const WORD * function, IDirect3DVertexShader9 ** result);

interface IDirect3DVertexShader9 : IUnknown

{

   HRESULT GetDevice(IDirect3DDevice9 ** value);

   HRESULT GetFunction(void * value, DWORD * size);
}

   通常一个应用程序将不会直接分配一个DWORD功能数组,但通常是通过D3DX在运行时编译汇编语言或者更高级shader语言为这个DWORD功能数组。



绘制Primitives

   一旦顶点数据被定义,并且顶点处理被配置,应用程序可以调用一个draw 方法进行渲染。draw方法很多,包括DrawPrimitiveUp,DrawIndexedPrimitiveUp,DrawPrimitive,DrawIndexedPrimitive,DrawTriPatch,DrawRectPatch等等。

    HRESULT DrawPrimitiveUp(D3DPRIMITIVETYPE kind, UINT primitive_count, const void * vertex_data, UINT vertex_stride); 这个方法绘制类型为kind的非索引的顶点数据,它的内部处理是先将输入的数据拷贝到一个新的vertex buffer, 然后绘制里面的primitive,最后在释放这个vertex buffer。创建,释放以及拷贝数据将是非常昂贵的操作,这个方法只能被用于测试或者渲染primitive 比较少的场景。

   HRESULT DrawIndexedPrimitiveUp 与DrawPrimitiveUp类似,只是它是绘制被索引的顶点。



顶点数据流

   顶点各个组件可以来自与一条或者多条数据流,这些流数据组合起来就是一个完整的顶点。这些组合起来的顶点只是direct3D pipeline流水线的第一步。这些流数据都有一个编号,并且都有一个相关的vertex buffer和一个strid(顶点组件的大小)。流的编号是连续,以0开始,按照编号顺序被连接。一个流必须包含一个或者数个顶点组件。

   不同的顶点组件到不同的流,可以让应用程序动态的改变一个模型中顶点组件的值,而不用锁住整个顶点buffer只改变其中某个部分的值。使用多个流,我们可以将顶点里面的静态组件放在一个流,而动态组件放在另外一个流。这允许我们可以在动态组件流的vertex buffer上使用D3DLOCK_DISCARD。可以通过HRESULT SetStreamSource(UINT index, IDirect3DVertexBuffer9 ** value, UINT offset, UINT stride)来设置与流关联的vertex buffer。 stride参数是顶点组件的大小,对于一个FVF vertex buffer ,它应该与FVF一样大,对于非FVF,它应该大于或者等于顶点声明计算出来的大小;offset是距离流的开始端的字节偏移量。如果D3DCAPS9::Caps2里面设置了D3DDEVCAPS2_STREAMOFFSET,才能支持偏移offset。

   流的最大数目是定义在D3DCAPS9::MaxStream。对于DirectX 8.1 或者以后的版本,这个值一般是1到16。但在DirectX 8.1 之前,这个值是0,暗示它只支持单一流。 MaxStreamStride给出了最大的流组件数据的大小。

   流sources提供顶点数据,并且也必须供应索引buffer的索引数据。SetIndices为被索引的primitive指定索引数据。可能存在高达16个顶点数据流,但是只有一个索引buffer。GetIndices返回索引属性值。

   随着所有的顶点组件和索引被载入,固定功能的顶点处理被选择,stream source被正确的选择,并且所有必须的状态被设置,就可以分别调用DrawPrimitive和DrawIndexedPrimitive。

   HRESULT DrawIndexedPrimitive(D3DPRIMITIVETYPE kind, UINT base_vertex_index, UINT min_index, UINT number_vertices, UINT start_index, UINT primitive_count); min_index 指顶点buffer里面的最小的顶点索引,start_index参数是在索引buffer里面的偏移,base_vertex_index参数是将索引buffer的值都加上这个值,根据所得的结果再去从顶点buffer里面取,这样就允许多组流被打包成一组索引流,而不需要改变索引值。

   设备的Draw primitive方法,将primitive的批量处理转化成一个单独方法的调用。这是最有效的方法,将primitive传送给设备。这个批量处理考虑了CPU 和设备之间更多的并发情况:当设备正在光栅化一组primitives的时候,cpu正准备传递下一组。

   D3DCAPS9的MaxPrimitiveCount给出了能够被draw 方法使用的最大primitive数目。MaxVertexIndex给出了最大的顶点索引值。如果DevCaps的D3DDEVCAPS_DRAWPRIMTLVERTEX被设置,则设备有drawprimitive的硬件支持。



增强型顶点

   Direct3D 提供了两种对基本primitive的增强方法(点Sprites和N-Patches)。点Sprites 可以让一个点primitves有一个用纹理化过的屏幕空间。N-Patches 可以提高了三角形的外观,并且不需要做很多的其他工作。

   Point sprites 可以使D3DPT_POINTLIST primitives 被光栅化后不只是占据一个像素的空间。如果D3DCaps9::MaxPointSize大于1,则设备支持点Sprites。Point sprites被渲染成屏幕空间里的纹理化的方块。点的大小可以在每个顶点的vertex buffer 用D3DFVF_POINT_SIZE来指定。

   如果point sprites使用是D3DFVF_XYZRHW,point sprites 的大小则是在屏幕空间的,否则这个size 必须要经过插值了的。

   point sprites 可以用于渲染一个symbol的许多实例,把symbol的图片存储在纹理里面,然后把symbol的位置作为point sprites。这种渲染方式,它的开销只有直接使用顶点渲染的1/4。point sprites渲染方式也可以用于渲染粒子系统,每个粒子都是一个point sprites。

   N-patches

   N-patches 基于顶点和表面法线信息为三角形做tessellation。如果D3DCAPS::DevCaps的D3DDEVCAPS_NPATCHES被设置,则设备支持N-patch。

   因为三角形近似光滑平面,N-patches 提供为surface 模型提供额外的detail,而不需要巨大的编程工作或者改变模型。当设备的N-patch mode属性大于1.0的时,N-Patches可以用于三角形。基于原有的三角形顶点和法线信息重建三角形光滑表面的patch,然后再根据N-patch属性细分那个patch,这样就产生了额外的三角形。顶点位置和顶点法线都可以产生对应的patches。新顶点位置patch的代数degree定义在RS Position Degree,新顶点法线patch的degree定义在RS Normal Degree。这两个render state值都是定义在D3DDEGREETYPE枚举类型里。RS position Degree可以是线性的或者立体型的,立体型是它的默认值。RS Normal degree也可以是线性的或者二次的,线性是它的默认值。



Higher order Surfaces(高维曲面)

   三角形和矩形patch提供了高质量参数化定义的primitives,这让应用程序可以指定真正smooth的曲面,而不只是一种真实面的三角形模拟。这里的面定义为高维,因为在数学里面曲面的方程要比三角形方程次数要高。

   高维曲面又称为spline 面,在计算机图形学里面已经有很多算法和研究了。

   为了定义一个三角形,我们一般定义它的每个顶点。如果想要定义面的patch,我们则需要提供控制点和一个patch产生函数,这个函数用来从控制点产生patch。曲面将会从控制点控制的数学方程集合中产生。这些数学方程集合通常称为patch的基本方程。

   每一个patch都通过基本函数来构造它的表面以及它的维度。D3DBASISTYPE和D3DORDERTYPE分别指定了它的基本方程和维度。Bezier基本方程的控制点是曲线的两个端点以及曲线附件的点;B-spline方程的控制点不必在曲线上;插值方程保证所有的控制点在曲线上。

typedef enum _D3DBASISTYPE

{

D3DBASIS_BEZIER = 0,

D3DBASIS_BSPLINE = 1,

D3DBAISS_INTERPOLATE = 2
}

   Direct3D处理高维曲面的方法是把面镶嵌到三角形集合,然后在后续的流水线里面作进一步的处理。镶嵌结果将会被缓存,并且将绑定到一个patch handle,在后续的patch渲染过程中就可以直接使用这个handle。

   如果D3DCAPS9:: DevCaps的D3DDEVCAPS_RTPATCHES被设置,这个设备就支持直接用DrawTriPatch和DrawRectPatch。如果D3DDEVCAPS_QUINTICRTPATCHES被设置,它将支持Bezier和B-spline RT patch。如果D3DDEVCAPS_PATCHHANDLEZERO被设置,它将意味着绘制未缓存和缓存的patch将会一样的高效。

   三角行和矩形patch分别使用DrawTriPatch和DrawRectPatch来渲染。一个patch的镶嵌会有大量的计算,所以缓存是很有意义的。如果handle为空,则会根据patch_info进行镶嵌。

HRESULT DeletePatch(UINT handle);

HRESULT   DrawRectPatch(UINT handle, const float * num_segments, const D3DRECTPATCH_INFO * patch_info);

HRESULT DrawTriPatch(UINT handle, const float *num_segments, const D3DTRIPATCH_INFO * patch_info);

   number_segments对于三角形是三个float的数组,对于矩形是四个float的数组,这允许更加精确的渲染一个patch的边。

   镶嵌结果可以直接通过硬件做渲染。或者按照传统方法三角形化然后渲染。

三角形patches
   在DrawTriPatch,patch_info 参数定义了一个三角形patch集合。Direct3D 对于三角形patch支持线性,立方型,二次型bezier模型。patch 的控制点的顶点数据也是存放在顶点buffer里面。

Baiss   Degree count

Bezier    Linear   3

             CUBIC   10

             Quintic 21

typedef struct _D3DTRIPATCH_INFO

{

   UINT StartVertexOffset; // 当前流里面第一个控制点的顶点索引。

   UINT NumVertices;

    D3DBASISTYPE Basis; //只能是D3DBASIS_BEZIER

     D3DDEGREETYPE Degree;

} D3DTRIPATCH_INFO;



矩形patches

Baiss                Degree                 width                    height

Bezier               Linear                   2                            2

                         Cubic                    4                           4

                         Quintic                  6                           6

B-Spline            Linear                  1                              1

                          Cubic                    3                             3

                           Quintic                  5                            5

Catmull-Rom      Cubic                  3                               3

[[i] 本帖最后由 ic.expert 于 2008-6-11 07:22 编辑 [/i]]

ic.expert 发表于 2008-6-11 07:21

Vertex Transformation(顶点变换)

Vertex Transformation(顶点变换)


  在前面的章节里面我们已经知道定义一个model需要顶点buffer,索引buffer和primitives,也知道可以使用多路流来实现动态顶点components.如果我们想要移动一个model,我们可以通过锁住顶点buffer,然后编辑它某些组件来达到这样的效果。Direct3D提供了顶点处理流水线来执行这样的转换,它包括平移,旋转和缩放。

  顶点处理是指发生在被渲染的每个primitive的每个顶点上的所有计算。顶点处理的每一个stage都会对它的一个或者多个components产生作用。Direct3D允许应用程序选择顶点的处理方式(硬件处理,软件处理或者两者结合)。也可以只是对顶点进行处理,而不进行渲染。

   顶点的变换矩阵一般是一个4*4的浮点矩阵。这个矩阵一般只执行平移,旋转或者缩放。

   当使用多个转换矩阵的时候,变换矩阵的顺序一般也会改变产生的结果,这是因为矩阵乘法不支持交换律。

   世界变换是顶点处理的第一步,它可以把模型空间的顶点转换到世界空间。转换矩阵可以通过方法GetTransform,SetTransform和MultiplyTransform来操作。

   也可以用矩阵变换来实现场景的层次结构。场景的层次结构可以用于绘制那些被连接起来的人物结构或者有相对定位的模型,例如一个机器人的手臂。

   一个顶点可以使用多个世界变换,这些变换结果通过加权结合在一起产生想要的结果,这样的变换方式叫做顶点blending. Direct3D提供了很多blending操作选项设置。最简单的顶点transform是Tweening,就是使用一个浮点值作为通过两个世界变换矩阵变换后顶点的权重值。顶点的权重值在顶点components里面被给出。

    通常的顶点blending一般会提供2到3个权重值以及相应的3个或者4个变换矩阵。被索引的顶点blending可以允许多大4个不同矩阵用于变换。对于未被索引顶点的blending,一个primitive的所有的顶点变换矩阵必须是一样的,只是他们的权重值可以不一样。对于索引顶点blending,每个顶点的索引可以变化,这样顶点的变换矩阵可以不一样。

    在这一章里面,我们将讲述雾化,面拣选,用户自定义剪裁面,视截体,同次除法和view port的应用。



顶点处理

   在以前的章节里面,设备支持软件,硬件以及软硬结合的顶点处理方式。当设备使用软硬结合的处理方式的时候,GetSoftwareVertexProcessing和SetSoftwareVertexProcessing用来控制是软件处理还是硬件处理。当这个render state为true时,就意味着软处理被选择,否则则硬处理被选择。当这个render state改变时,当前流,当前索引以及当前顶点shader需要重新还原到默认值。顶点处理包括:世界变换,纹理坐标产生,纹理变换,视觉变换(View Transform),顶点雾化,面拣选,用户剪裁面处理,视截体剪裁,同次除法和视口映射。顶点处理的结果被称为“transformed and lit”顶点,它携带一个屏幕空间的位置,diffuse和specular颜色,最多8组纹理坐标。这些信息都是需要传递给光栅器。如果顶点位置格式是D3DFVF_XYZRHW,就会忽略所有的顶点处理,直接传递给光栅器。



变换矩阵

   在数学上,坐标变换是从一个坐标系映射到另外一个坐标系。在三维坐标系,我们需要能够旋转,缩放,平移一个物体。我们可以列出一个跟一维类似的方程:

P'  =  PM +  b  = [x , y z ] M + [b1 b2 b3]

   这里M是一个3*3的矩阵, 它实现了旋转和缩放,而b实现了平移。但是如果使用同次坐标,我们则只需要一个矩阵就能实现这三个功能。当P的笛卡尔坐标扩展到同次坐标时,4*4的矩阵可以同时对三维的点做平移,旋转和缩放。P'= PM',M'是由M和b组成的,如:[x',y',z',1]= [x,y,z,1]M'。这个同次转换矩阵也可以利用它最右边的一栏用于透视图的距离缩短处理。

   当一个顶点包含一个面法线时,面法线将和顶点在同一坐标系。当顶点被转换到新的坐标系是,面法线也应该要转换。但是法线不应该被扭曲。所以,如果顶点用M做转换,面法线的变换矩阵应该是M的逆矩阵的转置。



世界变换

   在第五章里面我们已经知道怎么在模型空间建模。世界变换是顶点处理的第一步,将模型从模型空间转换到世界空间。

   如果顶点组件里面也包含面法线,则面法线也需要被转换。不过位置组件是一个点,但是法线是一个向量。为了保持表面法线的方向,Direct3D使用一个新矩阵来转换它。这个新的矩阵就是顶点变换矩阵的逆矩阵的转置。不过这个矩阵仍然包含缩放,可以改变法向量的长度。

  RS Vertex Blend 可以用来控制世界变换的类型,它的值定义在D3DVERTEXBLENDFLAGS里面。当RS Vertex Blend为D3DVBF_DISABLE时,顶点将只有一个变换矩阵。

typedef enum _D3DVERTEXBLENDFLAGS

{

     D3DVBF_DISABLE = 0,

     D3DVBF_0WEIGHTS = 256,

     D3DVBF_1WEIGHT = 1,

     D3DVBF_2WEIGHT = 2,

     D3DVBF_3WEIGHT= 3,

   D3DVBF_TWEENING = 255

}

   GetTransform和SetTransform方法管理设备的变换矩阵的属性。D3DTRANFORMSTATETYPE给出了不同类型变换的类型,当vertex blending 被disable的时候使用D3DTS_WORLD。

typedef enum _D3DTRANSFORMSTATETYPE

{

     D3DTS_WORLD= 256,

     D3DTS_WORLD1 = 257,

     D3DTS_WORLD2 = 258,

     D3DTS_WORLD3 = 259,

     D3DTS_VIEW   = 2,

     D3DTS_PROJECTION= 3,

   D3DTS_TEXTURE0 = 16,

     D3DTS_TEXTURE1 = 17,

     D3DTS_TEXTURE2 = 18,

     D3DTS_TEXTURE3 = 19,

     D3DTS_TEXTURE4 = 20,

     D3DTS_TEXTURE5  = 21,

     D3DTS_TEXTURE   = 22,

     D3DTS_TEXTURE   = 23

} D3DTRANSFORMSTATETYPE;





变换层次

   除了使用SetTransform来设置设备的变换属性外,你也可以在现有的矩阵上乘以一个新矩阵。如果一个模型由很多个相互关联的部分组成,MultiplyTransform可以用于每个部门的相对转换。



顶点Blending

  连接起来的linkage对于处理那些生硬的机器人还可以,但是其他物体如布,植物或者动物,这些都需要灵活的而需要精确描述的动作,用层次变化就行了。模拟动物行走的一种方法是把一个模型变换多次,每次都使用不同的矩阵,然后使用一组权重值把把这些结果组合起来产生一个最终的输出。最简单的例子,就是只使用一个权重值,两个变换矩阵,方程式:P' = bP1 + (1-b)P2= bPM1 + (1-b)PM2。

  这个权重值b,定义了每个顶点的转换比率,它通常是被一些3D建模工具。

基本顶点blending
  Direct3D 通过RS Vertex Blend的D3DVERTEXBLENDFLAGS来设定固定功能处理。当这个值是D3DVBF_1WEIGHTS, D3DVBF_2WEIGHTS, D3DVBF_3WEIGHTS分别表示每个顶点有1,2,3个权重值。如果指定D3DVBF_0WEIGHTS,那只有一个矩阵,它跟D3DVBF_DISABLE一样。D3DCAPS9::MaxVertexBlenMatices指定可以使用的最多变换矩阵的数目。当使用FVF定义顶点的格式时,D3DFVF_XYZBn 只是在位置组件里面存放了权重的数目。D3DFVF_XYZB1,D3DFVF_XYZB2,D3DFVF_XYZB3最后一个权重值是隐式的被计算,而D3DFVF_XYZB4则直接给出b0,b1,b2,b3,不需要额外计算最后一个权重值。D3DFVF_XYZB5给出了b0,b1,b2,b3,b4则是blend被索引顶点blending的矩阵索引。

  当使用定点shader声明来定义顶点时,权重值需要映射到blend weights usage. blend 矩阵索引映射到blend weights matrix indices. 权重b1,b2,b3,b4分别对应D3DTS_WORLD, D3DTS_WORLD1,D3DTS_WORLD2,D3DTS_WORLD3. 在Direct3D里面,每个定点blend矩阵通常包含变形矩阵和世界矩阵。当变形矩阵在世界空间中时,或者当在模型变形后在使用世界变换,两个矩阵可以很容易被组合起来。前者使用WD,后者使用DW.

被索引的顶点blending
   使用定点blending,能够实现很多模型的变形。但是在某种程度上它对于很多复杂的有很多连接点的模型来说,也很难描述的很好,例如人。并且顶点blending最多也只有四种变形,并且在每个DrawPrimitive的调用过程中这几种变形必须被所有的顶点使用。

   使用被索引顶点blending ,也称作矩阵palette蒙皮,矩的索引被保存在每个顶点,它可以从palette选择一个矩阵使用。携带N个权重值的将使用N+1个矩阵和N+1个矩阵索引。这允许每个顶点最多四个矩阵,每个三角形最多12个矩阵。被索引的顶点blending是通过RS Indexed Vertex Blend Enable控制,而顶点blending则是通过D3DRS_VERTEXBLEND. 矩阵palette的最多数目则是通过D3DCAPS9::MaxVertexBlendMatrixIndex.每个顶点支持的最大矩阵数与顶点blending一样。对于FVF顶点buffer,增加一个额外的“权重”和包含D3DFVF_LASTBYTE_UBYTE来指定矩阵索引值。D3DFVF_LASTBYTE_UBYTE指顶点的最后一个权重值为矩阵的索引。这个权重值的每个字节代表一个0到255的索引值。如果使用固定功能的顶点shader,矩阵索引流就被映射到它对应的blend indices usage. 如果可编程的shader,这些值可以用于任何寄存器。在这种情况,矩阵索引被声明称D3DDECLTYPE_D3DCOLOR,他们将被缩小到【0,1】范围,如果矩阵索引被声明为D3DDECLTYPE_SHORT2或者D3DECLTYPE_SHORT4,则不进行缩放。

顶点Tweening
   一些顶点blending效果并不能通过矩阵的顶点混合取得,但可以通过Tweening 方法实现。Tweening 这个名字是来源于电影动画,使用一个animator,绘制起始和结束姿势绘制角色,另外一个animator绘制中间的frame。固定功能的流水线提供了一种类似电影的顶点tweening。当D3DCAPS::VertexProcessingCaps 的D3DVTXPCAPS_TWEENING被设置,则设备就支持Tweening。

   每个顶点定义两个位置(P1, P2),两个法线是可选。在进行世界矩阵之前,P1和P2通过Tweening参数f结合起来。

   P' = ((1-f)P1 + fP2)M

  当RS Vertex Blend为D3DVBF_TWEENING,Tweening被激活。固定功能的顶点Tweening必须使用顶点shader声明。Tweening也需要上次出现在顶点里面的第二个位置组件和法线。顶点声明把P1和P2映射到顶点位置usage,usage索引是0和1.使用顶点Tweening,每个顶点的处理都是独立的,互不关联。但是Tweening不能引入和删除顶点。



顶点雾化

   雾化也称作depth-cueuing,它是根据物体距离照相机的距离来改变物体颜色的一种效果,通过雾化参数将物的颜色混合到顶点的颜色里面。

   C' = fC + (1-f)C

   f是根据物体距离照相机的距离计算出来的。雾化只是改变物体的颜色,但是它不会改变物体的透明度。因此,Alpha在雾化的过程不会改变。

   Direct3D 提供了两种雾化应用,顶点雾化和像素雾化。一次只能使用他们其中的一种,可编程的顶点shader只支持顶点雾化。使用顶点雾化,顶点处理的时候会计算出每个雾化系数,并且这些雾化系数也会被光栅器插值来计算每个像素的雾化系数。使用像素雾化,也称作table fog,光栅器计算出每个像素的雾化系数,根据距其depth来查询一张雾化系数表。Direct3D也允许应用程序计算雾化系数。一旦雾化系数被计算出来,雾化blend过程将会在像素处理的最后一个阶段进行。

   RS Fog Enable控制是否开启雾化处理。雾的颜色通过RS Fog Color指定,只有RGB channel。  RS Fog Vertex mode和RS Fog Table Mode从D3DFOGMODE中取值,并且分别选择顶点和像素雾化。如果D3DCAPS9::RasterCaps的D3DPRASTERCAPS_FOGVERTEX或者D3DPRASTERCAPS_FOGTABLE被设定,则设备支持顶点雾化或者像素雾化。如果D3DCAPS9::LineCaps的D3DLINECAPS_FOG被设置,则雾化只支持点和线primitive.

typedef enum _D3DFOGMOD

{

   D3DFOG_NONE= 0,

  D3DFOG_LINEAR  = 3,

   D3DFOG_EXP = 1,

   D3DFOG_EXP2 = 2

} D3DFOGMODE;

   当深度值在Zs(RS Fog start),Ze(RS Fog end)范围内,线性雾化是一个物体本来颜色到雾颜色线性转化的过程。Exponential 雾化提供能更加平滑的转化。Exponential有一个雾density系数。

   计算雾化距离是计算照相机离深度z的平面的距离,这种点到image中心的距离,其实不是真正的照相机到点的距离。Range-based的雾计算出点到照相机的真实距离。如果D3DCAPS9::RasterCaps的D3DPRASTERCAPS_FOGRANGE,设备支持基于range-based的雾。RS Range Fog Enable控制Range-based的雾化过程。

   使用在雾化计算过程的深度值的坐标空间可能变化。在顶点雾化过程中,雾化距离是在camera空间计算的。深度值的范围[Zn,Zf]他们是近平面和远平面的的位置。在像素雾化过程中,雾化距离是是使用Z buffering在【0,1】范围内,或者在camera空间使用w buffering。如果D3DCAPS9::RasterCaps的D3DPRASTERCAPS_ZFOG或者D3DPRASTERCAPS_WFOG被设置,设备将分别支持z buffer 雾化和w buffer雾化。

   使用固定功能顶点处理,雾化系数存储在specular component的Alpha channel。使用可编程顶点处理,雾化系数可以存储在数据流的任意位置,或者在shader里面通过其他数据计算出来。



Face Culling

   大部分模型都只有大约一般的三角形是可视的。当三角形已经转化到了camera空间,如果三角形可见,它的法向量是指向camera的,则那些背离camera朝向的三角形,它就不可见。我们可以使用这种方法做face culling. 这样就避免不可见的三角形做光栅处理。

   Face culling 使用三角形的面法线。但是Direct3D并不会使用顶点组件的法线来用来做Face culling. Face culling是三角形的属性,并不是三角形模拟的光滑表面的属性。法线是通过三角形的顶点计算的,任何面法线向量都是面里面两个向量的叉集。

   RS Cull mode指定Face culling的模式,它包括D3DCULL_NONE, D3DCULL_CW和D3DCULL_CCW.  D3DCULL_NONE禁止了face culling. D3DCULL_CW cull 那些被转换后的顶点以顺时针出现的三角形,D3DCLL_CCW 则是cull 被转换后的顶点以逆时针出现的三角形。

   D3DCAPS9的PrimitiveMiscCaps的三个bit来指定设备的支持能力。



Clipping

   当primitive在某个边界之外,则这个顶点就会被删除。这个边界可能是render target的边界,也可能是3维空间的任意的面。当一个primitive被删除的时候,边界以内的部分就确定了,就可以渲染这个部分了。剪裁面和模型的相交,可以产生新的顶点。新的顶点就是剪裁面和物体模型几何体相交的点。当线和三角形被剪裁掉,新的顶点产生。面的法线,纹理坐标,diffuse , specular可以通过插值取得。

   Direct3D 提供了集中面剪裁的方式,一种是用户自定义的面,一种是通过视截头体。视截头体提供的是一个照相机显示的空间。Guard band剪裁则类时与视截体空间剪裁,但是它在某些设备能提供更好的性能优化。还有一些frame buffer的操作,如Z test, alpha test, stencil test和一些像素shader,也可以被认为是剪裁操作。但是,这些操作的对象是像素,而不是集合体。

用户clip 面
  用户自定义的clip面最多提供6个面,每个面有一个组系数定义。它可以用于固定功能流水线和可编程顶点处理。clip面的序号是0到5。 可以通过GetClipPlane和SetClipPlane. RS Clip plane enable 控制是否使用这个clipping.

视截体clipping
  如果RS Clipping为真,则视截体clipping被使用。当视截体clipping或者用户定义面被clipping,设备返回clip状态的被剪裁的primitives的信息。当primitive被处理,并且它与剪裁面相交,它将会在clip状态里面设置一个位。如果一个primitive 完全在视截体的外面,它将被culled. Clip status使用GetClipStatus和SetClipStatus管理。这个状态本身是保存在D3DCLIPSTATUS9里面的。

typedef struct _D3DCLIPSTATUS9

{

   DWORD ClipUnion;

   DWORD ClipIntersection;

} D3DCLIPSTATUS9

Guard Band clipping
  它类似与视截体clipping,它是基于像素操作,不是基于模型集合。Guard band是一个覆盖当前视口的区域。任何在当前视口以外但是guard band以内的primitive都会被删除。

  如果能保证所有的几何体都在guard band内部,昂贵的几何clipping和插值将会被忽略,如果不能保证,视截体clipping必须要使用。Guard band 的范围 GuardBandLeft, GuardBandRight, GuardBandBotttom,GuardBandTop,它们的值都在屏幕空间。



Screen Space and ViewPort

    齐次除法和视口应用位于顶点和除法之间。在world ,view , projection转换后,顶点已经位于标准视图空间的齐次点(x,y,z,w)。在这个空间把所有的点除以w将会得到一个笛卡尔坐标系的标准空间。

    对于已经处于正交的笛卡尔坐标系统里面的顶点,他们已经准备映射到screen空间了。这种映射是被viewport定义的。它能把场景只渲染在render target的一个子区域,或者深度buffer一段小区域,或者两则结合。视口是屏幕空间的一个矩形,他的深度区间范围是在【0,1】。默认的视口占据整个render target区域,整个深度区域。

viewport 映射可以用下面的方程式表示:

M = S(w/2, h/2,Zf - Zn) T(Xs,Ys,Zn)

这个转换把x 从【0,1】映射到[Xs,Xs+w],y 从【0,1】映射到【Ys,Ys+h】,z 从【0,1】到[Zn, Zf]。只有位置组件受它的影响。

typedef struct _D3DVIEWPORT9

{

   DWORD X;// XY是左上的坐标

   DWORD Y;

   DOWRD Width;

   DWORD Height;

   float MinZ;

   float MaxZ;
}D3DVIEWPORT9;

Z的范围是让应用程序使用depth buffer执行一些可视效果检测。

一旦视口处理结束,顶点处理就结束了。

ic.expert 发表于 2008-6-11 07:23

至今,我们已经描述了顶点处理,顶点处理只是针对顶点的位置和法线。这一章将讲述光照和材质,它是用来决定物体的外表。 光照和材质是用来决定顶点的颜色的过程。

  理解光照的计算只是在物体的顶点上进行计算是很重要。这样,如果物体的顶点描述越粗糙,则计算出来来的光则也是越粗糙。一个通常的错误是创建一个很大的立方体,并且把一个光源放置在离其中一个面的中心的很近的位置。通常的解决方案是为立方体的面提供更多的顶点,来提高场景里面物体的光照采样密度,或者使用另外一种方法光照场景,如lightmaps使用纹理化或者在像素shader使用每像素光照。

  光照一个很复杂的过程,牵涉到很多渲染状态以及其他参数。光是从光源散发出来,并且经过面反射都在进入人的眼睛。Direct3D中的光照计算是在本地计算。也是说,每个顶点的光照计算是独立的。这意味着Direct3D并不直接计算阴影,因为每个顶点的光照计算是独立于其他它表面,即使这些面有可能遮挡了光。类似的,Direct3D并不会直接计算从另外一个表面反射到一个表面的光。 渲染技术如光线跟踪和光线传递都被称作global照明技术,因为他们是计算整个场景里面的光,这些光包括间接从一个表面反射到人的眼睛,阴影,内部反射以及其他全局光照效果和直接从面反射到人眼睛的光。以前仿照物理光照对于交互式的应用程序来说是一个很昂贵的计算过程。然而近来情况有所改善,大多数应用程序使用同样的近似计算来计算场景里面反射到人眼睛里面的光。

   在计算机图形里面,我们通常处理三种不同类型的反射光:ambient, diffuse 和 spcullar。 ambient 能反射到场景里面的所有方向,它的光源是没有方向的,并且可以照亮场景里面所有的物体。Diffuse 反射是基于表面法线的,但是与眼睛的位置没有关系。 specular 反射不仅与表面法线,光的方向有关,而且与眼睛的位置也有光。

   在进入Direct3D的光照计算之前,我们将告诉你怎样把没有光照过的顶点(lit vertices)提供给光栅器。接下来,我们将看怎么定义场景里面的光源。再接下来,我们将看Direct3D材质的属性,以及他们怎么样与场景里面的光进行交互。最后,我们将看看怎么从光的定义和场景的定义来计算每个顶点的光。

Tranformed Vertices

   通过为每个顶点提供diffuse和specular,应用程序可以执行它的光照计算和提供被lit的顶点。如果应用程序有很特殊的光照计算,你可以单独计算他们,然后直接修改他们的diffuse和specular。当你的目的只是绘制一个特定的颜色或者颜色渐变,而不需要必要的光照计算,lit vertices也是能用上的。渐变效果可以通过提供给一个primitive的不同颜色来实现它。当光被忽略使,Direct3D使用不透明的白色作为diffuse,透明和黑色作为specular颜色。

Lightting Calculations

[b][size=2][img]http://119.img.pp.sohu.com/images/blog/2007/11/19/15/22/116f291a8c5.jpg[/img][/size][/b]



计算diffuse和specular光照的总体流程如上图8.1所示。RS Lighting 可以用来enable 和 diable 光照计算。当这个渲染状态设置为FALSE,没有光照计算被执行,diffuse和specular来自于他们本来的顶点组件。当RS Lighting被设置为TRUE, Direct3D为每个顶点计算出光线的总量。一个顶点总的反射量被计算出来,是要根据当前材质属性,enabled光的集合,顶点的位置,面法线,diffuse 和specular颜色组件。所有顶点都有一个位置组件,光照需要顶点必须包含一个面法线组件。diffuse 和 specular 是可选的。如果你的模型没有面法线,但是你又需要计算光照,这样,你必须计算顶点的面法线。计算面法线的最好的方法是直接在创建顶点数据的过程里面创建,D3DXComputeNormals可以用于计算一个mesh的顶点法线。

  为了从一个三角形里面计算顶点的面法线,你能通过向量的差积来计算。顶点法线则是取包含这个顶点的所有面的法线的平均。为了正确的计算光照,顶点法线向量应该是单位向量。在chapter 6,我们已经谈到,缩放变换会改变面法线的长度。处理这个情况有好几种方法:在场景里面不要使用缩放变换,或者光照之前重新归一化法线向量。

   在固定功能的光照下,RS Normalize Normals将控制法线的重新归一化,这是处理缩放问题的最容易的方法。然而,重新归一化法线需要消耗,你可能使用在建模的时候避免缩放。

   Direct3D计算光照只是使用了顶点的本地信息,并不计算阴影和其他照明效果。光被建模成不同类型反射光的总和。这些光的属性不仅依靠顶点材质的反射属性,也依靠光源的颜色。

   Light = Ambient + Diffuse + Specular + Emissive

Ambient光是场景里面间接的光,可以被视为背景光。

Diffuse光: 粗糙的表面使入射的光反射,这样导致了diffuse反射。Direct3D使用朗伯cos法则。反射的强度取决于光和面法线的夹角,而与视线的方向无关。

Specular光:平滑的面以一种向镜子似的方式反射入射的光线,这种方式就是specular反射。Direct3D计算specular使用Blinn-Phong的模型,使平滑的面看起来就像被刨光的金属,玻璃和塑料。反射的强度取决于面法线,光线的方向以及视线的方向。

Emissive:这根本不是一种反射,它实际上是表面本身发出来的光,而且也是总反射量的一部分。虽然它能发光,但它不能算成是Direct3D的光源。例如,一个区域光源可能使用一个只携带反射组件的几何体来做它的模型。

Surface Material Properties

面的反射属性可能通过设备的状态以及每顶点的数据来设置。这些属性定义了物体面表怎么反射ambient, diffuse和specular光源。SetMaterial和GetMaterial用来管理material的属性,材质的属性是通过D3DMATERIAL9结构体来定义的。

HRESULT GetMaterial(D3DMATERIAL9* value);

HRESULT SetMaterial(const D3DMATERIAL9* value);

typedef struct _D3DMATERIAL9

{

   D3DCOLORVALUE Diffuse;

   D3DCOLORVALUE Ambient;

   D3DCOLORVALUE Specular;

   D3DCOLORVALUE Emissive;

   float Power;

} D3DMATERIAL9;

这个结构的diffuse, ambient, specular和emissive都是代表材质的反射属性的,这里每个成成员都是一个D3DCOLORVALUE的4浮点数,每个浮点数都代表被材质反射的部分光源。例如,diffuse值是{0.5,0.5,0.5,0.5}代表材质将反射50%的diffuse光。如果一个物体不是光源,场景中所有光的反射系数的和将等于1,符合能量守恒定律。Power成员是单个浮点数,它表示面上镜面高光的延伸度。越光滑的面,它的光泽度越高,power值就也高。

  RS Color Vertex 决定了材质属性的源,当它FALSE的时候,材质属性直接从当前的材质取,当它为TRUE时,材质属性是基于RS Ambient Material Source, RS Diffuse Material Source , RS Specular Material Source 和 RS Emissive Material Source.这些材质源属渲染状态都是D3DMATERIALCOLORSOURCE 类型。D3DMCS_COLOR1和D3DMCS_COLOR2分贝从顶点的diffuse和specular颜色组件里面选择材质属性。

typedef enum _D3DMATERIALCOLORSOURCE

{

   D3DMCS_MATERIAL = 0,

   D3DMCS_COLOR1 = 1,

   D3DMCS_COLOR2 = 2
} D3DMATERIALCOLORSOURCE

如果D3DCAPS9::VertexProcessingCaps的D3DVTXPCAPS_MATERIALSOURCE7被设置,设备支持材质源的渲染状态。否则,使用下面的表里的默认值。




Light Source

  在Direct3D的光照计算中,Ambient光总是被作为所有的间接光效果的源。RS Ambient定义了场景中环境光的强度,其值是D3DCOLOR值。

  对于其他光源,Direct3D维护D3DLIGHT9结构的数组,每一个都定义了一个光源。应用程序能够修改这个数组里面的任何元素,也能够在场景渲染的时候enable和disable任何一个元素。Direct3D是通过GetLight和SetLight函数来管理这个数组。GetLightEnable可以返回某个光源的状态。 LightEnable用来控制光源的状态。

typedef struct _D3DLIGHT9

{

  D3DLIGHTTYPE Type;

  D3DCOLORVALUE  Diffuse;

  D3DCOLORVALUE  Specular;

  D3DCOLORVALUE  Ambient;

  D3DVECTOR      Position;

  D3DVECTOR   Direction;

  float          range;

  float          falloff;

  float          Attenuation0;

  float          Attenuation1;

  float          Attenuation2;

  float          Theta;

  float          Phi;
} D3DLIGHT9;

typedef enum _D3DLIGHTTYPE

{

   D3DLIGHT_DIRECTIONAL = 3,

   D3DLIGHT_POINT = 1,

  D3DLIGHT_SPOT   = 2
} D3DLIGHTTYPE;

Direct3D 支持三种不同类型的光源:directional, point, spot. 并不是D3DLIGHT9的所有结构成员都可以用于所有类型的光源。Type, ambient,diffuse,specular成员可以用于所有的光源。如下图,总结了不同类型的光可以使用的成员。Position,Direction和Range都是定义在世界坐标系下的。



应用程序当然可以随意定义自己想要的光,只要在系统存储允许的条件下。但是,硬件顶点处理只支持有限的光,一般情况下是8。 D3DCAPS9::MAXActiveLights 给出了渲染时最大能使用的光的数量。处理有限光源最通常的策略是尽量激活那些对primitive影响最大的光。 如果VertexProcessingCAPS设置了D3DVTXPCAPS_DIRECTIONALLIGHTS,则设备支持directional光。如果VertexProcessingCAPS设置了D3DVTXPCAPS_POSITIONALLIGHTS,设备支持point和spot光。





Directional Lights
跟其他光源类似,directional light(Ld) 也指定了它的ambient(Al), diffuse(Dl), specular(Sl). 并且还指定了它的方向向量(Dl

Ld = {Al,Dl,Sl,Dl};

例如,太阳距离地球很远,所有它的光线感觉都是平行的,所以,太阳光线可视为平行的directional光。

Point Lights
点光源Lp可以向任何方向发射光线,它的发射点是Pl。 当点光源距离物体很远的时候,可以视作为directional light. 当点光源距离物体很近的时候,它不同的部分接收光线的角度将不一样。

Spot Lights
Spot light将从某个位置向某个方向发射一柱光线。

Light Attenuation
Spot light 和 point light都会随着距离的增加光线强度减弱。减弱度(al)可以通过一个[0,1]的系数表示。 Directional light不会减弱,al =1;point light是随着它距离顶点的范围来减弱的,al = ar; Spot light的减弱与距离和光锥的falloff有关, al = aras。

  Ar是根据顶点到光源的距离来计算出来的,当距离超过了光源的范围,则减弱度就等于0;当距离在光源的范围以内,减弱度的计算是与三个减弱系数有关,a0l,a1l,a2l。 公式如下:



spot light光锥的减弱度与它的内椎的角度,外锥的角度以及fallof都有关。它的公司如下:



   

  



The Illumination Model

ic.expert 发表于 2008-6-11 07:25

Viewing and Projection

Viewing and Projection

  Viewing和projection将一个三维的场景映射到render target的两维空间。Viewing 就是将一个虚拟的照相机放置和定位在场景里面。Projection就是将三维场景转换成一个两位面。

   Direct3D 应用程序使用了平面几何projection.  物体的平面几何投影由线组成,称作projector,它穿越物体的每个顶点以及投影仪的中心。当投影仪中心是有限的时候,每条从中心发起的projectors,都会产生透视projection. 当投影仪中心无限大时,所有的projector则是平行的,这样就产生了平行投影。被投影的物体将会落在投影平面上。每个被被映射的顶点将落在projector和投影平面的交点上。

   平面几何投影在艺术和工程制图上有很久的历史。透视projection产生的图片将与人眼所看到的物体更加相似,并且在外观上更加真实。然而,它扭曲了不平行于projection平面的线以及线之间的夹角。这种扭曲使越靠近相机的物体越大。这种扭曲就是非统一的透视缩短。

   平行projection能够保留线的相对长度和夹角。这种属性将使它能够用在工程制图。一个物体的多个平行投影将能准确的描述一个物体的形状,并且能直接从投影图里面标注机械制造尺寸。

   对于在三跟垂直轴里面的物体,投影将能够进一步分类。Projector的朝向,投影面以及一个物体主要坐标轴定义了这个分类。平行投影还能够被分成垂直和倾斜投影。垂直平行投影还可以分成axonometric ,isometric ,dimetric,trimetric。 Cavalier和cabinet则是通常的倾斜投影。 透视投影将被分成1 point, 2 point和3 point的透视投影。

   Direct3D将使用同次转换矩阵来定义平面几何投影。其他的投影也是可能的,但是Direct3D可能并不支持。场景里面照相机的运动能通过改变照相机的参数来实现,照相机位置和朝向的变化,是通过改变view转换矩阵。虚拟相机显示出来的那部分场景是通过改变投影转换矩阵来实现的。

  

Viewing Through a virtual camera

   Direct3D使用一个虚拟相机模型来构造场景的渲染,相机概念上是落在世界坐标系的。相机指向一个特定的方向,称作direction of gaze.  场景里面相机的位置和朝向定义了一个相对世界坐标的新的坐标系。这个坐标系被称作camera空间,或者eye空间,就好像通过一只眼睛去看。

  世界转换后,所有的模型都在世界坐标系里面了。View 转换再将世界空间装换成camera 空间。camera空间将照相机放在原点,y向上增加,x 向右增加,z将指向视线的方向。

  在左手坐标系里面,z 值将随着物体离相机的距离增加,增加x,就向右边移动,增加y,将向上移动。注意:在camera空间,y是向上增加的,在屏幕空间,y是向下增加的。对Direct3D来说,照相机模型只是一个转换矩阵,对一个应用程序,更加容易定用参数来定义一个照相机模型,然后从参数里面产生转换矩阵。

  简单的照相机模型是把照相机放在C(Xc,Yc,Zc),照相机指向场景里面的A(Xa,Ya,Za)。  相机朝向就是g = (Xa- Xc, Ya-Yc, Zz-Zc).  相机并且定义了一个向上的向量u. 使用这个模型,我们能定义一个转换矩阵V。

V = T(-C)Rg->zRz(@).  其中Rg->z是一个围绕Z轴的旋转矩阵。

  

平面几何投影

一个平面几何投影主要是通过它的投影面和投影仪的中心来确定的。

  

投影转换

在计算机图形学,我们对场景的动态视图非常感兴趣,主要是因为场景里面的物体和虚拟相机可以同时很自由的运动。在这种情况下,不同类别的平行投影或者透视投影的之间区别不是那么重要,因为场景里面的物体可能在场景里面的任意位置。

投影矩阵

至今,我们所接触过的矩阵,还未使用矩阵最右边的那一栏。这一栏是用在透视投影的。当使用这一栏的时候,计算出来的w将不是1,为了将他转换到笛卡尔坐标,我们需要除以w,这样经过projection转换后,顶点的笛卡尔坐标也相应改变了。

ic.expert 发表于 2008-6-11 07:26

Rasterization and Shading

Rasterization and Shading

   光栅化,我们可以将几何图形转化成屏幕上的像素。Direct3D使用scanline的渲染来产生像素。 当顶点处理结束之后,所有的primitives将被转化到screen space,在屏幕空间一个单位就是像素。点,线,三角形通过一组光栅规则被转化成像素。光栅规则定义了一套统一的法则来产生像素。

   光栅得到的像素一般会携带深度值,一个RGBA diffuse 颜色,一个RGB specular 颜色,一个雾化系数和一组或者多组纹理坐标。这些值都会被传给流水线的下一个阶段像素的处理,然后注入到render target。



Shading 和 Filling
   Shading 指Direct3D 怎么样为像素计算颜色,Filling指怎样为primitive的内部产生像素。

   RS Shade mode 定义三种shading 的方法,包括Flat, Gouraud, Phong。 Flat shading 在每个Primitive的内部选择一种颜色,这个颜色就是这个primitive 的第一个顶点的颜色信息。Gouraud shading 采取了线性插值的方法为primitive的内部像素产生颜色。Phong shading 现在不被Direct3D支持,这里就不仔细谈了。

   RS Fill Mode 定义filling的方法, 它包括point, wireframe, solid。 Point filling只是填充primitive的顶点,wire frame 只取每条边的像素。 Soild fill mode 将填充primitive内部。

   Shading 和 Filling 不光只是插值diffuse color,direct3D可以对顶点所有的信息插值,包括顶点的透明度(alpha), specular color, depth, 雾化系数,纹理坐标。

   D3DCAPS9:: ShadeCaps 定义了设备shading 的能力,所有的设备都支持flat shading。 ShadeCaps的每个位都定义了顶点颜色组件的,如D3DPSHADECAPS_COLORGOURAUDRGB,D3DPSHADECAPS_SPECULARGOURAUDRGB 等等。



像素的产生
   利用扫描线,我们能够很自然的判断一个像素的中心是否在三角形primitive的内部,这样就很容易判断这个像素是否应该产生。但是,当一个像素被多个primitive所共享的时候,为了让每个像素只产生一次。这个时候,就引入一个left-filling 的法则。 left filling就是对扫描线的span的左端是取, 右端的像素舍去。

   处理线primitive, 一般有两种方法,需要取决于线段的倾斜度。



像素数据
   扫描线rendering 能够确定每个像素的x, y坐标,其他值一般都是通过插值来获取。

ic.expert 发表于 2008-6-11 07:26

Basic Texturing(基本纹理技术)

Basic Texturing(基本纹理技术)

这几天一直都在看baisc texturing ,这一节写的是太精彩了,原本只是了解大致原理,细节基本很模糊的我,居然对texturing有了比较清晰的理解。为了帮助我更好的理解,我有必要写点读书笔记。
我们知道,通过光照计算和设置材质属性,我们可以为每个顶点计算一个颜色值;利用vetex shader,我们可以扩展固定流水线的功能,并且可以每个顶点随意计算其颜色值。利用这两种方法建模的颜色值,能够通过光栅器插值到屏幕中对应的像素中。但是,仅仅通过这两种方法,很难为物体做一个非常精细的“面孔”,所以就引入了Texturing 技术。

    纹理化技术,能把一张很复杂的图片绘制在物体的表面,它也可以允许我们为每个像素建立一张漫反射表,其索引可以通过每个对应被插值的顶点的纹理坐标计算得到。纹理的每个元素就好比对应一个像素,都是一个color。纹理坐标一般是有一维,两维,三维和四维。把查询漫反射表的结果与光栅化的结果混合起来,能产生一个漫反射颜色,一起送给frame buffer。当然,它也能支持multi texturing,每个texturing stage 的output 也就是下一个stage的input。

    固定功能的像素处理通过multi texturing 各个stage来产生丰富的颜色。可编程的pixel shader,当然就非常灵活的产生颜色了。

    现在主要有三种纹理资源,矩形纹理,立方体纹理以及大体积纹理(volume)。在这一章讨论了纹理资源的接口以及纹理资源的管理,并且详细的解释了纹理坐标的处理(非常精彩)。

   纹理坐标是由vertex buffer提供的或者由direct3d自动产生。在提交到teuring stage之前,纹理坐标能通过矩阵进行变换。不过,一旦纹理坐标绑定到一个源像素,这个纹理就会与一个stage绑定。这一章详细讲解了纹理采样技术,使用不同的纹理采样方法,将会得到不同的效果。



纹理坐标的处理

  理论上纹理坐标值范围在【0,1】,如线性纹理和矩形纹理。但是立方体纹理有点不一样,它的值范围是在【-1,1】,而且它的每个面上的纹理值都有一个值是-1或者1,其立方体的中心是其(0,0,0)位置。

 在顶点被转化到屏幕空间后,光栅将其纹理进行插值,这样就可以计算出来一个像素上纹理坐标的变化值。如果把这个变化值乘以纹理的分辨率,就可以得到一个像素对应多少个纹理像素。利用这个信息,我们就可以选择mipmap level。

   但是很多时候,我们需要直接将纹理像素值映射到屏幕中去,如用户界面。为了取得一对一的效果,primitive的位置相对屏幕空间位置得偏移0.5,这是因为三角形的光栅化规则,整数坐标位置是像素的中心位置,这个偏移可以使三角形位置正好对应于像素位置,这种效果可以很容易通过变换后顶点实现,因为它们的坐标已经是屏幕空间的坐标了。详细解释可以参考 DirectX SDK document ("Mapping Texels to Pixels")。

   顶点可以携带多组纹理坐标,默认第一组对应texturing的第一个阶段。TSS Tex Coord Index可以用来控制哪个stage 使用哪组坐标。

   Direct3D也会自动为顶点产生纹理坐标。

   纹理坐标也可以通过变换矩阵进行转换,转换矩阵一般是一个4*4矩阵的左上方的子矩阵,它的维度取决于纹理的维度和TSS Texture Transform Flag。

纹理寻址

   虽然纹理坐标一般是在【0,1】区间,但是你同样可以使用这个范围之外的纹理值,如(2,3)。D3DCAPS的MaxTextureRepeat 可以设置设置纹理区间的范围,比如如果这个值是5,设备支持的纹理坐标的区间就是【-5,5】。如果TextureCaps 的D3DTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE没有被设置,在进行插值之前设备将纹理坐标放大到纹理的实际尺寸,MaxTexureRepeat 实际上指定了坐标放大后能被设备支持的范围。

在单位区间之外的纹理坐标处理方式是通过纹理的寻址模式设置的,寻址模式包括Wrap, Mirror,Clamp,Border,MirrorOnce。默认是Wrap模式。

纹理 Wrapping

纹理wrapping是用于调节光栅器对纹理坐标进行插值的方式,注意千万不要将纹理的Wrapping 与寻址模式wrap 混为一谈,纹理wrapping 是通过每个stage的 render state的 RS wrap 0到RS wrap 15。启用wraping,光栅器插值就会采用纹理空间两个个坐标的最短路径的方法来实现,这里纹理空间被视为一个无限重复的网格。

纹理采样

纹理采样就是为每个像素选取纹理像素的操作。如果在一个像素对应的纹理空间尺寸是2的幂,并且也有一个miplevel 的纹理与它对应,这个时候采样能直接从这个miplevel的纹理采样。但是大多数时候,一个像素所对应的纹理像素空间不是2的幂。Direct3D 提供了3种纹理stage state来控制纹理的采样:SS Min Filter(纹理被缩小), SS max Filter(纹理被放大), SS Mip Filter(纹理被Mipmap)。这三种状态都可取值于D3DTEXTUREFILTERTYPE(D3DTEXF_NONE, D3DTEXF_POINT, D3DTEXF_LINEAR, D3DTEXF_ANISOTROPIC, D3DTEXF_FLATCUBIC, D3DTEXF_GAUSSIANCUBIC)。 在缩小化过程中,SS Min Filter将指定几个纹理像素怎么样合成一个像素值。 在放大过程中,SS Max Filter 怎么样插值纹理样例。除了只从一个纹理的mip level 取样外,我们可以从两个相邻的mip level取样,用SS Mip Filter来合成它们,用这种方法,当基本元素在场景里面移动或者纹理缩小量和放大量变化的时候,能产生比较平滑的过渡。



并不是所有的D3DTEXTUREFILTERTYPE的值都能应用到这三种状态。 D3DTEXF_POINT 和 D3DTEXF_LINEAR 能所有这三种状态, D3DTEXF_NONE 能被用于禁止mipmap。 D3DTEXF_ANISOTROPIC能被用于放大与缩小,然而,D3DTEXF_PYRAMIDALQUAD和D3DTEXF_GAUSSIANQUAD只能用户放大过滤。

点过滤选择离像素对应的纹理坐标最近的纹理像素或者mip map level。线性过滤,当用于放大与缩小的时候,选择相邻的四个纹理像素,用纹理坐标进行线性插值来获得,当用于mip level的时候,选择相邻的两个mip level,从他们之间采取线性插值采样。在线性mip level 插值的时候,先分别通过缩小和放大来取得采得连个mip level的样。 立方体过滤器提供更加精确的采样,他们通过3*3 相邻像素进行线性插值。

Anisotropic过滤可以用于非正方形区域的纹理采样。

纹理阶段处理

当一个纹理被地址化和被采样以后,纹理像素值就会产生。这个纹理像素值可以结合其他的input在一个纹理stage中来为像素产生一个新的diffuse颜色值。这个diffuse 能被传递到下一个Stage进行进一步的处理。每一个纹理stage处理包括一个RGB单元处理和一个alpha单元处理。这两个单元处理在direct3D中是独立的,串联起来的。你要分别设置这两个单元的状态。每个单元处理能从6个输入通道中选择3个作为它的参数。这两个单元处理后的结果也能够被送到两种可能目的地(Current, Temp)之一做进一步的处理。

6个输入通道包括:来自光栅器的被插值的diffuse , 来自光栅器的被插值的specular, 当前纹理取样的纹理像素,RS Texture Factor 值,前一个纹理Stage的结果和临时纹理stage寄存器里面的内容。这里的每个输入都有一个值与之对应。D3DTA_SELECTMASK可以用来选择这6个输入通道。

RGB 和alpha处理单元都能够计算多达3个参数的函数。每个参数都绑定到一个输入通道。 TSS Color Arg 0, TSS Color Arg1, TSS Color Arg2, TSS Alpha Arg 0 , TSS Alpha Arg 1, TSS Alpha Arg 2,分别代表各个参数。 TSS Color Op 和 TSS Alpha Op表示对应的函数。TSS Result Arg 指定输出结果。

ic.expert 发表于 2008-6-11 07:27

The Frame Buffer

The Frame Buffer


  光栅器将图形primitives转化成像素流,这些像素将与Frame buffer里的目的像素结合在一起。Frame buffer这个术语起源于早期的光栅图形,它就是一块能容纳一张图片的存储空间。随着计算机图形的发展,这个术语不仅包含图像数据,而且也包含一些图形渲染所需要的辅助信息。在Direct3D中,Frame Buffer 不仅包括当前所选择的render target的表面,而且也包含depth/stencil面。如果使用了多采样,需要更多的内存。

在光栅化以后,每个源像素都包含一个RGB颜色,一个Alpha channel里的透明度值,Z 值里面的depth值。Z值是由光栅器产生的固定精度值。在像素被结合到render target之前,可以进行雾化处理。雾化以后或者stenciling操作以后,基于每个像素的透明度值以及场景里面的深度,它们有可能被拒绝。Stenciling操作可以让Frame Buffer的任意区域的像素被masked away. 与alpha和depth不同的是,与源像素相关的stencil值是从一个render state获得的。像素经过alpha, depth和stencil测试以后,它将被结合到render target里面去。源像素深度值只用于可视计算,深度test以后,就没有与深度值相关的进一步处理被执行了。最后,当render target包含一个RGB颜色的reduced dynamic range,源像素执行抖动处理来减少任何banding artifacts.  抖动处理执行完毕后,源像素就可以写到render target,以及相关的depth/stencil表面。写操作是通过一组write mask控制的。

Multisampling 为Render Target每个像素提供了提供了多个颜色,深度,stencil值。多个颜色值结合在一起,最终产生出一副图片用于video scan out. 额外的depth和stencil值用来取得合适视觉和steniling效果。Multisampling可以用于antialiasing, depth of field, motion blur 和其他效果。 在渲染的过程中,你可以控制多采样render target的哪个采样将用于detination. 有的效果,是在一个pass里面渲染到与像素有关的所有的samples,然而有的像素在每个pass只渲染到这些采样里面的一部分。

Fog Blending
在Frame buffer处理之前,雾的颜色混合到源像素的RGB颜色里面。注意:雾只是影响源像素的颜色,并没有影响其Transparency.

Alpha Test
在雾化以后,像素进行基于transparency值的rejection测试。如果alpha Test enable,源像素的alpha值和由render state提供的固定alpha值将进行比较。如果测试失败,这个像素就会被删除掉。

你能完全reject透明的像素,这些像素在最后的渲染状态中将不会产生任何效果。在大量的像素是完全透明的情况下,这种方法将会减少Frame Buffer里面的工作并且还能提高渲染的效率。

  Alpha Test比较可以通过如下等式: As <op> Ar。

  在这里As是源像素的alpha值, Ar是参考alpha值。<op>是比较函数。 小心不要混淆Alpha Test和Alpha blending,Alpha Test基于源像素的alpha值做reject操作,Alpha blending使用源像素的alpha值将它与Frame buffer结合起来。RS Alpha Blend Enable 控制是否使用alpha test. RS Alpha Func 指定他们的比较函数, RS Alpha Ref指定参考alpha值,它是一个在[0,255]的DWORD值,0对应全透明,255对应全模糊。

typedef enum _D3DCMPFUNC

{

   D3DCMP_NEVER = 1,

   D3DCMP_LESS = 2,

   D3DCMP_EQUAL = 3,

   D3DCMP_LESSEQUAL = 4,

   D3DCMP_GREATER = 5,

   D3DCMP_NOTEQUAL= 6,

  D3DCMP_GREATEREQUAL = 7,

   D3DCMP_ALWAYS = 8

} D3DCMPFUNC;

  D3DCMPFUNC 对应常用的数学比较操作,但是D3DCMP_NEVER和D3DCMP_ALWAYS除外。D3DCMP_NEVER将总是失败,D3DCMP_ALWAYS将总是成功。D3DCAPS9::AlphaCmpCaps 描述了设备支持的比较函数,它的每个位对应一个比较函数。

The Z Buffer and Visiblity
Direct3D的可视性通常是由Z buffer决定的。也有其他可视性算法。但是Z Buffer在硬件上容易实现,并且是最通用的可视性算法。当设备创建的时候设置了AutoDepthStencil和AutoDepthStencilFormat,你能创建一个depth/stencil buffer。 如果你使用CreateRenderTarget创建额外的Render Target或者其他swap chains,那么你也可以为这些Render target创建它们的depth/stencil buffer。使用CreateDepthStencilSurface和SetRenderTarget可以到达这个目的。GetDepthStencilSurface返回当前render target的depth/stencil surface.

  Z buffer算法为每个像素保存一个深度值。 当场景被渲染的时候,Z buffer初始化为一个最远的Z 值。 随着源像素经过光栅化产生后,每个像素就有一个Z value。 源像素的Z值与Z buffer的相同位置像素的Z value进行比较。如果源像素的Z值比Z buffer里面的值更加靠近观察者,这个源像素将会被存储在Z buffer里面。 通过这样一种对每个像素的深度值的排序方法实现了可视性检测。

  RS Z Enbale控制是否Enable Z buffer test.

typedef enum _D3DZBUFFERTYPE

{

   D3DZB_FALSE = 0,

   D3DZB_TRUE = 1, //选择 z buffer

   D3DZB_USEW = 2 // 选择w buffering
}D3DZBUFFERTYPE;

Z buffer test的执行方程是:

   Zs <op>  Zd

Zs 是源像素的Z值, Zd是存储在Render Target里面的深度buffer里面的Z值。op是比较函数。

W Buffering
  当使用透视投影的时候,Z buffer对于深度的低位可能是个问题。透视投影使大部分Z buffer的值用于场景中靠近相机的部分。这对于距离相机远的primitive产生了视觉上的artifacts。 你也许可以通过将视截体的近平面和远平面拉近来降低这种影响,但是也不能消除这种问题。另外一种方法就是使用W buffer,W buffer存储的值是齐次深度w的倒数。在这种情况下,w buffer的值甚至会超出视截体的范围。对于一个16位的深度buffer,W buffer 能提高场景里面的可视性的分辨率。

插一段人家写的z buffer 和w buffer的比较:

z-buffering 和 w-buffering
z-buffering 和 w-buffering都表示深度值。几乎所有的3-D加速卡都支持z-buffering,这样就使得z-buffers成为现在最常用的深度缓冲类型。但是,z-buffers也有它本身的缺陷。由于它所使用的数学方法,使得一个z-buffer中产生的z值在它允许的范围内[0.0,1.0]并不是均匀分布的。特别是靠近剪切面与远离剪切面处的比例,更是影响了z值的均匀分布。如果远平面的距离与近平面的距离的比为100,那么导致深度缓冲区的90%被花费在前10%的场景深度。一般的娱乐程序或视觉仿真程序要求远近平面的距离比在1000到10000之间。比值为1000时,范围的98%花费在深度的前2%上,并且随着比值的增大,分布将变得更糟。这样可能会使较远距离的物体上的隐藏表面产生失真,特别是当使用16-bit深度缓冲时。


  w深度缓冲比z-buffer能更均匀地在远近剪切面之间进行分配。使用它的最大好处就是远近剪切面的距离比不再是关键因素。这样就允许程序使用更大的距离范围,同时仍能保持深度缓冲与观察位置间的精确联系。w-buffer也不是完美的,有时也会使近处物体的隐藏表面产生失真。w-buffer的另一个缺点是不能得到硬件的广泛支持。


Visibility of Transparent Primitives
  Z Buffer的可视性算法实际上就找到离相机最近的一种排序算法,你可能会忽略它的顺序。当primitive完全模糊,最近的pimitive将会出现在最后的渲染图像里面。然而,当primitive半透明,就会显示它后面的像素。为了能够正确的渲染,我们必须维护一个back-to-front的list。 但是这种渲染方式,不能只是通过简单的Z buffer的方式能实现的。

   首先对于完全模糊的物体使用标准的Z buffer可视性算法。接下来,对于场景里面的透明物体,基于他们的bouding box back-to-front排序。然后,对Z buffer的写入被disable,透明物体按照back-to-front的顺序一一被绘制出来。虽然Z buffer写入被禁止,但是Z test还是需要enable,透明的物体依然可以被模糊的物体所遮挡,但是它们还是可以渗透(interpenetrate)到其他透明物体。按照back-to-front的顺序绘制透明的物体,只要物体不相互渗透,它们还是可以遮挡透明物体。对于相互渗透物体的完美的渲染,你应该排序他们的每个三角形,保证每个三角形back-to-front的顺序。然而,取得这个额外的视觉效果往往需要很高的计算成本。

Biasing Primitive
  有时候你可能需要绘制两个在数学上是同一个位置的primitive。 比如,你可能想要绘制一个在立方体某一边上的primitive。 解决这种问题你可能通过建模的方法,连接重合的三角形。 另外一个更加简单的办法是使用Z buffer增加一个固定的Z值使它显示在在立方体的前面(front)。

  RSZBias代表增加的Z值,他一般在【0,16】范围以内。

  如果D3DCAPS9::RASTERCAPS的DDPRasterCapsZBias被设置,设置将支持这种处理。

Filling and Reading the Z Buffer
  可能有很多次你会往Z buffer里面写值和读取Z buffer的数据。然而,如果你不使用能够锁住的Z buffer格式,这可能不会直接操作。

Stencil Test
  stencil test需要与z buffer联合起来使用。stencil buffer基于depth test的结果和stencil test的结果可以对源像素进行任意的rejection. stencil test把当前的stecil 值与其参考值进行比较。 两个stencil值使用当前的stencil掩码进行And计算,这样就可以指定那些位进行比较计算。

  Stencil buffer是Z Buffer的一部分。 当Z Buffer格式是D3DFMT_Z24S8, D3DFMT_Z15S1, D3DFMT_Z24X4S4,S代表的就是stencil buffer。 RS Stencil Test 控制是否使用stencil test。 RS Stencil Mask控制stencil 参考值和stencil buffer值的那些位用于比较计算。 RS Stencil Func 定义了比较函数。如果比较函数的结果是FALSE,则源像素被discarded,没有后续的处理。

Stecil test可能产生的结果:stecil test 失败;stencil test成功而depth test失败;stencil test成功depth test也成功。这三个结果对应的渲染状态分别是RS Stencil Fail, RS Stencil Z Fail和RS Stencil Pass,他们都是从D3DSTENCILOP取值。

typedef enum _D3DSTENCILOP

{

   D3DSTENCILOP_KEEP= 1,

  D3DSTENCILOP_ZERO = 2,

   D3DSTENCILOP_REPLACE= 3,

   D3DSTENCILOP_INVERT = 6,

   D3DSTENCILOP_INCR = 7,

   D3DSTENCILOP_INCRSAT = 4,

   D3DSTENCILOP_DECRSAT = 5

} D3DSTENCILOP;



D3DCAPS9::StencilCaps 的每个位都对应设备支持一个stencil操作。

stencil buffer可以用来渲染很多特别效果。

Masking Irregular Regions
  Stencil test常用的例子就是掩盖render target里面的不规则的区域。首先,我们创建一个stencil buffer的mask image,然后我们绘制被stencil test masked的几何数据。

  为了创建mask, 首先将stencil buffer清0,接下来,设置渲染状态,一般在stencil buffer渲染产生的源像素位置存储1。 这就在几何体被绘制的地方,创建了掩码。在stencil test执行之前,alpha test reject像素,复杂的几何体使用简单的纹理化而且有透明区域的四边形进行绘制。

   RS Stencil Enable = FALSE

   RS  Stencil Func = D3DCMP_ALWAYS

   RS  Stencil Ref = 1

   RS  Stencil Fail = D3DSTENCILOP_REPLACE

   RS  Stencil Z Fail = D3DSTENCILOP_REPLACE

   RS Stencil Pass =  D3DSTENCILOP_REPLACE

   接下来设置rending state以便让几何体被掩码clipped掉。使用stencil test将那些目的stencil值为1的地方被reject掉,并且保持stencil buffer值不变。当你改变mask的时候,如果你清除stencil buffer, 你可能在每一帧重用那个mask。

   RS Stencil Enable = TRUE

   RS Stencil Func = D3DCMP_NOTEQUAL

   RS  Stencil Ref = 0

   RS Stencil Fail = D3DSTENCILOP_KEEP

   RS Stencil Z FAIL=D3DSTENCILOP_KEEP

   RS Stencil PASS = D3DSTENCILOP_KEEP

   

Screen Door Transparency and Stippling
设置stencil buffer的值为0和1,并且使用stencil buffer为掩码,我们能绘制出一个透过一个门看到场景里面的外观。在stencil buffer 为0的地方,screen 被绘制,在stencil buffer值为1的地方,透过屏幕的场景被绘制。 如果我们使用透明度的alpha 混合,就能取得更好的渲染质量。这种方法能提供一种屏幕空间的stipple的模式。一个值用于打开stipple模式,另外一个用于关闭stipple模式。

Filling and Reading the Stencil Buffer
因为stencil buffer是z buffer的一部分,所以,可以跟z buffer一样的方法来访问他们的buffer。

Alpha Blending
Alpha blending将源像素和目的像素使用一个函数结合起来,计算出新的像素值。当需要显示多层效果时,经常会用到它。RS Alpha Blend Enable 控制Alpha blending的应用。

  C = <r, g, b, a>

  f = <fr, fg,fb,fa>

  Cf = <rfr,gfg,bfb,afa>

  Cs'= <func><Csfs,Cdfd>

在这里Cs是源像素的RGBA颜色值,fs 是源blending系数,Cd是目的像素的RGBA颜色值。<func>是blending函数,Cs'是alpha blending操作的最后结果。在这个方程里面颜色和blending系数都是4维向量。如果render target的目的像素没有alpha channel,它的默认值是255。

RS Src Blend 和 RS Dest Blend 用于选择blend系数对fs和fd。 他们的值都是从D3DBEND枚举值里面取。 RS Blend Op指定blend函数,默认值是D3DBLENDOP_ADD。

typedef enum _D3DBLEND

{

   D3DBLEND_ZERO = 1,

   D3DBlEND_ONE = 2,

   D3DBLEND_SRCCOLOR =3,

   D3DBLEND_INVSRCCOLOR = 4,

   D3DBLEND_SRCALPHA = 5,

   D3DBLEND_INVSRCALPHA =6,

   D3DBLEND_DESTALPHA = 7,

   D3DBLEND_INVDESTALPHA = 8,

   D3DBLEND_DESTCOLOR = 9,

   D3DBLEND_INVDESTCOLOR = 10,

   D3DBLEND_SRCALPHASAT = 11,

   D3DBLEND_BOTHINVSRCALPHA = 13
}D3DBLEND



typedef enum _D3DBLENDOP

{

    D3DBLENDOP_ADD = 1,

    D3DBLENDOP_SUBTRACT = 2,

    D3DBLENDOP_REVSUBTRACT = 3,

    D3DBLENDOP_MIN = 4,

    D3DBELNDOP_MAX = 5
} D3DBLENDOP

Alpha blending 也可以执行组合计算,如下图所示。最常用的操作是over,用来组合back-to-front的每一层。列在下面表里面的操作数是已经被alpha处理过的,就是alpha通道的已经被乘到颜色通道。





当在exclusivem模式上使用D3DSWAPEFFECT_FLIP或者D3DSWAP_EFFECT_DISCARD,并且设备的D3DCAPS9::Caps3的D3DCAPS3_ALPHA_FULLSCREEN_FLIP_OR_DISCARD被设置,设备支持目的地的Alpha。否则,应用程序应该使用D3DSWAPEFFECT_COPY或者D3DSwapEffectCopyVSync。  当D3DCAPS9::PrimitiveMiscCaps的D3DPMISCCAPS_BLENDOP被设置,设备支持扩展的blend操作。D3DCAPS9的成员SrcBlendCaps和DestBlendCaps分别定义可支持的源和目的blend系数。每个位代表一个系数。

Dithering
在概念上,所有的像素处理都是发生在RGBA颜色的四个通道,并且每个通道一般至少8位。但是Render Target可能有少于8位的通道,如D3DFMT_R3G2B2。在一个颜色被写到render target之前,direct3d将会减少颜色通道的深度,来匹配render target的通道。当render target有一个被缩减的颜色通道时,这将导致banding artifacts。 Dithering算法则可以减少这种artifacts。

  Dithering 通过计算原始颜色和写入到render target的颜色的误差来进行操作。随着像素写入到render target,dithering算法尽量均衡的分布误差。

   RS Dither Enable enable dithering。 如果D3DCaps9::RasterCaps的D3DPRASTERCAPS_DITHER被设置, 设备将支持dithering。

MultiSampling
  使用multisampling, 将为render target的每个像素的创建多个颜色和depth/stencil buffer。 多个采样加权计算,就可以提供全屏抗锯齿和其他效果,如depth-of-field, soft shadow 和motion blur。 使用multisampling的时候,设备必须使用 discard swap effect。 Direct3D的每个render target支持2到16个采样,你可以通过IDirect3D9的接口CheckDeviceMultiSampleType来查看支持的multisampling。

   全屏抗锯齿是常用的multisampling的例子。当把RS Sample Antialias设置为true的时候,设备就enable 全屏Multisampling。 多个采样通过加权结合在一起产生平滑的多边形的边和轮廓。注意只有一个颜色值应用到所有的采样,它包括了所有的子采样。这样,pixel shader和固定功能的多纹理处理为每个像素都只执行一次,并不是像素的每个采样。但是depth和stencil需要基于每个采样来处理。

   当RS Multi Sample Antialias为False的时候,每个采样可以通过掩码 RS Multi Sample Mask控制。特别效果可以通过多个rendering pass和为每个pass使用不同的采样来实现。

    如果在render target上n个samples,渲染的时候enable第i个sample。则产生的图片有i/n强度。如果所有的sample被enable,则图片的强度跟未采样一样。

   如果D3DCAPS9::RasterCaps的DDDPRASTERCapsStretchBltMultiSample位被设置,就执行像stretchblt 操作一样的采样。全屏抗锯齿只能基于每一个frame,并不是每个primitive的。

   Depth of Field
  现实中的相机对场景里面的物体是有一段深度范围的。这段范围称作Depth of Field。 但是direct3d的相机模型却好像有无限的深度。 你可以通过multi sampling来模拟有限的深度。具体做法是在几个Pass里面绘制场景,在每个pass轻微的jitter 视截体。

   假定我们有16个sample,使用16个pass来渲染,每个pass都通过RS Multi Sample Mask enable一个不同的采样。 每个pass都是用稍微不一样的视截体。每个视截体都会贯穿同一个相同深度的矩形区域。这个共同的矩形局域就是焦点面,远离这个焦点面的物体将会被涂抹掉。

   Motion Blur
  当场景里面的物体以快于presentation的速度运动的时候,他们运动就会出现短暂的锯齿。 常用的例子是出现在19世纪电影里面的马车的轮子。在计算机图形里面,随时间采样场景的行为是发生在渲染过程中,动态渲染的每一帧只是捕捉每一个瞬间,并没有考虑物体的运动。

  使用multisampling,每帧渲染快速运动的物体多次,把每次渲染保存到采样的子集里面。

Writing to the Render Target
  在所有的frame buffer处理完毕后,它的depth, stencil , color值都会写到render target。 数据的写入是通过write mask 集合来控制的。

  RS Z write enable 控制是否把depth和stencil写入到render target的 depth/stencil buffer。 depth test 与这个write mask无关。 RS stencil Write mask 控制stencil buffer的那些位可以写。

  可以使用RS Color Write Enable选择render target的每个颜色通道。如果D3DCAPS9::PrimitiveMiscCaps的D3DPMISCCAPS_COLORWRITEENABLE被设置,设备支持颜色值的写入。

  RS Multi Sample Mask控制多采样的那几个sample被写。



参考文章: 浅谈抗锯齿技术

ic.expert 发表于 2008-6-11 07:28

出处

[url]http://blog.csdn.net/weili_2007/category/350284.aspx[/url]

lamputa 发表于 2009-11-6 17:38

我有这本书的英文版,可惜不全,因为只是作者的手稿,貌似完整的书没有出版,或者是我没有找到,这是一本我正在读并且认为是相当棒的书。苦于英文水平不够,读起来有点辛苦,不过基本都能理解。如果有一本完整的中文版,那真是太perfect了!

页: [1]

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.