天天看點

o3d紋理映射(轉)

這一章講紋理,紋理,不能顧名思義了,其實就是一張圖檔,我們要做的就是把這張圖檔貼到模型上面,進而讓模型一下子生動起來,說得有文采點就是栩栩如生,這個過程就叫紋理映射(也可以叫貼圖)。紋理映射有時候能産生非常神奇的效果,比如說凹凸貼圖,和凹凸貼圖衍生出來的法線貼圖,能夠讓隻有幾百個幾千個多邊形的模型産生幾萬個,幾十萬個多邊形的模型的效果。

         接下來就要介紹怎麼建立一個紋理采樣器 (texture sampler) ,如何設定采樣的屬性,然後把一張圖檔(紋理)成功地貼到那個立方體上面,圖檔的格式可以是 TGA , JPEG , PNG , DDS ,其中像 TGA,PNG 這樣的格式是支援半透明的,可以用來模拟陰影(當然隻是簡單地模拟)。

紋理采樣器負責怎麼把一張圖檔上的各個像素映射到模型的像素上,反過來說,就是為每個像素選取紋理像素。看似很簡單,但是當顯示的圖檔大于(或小于)要覆寫的那個面時,就得就結了,這個不能一一映射了,那得怎麼做呢,這是門學問,我們可以通過設定這個紋理采樣器的各個屬性來達到較好的效果。

Address Mode

首先是 addressModeU 和 addressModeV 屬性,這兩個屬性決定了當紋理坐标超出( 0 , 1 )後該怎麼做。紋理坐标,顧名思義,就是在紋理上的坐标,這是我們截取紋理中的一部分是要用到的。它的範圍是( 0 , 1 )。其中左下角的坐标是 (0.0 , 1.0) ,右上角的坐标是 (1.0 , 1.0)

         這兩個屬性有下面四種可能值

Value

 Meaning

WRAP

 ( 預設為此值)重複貼圖來填充貼圖區域

MIRROR

 重複貼圖,當遇到 UV 的邊界時反向( UV 邊界為 0.0 或者 1.0 )

CLAMP

 用貼圖的最後一行像素,重複覆寫沒貼到的區域

BORDER

 超過的貼圖區域,用一種特殊顔色的邊标記出來

Minification Filter

Minification filter, 即 minFilter 這個屬性具體說明了怎麼把紋理貼到一個像素比它少的模上面(這個上面有提到過),這個屬性可能有下面 3 個值

Value

 Meaning

POINT

 用最近的那個像素

LINEAR

 線性插值

ANISOTROPIC

 各向異性過濾

這三個濾鏡效果是單調遞增的,具體是怎麼實作的,這裡不多講了 ( 其實也不會講 -  -) ,想了解的話可以去看圖像處理方面的書籍。。。。

Mipmap Filter

     由于處理縮小紋理比處理放大紋理複雜多了,如果隻是簡單地取最近的那個像素,或者說隻是和這個像素的周圍幾個像素簡單的比較一下,獲得平均值就插進去的話,會嚴重降低圖檔縮小後的品質,而如果去周圍大量的像素,做大量的計算又會嚴重降低程式效率。于是有才的人就想到了 mipmap 這個方法。它先将這張圖檔按原來尺寸一半一半地壓縮,舉個例子,原來的是 64*64 的圖檔,就壓縮成一張 32*32 的,一張 16*16 的,一張 8*8 的......當這張圖檔需要映射成 20*20 的大小的,就取 32*32 和 16*16 這兩張圖檔來計算出 20*20 大小的圖檔,這比隻是計算單張圖檔的效率和效果要好得多 .

     下面是這個參數可能的值

Value of mipFilter

 Which Mipmap Is Used

NONE

 不用mipmap

POINT

 取與需要映射的大小最近的那張圖檔,然後依據上面的minification filter 進行過濾

LINEAR

 取與需要映射的大小最近的兩張圖檔,為每張圖檔按照上面的miniFilter 進行過濾,然後把這兩張圖檔一起進行線性插值等到最終的圖檔

   Magnification Filter

     放大紋理的處理就比較簡單了,有下面兩種可能的值。

Value

 Meaning

POINT

 Use the closest pixel.

LINEAR

 Perform a linear interpolation between neighboring pixels and use the result.

  跟 minFilter 差不多。

   Border Color

     目前面的 address mode 設成 border 時,這個屬性就派上用場了,它可以用來設定那個邊框的顔色。

   Anisotropy

     目前面的 minFilter 是 Anisotropy 時,這個值就是決定了各向異性過濾的品質。

    這些屬性介紹完了,下面就舉個執行個體,把那張求是潮的 logo 貼到那個立方體上。

     首先要建立一個 texture sampler ,同 shape 和 material 一樣,這也是一個 Object ,非常好地展現了"面向對象"的思想。

g_sampler = g_pack . createObject ( 'Sampler' );

g_sampler . minFilter = g_o3d . Sampler . ANISOTROPIC ;

