实时环境映射贴图技术(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
??????? 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>;
??????????????? ViewTransform = <matView>;
??????????????? ProjectionTransform = <matProject>;
??????????????? // Pixel state
??????????????? Texture[0] = <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;
??????????? }
??????? }
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
??????? NOTE:
??????? 1.用靜態環境貼圖代替實時渲染
??????? 除了實時的渲染環境貼圖之外,另外一個比較常見的做法是從文件中讀取一個張預先生成的環境貼圖。使用靜態貼圖實現起來比實時渲染簡單,它不需要把場景繪制 到Cube Map上,只需要在創建的時候從硬盤文件中載入即可。使用靜態貼圖可以大大降低渲染開銷,是一種適合在低端環境中使用的技術。不過使用靜態貼圖會大大降低 游戲真實性,舉例來說,車體上的貼圖不會隨環境的改變而變化。
??????? 2.減少實時渲染的渲染開銷
??????? 除了使用靜態環境貼圖外,還可以使用其他方式來降低系統開銷。比如《地下狂飚2》是使用了限制環境貼圖刷新率的辦法來實現實時渲染,就是說每秒環境貼圖只 渲染一定的次數,而不是每幀都做渲染。而且這個頻率可以讓玩家來調整。另外一個方法是限制場景中渲染的物體的數量。比如車子開到路段A上,就只會對路段A 兩邊的建筑進行繪制;或者可以只繪制一部分能夠被清晰映射的物體。除此之外還可以減小環境貼圖的大小,這會對渲染效果產生影響,車體上的環境映射效果會比 較模糊。
??????? 3.使用可編程渲染管道
??????? 使用可編程渲染管道大大提高了顯卡編程的靈活性,隨著顯卡性能的逐日提高,可以由顯卡繪制出更真實的特效。對于環境貼圖來說,上面的代碼是使用頂點計算貼 圖坐標,如果使用了逐象素(Per-Pixel)計算,將會把效果表現的更加真實,當然這樣做的開銷太大,目前僅限于頂級顯卡。
??????? 4.BLOG
訪問Coollen的Blog可以獲得更多信息。(http://blog.handsbrain.com/coollen_mmx)
總結
以上是生活随笔為你收集整理的实时环境映射贴图技术(Real-time Evironmnet Mapping)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis:缩容、扩容、渐进式rehas
- 下一篇: 《UCD火花集2:有效的互联网产品设计