实时环境映射贴图(Real-time Evironmnet Mapping)
環境映射貼圖技術最典型的應用就是車體的“流光”,這種現象在現實中非常普遍。當你開著車行駛在滿是霓虹燈的街道上時,周圍的燈光會在車身上投射出一個光斑或光帶(前提是車子洗得非常干凈)。當車子在街道上飛馳的時候,就好像一個個五彩的斑點在你的車體上流動,產生非常炫目的效果。甚至當你和別的車子擦肩而過時,別的車子也會映射在你的車體上。
要實現環境貼圖映射首先需要一組環境信息。假設在場景里有一輛車(本文中將一直使用這個的例子),車的環境信息,即除車以外,你在車的位置上向四周看到的全部畫面。你用相機對六個不同方向拍出六張照片,這樣就產生了一組環境信息。車上的每個象素都和這個環境信息中的一個點有對應關系(也就是所謂的映射)。在最后渲染車時,需要把車身上每個環境對應點與車體本身的效果做混合處理。
歸納起來要實現環境貼圖映射需要執行以下步驟:
A.創建環境貼圖
B.把場景中的物體渲染到環境貼圖上
C.渲染車體時,把原始貼圖和環境貼圖經過處理后最終渲染到屏幕上
準備好你的車子了嗎?你一定想馬上體驗一下給自己車子打上環境貼圖是什么感覺吧!不過在動手之前,你還需要對相關知識有所了解,否則是很難找到正確方法的。
一、什么是環境貼圖?
在上面我用照相機作過比喻,在這個比喻中,那六張照片就類似一組環境貼圖,它們代表了立方體的六個面。你可以想象一下,當你的眼睛處于這個立方體時,你會看到周圍所有的景物,因此我們將它們稱為環境貼圖。
一般來說我們所使用的環境貼圖都是用立方體作參照,因此通常稱為立方體貼圖(Cube Map)。每個Cube Map包含六個面,在DirectX中分別用+X、-X、+Y、-Y、+Z、-Z來表示,其每個面都在水平和垂直平面上都覆蓋了90度的視角。
二、我們要如何操作這組Cube Map呢?
在DirectX 9中,分別使用了六個不同的標識來表示Cube Map中的各個面。這組標識叫D3DCUBEMAP_FACES,其中的D3DCUBEMAP_FACE_POSITIVE_X就代表了Cube Map中以中心點為原點的正X軸所指向的那個面,依此類推。而Cube Map的每一個面都是一個LPDIRECT3DSURFACE9對象,當你需要對其中某個面進行某些操作時,就需要先調用GetCubeMapSurface函數來得到你所指定的那個面(Surface),然后像操作一般面一樣操作即可。而本文中將要對其進行的操作就是把面作為渲染目標(Render Target),把周圍環境中的景物全部繪制到這個Cube Map上。因為Cube Map在每個方向上有一個面,每次繪制其中一個面,就要對場景中所有的景物進行一次渲染。這個過程的開銷是十分大的。如果你的場景中某個物體上使用了環境映射,那么原來只需繪制一次的場景,就會增加6倍的渲染消耗!如果一個游戲原來跑70幀,當繪制一個使用了環境映射的特效時,馬上降到了10幀!這是一個相當可怕的數字!你也許會考慮:那么這項技術會不會因為開銷太大而失去使用的價值?答案是不會的。以目前人類的智慧已經可以非常好的處理好環境映射的效率問題,所以不必擔心,而且在后文中我們還會詳細講解這些方法。
三、如何使用Cube Map?
當你渲染好了貼圖坐標,下一步就是如何使用的問題了。要想將一張貼圖繪制到屏幕上,就需要用坐標和模型的頂點進行對應。我們都知道,2D貼圖使用u、v兩個坐標來表示頂點對應的貼圖坐標。而在Cube Map中,僅有2個量是無法表示一個點在立方體中的位置的,所以,Cube Map的貼圖坐標是由3個數的向量來表示的,你可以簡單把這個貼圖坐標對應的顏色理解為:從盒子中心向這個3D向量方向前進直至與盒子相交,其交點的象素顏色值。比如你要讓場景中的一個球映射出周圍的環境,最簡單的做法是把球上每個點的法線做為貼圖坐標傳給圖形處理芯片,這樣一來就能讓球具有金屬質感。不過,僅僅這樣簡單對應的話,繪制出的圖將會和現實世界的映射有所出入。
四、環境映射貼圖實際如何操作?為了應用程序開發方便,微軟DirectX 9提供了一個ID3DXRenderToEnvMap的接口,通過這個幫助類接口,我們可以十分快速地完成對環境貼圖的操作。當然,作為一個通用的接口,它不僅含有對Cube Map的操作,還支持其他一些環境貼圖,比如Sphere Map等。我們可以通過查看DirectX 9的相關文檔了解更多信息。
以下代碼實現的即是創建環境貼圖的工作:#define CUBEMAP_SIZE 128
IDirect3DCubeTexture9* m_pCubeMap;
ID3DXRenderToEnvMap m_pRenderToEnvMap;
// 創建RenderToEnvMap對象。其中m_pd3dDevice和m_d3dsdBackBuffer分別是指向Direct3D設備和顯示緩存頁面的指針。CUBEMAP_SIZE這個宏指定了Cube Map的邊長,它關系到環境貼圖的大小,值越大占用空間越多,繪制也越慢。
if( FAILED( D3DXCreateRenderToEnvMap( m_pd3dDevice, CUBEMAP_SIZE, 1,
m_d3dsdBackBuffer.Format, TRUE, D3DFMT_D16, &m_pRenderToEnvMap ) ) )
{
return E_FAIL;
}
// 創建空的Cube Map,D3DUSAGE_RENDERTARGET說明創建的Cube Map是一個渲染目標。
if( m_d3dCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP )
{
if( FAILED( D3DXCreateCubeTexture( m_pd3dDevice, CUBEMAP_SIZE, 1,
D3DUSAGE_RENDERTARGET, m_d3dsdBackBuffer.Format, D3DPOOL_DEFAULT, &m_pCubeMap ) ) )
{
return E_FAIL;
}
}
else
{
return E_FAIL;
}
繪制過程需要先得到一個新的投影矩陣(Projection Matrix),這個矩陣設置了攝像機的視角和場景深度范圍等。另外還要計算出一個新的觀察矩陣(View Matrix),這樣Cube Map才會在“車”上隨著視角的改變而變化。
HRESULT RenderSceneIntoEnvMap(){
HRESULT hr;
// 設置投影矩陣
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI * 0.5f, 1.0f, 0.5f, 1000.0f );
// 得到當前觀察矩陣
D3DXMATRIXA16 matViewDir( m_matView );
matViewDir._41 = 0.0f; matViewDir._42 = 0.0f; matViewDir._43 = 0.0f;
// 把場景繪制到Cube Map上
if( m_pCubeMap )
hr = m_pRenderToEnvMap->BeginCube( m_pCubeMap );
if(FAILED(hr))
return hr;
for( UINT i = 0; i < 6; i++ )
{
// 設置Cube Map中的一個面為當前的渲染目標
m_pRenderToEnvMap->Face( (D3DCUBEMAP_FACES) i, 0 );
// 計算新的觀察矩陣
D3DXMATRIXA16 matView;
matView = D3DUtil_GetCubeMapViewMatrix( (D3DCUBEMAP_FACES) i );
D3DXMatrixMultiply( &matView, &matViewDir, &matView );
// 設置投影和觀察矩陣并渲染場景(省略)。注意:這里的渲染場景中的物體并不包括使用環境貼圖的物體。
... ...
}
m_pRenderToEnvMap->End( 0 );
return S_OK;
}
渲染出環境貼圖后,需要把這個環境貼圖運用在目標物體上,即將其作用在我們的車子上。我們在渲染場景(真正繪制到屏幕的時候)中使用一個特效(Effect)來繪制。特效可以從內存中一段字串創建,也可以從一個特效文件(后綴名為fx,由DirectX9 SDK自帶的Effect Edit生成)來創建。特效的使用方法可以查看SDK中的相關文檔和例子,因為涉及的東西非常多,所以不在這里多說了。特效文件的代碼如下,這里給出了基于固定渲染管道(Fixed Function Pipeline)的實現代碼,除此之外還可以使用可編程渲染管道(Programmable Pipeline)來實現。
textureCUBE texCubeMap;
matrix matWorld;
matrix matView;
matrix matProject;
technique Cube
{
pass P0
{
// Vertex state
VertexShader = null;
WorldTransform[0] = <matworld></matworld>;
ViewTransform = <matview></matview>;
ProjectionTransform = <matproject></matproject>;
// Pixel state
Texture[0] = <texcubemap></texcubemap>;
MinFilter[0] = Linear;
MagFilter[0] = Linear;
AddressU[0] = Clamp;
AddressV[0] = Clamp;
AddressW[0] = Clamp;
ColorOp[0] = SelectArg1;
ColorArg1[0] = Texture;
TexCoordIndex[0] = CameraSpaceReflectionVector;
TextureTransformFlags[0] = Count3;
}
}
需要注意的幾點:
1.用靜態環境貼圖代替實時渲染
除了實時的渲染環境貼圖外,另一個比較常見的做法是從文件中讀取一張預先生成的環境貼圖。靜態貼圖實現起來比實時渲染簡單,它不需要把場景繪制到Cube Map上,只需在創建時從硬盤文件中載入即可。靜態貼圖可以大大降低渲染開銷,是一種非常適合低端環境的技術。不過靜態貼圖會大大降低游戲的真實性,如果采用了靜態貼圖,那么車體上的貼圖是不會隨著環境改變而變化的。
2.減少實時渲染的渲染開銷
除了使用靜態環境貼圖外,還可以使用其他方式來降低系統開銷。比如《地下狂飚2》使用了限制環境貼圖刷新率的辦法來實現實時渲染,就是說規定每秒環境貼圖只渲染一定的次數,而不是每幀都做渲染。而且這個頻率可以讓玩家來調整。另外一個方法是限制場景中渲染的物體數量。比如車子開到路段A上,就只會對路段A兩邊的建筑進行繪制;或者可以只繪制一部分能夠被清晰映射的物體。除此之外還可以減小環境貼圖的大小,但是這會對渲染效果產生影響,車體上的環境映射效果會比較模糊。
3.使用可編程渲染管道
使用可編程渲染管道大大提高了顯卡編程的靈活性,隨著顯卡性能的逐日提高,可以由顯卡繪制出更真實的特效。對于環境貼圖來說,上面的代碼是使用頂點計算貼圖坐標,如果使用了逐象素(Per-Pixel)計算,將會把效果表現的更加真實,當然這樣做的開銷太大,目前僅限于頂級顯卡。
總結
以上是生活随笔為你收集整理的实时环境映射贴图(Real-time Evironmnet Mapping)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云计算和大数据书籍
- 下一篇: 远程桌面控制软件 Splashtop 新