现在的位置: 首页 > 综合 > 正文

纹理混合和模板缓冲[翻译]

2012年04月13日 ⁄ 综合 ⁄ 共 20189字 ⁄ 字号 评论关闭

[第一遍]

构造3D世界

现在我们已经有了基本的天空盒,但是我们还没有大地,不按照次序,以下是构成世界的基本元素: 镜头,天空盒,地形,边界阻挡,天气,视锥,碰撞,纹理贴图,Mesh

其中大地生成,碰撞检测,纹理贴图,镜头,Mesh应该是各自独立的,不像前面说的有所交叉。

为了构成世界,碰撞检测是相当重要的,但是往往人们更容易被画面所迷惑,就是说人们更关注画面,毕竟碰撞是背后的事情,所以首要的贴图,地形, Mesh,镜头,构造世界的技术应该成熟,我们把碰撞规划的交互领域。

为了构造世界,除了Mesh等,还有阴影,光,物体,各种物体,也就涉及到Mesh优化的问题,更具体可以将创建世界的部分划分到场景编辑中,如果制作场景编辑器,那么世界的创建应该更容易,不建议使用Max Maya类毕竟它们是相当复杂的,不容易安插物品。

我们还是按照《Introduction等》相关的来介绍。

创造世界的第一目的就是要真实,好看,漂亮,又在速度可接受的范围内,所以速度和画质是评判的标准。

另外我们可以利用公告牌技术来通过文字来标识各种物品。

好,还是先看纹理

1.Texture Coordinates 纹理坐标

Direct3D使用纹理坐标系统,在水平上使用U轴,和垂直上使用 V轴。一对UV定义一个叫做texel的纹理元素。注意V轴肯定是向下的方向看(图6.2)

纹理坐标系统有时叫做纹理空间。

还有,注意规格化间隔的坐标,[0,1],因为Direct3D固定范围用纹理的变量工作。

因为每个3D三角形,我们想定义符合三角形在文理被贴图的3D三角型。(看图6.3)

这样,我们修改我们的顶点结构并且,添加一对纹理坐标定义在顶点上。

struct Vertex{ float _x,_y,_z; float nx,ny,nz; float _u,_v; static const DWORD FVF; };

const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL |D3DFVF_TEX1

<观察我们添加的D3DFVF_TEX1到我们的顶点格式描述里,这就是说我们的顶点结构包含一对纹理坐标。/p>

现在从每三个顶点对象也定义一个符合纹理三角形。

创建和使用纹理

纹理数据通常使用图片文件存储在磁盘和加载使用IDirect3DTexture9对象。这样,我们使用下面的D3DX函数:

D3DXCreateTextureFromFile(LPDIRECT3DDEVICE9 pDevice, LPCSTR pSrcFile,LPDIRECT3DTEXTURE9* ppTexture);

这个函数可以加载下面一些图片格式BMP,DDS,DIB,JPG,PNG,和TGA

例如,创建纹理从图片调用石头wall.bmp,我们将写作下面的样子:

IDirect3DTexture9* stonewall;

D3DXCreateTextureFromFile(device,"stonewall.bmp",&stonewall);

设置当前的纹理,我们使用下面的方法:

IDirect3DDevice9::SetTexture(DWORD State,IDirect3DBaseTexture9* pTexture);

例如:

Device->SetTexture(0,stonewall)

注意:在Direct3D,你可以设置8个文理合并到更多细节的图片。这叫多重文理,我们不使用知道第五部分;因此我们总是使用 stage为0,现在。

在特殊的纹理stage丧失能力,设置pTexture为0。例如,如果我们不想渲染对象使用纹理,我们这样写:

device->SetTexture(0,0);

renderObjectWithoutTexture();

如果我们的场景有使用不同纹理的三角形,我们将不得不用下面相似的代码:

device->SetTexture(0,tex0);

drawTrisUsingTex0();

Device->SetTexture(0,tex1);

drawTrisUsingTex1();

过滤器

材质过滤

最近取样

线性过滤 Linear Filtering

各向异性过滤

提到以前的纹理贴图到三角形到屏幕空间。通常,纹理三角形在屏幕上是不同大小的。当纹理三角形比屏幕三角形小时,纹理三角形是放大的图象。当纹理三角形比屏幕三角形大的时候,纹理三角形缩小的。这两种情况,将发生扭曲。在Direct3D中使用过滤器技术帮助这些方向平滑。

Direct3D提供了三种不同类型的过滤器;每种提供不同级别的质量。最好的质量也是组慢的,所以你必须在质量和速度之间做出交换。纹理过滤设置通过SetSamplerState设置。

Nearest point sampling - 这中默认过滤方法和看起来最坏的结果,但是也是最快的。下面的是最进点取样缩小和扩大过滤。

Linear filtering - 这类过滤产生了还算好的结果和可以非常快的完成。它使你可以去到线性的最小值。下面的设置线性过滤:

Device->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);

Device->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);

Anisotropic filtering - 这类过滤产生最好的结果,也最长的计算。下面代码设置

Device->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_ANISOTROPIC);

Device->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_ANISOTROPIC);

当使用各向异性过滤时,我们必须设置D3DSAMP_MAXANISOTROPY等级,来确定anisotropic的质量。最大值是最好的结果。检测D3DCAPS9结构获取设备支持的最大范围。下面代码设置为4

