象素shader入门(Introduction to Pixel Shaders)

  一个pixel shader是在每一像素光栅化处理期间物理卡的GPU上执行的一种程序。(不像vertex shader,Direct3D不会用软件方法仿效pixel shader的功能。)。它本质上代替了固定管道功能中的多重纹理阶段并给了我们直接操作单独像素和访问每一个像素纹理坐标的能力。这种直接访问像素和纹理坐标允许我们完成多种特殊效果,比如多重纹理、逐像素光照、视野深度、云层仿真、火焰仿真和复杂阴影技术。

你可以通过检查D3DCAPS9结构体的PixelShaderVersion成员和D3DVS_VERSION宏来检测你的物理卡支持的pixel shader版本。下面的代码片断举例说明了这个:

// 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.

Objectives

·         获得关于多重纹理概念的基本理解

·         学习如何写、创建并应用pixel shaders

·         学习如何应用pixel shader执行多重纹理

一、多重纹理概述(Multitexturing Overview)

多重纹理可能是用pixel shader可以执行的最简单的技术。此外,由于pixel shader替换了多重纹理阶段,接下来我们需要对多重纹理阶段是什么和如何做具有一些基本的理解。这一节简单概述一下多重纹理。

    当我们最初在以前的章节揭示纹理时,我们有两个原因在固定功能管道中省略讨论多重纹理:第一,multitexturing 是有点棘手的,并且我们当时考虑它为一种高级话题。另外,固定功能多重纹理阶段被新的更强大的pixel shader代替了。因此有意不花时间在过时的固定功能多重纹理阶段。

多重纹理背后的思想是有些方面代替混合(blending)。在前面讲混合的章节中我们学到用先前已写入后台缓冲的像素混合正被光栅化的纹理以完成一个特殊效果。我们扩展这个同样的思想到多重纹理。即,我们一次开启几个纹理并定义这些纹理如何混合到一起以完成一种特殊效果。多重纹理的一个普通应用是做光照(lighting)。代替应用Direct3D的在顶点处理阶段的光照模式,我们应用被称为光图(light map)的特殊的纹理图,它编码了一个表面如何被照明。例如,假设我们想用一个点光照亮一个很大的箱子。我们可以定义一个D3DLIGHT9结构的点光源,或者我们可以把一个表现箱子的纹理图与一个表现点光的光图混合在一起,如图Figure 18.1所示。


Figure 18.1: Rendering a crate lit by a spotlight using multitexturing. Here we combine the two textures by multiplying the corresponding texels together.

 

Note 

如“混合”那一章所介绍的,结果图形依赖于纹理如何混合。在固定功能多重纹理阶段,混合方程式控制了整个纹理混合阶段。通过pixel shader我们可以把混合函数编程在代码中成为一种简单表达式。这允许我们用任何我们想要的方法混合纹理。在我们讨论这一节的例程中我们详细说明混合纹理。

 

    混合纹理(这一例子中是两个)以照明箱子有两个比Direct3D光照的益处:

·         光照是在点光光图中预先计算好的。因而,光照不需要在运行时被计算,这节省了处理时间。当然,光照只能预先计算静态物体和静态光。

·         因为光图是预先计算的,我们就能比Direct3D模式应用更精确更变化多样的光照模式。(在很多现实场景中更好的光照结果)

 

Remark: 

多重纹理阶段的一个代表性应用是实现一个对静态物体的完全光照引擎(full lighting engine for static objects)。例如,我们可能有一个存贮物体颜色的纹理图,如一个箱子纹纹理图。然后我们可能有一个漫反射光图以保存漫反射表面阴影,并有一个细节图保存表面上小的、高发生率的细节。当这些所有纹理组合在一起时,它仅仅应用查找预先计算好的纹理表现场景的光、颜色和细节。

 

 

 

Note 

The spotlight light map is a trivial example of a very basic light map. Typically, special programs are used to generate light maps given a scene and light sources. Generating light maps goes beyond the scope of this book. For the interested reader, Alan Watt and Fabio Policarpo describe light mapping in 3D Games: Real-time Rendering and Software Technology.

