游戏开发基础(十八)

第十八章
像素着色器(pixel shader)是对每个像素进行光栅化处理期间运行在图形卡GPU上的一段程序.(不同于顶点着色器,Direct3D不会以软件运算方式来模拟像素着色器)像素着色器实质上市取代了固定功能流水线中的多重纹理(multitexturing)环节,而且赋予了直接操作单个像素以及访问每个像素的纹理坐标的能力。这种对像素和纹理坐标直接访问的能力使得能够获得各种各样的特殊效果,例如多重纹理,景深(depth of field),云彩模拟,火焰模拟以及比较复杂的阴影技术

可以通过检查D3DCAPS9结构成员PixelShaderVersion,并与宏D3DPS_VERSION进行比较,来测试图形卡是否支持某个顶点着色器版本
例:
 // If the device's supported version is less than version 2.0
 if (caps.PixelShaderVersion < D3DPS_VERSION(2,0))
  // then pixel shader version 2.0 is not supported on this device

多重纹理可能是像素着色器实现的最简单的技术,由于像素着色器取代了固定功能流水线中的多重纹理环节,应该对多重纹理环节及其功能有基本的理解

多重纹理是一种较为复杂的运算,当时认为它属于高级主题,此外,固定功能的多重纹理已被新的而且更为强大的像素着色器所取代,因此没有在固定功能多重纹理环节投入精力展开讨论是有意义的

多重纹理涵含的思想与融合有些关联,在第7章中,了解了如何将正在进行光栅化的像素与先前已写入后台缓存中的像素进行融合,以获得某种特殊效果。将这种思想沿用到多重纹理中,即,同时启用若干层纹理,并定义这些纹理的融合方式,以获得某种特殊效果
。多重纹理的一种典型应用就是进行光照运算,在顶点运算阶段不打算使用Direct3D的光照模型,而是使用一种特殊的纹理图,即光照纹理图(light maps),它规定了某一表面是如何被照亮的。例如,假定希望在一个较大的板条箱上投射一个聚光灯,可以结构D3DLIGHT9来定义该聚光灯,也可将代表板条箱的纹理图与代表聚光灯的纹理图进行融合

由第7章介绍的融合技术可知,最终得到的图像与这些纹理的融合方式有关,在固定功能多重纹理环节,是通过纹理绘制状态对融合方程进行控制的,而借助像素着色器,可以在代码中以可编程方式将融合函数写为一个简单表达式,这就使得能够以任何方式对多个纹理进行融合运算

通过对多个纹理进行融合来照亮板条箱与Direct3D的光照计算模型相比有以下优点:
#光照已预先计算好,并保存在聚光灯光照纹理图中,这样,在程序运行时,就不需要再对光照进行运算,从而节省了运算时间,当然,只有对于静止的物体和固定光源方可预先进行光照计算
# 由于光照纹理图是预先计算好的,可采用比Direct3D更精确,更复杂的光照模型(从而可以在更逼真的场景中获得更好的光照结果)

注意:多重纹理环节一般用于为静态物体实现一个完整的光照计算引擎,例如,现有一个保存了物体颜色信息的纹理图,如板条箱纹理图,此外还有一个用于保存漫反射表面明暗度的漫射光纹理图(diffuse light map),一个用于保存镜面反射表面的明暗度的镜面光纹理图(specular light map),一个用于保存表面上所覆盖的雾的总量的雾效纹理图(fog map),以及一个用于保存表面的微观高频细节的细节纹理图(detail map),当需要将这些纹理合成时,只需要对预先计算好的纹理图进行查询即可高效地为场景增加光照,上色并增加细节

注意:聚光灯纹理图(spotlight light map)是基本光照纹理图中较简单的一种,通常,当给定场景和光源时,用专门的程序来生成光照纹理图,用专门的程序来生成光照纹理图

启用多重纹理
纹理可用方法 IDirect3DDevice9::SetTexture来启用,采样器状态(sampler state)可用方法 IDirect3DDevie9::SetSamplerState来设置
例:

HRESULT IDirect3DDevice9::SetTexture(DWORD Stage,IDirect3DBaseTexture9* pTexture);

HRESULT IDirect3DDevice9::SetSamplerState(DWORD Sampler,D3DSAMPLERSTATETYPE Type,DWORD Value);

注意:一个特定采样器的级数(一下简称为采样级)索引(sampler stage index)i与第i层纹理是关联的,即,第i个采样级为第i层纹理指定了采样器状态

纹理层/采样级索引标识了想要将纹理/采样器设置成的纹理层/采样级,这样,就可激活多个纹理,并通过不同的层索引来为这些纹理设置相应的采样状态,在此之前,总是将层索引指定为0(表示第一层索引),这是因为那时只需要使用一层纹理就够了,如果需要3层纹理,可用层序号0,1,2分别标识每一层纹理:
//set first texture and corresponding sampler states
Device->SetTexture(0,Tex1);
Device->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);

