轉發自:https://www.cnblogs.com/lancidie/archive/2011/02/05/1949366.html
第六集 紋理映射技術
為使建立的3D模型更接近現實世界中的物體, 簡單的顏色變換已經無能為力, 這時我們就需要紋理映射技術了.
這一集我們講解基礎的紋理映射技術的數學模型, 對於在粒子系統使用的過程紋理技術在高級部分講解.
6.1 二維紋理映射
6.1.1 紋理映射的簡單建模
二維紋理映射就是從二維紋理平面到三維物體表面的映射. 一般二維紋理平面是有范圍限制的, 在這個平面區域內, 每點都可用數學函數表達, 從而可以離散的分離出每點的灰度值和顏色值, 這個平面區域稱為紋理空間, 一般將紋理空間的平面區域定義在[0, 1] * [0, 1].
紋理映射是確定物體表面一點P在紋理空間中的對應點(u, v), 從而紋理空間中的點(u, v)處的紋理值就是物體表面點P的紋理屬性. 這樣屏幕上顯示的像素的顏色可通過下面的映射得到,
F : Screen Space --> Object Space
G : Object Space --> Texture Space
其中F我們在第二集中已詳細講解過, 所以我們關心的是G. 只要確定了物體表面的紋理屬性, 接着就是將物體表面上各點所對應的紋理值作為光照明模型中的相應參數進行光強度計算, 再繪制畫面.
所以G函數的定義直接影響畫面的效果, 如何確定G非常重要. G可表示為,
(u, v) = H(x, y, z)
其中(u, v)和(x, y, z)分別是紋理空間和物體空間中的點. 現在從簡單的圓柱開始, 一個高h, 半徑為r的圓柱的紋理映射方法, 我們用參數形式表示圓柱,
x = r * cos a
y = r * sin a
z = h b
0 <= a <= 2PI, 0<= b <= 1
從紋理空間 [0, 1] * [0, 1] 到 [0, 2PI] * [0, 1] 的線性變換為,
u = a / 2PI
v = b
這樣, 我們建立了物體空間到紋理空間映射的表達式. 如圖6.1
圖6.1
物體表面沒有象圓柱一樣, 所以上面的方法不實用.
6.1.2 兩步法紋理映射
1986年, Bier和Sloan提出了一種獨立於物體表面的紋理影射技術, 將紋理空間到物體空間的映射分為兩個簡單的映射復合. 兩步法紋理映射的核心是引進一個包圍物體的中介三維曲面作為中間映射媒體, 主要過程分兩步,
(a). 將二維紋理映射到一個簡單的三維物體表面, 如平面, 球面, 圓柱面, 立方體表面, 采用不同的中間映射媒體生成的紋理效果是不同的, 要根據目標物體表面來選擇.
S : (u, v) --> (x', y', z') --- S-映射
如半徑為R的球的S-映射為,
x = R * cos a * sin b
y = R * sin a * sin b
z = R * cos b
(0 <= a <= 2PI, 0<= b <= PI)
(b). 將(a)的三維物體表面上的紋理映射到目標物體表面,
O : (x', y', z') --> (x, y, z) --- O-映射
O-映射一般有4種, 如圖6.2,
圖6.2
(1). 取視線在物體表面可見點(x, y, z)處的反射與
中間映射媒體表面上的交點(x', y', z')作為
(x, y, z)的映射點.
(2). 取物體表面可見點(x, y, z)處的法線與中間映射
媒體表面上的交點作為映射點.
(3). 取物體中心到可見點(x, y, z)的射線與中間映射
媒體表面上的交點作為映射點.
(4). 中間映射媒體表面上的點(x', y', z')處的法線
與物體表面的交點為(x, y, z), (x', y', z')
為(x, y, z)的映射點.
其中(1)映射方式應用的最廣, 稱為環境映射, 我們下一節會簡單的說明一下. 其余的三種O-映射和上面4種S-映射有12種組成, 其中可用的為5種, 如下表.
三種O-映射和4種S-映射有12種組成
| \ | | | | |
| \ S-映射 | 平面 | 圓柱面 | 立方體 | 球面 |
O-映射 \ | ||||
---|---|---|---|---|
表面法向(2) | 冗余 | 不適合 | 不太適合 | 不太適合 |
--------------- | ---------- | ---------- | ---------- | ---------- |
物體中心(3) | 冗余 | 不適合 | 適合 | 適合 |
--------------- | ---------- | ---------- | ---------- | ---------- |
中介面法向(4) | 適合 | 適合 | 適合 | 冗余 |
+---------------+----------+----------+----------+----------+
6.1.3 環境映射
環境映射是兩步法紋理映射的特例, 環境映射是近似的模擬光線跟蹤, 但沒有光線跟蹤那么復雜, 所以能大大提高光線跟蹤算法的效率, 目前在商業應用中廣泛使用的紋理影射技術. 環境映射的過程如圖6.3,
圖6.3
(1). 物體被中介曲面球包圍, 中介曲面球記錄了物體表面的紋理值.
(2). 視點的光線經過一像素的四個角點投射到物體表面, 經物體表面的反射到達中介曲面球, 圖6.3中P的紋理屬性就由中介曲面球上的E點唯一確定.
(3). 中介曲面球的缺點是在球的兩極點紋理變化太快, 形成紋理的突變.
為避免中介曲面球的極點紋理突變, 環境映射中可采用立方體表面為中介表面, 整個環境紋理映射由六幅圖(mip-map表)組成, 六幅圖象是六個90度視域方向拍攝的或繪制的真實景物圖象, 其映射過程簡單的將入射光線束通過物體表面片投射到立方體上, 入射光線束形成的錐和立方體表面的交集就是該物體表面片的紋理, 如圖6.4.
圖6.4
立方體環境映射計算量比球面環境映射大很多, 主要因為光線和立方體表面的交集區域計算復雜. 為次可以以立方體中心為投影中心, 將立方體表面包含的紋理屬性投影到立方體的外接球面上, 再由球面環境映射來確定物體表面紋理, 這樣既省計算時間, 又可避免球面環境映射在極點的紋理突變.
6.2 特殊紋理映射
這節的例子以后給出.
6.2.1 凹凸紋理映射
兩步法紋理映射方法只能實現物體表面的顏色(花紋)紋理, 但不能實現物體表面幾何形狀的凹凸不平而形成的粗糙質感. 為此, 出現了無須修改物體模型就能實現物體表面凹凸不平效果的紋理技術 -- 凹凸紋理映射(Bump mapping).
由於物體表面的光強度是由物體表面的法向量決定的, 凹凸紋理映射通過對物體表面各采樣點的法向量作微小的調整來改變物體表面的光強度, 達到凹凸不平效果.
但不是任何調整都能產生凹凸不平的真實感效果, 我們需要建立數學模型, 定義物體表面參數方程, 在(m, n)點的法向量為,
V = V(m, n)
設Vm, Vn分別是V在m, n上的分量, 那么在(m, n)點的單位法向為,
N = N(m, n) = Vm X Vn / | Vm X Vn |
現在用一個微小調整的函數P(m, n), 一般這個函數是連續可微的, 那么通過函數P(m, n)調整的點(m, n)的新法向量為,
| V'm = Vm + Pm N
V'(m, n) = V(m, n) + P(m, n) N = |
| V'n = Vn + Pn N
從而得到新的單位法向, 如圖6.5
N' = V'm X V'n = N + D
D = Pm A - Pn B
A = N X Vn, B = N X Vm
圖6.5
對於函數P(m, n), 可以用一張凹凸圖來離散的給出各點的值.
6.2.2 位移紋理映射(Displacement mapping)
凹凸紋理映射能很好的模擬面的凹凸不平的真實感效果, 但在物體的輪廓線上的凹凸不平效果無法表達, 如圖6.6
圖6.6
我們將含有凹凸信息的面按一定方向旋轉后, 原來的凹凸不平的真實感效果就不明顯了.為此出現了位移紋理映射技術, 它和凹凸紋理映射技術相似, 只是位移紋理映射是沿法向方向調整的是采樣點的位置, 將凹凸信息的顯示用點坐標的調整來得到, 如圖6.7
圖6.7
當含有凹凸信息的面轉過90度后, 我們還是能看出面的凹凸信息.
值得注意的是, 位移紋理映射對物體表面的造型進行了改變, 所以呈現的凹凸信息能實現陰影效果.
6.3 紋理映射中的反走樣
紋理映射常常需要將紋理圖案映射到不同大小的物體表面, 理想的是物體表面大小和紋理圖案大小一致. 但實際物體表面大於或小於紋理圖案的情況多, 這時必須取這區域的紋理屬性值--如像素顏色的平均值作為對應表面的紋理屬性, 但當物體表面形狀復雜時, 計算平均值並不能精確得到物體表面的紋理屬性.
另外將一張黑白的世界象棋圖映射到物體表面, 當物體表面小到接近單位像素時, 表面只能顯示黑色或白色, 當物體位移或旋轉時, 會出現黑白閃爍現象.
上面都是紋理映射的走樣問題, 相應的就有反走樣技術, 我們講解mip-map技術.
6.3.1 mip-map
mip是拉丁文multum in parvo(聚集在一塊小區域內的許多東西)的縮寫, mip-map技術根據初始的紋理圖案每邊的分辨率S, 形成一個四棱錐型的mip-map, 如圖6.8
圖6.8
圖左邊是四棱錐型的mip-map, 其中紋理圖案的層數是[log2S] + 1, 也就是設置一張64X64的紋理圖案, mip-map就以64X64為底的四棱錐, 一共有[log264] + 1 = 7層, 最上層為一個像素, 同時我們也知道為何紋理圖案都建議是2的冪次的原因.
mip-map的存儲是以一張查找表的形式存儲的, 如圖6.8的右邊, 一張64X64的紋理圖案的查找表的大小是128X128. 存儲時先提取紋理圖案中每像素的R, G, B值, 然后對R, G, B值逐級壓縮來得到.
mip-map在確定屏幕上可見表面的紋理過程如下,
(1). 計算屏幕上可見表面的中心在紋理空間上的映射點坐標(u, v).
(2). 確定紋理空間中以(u, v)為中心, 邊長為d的正方形, 要求正方形能覆蓋表面在紋理空間中映射的區域.(實際這樣算d太復雜, 一般d為表面在紋理空間中映射的區域的最大邊長)
(3). 根據d的大小確定使用哪一級的紋理map.
因為mip-map中的紋理圖案存儲的是特定的圖案, 即只有邊長d = 2^k, k = 0, 1, ..., [log2S]的圖案, 對於在2^k < d < 2^(k + 1)的邊長d, mip-map通過線性插值第k層的紋理和第k + 1層的紋理得到.
6.4 紋理映射的例子
6.4.1 代碼更新
這集的例子是game4 project.
我們在這例子里改變了頂點的屬性, 加了頂點的紋理屬性, 紋理屬性是確定頂點在紋理空間中的映射點的坐標的.
我們在確定頂點在世界中的坐標的同時, 還要確定頂點對應的紋理空間中的坐標. 一般紋理空間中的坐標都在[0, 1]之間的, 例子中可以該成[0, 2]或[0, 4]之間, 看看物體的紋理又會如何.
// gamedef.h中改了頂點的屬性
define D3DFVF_MYVERTEXTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
struct MYVERTEXTEX
{
FLOAT x, y, z;
DWORD colour;
FLOAT tu, tv;
};
// d9texobject.cpp中, 加入load texture file的函數
HRESULT CD9TexObject::SetTexture(LPCTSTR pName)
{
if (m_bInit)
{
return D3DXCreateTextureFromFile(m_pD3DDev, pName, &m_pD3DTexture);
}
return E_FAIL;
}
// d9texobject.cpp中, 確定頂點在世界中的坐標的同時,
// 確定頂點在紋理空間中映射點的坐標,
// 我們只是簡單將紋理圖案映射在四邊形的表面上
HRESULT CD9TexObject::UpdateD3DVertex()
{
LPVOID pV = NULL;
UINT nSize = 18 * sizeof(MYVERTEXTEX);
MYVERTEXTEX aVertex[] =
{
{m_fx - m_fW, m_fy + m_fH, m_fz - m_fD, D3DCOLOR_XRGB(0, 0, 255), 0.0f, 1.0f },
{m_fx - m_fW, m_fy + m_fH, m_fz + m_fD, D3DCOLOR_XRGB(255, 0, 0), 0.0f, 0.0f },
{m_fx + m_fW, m_fy + m_fH, m_fz - m_fD, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 1.0f },
{m_fx + m_fW, m_fy + m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0.0f },
{m_fx - m_fW, m_fy - m_fH, m_fz - m_fD, D3DCOLOR_XRGB(255, 0, 0), 0.0f, 1.0f },
{m_fx - m_fW, m_fy + m_fH, m_fz - m_fD, D3DCOLOR_XRGB(0, 0, 255), 0.0f, 0.0f },
{m_fx + m_fW, m_fy - m_fH, m_fz - m_fD, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 1.0f },
{m_fx + m_fW, m_fy + m_fH, m_fz - m_fD, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0.0f },
{m_fx + m_fW, m_fy - m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 0, 255), 0.0f, 1.0f },
{m_fx + m_fW, m_fy + m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 255, 0), 0.0f, 0.0f },
{m_fx - m_fW, m_fy - m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 1.0f },
{m_fx - m_fW, m_fy + m_fH, m_fz + m_fD, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0.0f },
{m_fx - m_fW, m_fy - m_fH, m_fz - m_fD, D3DCOLOR_XRGB(255, 0, 0), 0.0f, 1.0f },
{m_fx - m_fW, m_fy + m_fH, m_fz - m_fD, D3DCOLOR_XRGB(0, 0, 255), 0.0f, 0.0f },
{m_fx + m_fW, m_fy - m_fH, m_fz - m_fD, D3DCOLOR_XRGB(0, 255, 0), 0.0f, 1.0f },
{m_fx + m_fW, m_fy - m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 0, 255), 0.0f, 0.0f },
{m_fx - m_fW, m_fy - m_fH, m_fz - m_fD, D3DCOLOR_XRGB(255, 0, 0), 1.0f, 1.0f },
{m_fx - m_fW, m_fy - m_fH, m_fz + m_fD, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0.0f }
};
if(FAILED(m_pD3DVBuffer->Lock(0, nSize, &pV, 0)))
{
return E_FAIL;
}
MoveMemory(pV, aVertex, nSize);
m_pD3DVBuffer->Unlock();
return S_OK;
}
// d9texobject.cpp中, 渲染時設置物體紋理
VOID CD9TexObject::Render()
{
if (m_bInit)
{
m_pD3DDev->SetStreamSource(0, m_pD3DVBuffer, 0, sizeof(MYVERTEXTEX));
m_pD3DDev->SetFVF(D3DFVF_MYVERTEXTEX);
if (m_pD3DTexture != NULL)
{
m_pD3DDev->SetTexture(0, m_pD3DTexture);
m_pD3DDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
}
else
{
m_pD3DDev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DISABLE);
}
m_pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
m_pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 8);
m_pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 2);
}
}
6.4.2 說明
6.4.2.1 函數
一般紋理文件是單獨的存放的, 這是要創建紋理用到了
HRESULT D3DXCreateTextureFromFile(LPDIRECT3DDEVICE9 pDevice,
LPCTSTR pSrcFile,
LPDIRECT3DTEXTURE9 * ppTexture);
函數從單獨的紋理文件中創建紋理, 用這函數的好處是隨時可改紋理文件但不用編譯代碼. 如果為提高速度, 可以將紋理加入可執行文件中作為資源, 這是使用
HRESULT D3DXCreateTextureFromResource(LPDIRECT3DDEVICE9 pDevice,
HMODULE hSrcModule,
LPCTSTR pSrcResource,
LPDIRECT3DTEXTURE9 * ppTexture);
在渲染時, 要告訴IDirect3DDevice9物體的紋理, 使用
HRESULT SetTexture(DWORD Sampler,
IDirect3DBaseTexture9 * pTexture);
現在的DirectX Graphics最多能使用8各紋理的多重紋理, 現在我們直用一個, 所以Sampler設為0.
6.4.2.2 紋理過濾器
紋理到物體表面的映射需放大或縮小, 過濾器的作用就是確定紋理的縮放變化的平滑等級的. 紋理過濾器是使用IDirect3DDevice9中的SetSamplerState函數來設置的.
我們將平滑等級按一般到高級列出, 同時也要注意, 平滑等級越高, 計算越耗時.
(1). 使用最近點采樣技術(這是系統默認值)
SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
(2). 使用線性過濾技術(建議使用, 以點的2X2區域線性插值)
SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
(3). 使用各向異性過濾技術
SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
這時需要設置D3DSAMP_MAXANISOTROPY的等級, 默認為1
SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 4);
還有D3DTEXF_PYRAMIDALQUAD和D3DTEXF_GAUSSIANQUAD過濾技術.
6.4.2.3 mip-map設置
mip-map的過濾器平滑等級和上面的相同, 也是通過SetSamplerState函數來設置. 系統默認是D3DTEXF_NONE, 即沒有過濾. 同時需要設置mip-map的層數
SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
SetSamplerState(0, D3DSAMP_MIPMAPLODBIAS, 4);
SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 4);
6.4.2.4 texture-address mode
紋理空間總是在[0, 1]之間的, 但是物體表面點映射的紋理坐標可大於1, 這時, 對於大於1的紋理映射可使用紋理重復, 鏡像, 最近點采樣, 邊界點采樣, 單次鏡像等. 通過函數SetSamplerState設置
SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
可以修改例子中物體表面點映射的紋理坐標可大於1, 然后改變texture-address mode觀察物體表面紋理映射的區別.
下面的圖是使用[0, 2]的鏡像紋理技術的截圖
第六集 小結
這一集我們學習了要進行DirectX Graphics 3D編程中的二維紋理映射技術, 紋理空間總是在[0, 1]之間.
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/zdl1016/archive/2007/05/23/1623324.aspx