g_sampler . maxAnisotropy = 4 ;

     可以看到第一句就是建立 sampler 對象,後面兩句就是設定上面提到的屬性, minFilter 設為各向異性過濾, maxAnisotropy 設為 4 。

     這樣一個簡單的 sampler 就建立了,但是比較打擊人的是這個還隻是設了下最基本的參數,這個參數設了給誰用呢,又是在哪裡把圖檔貼到那個模型上面去的呢

     恩 ~ 在 o3d 裡面紋理貼圖就是在 pixel shader 裡面用了一個函數 tex2D 把圖檔像素一個個根據上面設好的各個參數填進已經經過 vertex shader 變換和 primitive assembly (就是 shape 一章裡講過的把各個頂點按照一定規則連接配接成圖元的操作)了的東西裡面。而傳給 tex2D 的參數有兩個,一個是一開始提到的紋理坐标( texCoord ),還有一個就是采樣器的各個參數了(這裡面也包括了整個紋理素材),要把 js 中設的參數傳到 shader 裡面, o3d 中的做法是先在 shader 裡面建一個變量,比如 texSampler0 ,然後在 js 中用 material 的 getParam 方法擷取這個 texSampler0 變量,具體的如下:

effect . createUniformParameters ( material );   // 這句話是不可少的,否則就不能 擷取shader 裡面設的變量了

var samplerParam = material . getParam ( 'texSampler0' );

samplerParam . value = g_sampler ;            // 把 g_sampler 的參數賦給 samplerParam

    這裡的 material 和 effect 就是前面幾章為那個立方體建立的材質對象。是以可把上面幾句代碼加在原來的建立 material 和 effect 的代碼的後面 ( 具體的 shader 代碼在最後放出來 )

     下面要做的是把紋理資源載入記憶體。

o3djs . io . loadTexture ( g_pack , textureUrl , function ( texture ) {

          // set the texture on the sampler object to the newly created texture

      // object returned by the request.

      g_sampler . texture = texture ; // 可以看到這裡把紋理資源也附到sampler 裡 了以供shader 使用

})

其中 function(texuture) 。。。。。是回調函數,這裡聲明成匿名函數了( js 非常好用的一個特性),如果要處理的量比較大的話,可以獨立出來寫成一個函數。也可以在其它紋理載入的時候使用。 textureURL 顧名思義就是圖檔位址了。

     最後是聲明各個頂點的紋理坐标,前面說到過要傳給 shader 的,這個和聲明頂點坐标差不多(頂點坐标數組也得相應的改過,不能幾個面公用同一個頂點了,具體的建 sample 裡的代碼吧,再放出來就太長了 = = )

var texCoordsArray = [

    0 , 0 ,

    1 , 0 ,

    1 , 1 ,

    0 , 1 ,

    0 , 0 ,

    1 , 0 ,

    1 , 1 ,

    0 , 1 ,

    1 , 1 ,

    0 , 1 ,

    0 , 0 ,

    1 , 0 ,

    0 , 0 ,

    1 , 0 ,

    1 , 1 ,

    0 , 1 ,

    0 , 0 ,

    1 , 0 ,

    1 , 1 ,

    0 , 1 ,

    0 , 0 ,

    1 , 0 ,

    1 , 1 ,

    0 , 1

  ];

var texCoordsBuffer = g_pack . createObject ( 'VertexBuffer' );

var texCoordsField = texCoordsBuffer . createField ( 'FloatField' , 2 );

texCoordsBuffer . set ( texCoordsArray );

streamBank . setVertexStream (

   g_o3d . Stream . TEXCOORD ,   // semantic

    0 ,                       // semantic index

   texCoordsField ,         // field

    0 );                     // start_index

不多做解釋了。

最後的最後:

Shader 代碼

// World View Projection matrix that will transform the input vertices

  // to screen space.

  float4x4 worldViewProjection : WorldViewProjection;

  // The texture sampler is used to access the texture bitmap in the fragment

  // shader.

  sampler texSampler0;

  // input for our vertex shader

  struct VertexShaderInput {

    float4 position : POSITION;

    float2 tex : TEXCOORD0;  // Texture coordinates

  };

  // input for our pixel shader

  struct PixelShaderInput {

    float4 position : POSITION;

    float2 tex : TEXCOORD0;  // Texture coordinates

  };

  PixelShaderInput vertexShaderFunction(VertexShaderInput input) {

    PixelShaderInput output;

    // Multiply the vertex positions by the worldViewProjection matrix to

    // transform them to screen space.

    output.position = mul(input.position, worldViewProjection);

    output.tex = input.tex;

    return output;

  }

  float4 pixelShaderFunction(PixelShaderInput input): COLOR {

    return tex2D(texSampler0, input.tex);

  }

  // Here we tell our effect file *which* functions are

  // our vertex and pixel shaders.

  // #o3d VertexShaderEntryPoint vertexShaderFunction

  // #o3d PixelShaderEntryPoint pixelShaderFunction

  // #o3d MatrixLoadOrder RowMajor

Shader 的代碼就不解釋了,下一章會比較詳細地講下 shading language 和圖形渲軟管線到底是怎麼樣的,下下章可能會講光照,光照就是都在 shader 裡面計算了,這個純考數學實體知識啊,不過幸好已經有前人弄好的公式給我們套了。然後就是講下陰影 (shadow) 的生成了,注意的是并不是有光照就會自然而然地産生陰影了,陰影的處理是遊戲中比較關鍵的,也是很占資源的一個地方。陰影處理的好的話遊戲的真實性倍增啊啊。。。。

還有要說的就是 shapes 和這章的紋理映射以後不會這麼詳細地用到,不會讓你一個個頂點地去定義,更多的是載入一個用 3d 軟體諸如 3DMAX 或 maya 做的模型,或者是用自帶的函數簡單地創個球,畢竟像這次這樣建立一個帶有貼圖的立方體會讓人崩潰的。