Device->SetSampleState(0,D3DSAMP_MAXANISOTROPY,4);

Mipmaps - 近清晰远模糊

As said in section 6.3,在三角形在屏幕上通常是不同的纹理三角形。In an effort to make the size difference less drastic,我们可以创建一系列的小图到纹理。这种想法是制作一系列小的低分辨率纹理但是自定义过滤每个等级的细节对我们是重要的。

mipmap过滤使用下面的顾虑

Device->SetSamplerState(0,D3DSAMP_MIPFILTER,Filter);

Filter有下面的选项

D3DTEXF_NONE - 关闭mipmap

D3DTEXF_POINT - 使用Filter,Direct3D将选择最近的屏幕三角形。,基于指定的min和最大的过滤器。

D3DTEXF_LINEAR - 使用Filter,Direct3D将设置另个最近的等级,每个登记有最小和最大的过滤器,线性整合两个等级到最后的颜色值。

在Direct3D上使用Mipmaps

使用Mipmaps很容易。如果设备支持mipmaps,通过D3DXCreateTextureFromFile将产生一系列的mipmap.另外,Direct3D将自动选择匹配的屏幕最好的三角形。所以使用mipmap非常漂亮,自动的完成。

地址模式

前面,我们讲述了纹理坐标将在[0,1]范围内指定。技术让那是不恰当的;它们可能超出范围这种超过[0,1]范围的被Direct3D定义为地址模式。有四种地址模式:wrap border color clamp 和mirror

图6.5 6.6 6.7 6.8进行了说明。

这些图中,纹理坐标四种独特的顶点范围定义在(0,0) (0,3) (3,0) (3,3)。三个在UV方向上,这个范围再分为3x3矩阵区域。如果,例如,你想纹理贴在5x5超过范围,你可以指定wrap地址模式和纹理坐标(0,0) (0,5) (5,0)和(5,5).

下面代码指定Wrap地址模式和纹理坐标(0,0)(0,5) (5,0) (5,5).

下面代码从死个地址模式设置来说是微不足道的:

摘要:纹理坐标是使用3D三角形在纹理上定义贴图

我们可以通过加载磁盘上的图片文件来加载纹理用D3DXCreateTextureFromFile

我们可以缩小和放大,和使用mipmap来取样,对纹理进行过滤。

地址模式被定义为超过0,1范围的,举例来说,将纹理贴成瓷砖,反转,clamp,等

2 混合 Blending

在本章我们使用一种技术叫做混合,允许你混合我们当前的光栅化像素和前一个光栅化像素,在相同的像素位置上。换句话说,我们混合了原始绘图。这种技术允许你完成多样的效果(特殊,透明)

目标 - 理解怎样使用混合工作

学习关于不同Direct3D支持模式的混合

找到怎样使用控制原始的透明度使用Alpha组件

混合因素 The Blending Equation

假设我们想画一透明的我们能看见背景的透明茶壶。

我们怎样完成呢?我们需要整合被计算的茶壶的像素颜色,像这种方式,展示一个茶壶。这个想法,郑和当前计算的源像素值到目标像素的颜色值叫做混合。

注意,混合出像玻璃一样的效果是没有限制的。我们可以混合出各种颜色值。

重要的是认识到当前三角形的光栅化使用像素和提前写入后缓冲区。举例说明,我们创建图片写到当前的后缓冲区。我们然后画茶壶所以茶壶被创建。因此,下面的规则将允许使用混合。

画一个对象不使用混合。然后拣选出对象和他们的距离产生混合;这样大部分有效的混合你可以简单使用Z-component.最后,画出使用混合的对象。下面的公式是混合两个像素值。

每个变量都是一个4D的颜色向量(r,g,b,a)和denotes component-wise的指示。

OutputPixel - 输出混合像素

SourcePixel - 当前像素和使用后缓冲计算像素。

SourceBlendFactor - [0,1]间的像素值,混合的百分含量。

DestPixel - 目标像素值在后缓冲上。

DestBlendFactor - 混合后的像素值

源和目标混合因素让我们修改最初的源和目标像素有多种方式,允许不同的效果,7.2覆盖了预先设定的各种值。

混合默认是关闭的;你可以通过如下设置开启它,渲染状态

Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);

混合不是便宜的操作将仅仅需要几何的支持。但你完成渲染几何,你将关闭Alpha混合。也试图在一批三角形上使用混合和渲染它们在,所以你应该避免关闭混合在每帧。

混合因素

设置不同的混合源和目标混合要素,你可以创建不同的混合效果。使用不同的混合值进行实验。你可以设置源为都使用 D3DRS_SRCBLEND和D3DRS_DESTBLEND渲染状态,例如写做:

Device->SetRenderState(D3DRS_SRCBLEND,Source);

Device->SetRenderState(D3DRS_DESTBLEND,Destination);

源和目标可以有下面的混合要素值。

默认的源和目标值为D3DBLEND_SRCALPHA和D3DBLEND_INVSRCALPHA。

透明

在前面的章节我们忽略了Alpha组件的顶点颜色和材质,因为不需要,然而他们主要使用混合。然而,Alpha值从每个顶点颜色着色到跨过多个三角形的颜色,但是定义了像素颜色,它确定了像素的Alpha内容。Alpha主要用来指定像素的透明度的。假设每个像素有8bit的Alpha,有效的间隔Alpha内容将在[0,255],符合[0%,100%]的不透明。因此,像素值(0)黑色值日完成透明,每个像素的128将是灰色值,50%透明,白色将是255