// set second texture and corresponding sampler states
Device->SetTexture(1,Tex2);
Device->SetSamplerState(1,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(1,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(1,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);

// set third texture and corresponding sampler states
Device->SetTexture(2,Tex3);
Device->SetSamplerState(2,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(2,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(2,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);

多重纹理坐标

每个3D三角形,需要在纹理图中为之定义一个相应的三角形,以确定被映射到该3D三角形中的纹理数据,以前是通过为每个顶点增加纹理坐标来实现的,这样,定义了一个三角形的每3个顶点在纹理中就定义了一个相应的三角形

现在需要使用多重纹理,对于定义了三角形的每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;
}

cosnt DWORD MultiTexVertex::FVF = D3DFVF_XYZ|D3DFVF_TEX3;
#D3DFVF_TEX3 表面该结构包含了3个纹理坐标对,固定功能流水线至多支持8层纹理,如果想使用的纹理层数超过8,则必须使用顶点声明和可编程顶点流水线

在较新的像素着色器版本中,可用一个纹理坐标对来索引多个纹理,这样就无需使用多个顶点坐标对,当然,这样做的前提是对每个纹理层都是用了相同的纹理坐标,如果每层纹理中的纹理坐标存在差异,则需要多个纹理坐标对


像素着色器的输入和输出

像素着色器的输入包括每个像素颜色和纹理坐标

注意 顶点的颜色应在图元的整个表面上进行插值处理
每个像素的纹理坐标其实就是指定了纹理中将被映射到当前像素的纹理元的坐标(u,v).在进入像素着色器之前,Direct3D 先根据顶点颜色和顶点纹理坐标计算出每个像素的颜色和纹理坐标。输入像素着色器的颜色和纹理坐标对的个数由顶点着色器输出的颜色和纹理坐标对的个数决定,例如,如果一个顶点着色器输出两种颜色值和3个纹理坐标对,则Direct3D将计算出每个像素的两种颜色值和3个纹理坐标对,并将这些结果输入像素着色器。我们需借助语义语法来将输入的颜色和纹理坐标映射为像素着色器程序中的变量
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用IDirect3DDevice9::SetPixelShader方法启用像素着色器

像素着色器使用完毕后,必须对其进行销毁

像素着色器的编写和编译
像素着色器的编译方式与顶点着色器完全相同,首先,必须编写好一个像素着色器程序,用HLSL语言来编写着色器程序,用函数D3DXCompileShaderFromFile对着色器程序进行编译、

注意,使用像素着色器,必须将编译目标修改为像素着色器目标(如ps_2_0)而非顶点着色器目标(如vs_2_0),编译目标是通过函数D3DXCompileShaderFromFile中的一个参数来指定的

像素着色器的创建
一旦着色器代码经过了编译,就可借助下述方法获取指向IDirect3DPixelShader9接口的指针,该接口代表了一个像素着色器
HRESULT CreatePixelShader(
    CONST DWORD *pFunction,
    IDirect3DPixelShader9** ppShader);

#pFunction 指向经过编译的着色器代码的指针
#ppShader 返回一个指向IDirect3DPixelShader9接口的指针
例:假定变量shader是ID3DXBuffer接口的对象,它包含了经过编译的着色器代码,为了获取IDirect3DPixelShader9接口的指针,例:
IDirect3DPixelShader9* MultiTexPS = 0;
hr = Device->CreatePixelShader((DWORD*)Shader->GetBufferPointer(),
    &MultiTexPS);

像素着色器的设置
当获取了代表像素着色器的接口IDirect3DPixelShader9指针后,启用:
HRESULT SetPixelShader(IDirect3DPixelShader9* pShader);
该方法只接收一个参数,可将指定希望启用的像素着色器的指针传给该参数,
例:
Device->SetPixelShader(MultiTexPS);

像素着色器的销毁
接口IDirect3DPixelShader9在使用完毕后必须调用其自身的Release方法来释放它所占用的资源
d3d::Release<IDirect3DPixelShader9*>(MultiTexPS);

HLSL采样器对象
要在像素着色器对纹理进行采样,可使用专门与tex*相关的HLSL内置函数。
注意,采样是指根据像素的纹理坐标和采样状态(texture filter state,纹理过滤器状态)来检索某一像素所对应的纹理元

通常,这些函数都需要指定两件事
#用于检索纹理的纹理坐标(u,v);
#想要检索的特定纹理
纹理坐标(u,v)当然是作为像素着色器的输入,想要检索的特定纹理在像素着色器中用一个特别的HLSL对象---采样器(sampler)来标识,可将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 description of the constant;
D3DXCONSTANT_DESC FirstTexDesc;
UINT count ;
MultiTexCT->GetConstantDesc(FirstTexHandle,&FirstTexDesc,&count);
...
// set texture/campler states for the sampler FirstTex ,we identify
// the stage FirstTex is associated with form the
// D3DXCONSTANT_DESC::RegisterIndex member;
Device->SetTexture(FirstTexDesc.RegisterIndex,Tex);
Device->SetSamplerState(FirstTexDesc.RegisterIndex,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(FirstTexDesc.RegisterIndex,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
Device->SetSamplerState(FirstTexDesc.RegisterIndex,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);

注意,除了使用sampler类型外,还可使用更具体,类型检查更严格的sampler1D,sampler2D,sampler3D以及samplerCube类型,这些类型在类型安全上更为突出,并能够保证上述类型仅可用于相应的tex*函数中,例如,一个sampler2D对象只能用在tex2D*函数中,类似地,一个sampler3D对象只能用在tex3D*函数中.
(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值