每日一语:
过完五一,又回来工作了,哎,本来想着2号,3号可以好好休息下,没想到。真的是计划不如变化啊。反正现在,又开始我的学习了。之前,看过一篇文章,很受启发,我们这些所谓着被呼喊的大牛,大神,有没有时刻保持着自省的态度,有没有时刻的审视自己。现在越来越感到,知道的越多,知道不知道的也就越多。我们时刻应该保持谦逊的态度去学习,去请教。但是现在,我们一直被浮躁之气笼罩着。知道一点皮毛,就以为自己很厉害,了不起,就忘乎所以。其实,我们什么都不是,只不过是井底之蛙。都是玩别人剩下的。当然,我们也不能气馁,因为每个人都需要这个过程,我们应该从现在开始,主动的学习,积极的学习。学习不止,奋斗不止。
正文:
像素着色器:
像素着色器是在对每个像素进行光栅化处理期间运行在图形卡GPU上一段程序.(不同于顶点着色器,Direct3D不会以软件方式来模拟像素着色器)。像素着色器实质上是取代了固定功能流水线中的多重纹理环节。而且赋予了我们直接操纵单个像素以及访问每个像素的纹理坐标的能力。这种对像素和纹理坐标直接访问的能力使得我们能够获得各种各样的特殊效果,例如,多重纹理,景深,云彩模拟,火焰模拟以及较复杂的阴影计数。
可通过检查D3DCAPS9结构的成员PixelShaderVersion,并与宏D3DPS_VERSION进行比较,来测试图形卡是否支持某个顶点着色器版本。下面的代码段解释了上述检测过程。
if (caps.PixelShaderVersion < D3DPS_VERSION(2.0))
多重纹理概述:
多重纹理蕴含的思想和融合有些联系。在前面,我们了解了如何将正在进行光栅化的像素与先前已写入后台缓存中的像素进行融合,以获得某种特殊效果。我们将这种思想沿用到多重纹理中,即,我们同时启用若干层纹理,并定义这些纹理的融合方式,以获得某种特殊效果。多重纹理的一种典型应用就是进行光照运算。在顶点运算阶段我们不打算使用Direct3D的光照模型,而是使用了一种特殊的纹理图,即光照纹理图,它规定了某一表面是如何被照亮的。例如,假定我们希望在一个较大的板条箱上投射一个聚光灯。我们可用结构D3DLIGHT9来定义聚光灯,也可将代表纸条箱的纹理图与代表聚光灯的纹理图进行融合。
由之前的融合技术所知,最终得到的图像与这些纹理的融合方式有关。在固定功能多重纹理环节,我们是通过纹理绘制状态对融合方程进行控制的。而借助像素着色器,我们可以在代码中以可编程方式将融合函数写成一个简单的表达式。这就使得我们能够以任何方式对多个纹理进行融合运算。
通过对问个纹理进行融合来照亮板条箱与Direct3D的光照计算模型相比有以下优点:
1,光照已预先计算好,并保存在聚光灯光照纹理图中。这样,在程序运行时,就不需要再对光照进行计算了,从而节省了运算时间。当然,只有对静止的物体和固定光源方可预先进行光照计算。
2,由于光照纹理图是预先计算好的,我们可采用比Direct3D更精确,更复杂的光照模型。(从而可以在更逼真的场景中获得更好的光照结果)
多重纹理环节一般可用于为静态物体实现一个完整的光照计算引擎。例如,我们现有一个保存了物体颜色信息的纹理图,例如板条箱纹理图。此外,我们还有一个用于保存漫反射表面明暗度的漫射光纹理图,一个用于保存镜面反射表面的明暗度的镜面光纹理图,一个用于保存表面上所覆盖的雾的总量的雾效纹理图,以及一个用于保存表面的微观高频细节的细节纹理图。当需要将这些纹理合成时,我们只需要对预先计算好的纹理图进行查询即可高效地为场景增加光照,上色并增加细节。
注意:聚光灯,纹理图是基本光照纹理图中较简单的一种。通常,当给定场景和光源时,我们用专门的程序来生成光照纹理图。
启用多重纹理:
前面提到,启用纹理可用方法,IDirect3DDevice9::SetTexture来启用,采样器状态可用方法
IDirect3DDevice9::SetSampleState来设置。函数原型为:
HRESULT IDirect3DDevice9::SetTexture(
DWORD Stage,
IDirect3DBaseTexture9 *pTexture
)
HRESULT IDirect3DDevice9::SetSampleState(
DWORD Sampler,
D3DSAMPLERSTATETYPE Type,
DWORD Value
)
注意:一个特定采样器的级数(以下简称采样级)索引i与第i层纹理是关联的。即,第i个采样级为第i层纹理指定了采样器状态。
纹理层/采样级索引标识了我们想要将纹理/采样器设置成的纹理层/采样级。这样,我们就可激活多个纹理,并通过不同的层索引来为这些纹理设置相应的采样状态。在此之前,我们总是将层索引指定为0(表示第一层纹理),这是因为那时只需要一层纹理就够了。如果我们需要3层纹理,可用层序号0,1,2分别标识每一层纹理:
Device -> SeTexture(0,Tex1);
Device -> SetSampleState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
Device -> SeTexture(1,Tex2);
Device -> SetSampleState(1,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(1,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(1,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
Device -> SeTexture(2,Tex3);
Device -> SetSampleState(2,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(2,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(2,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
多重纹理坐标:
之前我们讲过,对于每个3D三角形,我们需要在纹理图中为之定义一个相应的三角形,以确定被映射到该3D三角形中的纹理数据。以前我们是通过为每个顶点增加纹理坐标来实现的。这样,定义了一个三角形的每3个顶点在纹理中就定义了一个相应的三角形。
由于我们现在要使用多重纹理,对于定义了三角形的每3个顶点,我们需要在启用的每个纹理中定义相应的三角形。为此,我们可为每个顶点额外增加若干纹理坐标对--每对纹理坐标对应于某一层被启用的纹理。例如,如果要将3层纹理进行融合,则每个顶点都必须有3对分别与这3层纹理对应的纹理坐标。这样,需要使用具有3层纹理的多重纹理的顶点结构就可这样来定义:
struct MultiTexVertex
{
MultiTexVertex(float x,float y,float z,float u0,float v0,float u1,float v1,float u2,float v2)
{
_x = x; _y = y; _z = z;
_u0 = u0;_v0 = v0;
_u1 = u1;_v1 = v1;
_u2 = u2;_v2 = v2;
}
float _x,_y,_z;
float _u0,_v0;
float _u1,_v1;
float _u2,_v2;
static const DWORD FVF;
};
const DWORD MultiTexVertex:FVF = D3DFVF_XYZ | D3DFVF_TEX3;
注意,上面的灵活顶点格式定义中,使用了标记D3DFVF_TEX3,这表明该结构包含了3个纹理坐标对。固定功能流水线至多支持8层纹理。如果你想使用的纹理层数超过8,那必须使用顶点声明和可编程顶点流水线。
注意:在较新的像素着色器版本中,我们可用一个纹理坐标对来索引多个纹理,这样就无需使用多个顶点坐标时,当然,这样做的前提是我们对每个纹理层都是用了相同的纹理坐标。如果每层纹理中的纹理坐标存在差异,我们就需要定义多个纹理坐标对。
像素着色器的输入和输出:
像素着色器的输入包括每个像素颜色和纹理坐标。
注意:顶点的颜色应在图元的整个表面上进行插值处理。
每个像素的纹理坐标其实就是指定了纹理中将被映射到当前像素的纹理元的坐标(u,v),在进入像素着色器之前,Direct3D先根据顶点颜色和顶点纹理坐标计算出每个像素的颜色和纹理坐标。输入像素着色器的颜色和纹理坐标对的个数由顶点着色器输出的颜色和纹理坐标对的个数决定。我们需借助语义语法来将输入的颜色和纹理坐标映射为像素着色器程序中的变量。仍然用前面的例子来说明,我们可以写作:
struct PS_INPUT
{
vector c0 : COLOR0;
vector c1 : COLOR1;
float2 t0 : TEXCOORD0;
float2 t1 : TEXCOORD1;
float2 t2 : TEXCOORD2;
}
就输出而言,像素着色器将输出计算所得的每个像素的单个颜色值。
struct PS_OUTPUT
{
vector finalPixelColor: COLOR0;
}
使用像素着色器的步骤:
下面列出了要创建和使用像素着色器所必须采取的步骤。
(1)编写像素着色器并进行编译。
(2)创建一个IDirect3DPixelShader9接口对象,来表示基于经过编译的着色器代码的像素着色器。
(3)用IDirect3DDevice::SetPixelShader 方法启用像素着色器。
当然,当像素着色器使用完毕后,我们必须对其进行销毁。
像素着色器的编写和编译:
像素着色器的编译方式与顶点着色器完全相同。首先,我们必须编写好一个像素着色器程序。在本书中,我们用HLSL语言来编写着色器程序。一旦着色器代码编写好,我们就可用函数D3DXComplierShaderFromFile对着色器程序进行编译,前面我们讲到该函数将返回一个指向ID3DXBuffer接口的指针,该接口包含了经过编译的着色器代码。
注意:由于我们打算使用像素着色器,所以必须将编译目标修改为像素着色器目标(如ps_2_0)而非顶点着色器目标(如vs_2_0).编译目标是通过函数D3DXComplieShaderFromFile中的一个桉树来指定的。
像素着色器的创建:
一旦着色器代码经过了编译,我们就可借助下述方法获取指向IDirect3DPixelShader9接口的指针,该接口代表了一个像素着色器。
HRESULT CreatePixelShader(
CONST DWORD * pFunction,
IDirect3DPixelShader9 ** ppShader
);
pFunction 指向经过编译的着色器代码的指针。
ppShader 返回一个指向IDirect3DPixelShader9接口的指针。
例如:假定变量shader 是 ID3DXBuffer接口的对象,它包含了经过编译的着色器代码。为了获取IDirect3DPixelShader9接口的指针,我们可以这样做:
IDirect3DPixelShader9 * MultiTexPS = 0;
hr = Device -> CreatePixelShader(
(DWORD*)Shader -> GetBufferPointer(),
&MultiTexPS);
再次强调,函数D3DXComplierShaderFromFile将返回经过编译的着色器代码(shader).
像素着色器的设置:
当获取了代表像素着色器的接口IDirect3DPixelShader9的指针后,我们可用如下方法将其启用:
HRESULT SetPixelShader(
IDirect3DPixelShader9 *pShader
);
该方法只接收一个参数,我们可将指向希望启用的像素着色器的指针传给该参数。为了启用创建的着色器,可以这样:
Device -> SetPixelShader(MultiTexPS);
像素着色器的销毁:
像Direct3D的所有其他接口一样,接口IDirect3DPixelShader9在使用完毕之后也必须调用其自身的Release方法来释放它所占用的资源。
d3d::Release<IDirect3DPixelShader9*>(MultiTexPS);
HLSL采样器对象:
要在像素着色器对纹理进行采样,可使用专门与tex * 相关的HLSL内置函数。
注意:采样是根据像素的纹理坐标和采样状态(texture filter state,纹理过滤器状态)来检索某一像素所对应的纹理元。
通常这些函数多需要指定两件事:
用于检索纹理的纹理坐标(u,v).
我们想要检索的特定纹理。
纹理坐标(u,v)当然是作为像素着色器的输入。我们想要检索的特定纹理在像素着色器中用一个特别的HLSL对象,采样器来标识。我们可将sampler对象视作标识纹理层和采样级的对象。例如:假定我们要使用3层纹理,这就意味着我们应该能够在像素着色器中引用每层纹理。在像素着色器程序中,我们可以这样写:
sampler FirstTex;
sampler SecondTex;
sampler ThirdTex;
Direct3D将把每一个sampler对象唯一地与某一纹理层关联起来。在应用程序中,我们只需找出sampler对象所对应的纹理层,然后为该纹理层设置合适的纹理及其相应的采样器状态。下面的代号示范了如何为FirstTex设置纹理和采样器状态:
// Create texture
IDirect3DTexture9 * Tex;
D3DXCreateTextureFromFile(Device,"tex.bmp",&Tex);
//Get handle to constant
FirstTexHandle = MultiTexCT -> GetConstantByName(0,"FirstTex");
//Get a desription of the constant;
D3DXCONSTANT_DESC FirstTexDesc;
UINT count;
MultiTexCT -> GetConstantDesc(FirstTexHandle,&FirstTexDesc,&count);
Device -> SetTexture(FirstTexDesc.RegisterIndex,Tex);
Device -> SetSampleState(FirstTexDesc.RegisterIndex,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(FirstTexDesc.RegisterIndex,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device -> SetSampleState(FirstTexDesc.RegisterIndex,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
除了使用sampler类型外,还可使用更具体,类型检查更严格的sampler1D,sampler2D,sampler3D以及samplerCube类型。这些类型在类型安全上更为突出,并能够保证上述类型仅可用于相应的tex*函数中。例如,一个sampler2D对象只能用在tex2D*函数中。类似的,一个sampler3D对象只可用在tex3D*函数中。