为了让Alpha描述不同的透明,我们必须设置源混合要素为D3DBLEND_SRCALPHA和目标为D3DBLEND_INVSRCALPHA,这样值将发生混合。

Alpha通道

取代使用Alpha内容计算着色。我们可以从纹理Alpha-channel中获取Alpha通道。这个Alpha通道是一个额外的位类为每个texel存储Alpha内容。当Texture贴图到原始顶点上,Alpha内容的Alpha通道也被贴上,它们改变了Alpha内容的纹理原始信息的像素值。图7.3是个开了8bitAlpha通道的图片。

指定源Alpha

默认的,如果当前设置纹理的Alpha通道,Alpha是从Alpha通道开始。如果没有Alpha通道,Alpha从顶点颜色里。然后你可以指定源(diffuse color或Alpha Channel)使用下面的渲染状态。

Device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_DIFFUSE);

Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1);

Device->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE);

Device->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_SELECTARG1);

Stenciling 模板

这章带给我们的是模板缓冲和结束文本的第二部分。stencil缓冲是一个离屏缓冲,我们可以使用它完成特殊效果。stencil缓冲有一些resolution作为后缓冲和深度缓冲,像ij-th像素在stencil缓冲符合使用ij-th像素到后缓冲和深度缓冲。The stencil buffer has the same resolution as the back buffer and depth buffer so that the ij-th pixel in the stencil buffer corresponds with the ij-th pixel in the back buffer and depth buffer.作为命名建议,stencil缓冲工作允许我们成块的渲染后缓冲的一个主要部分。

例如,当完成反射,我们简单需要反射出特殊对象跨过一个平面;然而,我们仅仅想画出反射到镜子。我们可以使用stencil缓冲渲染在镜子反射出的。

stencil缓冲是Direct3D中较小的一部分是控制简单的接口。像 blending,简单的灵活强大的接口。学习使用stencil缓冲,能够更有效的完成存在的应用。一旦你理解了程序中的stencil缓冲,你将有个更好的想法,它可以应用到指定的需要上。基于这个原因,本章设置特殊强调学习两种指定使用stencil(在特殊,完成镜子和平面阴影).

Objectives - 目标

理解stencil缓冲是怎样工作的,怎样创建stencil缓冲,和怎样控制stencil缓冲。

学习怎样完成镜面和使用stencil缓冲到防止反射从drawn到非镜面的接口。

发现怎样渲染阴影和反之双混合使用stencil缓冲。

Using the Stencil Buffer使用模板缓冲

使用模板缓冲,我们必须首先当初始化Direct3D的时候我们开启它。我们描述请求在8.1.1里。开启stencil缓冲,我们必须设置 D3DRS_STENCILENABLE渲染状态,指定真。关闭为false.下面小片代码使用开启和关闭

Device->SetRenderState(D3DRS_STENCILENABLE,true);

Device->SetRenderState(D3DRS_STENCILENABLE,false);

尽管本书不使用,DirectX9.0已经添加一个双向的体积阴影渲染来产生阴影。看SDK文档更多细节。

我们可以清理stencil缓冲到默认值,使用IDirect3DDevice9::Clear方法。重复调用在同样的后缓冲和深度缓冲。

足以我们已经添加了D3DCLEAR_STENCIL到三个参数,指出我们清理目标的模板缓冲和后缓冲。参数6指定了清理模板缓冲的值;例子为0.

注意:使用模板缓冲可以考虑自由操作硬件如果你已经准备了深度缓冲,依照NVIDIA陈述的创建反射和阴影使用模板缓冲 Mark J. Kigard

请求模板可以我们创建深度缓冲的时候同时创建。当指定深度缓冲格式时,我们同时指定模板缓冲的格式。实际上,模板缓冲和深度缓冲共享同一个离屏表面,但是每儿歌像素是设计为分割到每个特殊的缓冲。例如,考虑下面三个深度/模板缓冲格式:

注意这些格式不分配任何位到模板缓冲,例如,D3DFMT_D32格式创建32bit的深度缓冲。

还有,支持变化的各种图形卡,也许不支持8bie模板缓冲。

The Stencil Test

前面声明,我们可以使用模板缓冲渲染主要后缓冲的一块。从决定了模板缓冲的测试,使用下面的表达式:

(ref & mask) ComparisonOperation (value & mask)

模板测试完成每个像素的任务,假设模本是开启的,两个操作:左操作(LHS)确定ANDing一个应用-定义模本引用值(ref)定义模糊值

RHS-右手规则确定ANDing空的模板缓冲到特殊像素测试定义模糊值。

模板测试比较LHS和RHS,测试比较操作。全部表达式为假值。我们写像素到后缓冲区如果测试值为真。如果测试值为假,我们从后缓冲区渲染每个像素。当然,如果像素不写在后缓冲区,特写在深入缓冲区。

Controlling the Stencil Test 控制模板缓冲测试

ZBuffer WBuffer StencilBuffer