点光光图是一个对非常基本的光图中价值不高的例子。代表性的,特殊的程序被用于当给定一个场景和一个光源时产生光图。产生光图超出了教程的范围。对于感兴趣的学生。Alan Watt 和Fabio Policarpo 描述了3D游戏中的光图:Real-time Rendering and Software Technology.

 

1.1 开启多重纹理(Enabling Multiple Textures)

    回忆一下纹理通过IDirect3DDevice9::SetTexture方法设置并且取样器阶段通过IDirect3DDevice9::SetSamplerState方法设置,原形如下:

HRESULT IDirect3DDevice9::SetTexture(
     DWORD Stage, // specifies the texture stage index
     IDirect3DBaseTexture9 *pTexture
);
 
HRESULT IDirect3DDevice9::SetSamplerState(
     DWORD Sampler, // specifies the sampler stage index
     D3DSAMPLERSTATETYPE Type,
     DWORD Value
);
 

 

Note 

一个特殊的采样器阶段索引i是关联到第i纹理阶段。即,第i采样阶段指定采样阶段为第i次设置纹理。

 

        texture/sampler 阶段索引识别我们想设置texture/sampler的texture/sampler阶段。因而,我们能开启多重纹理并且用不同的阶段索引设置它们的相应采样阶段。在以前的讲解中,我们通常指定0,表示第一阶段,因为我们在一个时间只使用一层纹理。所以举个例子,如果我们需要开启三层纹理,我们用阶段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);

这一代码开启了Tex1, Tex2, and Tex3并设置每一纹理的采样模式。

1.2 多重纹理坐标(Multiple Texture Coordinates)

回忆以前章节有关每个3D三角形,我们想定义一个被画于3D三角形上的相应的纹理上的三角形。我们通过加入纹理坐标到每一顶点做到这点。因而,每三个三角形顶点定义一个相应的纹理上的三角形。

    因为我们现在应用多重纹理,对于每三个顶点定义一个三角形我们需要定义一个相应的每个形启的纹理上的三角形。我们通过加入额外的对于每个顶点的纹理坐标的设置来做到这点—一种设置是为了,相应于,每个开启的纹理。例如,如果我们混合三重纹理到一起,而每个顶点必须有三个纹理坐标设置索引三个开启的坐标。因而,一个具有三重纹理的多重纹理的顶点结构体看起来象这样:

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; // Texture coordinates for texture at stage 0.
     float _u1, _v1; // Texture coordinates for texture at stage 1.
     float _u2, _v2; // Texture coordinates for texture at stage 2.
 
     static const DWORD FVF;
};
const DWORD MultiTexVertex::FVF = D3DFVF_XYZ | D3DFVF_TEX3;

    观察灵活顶点格式标志D3DFVF_TEX3被指定为表示顶点结构包含三重纹理坐标设置。固定功能管道支持8个纹理坐标设置。为了应用多于8的,你必须用一个顶点声明(vertex declaration)和可编程顶点管道。

 

Note 

在较机关报的pixel shader版本中,我们能用一个纹理坐标设置索引到多重纹理,因而移除多重纹理坐标的需要。当然这要假定同样纹理坐标被用于每一纹理阶段。如果每一阶段的纹理坐标不同,那么我们将仍然需要多重纹理坐标。

 

二、Pixel Shader Inputs and Outputs

两样东西被输入一个pixel shader:颜色和纹理坐标。两者都在每个象素。

 

Note 

回忆一下顶点颜色是被原始外观插值的。(Recall that vertex colors are interpolated across the face of a primitive.)

 

A per pixel texture coordinate is simply the (u, v) coordinates that specify the texel in the texture that is to be mapped to the pixel in question. Direct3D computes both colors and texture coordinates per pixel, from vertex colors and vertex texture coordinates, before entering the pixel shader. The number of colors and texture coordinates input into the pixel shader depends on how many colors and texture coordinates were output by the vertex shader. For example, if a vertex shader outputs two colors and three texture coordinates, then Direct3D will calculate two colors and three texture coordinates per pixel and input them into the pixel shader. We map the input colors and texture coordinates to variables in our pixel shader program using the semantic syntax. Using the previous example, we would write:

每一像素纹理坐标是简单的(u, v)坐标,那个被指定为纹理中的图素(texel),被映射到像素的东东。在进入pixel shader前,通过顶点颜色和顶点纹理坐标,Direct3D计算每一像素的颜色和纹理坐标两者。

