1. 程式人生 > >象素shader入門(Introduction to Pixel Shaders)

象素shader入門(Introduction to Pixel Shaders)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

  一個pixel shader是在每一畫素光柵化處理期間物理卡的GPU上執行的一種程式。(不像vertex shaderDirect3D不會用軟體方法仿效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 in3D Games: Real-time Rendering and Software Technology.

點光光圖是一個對非常基本的光圖中價值不高的例子。代表性的,特殊的程式被用於當給定一個場景和一個光源時產生光圖。產生光圖超出了教程的範圍。對於感興趣的學生。Alan WattFabio 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/samplertexture/sampler階段。因而,我們能開啟多重紋理並且用不同的階段索引設定它們的相應取樣階段。在以前的講解中,我們通常指定0,表示第一階段,因為我們在一個時間只使用一層紋理。所以舉個例子,如果我們需要開啟三層紋理,我們用階段012象這樣:
// 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 coordinatesinput 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 variableshader is an ID3DXBuffer that contains the compiled shader code. Then to obtain anIDirect3DPixelShader9 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 shaderIDirect3DPixelShader9表面的指標後,我們開以用下面的方法開啟(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宣告三個取樣器物件,每一個對應我們正在混合的一個紋理。接下來inputoutput結構被定義。注意我們沒把任何顏色值輸入這個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);