为了给我们灵活,Direct3D允许我们控制在Stencil测试中使用变量。换句话说,我们获取了指定的模板参考值,模糊值,甚至比较值。虽然我们不能获取明确的设置模板缓冲值,我们有一些控制覆盖写入在模板缓冲另外清理模板缓冲。

Stencil Reference Value模板缓冲值

模板缓冲参考值默认是0,但是我们可以改变它用D3DRS_STENCILREF渲染状态。例如,下面代码设置模板引用值

Device->SetRenderState(D3DRS_STENCILREF,0x1);

注意,我们往往是使用十六进制因为更容易看清位的分配的整数,这样好的位操作,想ANDing.

Stencil Mask 模板模糊

模板模糊值模糊是使用mask(hide)位在双ref和value变量。默认模糊是0xffffffff,任意位不mask.我们可以改变mask用D3DRS_STENCILMASK渲染状态。下面例子mask为16高位:

Device->SetRenderState(D3DRS_STENCILMASK,0x0000ffff);

注意:如果你不理解谈论位和masking,它意味者你需要复习你的二进制,十六进制和bit-wise操作。

Stencil Value - 模板值

前面讲过,这个值是当前模板测试的像素值。例如,如果我们完成模板测试在ij-th像素,然后值将被值在ij-th的模板缓冲的入口。我们不明确的设置单独的模板缓冲值,但是回想我们清理了模板缓冲。另外我们可以使用模板渲染状态控制写入在模板缓冲。模板和渲染状态短的发生联系。

Comparison Operation - 比较操作

我们可以通过D3DRS_STENCILFUNC设置比较操作。比较操作任何D3DCMPFUNC枚举类型:

更新模板缓冲 Updating the Stencil Buffer

另外决定是否写入块从写入后缓冲的特殊像素,我们定义模板缓冲entry将被基于三种可能的更新:

模板缓冲测试ij-th像素失败。我们可以定义怎样更新ijentry在模板缓冲响应这种情况被设置D3DRS_STENCILFAIL渲染状态:

Device->SetRenderState(D3DRS_STENCILFAIL,StencilOperation);

深度测试对ij-th像素失败。我们可以定义怎样更新ij-th入口符合设置D3DRS_STENCILZFALL渲染状态

Device->SetRenderState(D3DRS_STENCILZFALL,StencilOperation);

深度测试和模板测试成功对于ij-th像素。我们可以定义怎样更新ij-th入口在符合设置D3DRS_STENCILPASS渲染状态:

Device->SetRenderState(D3DRS_STENCILPASS,StencilOperation);

在StencilOperation可以下面预定义的常量:

模板写入Mask

另外提及我们可以设置渲染状态写入mask到模板缓冲。任意值,为0xffffffff.我们可以设置些如mask通过D3DRS_STENCILWRITEMASK。默认值日,下面例子写入16bits/p>

Device->SetRenderState(D3DRS_STENCILWRITEMASK,0x0000ffff);

通常使用Stencil缓冲完成溶解,阴影等特殊效果。

例子应用:镜子

在自然领域镜子允许我们看到镜子反射的对象。这章描述我们怎样在3D应用程序中模拟镜子。注意我们仅仅简单的完成镜子和平坦阴影。例如,有光泽的汽车可以;然而,汽车身体是平滑的,圆的,不是平坦的。取代的,我们渲染反射像有光泽的地板被显示为镜子-换句话说,镜子展开在一个平面上。

完成镜子在程序上需要完成两个问题,第一,我们必须学习怎样反射一个任意的对象因此我们可以直接画出反射。第二,我们必须仅仅显示在镜子里。就是说,我们必须标记一个表面为镜子,然后我们如果它在镜子前我们仅仅反射对象。参考图8.1,介绍了基本概念。

第一个问题使用一些向量几何是可以解决的。我们可以用模板缓冲解决第二个问题。另外两节将解释分别解决问题。第三段结合复习相关的代码第一个例子本章-镜子。

反射的数学

我们现在展示计算点v'=(vx'vy'vz')的反射点v=(vx,vy,vz)关于任意平面n^.p_d = 0.参考贯穿讨论的。

D3DX库提供了下面函数创建R关于任意平面的反射矩阵函数:

D3DXMatrixReflect(D3DXMATRIX *pOut,CONST D3DXPLANE *pPlane);

自从我们在反射变换的话题上,让我们三种特殊情况的反射矩阵变换。他们是标准坐标平面,yz,xz,xy平面。被下面的矩阵描述

反射点跨越了yz平面,我们简单的用相对的Z-component.相似的,反射点跨越xz平面,我们取相反的y-component.最后,反射点跨越,xy面,我们取到Z-component。这些点通常都是对称每个标准的坐标平面。

镜子完成概述

当完成镜子,一个对象仅仅反射如果它在镜子前。然而,我们不想测试如果一个对象在镜子前,它会变得复杂。因此,简单的东西,我们总是反射物体和渲染它,不管在哪。但是这样产生图8.1的问题,命名对象反射渲染表面不是镜子(像墙一样),我们可以使用模板缓冲解决这个问题因为模板缓冲允许我们渲染特定区域的一部分。因此,我们可以使用模板缓冲渲染如果在镜子中被渲染的一部分。下面的轮廓简要的解释了怎样完成的步骤:

1.默认渲染整个场景-地板,墙,镜子和茶壶-但是茶壶不反射。注意这不没有修改模板缓冲。