struct PS_INPUT
{
     vector c0 : COLOR0;
     vector c1 : COLOR1;
     float2 t0 : TEXCOORD0;
     float2 t1 : TEXCOORD1;
     float2 t2 : TEXCOORD2;
};

对于输出,一个pixel shader输出一个单一的对于像素的计算后的颜色值:

struct PS_OUTPUT
{
     vector finalPixelColor : COLOR0;
};

 

三、使用pixel shader的步骤(Steps to Using a Pixel Shader)

下面的列表是创建和使用一个pixel shader的必要步骤的概要。

1.    写出并编译pixel shader。

2.    基于编译后的shader代码创建一个IDirect3DPixelShader9界面以表示pixel shader。

3.    通过IDirect3DDevice9::SetPixelShader方法开启pixel shader。

当然,我们必须用完后销毁这个pixel shader。下几个小节相信讲述这些步骤。

18.3.1Writing and Compiling a Pixel Shader

我们编译一个pixel shader与编译一个vertex shader采用同样方法。首先,我们必须写一个pixel shader程序。在本书中,我们用HLSL写我们的shader。一旦shader代码被写好,我们用D3DXCompileShaderFromFile函数进行编译,如以前章节介绍的一样。回忆一下这个函数返回一个指向ID3DXBuffer的指针以包括编译后的shader代码。

 

Note 

因为我们使用pixel shader,我们将要记住更改编译目标到pixel shader target (e.g., ps_2_0)替代一个 vertex shader target (e.g., vs_2_0)。编译目标能过D3DXCompileShaderFromFile函数的一个参数指定。以前章节有详细描述。

 

3.2创建一个pixel Shader(Creating a Pixel Shader)

一旦我们拥有了编译后的shader代码,我们就能获得一个指向IDirect3DPixelShader9表面的指针,它描述一个pixel shader,用下面的方法:

HRESULT IDirect3DDevice9::CreatePixelShader(
      CONST DWORD *pFunction,
      IDirect3DPixelShader9** ppShader
);

·         pFunction—指向编译后的shader代码的指针

·         ppShader—返回一个指针指向一个IDirect3DPixelShader9表面

For example, suppose the variable shader is an ID3DXBuffer that contains the compiled shader code. Then to obtain an IDirect3DPixelShader9 interface, we would write:

举例来说,假设变量shader是一个包括编译后的shader代码的ID3DXBuffer。然后为了获得一个IDirect3DPixelShader9表面,我们将这样写:

IDirect3DPixelShader9* MultiTexPS = 0;
hr = Device->CreatePixelShader(
           (DWORD*)shader->GetBufferPointer(),
           &MultiTexPS);
 

 

Note 

重申一下,D3DXCompileShaderFromFile是一个返回编译后的shader代码的函数。

 

3.3 设置一个 Pixel Shader

当我们已经获得一个指向一个描述我们的pixel shader的IDirect3DPixelShader9表面的指针后,我们开以用下面的方法开启(enable)它:

HRESULT IDirect3DDevice9::SetPixelShader(
      IDirect3DPixelShader9* pShader
);

这个方法得到一个单一的参数,这里我们传递一个指向我们想开启的pixel shader的指针。为了开启一个我们在3.2节创建的shader,我们这样写:

Device->SetPixelShader(MultiTexPS);

3.4销毁一个 Pixel Shader

如同所有Direct3D界面,为了清除它们我们必须在用完后调用它们的Release方法。继续用我们在3.2节创建的pixel shader,我们有:

d3d::Release(MultiTexPS);

四、HLSL采样器对象( HLSL Sampler Objects)

纹理在一个pixel shader中使用特定的tex*HLSL中的有关的内部函数 被采样。

 

Note 

采样参考一个图素(对于一个像素的基于纹理坐标的一个像素)和采样器状态(纹理过滤器状态texture filter states)。

 

查看前面章节中对于这些函数的详细描述。通常的,这些函数要求我们指定两件事:

·          (u, v)纹理坐标用于索引纹理

