A. 如何用Cocos引擎打造次世代3D畫質『游戲大觀
從Cocos 2d-x 3.0起我們已經可以在游戲中使用3D元素。Cocos引擎推出3D功能的時間不算太遲,我們已經可以看到越來越多的手機上能流暢地渲染3D游戲,而且這些機型正在成為主流。在最近兩年我們可以看到,高端手機游戲從2D轉到3D的傾向很明顯。許多游戲開發商試圖在競爭激烈的紅海里佔有一席之地,那麼選擇開發3D游戲或許會是一個強有力的競爭手段。
上面的視頻是我的下一款游戲作品《Food of the Gods》。這游戲使用了Cocos 2d-x 3.3,視頻是從我iPhone上錄制的實際運行效果。在這篇文章里我將要介紹我是如何製作它、如何把它跑在cocos引擎上的。對於熟悉cocos官方提供的3D示例游戲 《Fantasy Warrior》的開發者,將會看到以下一些主要不同點:
1. 光照貼圖(Light Mapping):你將看到每件物體都有被照亮並且投射陰影。光影效果的質量是由你的3D工具軟體決定的,用3D軟體能烘焙出復雜的光效,包括直接光照,反射光照,以及陰影。
2. 頂點合並(Vertex Blending):請注意看路、草地和懸崖交接的地方,看不到任何可見的接縫。
3. 透明遮罩(Alpha Masks):灌木如果沒有透明遮罩就跟紙片一樣。
4. 濾色疊加的公告板(Billboards):增加一些光束和其他環境的效果。
所有的模型都是用一個叫Modo的3D 軟體建模製作的,貼圖則是使用Photoshop。關於3D模型的製作和貼圖的繪制在此就不再贅述,網上已經有很多教程,在此主要介紹下跟Cocos 2d-x有關的部分。
模型網格和貼圖(Meshes and Textures)
如下圖所示,每個模型的貼圖都是由幾個256 x 256或者更小的貼圖組成的。同時你也會注意到我把所有的小圖片都合在了一張貼圖上,這是減少GPU繪制次數(draw call)最簡單的方法之一。貼圖是從http://www.cgtextures.com 或者網上找的。
為了把這些圖片拼接起來,我使用的是Photoshop的補償濾鏡(offset filter)然後在接縫的地方用修復畫筆來做一些自然的過渡。為了獲得一種油畫的視覺效果我會先使用cutout濾鏡(注意:cutout濾鏡也會使得png格式圖片的壓縮效果更好),然後在需要的地方繪制一些高光和陰影的效果。我發現如果直接拿照片當貼圖的話,當你把它尺寸縮小的時候會出現圖像噪點。
另一種方案是為每一個模型網格製作一整張獨立的貼圖。當網格比較小或者攝像機不是很靠近網格的時候這種方法是可行的。如果你的photoshop技術過硬的話,出來的效果會更好。附帶的好處是,因為只使用一張貼圖因此只有一次GPU繪制調用。但我不建議採用這種方法來製作第一人稱射擊游戲(FPS)中的建築,因為當你走得很靠近建築物的時候,貼圖解析度過低的問題就會顯露出來。我不喜歡用這種整張貼圖方法,因為這實在太費時耗力了。這個場景的製作花了我足足四天時間。
光照貼圖(Light Maps)
當你做好模型和貼圖之後,現在就可以來烘焙光照貼圖了。Cocos 2d-x目前還不像Unreal或Unity一樣在官方編輯器里提供烘焙光照貼圖的功能,但是別失望,大部分的製作3D模型的軟體都可以烘焙光照貼圖,並且效果比市面上任何游戲引擎的效果還好。首先,在你的3D工具軟體里,先給場景打好燈光,照亮場景,然後為每份網格製作第二張UV map。每份網格的表面都必須被映射在0到1范圍內的UV 平面上。這聽起來好像很復雜且耗時,但在Modo里這是非常簡單的。我先後使用 「Atlas map」的UV 工具和「Pack UV」工具,這兩個工具會自動將網格展開成一個相當不錯的排布圖。
這些都完成之後,設置3D工具軟體的渲染器為「只渲染烘焙的光照」,然後開始渲染。當然了,如果你想做一些環境光遮罩的效果也是可以的。
你也可以使用一些解析度較低的光照貼圖。有時候這樣的效果反而會看起來更好,因為相互混疊的模糊像素會讓陰影看起來更柔和。上面的這些建築都映射到一張512 x 512的光照貼圖上。整個場景總共使用了4 張512 x 512的光照貼圖。請確保每個小圖塊之間有一定的空隙,且讓你的渲染范圍比這些圖塊的邊界多出幾個像素。這樣可以防止當較低的mip-maps(一種紋理采樣)起作用時黑邊出現在網格周圍的角落裡。
最後一點聽起來像是3D技術的行話。如果是對Texture Packer熟悉的話,那麼其中的「Extrude」值起到的作用就是剛剛我所描述的。對貼圖的邊緣接縫做一些塗抹處理,這樣在精靈之間就不會有那些煩人的縫隙了,那些縫隙在這里會變成多邊形邊緣的黑邊。
如果你想犧牲內存和包大小來提高性能的話,你可以把顏色和光照信息都烘焙到一張貼圖上並避免共同使用一張光照貼圖。但是這樣做的話,同樣的像素密度,貼圖的大小至少得翻一倍。這完全取決於你個人、以及你游戲的要求。
接下來,添加頂點顏色。我在地形上提供了頂點顏色,這可以讓著色器在合成懸崖頂上的草地貼圖時,不會有任何可見的接縫。下圖中塗成白色的頂點部分可以合成你指定的貼圖。在這個例子里實際上我只使用紅色通道,當然了根據實際需要你可以使用4個通道(RGBA)去合成不同的貼圖。
最後,我把整個場景分成了很多獨立的網格(mesh):每個建築都有自己獨立的網格,地形獨立一個網格,水也是獨立一個。帶透明遮罩的貼圖也會有一個網格——比如視頻中看到的植物葉子和小旗子。我這樣做有兩個原因,首先,讓地形、建築、水和帶透明遮罩的貼圖各自使用不同的著色器。其次,我們打算通過不渲染攝像機范圍外的對象來減少性能開支。很重要的一點是攝像機會根據網格的包圍盒來決定對象是否可見,因此盡量把網格弄成小塊,這樣包圍盒會比較小。
導出
完成了模型和貼圖之後,我們需要把每個mesh導出為一個.fbx文件。幸運的是,大多數的3D建模軟體都支持這個功能。Autodesk為此格式提供了一個免費SDK。但不幸的是,Modo 701在導出fbx格式時會出現相當多的錯誤。因此我必須自己寫一些腳本來保證第二組貼圖坐標和頂點顏色的正確導出。你可以從我個人網站上的「Modo Scripts」部分下載這個導出腳本。搞定fbx之後,你將需要用到Cocos 2d-x自帶的fbx-conv.exe命令行工具,它位於Cocos 2d-x根目錄的/tools下。
fbx-conv.exe -a your_mesh_name_here.fbx
使用「-a」參數後,工具會同時導出mesh的二進制文件(.c3b)和文本格式文件(.c3t)。文本格式的文件非常的有用,你可以利用它來查看所有的東西是否被正確導出,但千萬不要把它放到resource目錄下。如果所有的都被正確地導出的話,你將在c3t文件的開頭看到以下的內容:
「attributes」: [{
「size」: 3,
「type」: 「GL_FLOAT」,
「attribute」: 「VERTEX_ATTRIB_POSITION」
}, {
「size」: 3,
「type」: 「GL_FLOAT」,
「attribute」: 「VERTEX_ATTRIB_NORMAL」
}, {
「size」: 2,
「type」: 「GL_FLOAT」,
「attribute」: 「VERTEX_ATTRIB_TEX_COORD」
}, {
「size」: 2,
「type」: 「GL_FLOAT」,
「attribute」: 「VERTEX_ATTRIB_TEX_COORD1″
}]
注意VERTEX_ATTRIB_TEX_COORD1這個屬性。如果沒有它光照貼圖將無法顯示。如果你導出了一張帶頂點顏色的mesh,你也應該要看到一個類似的屬性才行。還有一點很重要,貼圖的坐標也必須按正確的順序才行。我通常採用的是第一個tex_coord是瓦片貼圖,最後一個tex_coord是光照貼圖。使用Modo的話,uv maps會按照字母順序排列。
著色器(Shaders)
我花了很長的一段時間來搞懂GLSL和著色器,但正如編程中經常遇到的,有時候一個點通了,其他的就都好理解了。一旦理解了其中的原理,你便會發現著色器真的很簡單。如果你不只是想用Cocos 2d-x來把貼圖套到模型網格上的話,你需要學會如何寫著色器。目前Cocos 2d-x沒有Unreal那樣好用的著色器可視化編輯器(visual shader editor),所以我們只能自己動手焊代碼。
本節我將講解我為視頻中的游戲場景所寫的著色器,並說明我做了什麼、為什麼這樣做。如果你對著色器已經非常熟悉了,那麼可以快速跳過本節。
首先,先來看一下如何將著色器應用到模型網格上。
這段代碼摘自Cocos 2d-x的測試集cpp-tests工程。如果你用不同的著色器來載入大量的meshes,那麼最好根據功能來進行,這樣可以避免冗餘。那麼現在我們只關心如下的代碼段,來看下這個著色器。
GLProgram* shader =GLProgram::createWithFilenames(「shaders/lightmap1.vert」,」shaders/lightmap2.frag」);
GLProgramState* state = GLProgramState::create(shader);
mesh->setGLProgramState(state);
Texture2D* lightmap =Director::getInstance()->getTextureCache()->addImage(「lightmap.png」);
state->setUniformTexture(「lightmap」,lightmap);
「lightmap1.vert」是頂點著色器(vertex shader)。如果將其應用到網格上,那麼每個頂點的每一幀都將執行這個操作。而「lightmap2.frag」是片段著色器(fragment shader),網格上貼圖的每個像素的每一幀都將執行這個操作。我不太確定為什麼將其命名為「片段著色器」,我一直認為應叫做「像素」著色器(pixel shader)。從這段描述,我們可以很容易理解為什麼大量著色器指令會降低幀率,尤其是你用片段著色器的話。
接下來我們詳細地分解頂點著色器:
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_texCoord1;
這些屬性是由渲染器提供的。「a_position」是頂點的位置。「a_texCoord」 和 「a_texCoord1」對應你那兩個UV坐標。還記得在.cbt文本格式文件中開頭部分的「VERTEX_ATTRIB_TEX_COORD」么?這些值與屬性對應起來了。你可以在渲染器中獲取更多其他的屬性,包括頂點法線(vertexnormal)和頂點顏色(vertex color)。請在cocos引擎的CCGLProgram.cpp中查看完整屬性列表。
varying vec2 v_texture_coord;
varying vec2 v_texture_coord1;
「varying」值將被傳到片段著色器中(fragment shader)。片段著色器所需要的任何變數前都需要添加「varying」限定符。這個例子中,我們僅需要知道這兩個貼圖的坐標。
void main(void)
{
gl_Position = CC_MVPMatrix * a_position;
v_texture_coord.x = a_texCoord.x;
v_texture_coord.y = (1.0 – a_texCoord.y);
v_texture_coord1.x = a_texCoord1.x;
v_texture_coord1.y = (1.0 – a_texCoord1.y);
}
設置頂點位置,拷貝貼圖的坐標給varying values,這樣片段著色器就可以使用這些值。現在我們一起來分解片段著色器。
#ifdef GL_ES
varying mediump vec2 v_texture_coord;
varying mediump vec2 v_texture_coord1;
#else
varying vec2 v_texture_coord;
varying vec2 v_texture_coord1;
#endif
聲明從頂點著色器傳遞過來的「varying」 值
uniform sampler2D lightmap;
還記得在將著色器應用到網格時所使用的 state->setUniformTexture(「lightmap「,light map); 語句么?這個值就是對應語句中的那個貼圖。
void main(void)
{
gl_FragColor = texture2D(CC_Texture0, v_texture_coord) *(texture2D(lightmap, v_texture_coord1) * 2.0);
}
這個語句設置像素顏色。首先你會注意到從未聲明過的 CC_Texture0變數。Cocos 2d-x中有大量可在著色器中使用的默認統一變數。再次強調,可在CCGLProgram.cpp中查看完整屬性列表。這個例子中,CC_Texture0對應在3D模型中所應用到網格中的貼圖。texture2D命令會在給定的貼圖坐標中去查找貼圖的像素顏色和透明度。它會返回一個包含了那個像素的RGBA值的vec4值 。所以這里我會在UV1中查找到瓦片貼圖的顏色值,然後在UV2中查到光照貼圖的顏色值,最後把兩個值相乘。
你應該注意到了我先是把光照貼圖的顏色值兩兩相乘了。因為貼圖顏色值范圍為0.0-1.0,所以很顯然,如果用白色值vec4(1.0, 1.0, 1.0, 1.0)去乘中間灰值vec4( 0.5, 0.5, 0.5,1.0 ),那麼你仍是得到一個中間灰值vec4( 0.5, 0.5, 0.5,1.0 )。將兩個值相乘可以使貼圖更亮,同時也可以使貼圖更暗,這將使你獲得一個很好的可變的亮度范圍。
B. apexcreatetexture2d閿欒鎬庝箞瑙e喅
灝嗘樉鍗¢┍鍔ㄦ洿鏂板嵆鍙銆俛pexcreatetexture2d閿欒鏄鏄懼崱椹卞姩閿欒錛屽綋鐢佃剳鐨勬樉鍗¢┍鍔ㄥ嚭鐜伴敊璇鏃訛紝灝變細瀵艱嚧璇ユ父鎴忕殑鍥劇墖娓叉煋鍑虹幇閿欒錛屼粠鑰屽脊鍑2d閿欒鐨勬彁紺猴紝鍙闇瑕佸皢鏄懼崱椹卞姩鏇存柊鍗沖彲銆
C. 如何使用Unity Studio
1、首先下載一個UnityStudio。