2.清理模板缓冲为0。图8.3展示了后缓冲和模板缓冲的点。

3.仅仅渲染原始的到镜子到模板缓冲。设置模板缓冲测试总是成功,指定模板缓冲替换用1如果测试通过。自从我们仅仅渲染镜子,所以在模板缓冲除了0像素符合镜子-他们将有8.4展示更新模板缓冲。本质上,我们在模板缓冲标记了每个像素。

4.现在我们在后缓冲和模板缓冲渲染反射的茶壶。但是回想我们仅仅如果模板缓冲测试通过,我们仅仅渲染后缓冲。这次我们设置模板测试仅仅如果值在模板缓冲为1时渲染。这样茶壶仅仅在它们符合模板缓冲入口区域内才渲染。自从模板缓冲符合此区域仅仅有1,在镜子中仅仅渲染反射的茶壶。

代码和解释

代码中RenderMirror函数,渲染镜子原始信息到模板缓冲且然后仅仅如果它开始在镜子里然后渲染反射的茶壶。We walk through the RenderMiror function almost line by line 并且解释发生什么,更重要为什么。

如果你使用8.2.2章的步骤 to serve as an overall guide to the code,注意从第1和2步骤开始到第3步,没有利用到模板缓冲。也要知道,我们讨论渲染镜子的解释。

注意我们分开解释没有其他原因是为了提出更多模块化的讨论。

第一部分

我们开始开启模板缓冲和设置关联的渲染状态:

void RenderMirror(){ 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_REPALCE); }

这是直接的设置。我们设置模板比较操作用D3DCMP_ALWAYS,指定模板总是通过测试。

如果深度测试失败,我们指定D3DSTENCILOP_KEEP,指出不更新模板缓冲的入口。就是说,我们维持它的当前值。我们这样因为如果深度测试失败,它意味着像素是模糊的。因此,我们不想渲染模糊的反射部分。

我们也指定D3DSTENCILOP_KEEP如果模板测试失败。但是这里不是真正必要的,自从测试不再失败因为我们指定了D3DCMP_ALWAYS.然而,我们改变了比较操作位,所以设置模板渲染状态失败;we just do it now.

如果深度和模板测试通过,我们指定D3DSTENCILOP_REPLACE替换模板入口用模板引用值0x1

第二部分

下块代码片段,但是仅仅到模板缓冲。我们可以停止写入深度缓冲用D3DRS_ZWRITEENABLE和指定假。我们防止更新后缓冲用混合和设置源要素D3DBLEND_ZERO和目标要素D3DBLEND_ONE.插入混合要素在混合平衡,我们展示后缓冲的不改变:

Device->SetRenderState(D3DRS_ZWRITEENABLE,false);

Device->SetRenderState(D3DRS_ALPHABLENDENABLE,true);

Device->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ZERO);

Device->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_ONE);

Device->SetStreamSource(0,VB,0,sizeof(Vertex));

Device->SetFVF(Vertex::FVF);

Device->SetMaterial(&MirrorMtrl);

Device->SetTexture(0,MirrorTex);

D3DXMATRIX i;

D3DXMatrixIdentity(&i);

Device->SetTransform(D3DTS_WORLD,&I);

Device->DrawPrimitive(D3DPT_TRIANGLELIST,18,2);

Device->SetRenderState(D3DRS_ZWRITEENABLE,true);

第三部分,

在这点上,在符合可见像素的镜子有入口0x1,因此区域已经被渲染。我们现在准备渲染反射的茶壶。回想我们仅仅渲染反射的部分到镜子。我们可以容易的我们在使用模板缓冲。我们设置下面的状态。

这仅仅是测试值是否为0x1。自从在模板区内值是否为0x1符合镜子,如果我们在那些区域渲染就成功。因此,反射的茶壶仅仅画到镜子里而不画在其他表面上。

注意我们有改变D3DRS_STENCILPASS渲染状态D3DSTENCILOP_KEEP,简单的说维持在如果通过测试维持的值。因此,下一次渲染通过,我们不改变在模板缓冲(所有控制是D3DSTENCILOP_KEEP)的值。我们仅仅使用模板缓冲标记符合镜子的像素。

第四部分

这部分函数计算矩阵和反射位置:

注意我们第一转换非反射茶壶的位置。然后,一次位置那里,我们反射跨越XY平面。为了变换指定的矩阵乘积。

第五部分

我们总是准备清理,渲染反射的茶壶。然而,如果我们现在渲染它,它将不被显示。为什么?因为反射茶壶的深度缓冲大于镜子的深度,因此我们的镜子原始信息是暗的反射茶壶。围绕这个我们清理深度缓冲:

不是所有问题都解决了,然而,如果我们简单的清理深度缓冲,反射茶壶在镜子前面和东西不被看见的。我们想清理深度缓冲反射茶壶用镜子。换句话说,反射茶壶看起来像在镜子里的茶壶。我们可以缓和反射茶壶用镜子下面的混合等式:

自从源像素将从反射茶壶和目标像素将从镜子,我们可以从等式看到它们怎样将混合在一起。代码:

最后,我们准备画反射茶壶:

回想8.2.3.4设置w变换到场景中适当的位置。再次,观察我们改变拣选后表面。我们必须做因为当一个对象反射时,它的前表面被它的后表面交换;然而,这不是一成不变的。因此,新的前表面在Direct3D中为后表面。类似的新白面三角形将指定为前表面。因此,正确的,我们必须改变后表面的拣选条件。

清理,我们关闭混合和模板,存储为通用的拣选模式:

例子程序:Planar Shadows - 平坦阴影

在我们理解的范围内阴影是通过光来产生的。并且使场景有更多的真实性。在本节我们展示了如何完成平坦阴影-换句话说,阴影位于平面上(图8.5)

注意这些阴影的类型是快的,且虽然它们提高了场景,它们不是真正的体积阴影。阴影体积是书外最高级的概念。然而它在DirectX SDK中有一个示范性的例子。

要完成平坦阴影,我们必须找到阴影对象转换到平面和几何模型以至我们可以渲染它。这可能容易使用3D数学库完成。我们然后渲染这个描述阴影的用黑色材质来渲染多边形在50%透明。渲染阴影可以介绍些概念命名为"双重混合",我们在后面解释。我们使用模板缓冲来防止发生双混合。

Parallel 光影 - 平行光影

图8.6展示了阴影是对象投放用平行的光源来投射。光线是平行光,使用方向L,到达任意的顶点P在r(t) = p + tL.光线的交叉平面r(t)在平面n*p + d = 0给s.设置交叉点找到r(t)穿过用顶点对象到平面定义的几何阴影。一个交叉点s是容易用光线/平面测试出来的:

点光源阴影:

图8.7展示了描述用L投射点光源的到物体。光线从点p发射r(t) = p + t(p-L).交点光线r(t)用平面n.p + d = 0给s.设置交点找到每个对象发射顶点使用定义几何阴影。s可以用同样的平面光线交叉得到。

阴影矩阵

注意从图8.6到平行光,阴影是本来平行投影对象到平面指定光的方向。类似,图8.7展示点光源,阴影本来的透视发射到对象n.p+d=0从光源到视口。

我们可以描述从顶点p到它的投影s用平面n.p+d=0作为一个矩阵。而且,我们可以描述两个直接方向和透视投影用同样的矩阵更灵活的使用。

用(nxnynzd)4D向量描述通常平面的系数我们希望转换出阴影。让L=(LxLyLzLw)被4D向量描述平行光或者点光源的位置。我们使用w坐标系指示:

1.如果w=0,然后L描述在平行的光线。

如果w=1,然后L描述点光源的方向。

假设正规的平面规格化,我们让k=(nxnynzd).(LxLyLzLw)=nxLx+nyLy+nzLz+dLw,

然后我们从顶点p到它的投影s用下面阴影矩阵描述:

因为它已经完成描述有重要意义给我们,we do not show how to derive this matrix.然而,兴趣阅读,我们参考章6,"我和我们的阴影",Jim Blinn's Corner:A Trip Down the Graphics Pipeline,里面展示了这个矩阵的原形。

D3DX库提供了下面的函数构造阴影矩阵,我们系统项目的阴影和向量描述平行光如果w=9或点光源w=1:

D3DXMATRIX *D3DXMatrixShadow(D3DXMATRIX *pOut,CONST D3DXVECTOR4 *pLight, CONST D3DXPLANE *pPlane);

使用模板缓冲防止双混合

当我们变换几何对象到它的阴影时,它可能有两个或多个变平的三角形将交叉。当我们用透明(使用混合)渲染阴影时,这些区域有交迭的三角形将被多次混合,因此出现黑色。图8.8展示这种情况。

我们可以使用模板缓冲来解决。我们设置了模板测试概念的像素它们被渲染。换句话说,我们的阴影是像素到后缓冲,我们标记符合模板缓冲的入口。然后,如果我们努力写像素到一个区域将已经被渲染(将在模板缓冲里被标记),模板测试将失败。这样,我们防止写交迭像素因此避免双混合出现。

代码和解释

下面的代码解释公共文件的阴影例子。

RenderShadow函数。注意我们假设模板缓冲已经被清0。

我们开始设置模板缓冲状态。我们设置模板分隔函数D3DCMP_EQUAL和D3DRS_STENCILREF渲染状态到0x0,因此指定渲染阴影到后缓冲如果符合模板相等的入口0x0.

自从模板缓冲被清0,这将总是真的第一时间我们写一个特殊像素的阴影;但是因为我们设置D3DRS_STENCILPASS到D3DSTENCILOP_INCR,如果我们试着白色的像素我们已经写入。我们将失败。像素的模板入口将被增加到0x1到最初时间,被写入,因此模板测试将失败如果我们试着再次写入。因此,我们避免写入像素避免写双混合。

下次,我们计算阴影变换和转换阴影到场景适当的平面。

最后,我们设置后面材质为50%透明,关闭深度测试,渲染阴影,然后清理,重开深度缓冲和关闭Alpha混合和模板测试。我们关闭深度缓冲z-fighting,我们关闭虚拟的当前然后两儿歌不同表面有同样的深度值在深度缓冲;深度缓冲不知道将在前面;会发生另人苦恼的抖动,因为阴影和地面位于同一个平面,z-fignting将发生在之间。在渲染地面和阴影后用深度测试关闭,我们保证我们的阴影覆盖在地板上。

注意:可供选择的方法渲染使用深度偏转机制可以改变Z抖动。