·         我们想要索引的详细的纹理

 (u, v)纹理坐标是,当然,由pixel shader的输入给定。我们想要索引的详细的纹理是在pixel shader中用一个特定的HLSL对象叫做一个采样器(sampler)指定的。我们可以想象一个采样器对象为一个识别一个纹理和采样阶段的对象。例如,假设我们正用到三个纹理阶段,这意味着我们需要能够涉及在pixel shader中的这些阶段的每一个。在pixel shader中,我们可以这样写:

sampler FirstTex;
sampler SecondTex;
sampler ThirdTex;

Direct3D将用一个唯一的纹理阶段关联这些采样器的每个对象。然后在应用程序中我们找到一个采样器对象相关的阶段并设置适当的纹理和这个阶段的采样器状态。下面的代码举例说明了应用程序如何设置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/sampler states for the sampler FirstTex. We identify
// the stage FirstTex is associated with from 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);
 

 

Note 

替代方案,代替使用sampler类型,我们能够使用更精确更健壮的类型为sampler1D, sampler2D, sampler3D,和 samplerCube的类型这些类型是更安全并确保它们只用于相应的tex*的函数。例如,一个sampler2D对象只能被tex2D*函数应用。同样的,一个sampler3D对象只能被tex3D*函数应用

 

五、例程: Multitexturing in a Pixel Shader

本节的例程示范了使用pixel shader的多重纹理。本例将基于图Figure 18.2中的"result"纹理化一个距形,用一个箱子纹理、一个点光纹理、一个包含字符串"Pixel Shader Sample."的纹理混合到一起。


Figure 18.2: Combining the textures. Let b, s, and t be the colors of corresponding texels from the crate texture, spotlight texture, and text texture, respectively. We define how these colors are combined as c = b ⊗ s + t, where ⊗ denotes component-wise multiplication.

    本例能不用pixel shader完成。然而,它能简单并直接的实现这一应用,它允许我们示范如何写、创建并使用pixel shader而不用心烦意乱于一些特殊效果的运算法则。

    尽管在本例中我们仅仅一次用到三个纹理,也值得检查用于每一pixel shader版本的采样器对象的版本号。换句话说,我们能一次用到多少个纹理取决于我们用的pixel shader版本。

·         Pixel shader versions ps_1_1 to ps_1_3 support up to four texture samples.

·         Pixel shader version ps_1_4 supports up to six texture samples.

·         Pixel shader versions ps_2_0 to ps_3_0 support up to 16 texture samples.

有两重纹理的多重纺理pixel shader执行如下:

//
// File: ps_multitex.txt
//
// Desc: Pixel shader that does multitexturing.
//
 
//
// Globals
//
 
sampler BaseTex;
sampler SpotLightTex;
sampler StringTex;
 
//
// Structures
//
 
struct PS_INPUT
{
     float2 base      : TEXCOORD0;
     float2 spotlight : TEXCOORD1;
     float2 text      : TEXCOORD2;
};
 
struct PS_OUTPUT
{
     vector diffuse : COLOR0;
};
 
//
// Main
//
 
PS_OUTPUT Main(PS_INPUT input)
{
     // zero out members of output
     PS_OUTPUT output = (PS_OUTPUT)0;
 
     // sample appropriate textures
     vector b = tex2D(BaseTex,      input.base);
     vector s = tex2D(SpotLightTex, input.spotlight);
     vector t = tex2D(StringTex,    input.text);
 
     // combine texel colors
     vector c =b *s +t;
 
     // increase the intensity of the pixel slightly
     c += 0.1f;
 
     // save the resulting pixel color
     output.diffuse = c;
 
     return output;
}

    首先pixel shader声明三个采样器对象,每一个对应我们正在混合的一个纹理。接下来input 和output结构被定义。注意我们没把任何颜色值输入这个pixel shader,这是因为我们正在应用专用于颜色和光照的纹理,就是说,持有我们的表面颜色的基本纹理和点光纹理是我们的光图(light map)。这个pixel shader只输出一个颜色值,指定对于特定的像素我们已计算好的颜色。

    主函数用tex2D函数采样了三个纹理。也就是说,它从每个画到这个我们现在正在计算的像素上的纹理上,基于指定的坐标和采样器对象,吸取图素。然后我们用公式c = b * s + t联合图素颜色。接着我们用加上0.1f到每个成员来加亮一点全面的像素颜色。最后,我们保存结果像素颜色并返回它。

    现在我们已经看到了实际的pixel shader代码,我们切换一下看一下应用程序代码。应用程序有如下有关的全局变量:

IDirect3DPixelShader9* MultiTexPS = 0;
ID3DXConstantTable* MultiTexCT    = 0;
 
IDirect3DVertexBuffer9* QuadVB = 0;
 
IDirect3DTexture9* BaseTex      = 0;
IDirect3DTexture9* SpotLightTex = 0;
IDirect3DTexture9* StringTex    = 0;
D3DXHANDLE BaseTexHandle      = 0;
D3DXHANDLE SpotLightTexHandle = 0;
D3DXHANDLE StringTexHandle    = 0;
 
D3DXCONSTANT_DESC BaseTexDesc;
D3DXCONSTANT_DESC SpotLightTexDesc;
D3DXCONSTANT_DESC StringTexDesc;

多重纹理例程的顶点结构这样定义:

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;

观察一下它包含三个纹理坐标设置。

Setup函数执行了下面的任务:

·         填充表示距形的顶点缓冲

·         编译pixel shader

·         创建pixel shader

·         加载纹理

·         设置放射距阵并关闭灯光

·         取得采样器对象的句柄

·         取得采样器对象的描述

bool Setup()
{
HRESULT hr = 0;
 
//
// Create quad geometry.
//
 
Device->CreateVertexBuffer(
     6 * sizeof(MultiTexVertex),
     D3DUSAGE_WRITEONLY,
     MultiTexVertex::FVF,
     D3DPOOL_MANAGED,
     &QuadVB,
     0);
 
MultiTexVertex*v =0;
QuadVB->Lock(0, 0, (void**)&v, 0);
 
v[0] = MultiTexVertex(-10.0f, -10.0f, 5.0f,
                       0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
v[1] = MultiTexVertex(-10.0f, 10.0f, 5.0f,
                       0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[2] = MultiTexVertex( 10.0f, 10.0f, 5.0f,
                       1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
 
v[3] = MultiTexVertex(-10.0f, -10.0f, 5.0f,
                       0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
v[4] = MultiTexVertex( 10.0f, 10.0f, 5.0f,
                       1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
v[5] = MultiTexVertex( 10.0f, -10.0f, 5.0f,
                       1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
 
QuadVB->Unlock();
 
//
// Compile shader
//
 
ID3DXBuffer* shader      = 0;
ID3DXBuffer* errorBuffer = 0;
 
hr = D3DXCompileShaderFromFile(
     "ps_multitex.txt",
     0,
     0,
     "Main", // entry point function name
     "ps_1_1",
     D3DXSHADER_DEBUG,
     &shader,
     &errorBuffer,
     &MultiTexCT);
 
// output any error messages
if( errorBuffer )
{
   ::MessageBox(0, (char*)errorBuffer->GetBufferPointer(), 0, 0);
   d3d::Release(errorBuffer);
}
 
if(FAILED(hr))
{
   ::MessageBox(0, "D3DXCompileShaderFromFile() - FAILED", 0, 0);
   return false;
}
 
//
// Create Pixel Shader
//
hr = Device->CreatePixelShader(
     (DWORD*)shader->GetBufferPointer(),
     &MultiTexPS);
 
if(FAILED(hr))
{
     ::MessageBox(0, "CreateVertexShader - FAILED", 0, 0);
     return false;
}
 
d3d::Release(shader);
 
//
// Load textures.
//
 
D3DXCreateTextureFromFile(Device, "crate.bmp", &BaseTex);
D3DXCreateTextureFromFile(Device, "spotlight.bmp", &SpotLightTex);
D3DXCreateTextureFromFile(Device, "text.bmp", &StringTex);
 
//
// Set projection matrix
//
 
D3DXMATRIX P;
D3DXMatrixPerspectiveFovLH(
           &P, D3DX_PI * 0.25f,
           (float)Width / (float)Height, 1.0f, 1000.0f);
 
Device->SetTransform(D3DTS_PROJECTION, &P);
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.****.net/jiangjunshow
上一篇:GitLab Wiki


下一篇:基于STM32F407的RTX5+RL-USB+RL-FlashFS+RL-TCPnet+emWin6.x综合模板发布,含MDK AC5和AC6(2021-02-15)