D3DRS_DEPTHBIAS和D3DRS_SLOPESCAILDEPTHBIAS渲染状态在SDK中细节。

摘要

模板缓冲和深度缓冲共享同一个表面可以同时创建。我们定义depth/stencil表面格式用D3DFORMAT类型。

模板是使用确定的光栅化的像素块工作。在这章我们已经看到,对完成镜子和阴影和其他应用是非常有用的。

我们可以控制模板操作和怎样更新模板状态。

一些其他应用可以使用模板缓冲完成:

阴影体积,溶解和渐变,深度复杂观察,轮廓和侧像

固定Z-fighting导致公共的几何。

[第二遍部分译]
什么是模板缓冲?它能做什么?

 

2.模板缓冲的工作原理

模板缓冲 Stencil Buffer

本章我们学习模板缓冲就是第二部分提到的。模板缓冲(Stencil Buffer)是一个离屏缓冲,我们可以使用它完成特殊效果。The Stencil BUffer has the same resolution as the back buffer and depth buffer so that the ijth pixel in the stencil buffer corresponds with the ijth pixel in the back buffer and depth buffer.模板缓冲在相同分辨率下作为后缓冲和深度缓冲,在后缓冲和深度缓冲中在模板缓冲中的ijth像素符合深度缓冲中的ijth像素时。

As the name suggests,the stencil buffer works as a stencil and allows us to block rendering to certain parts of the back buffer.

作为命名暗示,模板缓冲工作作为一个蒙板,它允许我们把后缓冲某个部分渲染出来。
这有点类似2D镂空图中蒙板的概念,但是又有许多的不同。2D的镂空蒙板和原图的要求是原图中不需要显示的部分必须是白色或黑色。那毕竟对原图有要求,而Stencil Buffer处理的ZBuffer中的内容要多很多,不仅仅是黑色或者白色的部分,应该是通过StencilBuffer我们可以把ZBuffer中的某个区域作为一个蒙板,然后通过这个蒙板就标识出了ZBuffer中的像素,然后对蒙板的处理就是对该部分的处理,这样仅仅就该部分产生反射,阴影,轮廓,渐隐等。

例如,当实现一个镜子,我们简单的需要把穿过镜子平面的特殊对象反射;然而,我们仅仅想在镜子里画。我们可以使用模板缓冲反射出物体被映射到镜子中的部分。

图8.1为我们展示了一个没有使用模板缓冲,和一个使用了模板缓冲的例子。

模板缓冲是Direct3D中控制的一个简单接口。像混合,简单的接口提供了灵活强大的能力。为了有效的学习模板缓冲最有效的是完成些存在的应用。一旦你理解了这些应用中的模板缓冲,在完成指定需要时你就有了好主意。因此,本章重点在于学习模板的两个重要应用(特殊的,实现镜子和平面阴影)

目标

1. 增加对模板缓冲工作的理解,怎样创建模板缓冲,和怎样控制模板缓冲。

学习怎样完成镜子并且使用模板缓冲防止在非镜子表面的反射部分被渲染。

知道怎样渲染阴影并且通过模板缓冲防止"二次混合"

8.1使用模板缓冲

使用模板缓冲步骤:
1.指定模板缓冲格式,在创建深度缓冲同时指定为带有模板缓冲的格式D3DFMT_D24S8等,详细请看D3DFORMAT说明

2.清空模板缓冲通过IDirect3DDevice9::Clear方法 Device->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL,0xff000000,1.0f,0);

3.通过Device->SetRenderState(D3DRS_STENCILENABLE,true) Device->SetRenderState(D3DRS_STENCILENABLE,flase)开启和关闭模板缓冲。

4.通过模板参考值,模板掩膜,模板值,比较操作,更新模板,写入掩膜等来控制模板。

指定模板缓冲格式

在我们创建深度缓冲时就可以创建模板缓冲。当我们指定深度缓冲格式时,我们可以同时指定模板缓冲的格式。实际上,模板缓冲和深度缓冲共享同一个离屏表面,但是内存段中每个像素会被指派到每个特殊的缓冲。例如,下面的三种深度/模板格式:

D3DFMT_D24S8 - 这种格式说创建一个32位深度/模板缓冲且深度缓冲位每像素24位和模板缓冲每像素8位。

D3DFMT_D24X4S4 - 同理,创建一个32位深度/模板缓冲,24位深度缓冲,4位不用,4位为模板缓冲。

D3DFMT_D15S1 - 16位的深度/模板缓冲,15位深度缓冲,1位模板缓冲。

注意还有些格式不分配任何位给模板缓冲。例如,D3DFMT_D32就是仅仅创建32位的深度缓冲。另外由于显卡繁多,有的将不支持8位的模板缓冲。

 

下面给出两个片段说明如何指定模板缓冲格式:

1.没有使用Dx框架

D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp,sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = d3ddm.Format; //这里即可设置格式 如
d3dpp.BackBufferFormat = D3DFMT_D24S8;
g_pd3d->CreateDevice(D3DADAPTER_DEFAULT,D3DDEVTYPE_HAL,hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp,&g_pd3dDevice);

2.使用Dx框架的

CMyD3DApplication::CMyD3DApplication() { m_d3dEnumeration.AppUsesDepthBuffer = true;
m_d3dEnumeration.AppMinDepthBits = 24; //这里设置深度占用位
m_d3dEnumeration.AppMinStencilBits = 8; //这里设置模板占用位

this->m_dwCreationWidth = 640; m_dwCreationHeight = 480; this->m_strWindowTitle = TEXT("反射面问题"); m_pFont = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD ); }

清空模板缓冲

m_pd3dDevice->Clear(0,0,D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL,0x00000000,1.0f,0);

开启模板缓冲状态

m_pd3dDeivce->SetRenderState(D3DRS_STENCILENABLE,true);
m_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE,false);

接下就是怎样将指定的像素写入模板缓冲区,并操作。

8.1.2模板测试

前面说,我们可以通过模板缓冲渲染后缓冲的某个部分。决定写入特殊像素结果的是通过模板测试,它遵照下面的表达式:

(ref & mask) ComparisonOperation (value & mask)

模板测试对每个像素都执行,假设模板是开始的,需要两个操作数:

左操作和右操作(LHS) (RHS)

右边确定摸班缓冲被测试册特殊像素,是入口。

左边确定模板的引用值

然后比较右边和左边,指定比较操作。整个表达式是个Boolean值。如果测试求的值为真(通过)我们将像素写入后缓冲区。如果测试失败,我们阻止像素写入后缓冲区。当然,如果像素不被写入后缓冲区,它也不被写入深度缓冲。就看不见了。

然后比较左右的值,若相等或者符合条件的就应该是需要特殊处理的区域了。

控制模板测试

为了灵活,Direct3D允许我们在模板测试中使用变量。换句话说我们可以指定模板引用值,掩膜值,甚至比较操作。虽然我们不能明确的获取模板值,我们可以进行些覆盖写入模板缓冲值

模板引用值

Device->SetRenderState(D3DRS_STENCILREF,0x1) - ref

Device->SetRenderState(D3DRS_STENCILMASK,0xffffff) - mask

RHS - Device->

前面说过,这个值是我们模板要测试的值。例如,如果我们在ijth像素执行模板测试,那么值将是模板缓冲的ijth入口。我们不能设置明确的的值,但是回想我们可以清除模板缓冲。

另外我们可以使用模板缓冲状态控制写入模板缓冲。就是说,先把指定的后缓冲的像素写入模板缓冲,然后再清楚模板缓冲,然后做Anding运算,或者混合运算,就可以留下特定的像素了。

镜子实现步骤:

1.渲染整个创建设置法线-地板,墙,镜子和茶壶-但是茶壶的反射。注意这步不修改模板缓冲。

2.RenderMirror - 清空模板缓冲为0.

3.在模板缓冲上仅渲染个镜子。设置模板测试总是通过,然后设置如果测试通过将用1全部替换。因为我们仅渲染镜子,那么在模板缓冲中,镜子都是1,之外都是0

4.渲染反射茶壶到后缓冲和模板缓冲。但是回想如果模板通过测试我们仅仅渲染后缓冲。这次我们如果模板值是1则通过测试,就是说茶壶仅仅被渲染在1的模板缓冲上。在模板缓冲区符合1的部分就是,茶壶仅仅渲染在镜子上。

以下代码为实现模板的核心,参考了Dx例子。
//设置模板
 m_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILFUNC,     D3DCMP_ALWAYS );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILREF,      0x1 );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILMASK,     0xffffffff );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILWRITEMASK,0xffffffff );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILFAIL,  D3DSTENCILOP_KEEP );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILPASS,  D3DSTENCILOP_REPLACE );

 //关闭ZBuffer以向模板缓冲区写入被发射的对象
 m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE,  false );
    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_ZERO );
    m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );

 //将镜子写入反射面
 m_pd3dDevice->SetTexture( 0, NULL);
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &m_matMirrorMatrix );
    m_pd3dDevice->SetFVF( MIRRORVERTEX::FVF );
    m_pd3dDevice->SetStreamSource( 0, m_pMirrorVB, 0, sizeof(MIRRORVERTEX) );
    m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );

 //设置反射
 D3DXMATRIX W,R;
    D3DXPLANE plane(0.0f,0.0f,1.0f,0.0f);
 D3DXMatrixReflect(&R,&plane);
 W = m_matHelicopterMatrix * R;
 m_pd3dDevice->SetRenderState( D3DRS_CULLMODE,     D3DCULL_CW );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &W);
   
 //裁剪
 m_pd3dDevice->SetClipPlane( 0, plane );
    m_pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, true );

 //重新开启ZBuffer
    m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
 m_pd3dDevice->SetRenderState( D3DRS_STENCILFUNC,  D3DCMP_EQUAL );
    m_pd3dDevice->SetRenderState( D3DRS_STENCILPASS,  D3DSTENCILOP_KEEP );
    m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND,     D3DBLEND_DESTCOLOR );
    m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND,    D3DBLEND_ZERO );

    m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_ZBUFFER, 0L, 1.0f, 0L );
    m_pHelicopter->Render( m_pd3dDevice );

    //关闭
 m_pd3dDevice->SetRenderState( D3DRS_CULLMODE,         D3DCULL_CCW );
 m_pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE,  false );
 m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, false );
 m_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE,    false );
详细介绍请看《Introduction to 3D Game Programming with DirectX9.0》

抱歉!评论已关闭.