<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          V7大神獎專訪|Cocos Creator3.x 程序化大世界地圖!

          共 16512字,需瀏覽 34分鐘

           ·

          2024-06-07 16:11

          在 Cocos 第 7 期社區(qū)投稿活動中,開發(fā)者 GreenPack 的投稿文章《Hello World: Cocos Creator3.x 程序化地圖》榮獲了“大神獎”。

          我們有幸對 GreenPack 進行了專訪,在閱讀大佬的文章之前,我們先一起來看看他在游戲開發(fā)道路上的心得與經(jīng)驗吧。

          大佬能不能介紹下自己?

          大家好,我是 GreenPack,很榮幸獲得這次 Cocos v7 投稿的”大神獎“。

          我是一個普通的游戲客戶端開發(fā)者,為了尋求技術增長,自學和應用了一些渲染相關的技術。這次 v7 投稿活動,我將我自己的過去技術整合,結合 Cocos Creator 引擎,做了一套簡單的程序化大地圖渲染方案。

          老實說,這次投稿活動的競爭十分激烈,看著各位大佬的作品,我曾經(jīng)也是一度懷疑自己的投稿的分量到底夠不夠,這次能得大神獎其實也有點意外和驚喜。

          大佬得獎有什么感言?

          首先非常感謝官方給我這個獎,說實在的,這個稿子投了之后,我后續(xù)自我感覺還是有好多的提升空間,后續(xù)還在考慮繼續(xù)優(yōu)化迭代。

          其次要感謝各位開發(fā)者同僚們,最近一段時間我經(jīng)常在論壇發(fā)一些技術貼,不得不說 Cocos 的論壇氛圍是真的好,有問題可以快速得到解答,分享技術經(jīng)驗能獲得成就感。

          我從菜雞到一個勉強有一技之長的 Cocos 論壇鍵客,和各位的一聲聲胡吹分不開,尤其是一些群里的大佬,成天說“群除我佬”,說的我自己都差點相信我是大佬了。

          最后祝 Cocos 越來越好,各位開發(fā)者們早日實現(xiàn)財富自由!

          看完 GreenPack 的采訪,來看看這篇獲得”大神獎“的文章:《Hello World: Cocos Creator3.x 程序化地圖》

          前言

          坑挖得太大了……

          14 號活動帖子發(fā)出來時,我就開始整這個投稿了,當時設想了很多東西,什么日月星辰、風雨雷電、花草樹木、滄海桑田都想加進去。后來實踐起來,工作量遠超自己的想象。真這樣搞起來 10 年都填不完了。

          眼看著論壇里大神們一個接一個地拋出好東西,忍不住了,先把當前的成果搬出來了,以后有時間再繼續(xù)加工。

          關于噪聲生成算法

          大就是強,大就是美!誰會不喜歡大的呢?我第一次接觸到隨機生成無限大世界這個概念,是看到了這一篇文章 《「無人深空」是怎樣生成一個宇宙的?》

          文章鏈接:https://indienova.com/indie-game-news/how-does-no-mans-sky-generate-the-universe/

          里面提到了用一個自然界存在的擁有著無限量信息的無理數(shù)來構造世界,沒錯,那位大人就是:π!

          文章鏈接:https://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/web/3bc46f1e8e0be128adb98a76294bf65fa13f60ae

          但是當時我還是個小菜雞,只是隱約理解了程序化生成世界使用了π,但是具體怎么操作,完全沒有頭緒,直到后來學 shader 時看了這本經(jīng)典的電子書:thebookofshaders。

          文章鏈接:https://thebookofshaders.com/?lan=ch

          這本電子書里有不少內(nèi)容,這里不多贅述,簡單來說,它寫了怎么程序化生成一個噪聲。這里我會用 三個步驟 簡單地告訴大家,程序化生成噪聲的幾個核心要點:

          1. 一步計算即可獲得的隨機數(shù)

          首先拋開復雜的東西,要想生成一個隨機的內(nèi)容,我們第一步要生成一個隨機數(shù),不,等等,在程序化生成世界的過程中,我們需要大量隨機數(shù),并且這些隨機數(shù)會由它們的坐標唯一確定。

          theshaderofbooks 里描述了隨機數(shù)生成的計算,并且用了可以編寫實時查看的網(wǎng)頁元素的方式來呈現(xiàn),非常地高端大氣,但是關于隨機數(shù)這第一步,也許是因為翻譯的問題,我個人覺得書里沒有很好的說清楚。下面我將結合圖片來說說我的理解,先看噪聲那一章節(jié)的圖:

          看代碼,輸入值x經(jīng)過了一次 fract 操作,也就是取小數(shù)部分。我們可以理解為它將曲線劃分成了以 1 為周期的無限區(qū)塊。

          再回頭看隨機那一章節(jié)的圖:

          這里對輸入的x值進行了一次 sin 操作。

          好了,大聲告訴我, sin的周期是多少 !

          這就是核心操作了,一個周期為 2π 的曲線,我們每隔單位 1 取一個值,取出的數(shù)值構成的數(shù)列,從數(shù)學邏輯上保證了沒有周期性。所以,它是一個隨機數(shù)列。

          2. 平滑過渡,一維變二維

          第一步我們獲得的是一些離散的隨機數(shù),但是現(xiàn)實中的噪聲,非常接近的兩個坐標點之間的值也是非常接近的,也就是說噪聲是具有連續(xù)性的。

          從離散的數(shù)列到具有連續(xù)性的曲線,很簡單,兩個點之間平滑過渡一下即可:

          一般情況下,會用常用的 smoothstep 算法來做平滑過渡,thebookofshader 中還提到了 simplexNoise 中使用到的四次 Hermite函數(shù)。

          關于這方面我并沒有過多的研究,只有一個小小的個人經(jīng)驗,那就是 smoothstep 函數(shù),在端點處的斜率是 0,也就是說上面那個曲線,每隔單位1就會出現(xiàn)一個“平地”。

          平滑過渡之后,再將一維曲線升級一下,變成二維的噪聲圖即可,這里就不多贅述了,網(wǎng)上教程很多。

          3. 迭代、分形

          噪聲生成相關的技術中,有個聽起來非常牛批的東西, 分形布朗運動(Fractal Brownian Motion) ,簡稱 fbm 。別被它嚇到,這玩意用簡單地白話來說,就是用了一次for循環(huán),對我們上面步驟獲得的波進行了疊加而已。

          布朗運動是指懸浮在液體或氣體中的微粒所做的永不停息的無規(guī)則運動。分形則是數(shù)學中的一個概念,大家可能已經(jīng)忘了,一個例子幫你回憶起來:雪花晶體!所謂分形,就是放大了之后看,小的部分和大的形狀相似。

          那么怎么整出分形布朗運動呢?簡單,把我們上面步驟獲得的曲線,縮小振幅,加大頻率,經(jīng)過多次迭代即可。

          這里還引入了一個術語:** octaves(八度)** 。這是音樂中的一個概念,在這里,fbm 進行了幾次迭代,就稱為幾個八度。

          Cocos Creator 代碼生成網(wǎng)格

          1.代碼接口

          Cocos Creator 提供了代碼生成網(wǎng)格的接口,直接看官方文檔即可,下面是簡單的代碼示例。

          let mesh:Mesh = utils.MeshUtils.createMesh({
            colors:[1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1],
            positions:[0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1],
            indices:[0, 3, 2, 0, 2, 1],
            minPos:v3(0, 0, 0),
            maxPos:v3(1,1,1),
            // attributes: [
            // new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F),
            // ],
          });

          參數(shù)說明:

          • positions:頂點坐標數(shù)組。長度必定為3的倍數(shù),每三個值代表一個頂點的x、y、z。
          • colors:頂點顏色數(shù)組。長度必定為4的倍數(shù),每四個值代表一個頂點的rgba(這里的顏色值已經(jīng)歸一化)。
          • indices:頂點數(shù)據(jù)是可以復用的,indices中的值是頂點數(shù)據(jù)的索引,每三個值構成一個三角面。
          • minPos & maxPos:包圍盒的起點和終點。也可以缺省,然后在options參數(shù)中設置讓網(wǎng)格自己計算,但是這樣會有較大的計算量。

          程序化生成網(wǎng)格的時候要注意:所有三角面需要從相機方向看過去呈順時針。這是因為一般 3d 引擎中,為了減少無用的渲染,通常會有正面、背面的區(qū)分,并且一般默認開啟背面剔除。而正面背面的區(qū)分方式則是三角面的三個頂點的順逆時針順序。

          • attributes:頂點數(shù)據(jù)格式。可以修改默認的頂點數(shù)據(jù)的格式。在create-mesh.ts中有定義了程序化網(wǎng)格接口默認的頂點數(shù)據(jù)格式:
          const _defAttrs: Attribute[] = [
            new Attribute(AttributeName.ATTR_POSITION, Format.RGB32F),
            new Attribute(AttributeName.ATTR_NORMAL, Format.RGB32F),
            new Attribute(AttributeName.ATTR_TEX_COORD, Format.RG32F),
            new Attribute(AttributeName.ATTR_TANGENT, Format.RGBA32F),
            new Attribute(AttributeName.ATTR_COLOR, Format.RGBA32F),
          ];

          5 個默認的頂點數(shù)據(jù)的數(shù)據(jù)類型都是 rgba32f,如果對某個值精度要求不那么高,可以自己改一下,減少帶寬(最常見的就是把顏色值改掉)。

          2.查看網(wǎng)格

          我們使用裝飾器executeInEditMode來裝飾腳本類,讓腳本在編輯器中運行,便于我們觀察生成的網(wǎng)格。

          為了處理每次修改代碼后,腳本反復執(zhí)行創(chuàng)建網(wǎng)格產(chǎn)生的問題。我們將生成一個子節(jié)點,并且將網(wǎng)格放在子節(jié)點上,在生成之前做一次 removeAllChildren 操作。

          @ccclass('procedural1')
          @executeInEditMode(true)
          export class procedural1 extends Component {
            start() {
              this.node.removeAllChildren()
              let node = new Node()
              this.node.addChild(node)
              let meshRenderer:MeshRenderer = node.addComponent(MeshRenderer);
              let mesh:Mesh = utils.MeshUtils.createMesh({
                colors:[1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1],
                positions:[0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1],
                indices:[0, 3, 2, 0, 2, 1],
                minPos:v3(0, 0, 0),
                maxPos:v3(1,1,1),
                // attributes: [
                  // new gfx.Attribute(gfx.AttributeName.ATTR_POSITION, gfx.Format.RGB32F),
               // ],
              });
              meshRenderer.mesh = mesh;
              meshRenderer.onGeometryChanged();
            }
          }

          可以在編輯器中看到我們的網(wǎng)格了,選中后會有一個正方形的盒子邊框出來,那是我們自己定義的包圍盒。

          移動鏡頭到下方看上去,這時網(wǎng)格不見了,因為視角到了網(wǎng)格的背面。有興趣的同學可以自己試試再改下頂點索引的順逆時針,或者在后續(xù)添加了材質(zhì)后,修改正面剔除、背面剔除,加深一下對正面剔除、背面剔除機制的理解。

          3.蜂窩網(wǎng)格結構

          構造網(wǎng)格時,用兩個三角形拼湊成一個正方形是最簡單的,但是這樣的網(wǎng)格效率低下了一點。在相同頂點數(shù)量下,如何讓面積盡可能得大,很顯然是用等邊三角形。而等邊三角形拼湊在一起,就形成了無數(shù)個正六邊形。很自然地,我就用起來六邊形網(wǎng)格結構。

          關于六角網(wǎng)格,可以參考這一篇文章,干貨超多,并且?guī)缀趺總€圖都可以交互:《六角網(wǎng)格大觀》

          文章鏈接:https://indienova.com/indie-game-development/hex-grids-reference/#iah-7

          文中內(nèi)容超多,不多贅述,請大家自己去看。這里只簡單說下六角網(wǎng)格相關的計算的一個核心要點:立方坐標。

          立方坐標,顧名思義,就是具有 xyz 三個軸的坐標系,如何在平面中用上三個軸,大家看這個截圖就行了。

          想象一下我們有很多立方形的方塊堆在空間中。這時我們從斜著的方向看去,并且對這些方塊進行一個截面的切割:

          六角網(wǎng)格和立方坐標,一目了然。

          4.六邊形頂點規(guī)化為中心點

          上面那篇文章中幾乎包含了所有六角網(wǎng)格相關的計算的方法,但是我在構造六角網(wǎng)格的時候,還是遇到了一個問題:六角網(wǎng)格的“頂點”(相對于中心點,一個六邊形周圍的六個角上的點)計算坐標容易,但是重用去重時不好處理。

          后來,我經(jīng)過思考繪圖,想到了將頂點規(guī)化為中心點:

          如圖,一個大六邊形的頂點,可以視為其尺寸 1/3 的小六邊形的中心點。

          我們用一個二級 map 來存儲頂點對應的索引值,用立方坐標的x、y來作為鍵值(立方坐標中 x + y + z = 0,所以只需要 xy 兩個值,詳情可以看六角網(wǎng)格大觀里的說明)。

          為了顯示出六角網(wǎng)格的形狀,我們將所有六角網(wǎng)格的中心點頂點色用黑色,邊角頂點用白色

          addPoint(aid:Vec3, a:number, isCenter:boolean = false) {
            let map = this.idxMap.get(aid.x);
            if (!map) {
              map = new Map()
              this.idxMap.set(aid.x, map)
            }
            if (map.has(aid.y)) {
              return map.get(aid.y)
            }
            let pos = HexUtils.getCellPositionWithAxisID(aid, a);
            this.positions.push(pos.x)
            this.positions.push(0)
            this.positions.push(pos.y)
            if (isCenter) {
              this.colors.push(0)
              this.colors.push(0)
              this.colors.push(0)
              this.colors.push(0)
            } else {
              this.colors.push(1)
              this.colors.push(1)
              this.colors.push(1)
              this.colors.push(1)
            }
           
            let idx = this.positions.length / 3 - 1;
            map.set(aid.y, idx);
            return idx;
          }

          5.擴圈

          定義一個包圍圈數(shù)值 N,做循環(huán)構造六角網(wǎng)格:

          let N = 5;
          let a = 1;
          for (let cx = -N; cx <= N; cx++) {
            for (let cy = Math.max(-N - cx, -N); cy <= Math.min(N, -cx + N); cy++) {
              let cz = -cx - cy
              let center = v3(cx * 3, cy * 3, cz * 3)
              let idx0 = this.addPoint(center, a / 3, true)
           
              let offset = v3(1, 1, -2)
              let v1 = v3()
              Vec3.add(v1, center, offset)
              let idx1 = this.addPoint(v1, a / 3)
           
              let v2 = v3()
              for (let i = 0; i < 6; i++) {
                // 旋轉(zhuǎn)60度
                offset.set(-offset.z, -offset.x, -offset.y)
                Vec3.add(v2, center, offset)
                let idx2 = this.addPoint(v2, a / 3)
           
                this.indices.push(idx0)
                this.indices.push(idx1)
                this.indices.push(idx2)
           
                v1.set(v2)
                idx1 = idx2
              }
            }
          }

          6.旋轉(zhuǎn)

          六邊形中心點規(guī)化成小六邊形直接坐標乘以 3 即可,邊角頂點我們先找到一個點,這里找的是中心點偏移(1, 1,-1)后的點,然后用六角網(wǎng)格大觀中提到的一個非常好用的技巧,通過立方坐標轉(zhuǎn)置來獲得旋轉(zhuǎn) 60 度后的格子的立方坐標:

          創(chuàng)建一個材質(zhì)和 shader 用上,shader 里的輸出乘以一下頂點顏色,效果出來了:

          好了,你已經(jīng)學會了噪聲和程序化網(wǎng)格了,接下來只要加億點點細節(jié),就能構造出程序化地形了,前提是你的機子具有理論上無限強大的性能。

          億點點細節(jié)

          1.lod

          lod(level of details) 是一個 3d 渲染常見的技術。一般我們會在兩個地方看到這個詞,一個是模型 lod。大致就是將制作高模、中模、低模三個模型(或者更多),然后通過模型和相機的距離,動態(tài)地替換不同精度地模型。保證近距離下能夠較高的視覺體驗,遠距離時能有較低的性能消耗。

          另一個常見的地方就是某些 shader 中會定義一個 lod 值,用于在不同性能設備上,使用不同的 shader,來兼容性能差異。

          這里我們也用上大致的一個 lod 技術概念,不過我們不需要做的很動態(tài),由于這個 demo 設定是從第一人稱視角看過去的,所以,只要以角色中心,附近使用高精度網(wǎng)格,距離遠的地方使用低精度網(wǎng)格即可。

          我們也用下 fbm 里的術語,用八度 octaves 表示一個分級,每一級的六邊形邊長是上一級的 2 倍,用這樣的方式,可以用較少的網(wǎng)格數(shù)量構造非常大的地圖。

          let octaves = 5
          let N = 5;
          let n = N / 2
          let a = 1;
          for (let oct = 0; oct < octaves; oct++) {
            let mul = Math.pow(2, oct)
            for (let dx = -N; dx <= N; dx++) {
              for (let dy = Math.max(-N - dx, -N); dy <= Math.min(N, -dx + N); dy++) {
                let dz = -dx - dy
                // oct>0的六角網(wǎng)格,內(nèi)部一半部分可以不要,由小網(wǎng)格填充
                if (oct > 0 && Math.abs(dx) < n && Math.abs(dy) < n && Math.abs(dz) < n) {
                  continue
               }
               // 略 大體同上
              }
            }
          }

          2.靜態(tài)網(wǎng)格&動態(tài)網(wǎng)格

          在進行程序化地形的嘗試的過程中,我試過用 ccc 的動態(tài)網(wǎng)格,官方有個示例可以參考。

          一個程序化生成的無邊大地圖,必定時要時刻修改更新地形的,所以理論上動態(tài)網(wǎng)格會更高效。但是使用時,當我將網(wǎng)格的量增大后,遇到了一個報錯:

          個人猜測是網(wǎng)格的數(shù)據(jù)量達到上限了。

          并且,即時不考慮網(wǎng)格上限的問題,一個非常大的網(wǎng)格,更新時的這么多頂點的計算量也肯定非常夸張。

          然后,我經(jīng)過了幾次嘗試:

          方法一:使用shader進行地點坐標偏移

          整個網(wǎng)格的y坐標全都設置為 0 即可,實際的高度,在 shader 中進行計算偏移。這樣的好處是,完全不需要更新網(wǎng)格。你只要將一個扁平的網(wǎng)格移動,shader 會幫你顯示出該有的高度。

          缺點1:只適合單純顯示一個地形。當我們需要有單位在地形上移動時,需要獲取地形高度,此時還需要在 cpu 中進行計算高度。并且,經(jīng)過我的測試,shader 中實時算出的高度由于精度問題,會和 cpu 端算出的值有較大的誤差,這個誤差放大到一個非常大的地圖中,會非常致命。

          缺點2:整個高度全有 shader 計算,對于超大地圖來說,fbm 的 octaves 需要很大,計算量會變得極大。

          方法二:使用“瓦片”拼湊

          參考 2d 的瓦片地圖,我們可以用大量瓦片來拼湊成一個大地圖。瓦片可以通過 gpu instancing 技術進行合批渲染。在 cpu 端對單個瓦片計算出各自的高度,然后在 shader 中加上頂點偏移矯正。

          拼湊的問題:我以六邊形為單位瓦片,將計算好各個六邊形的坐標,高度按六邊形中心點的噪聲值來算。然后算出六個邊角頂點的高度和法線,通過 gpu 示例化屬性的方式傳遞給 gpu,用 shader 來偏移對齊。但是這時卻遇到了一個報錯,經(jīng)過幾番嘗試得出結論:gpu 實例化屬性的數(shù)據(jù)量上限為 4 個 vec4 ,也就是一個 mat4 矩陣。

          因此我不得不將所有六邊形拆分成 3 個四邊形(mesh 只要構建一個即可,通過旋轉(zhuǎn)來用三個拼湊成一個六邊形)。

          3.使用噪聲圖shader 中的 fbm 計算直接換成一個噪聲圖采樣,減少計算量。

          通過頂點法線和采樣到的 fbm 計算出的法線疊加,用一個簡單的半蘭伯特模型計算明暗。

          4.著色

          程序化地形的著色一般有兩種方式,一種是 三向紋理采樣 ,即從 x、y、z 三個方向去采樣紋理,然后根據(jù)當前點的 法線 來對三個采樣值進行融合。

          如果不用三向紋理采樣,單純用一個方向,比如從俯視方向,用xz向量來采樣紋理,那么在一些比較陡峭的斜坡處,會呈現(xiàn)出明顯的拉伸現(xiàn)象。

          另一種方式則簡單了,使用單純的顏色,不過為了展現(xiàn)出不同的地形風貌,我們不該用單純的一個顏色,而是應該用一個色帶。

          這里推薦一下 shadertoy 作者們常用的一個函數(shù):

          vec3 palette(in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d) {
            return a + b * cos(6.28318 * (c * t + d));
          }

          6.28318 是2π;t 是一個 0~1 之間的參數(shù)(實際上超出了也沒關系,反正取色會用 cos);abcd 參數(shù)控制了 rgb 三個顏色的曲線,可以去這個網(wǎng)頁調(diào)節(jié)自己想要的色帶,然后把參數(shù)拿來用即可。

          文章鏈接:http://dev.thi.ng/gradients/

          著色后,在將高處的部分加上一些積雪。積雪用簡單地法線和豎直方向的夾角來處理計算即可,越是平坦的地方越容易積雪。

          然后用高度值來定義一個雪線,在一定高度之上才會有雪。

          在給雪線通過我們的噪聲圖加上一點隨機。

            float snowHeightValue = smoothstep(40.0, 50.0, v_position.y + texture(mainTexture, p * 0.1).r * 10.0);
            float s1 = clamp(1.0 - snowHeightValue, 0.0, 1.0);
            float snowValue = smoothstep(s1, s1 + 0.002 / (s1 + 0.1), dot(vec3(0.0, 1.0, 0.0), n)) * 0.5 + 0.5;

          5.移動更新

          我們需要在跟著主角移動時更新我們視野內(nèi)的地形。由于地形很大,所以,我們要盡可能地使用少的變動來實現(xiàn)效果。

          由于我們已經(jīng)使用 lod 的概念,將網(wǎng)格進行了分級,所以我們只要在相應級別的網(wǎng)格需要變更時才即可。

          這樣說起來可能有點繞,我用一個簡單的例子來說明:主角移動,時刻判定主角所在街道;主角所在街道變了,更新街道網(wǎng)格;更新街道網(wǎng)格時,如果城市變了,更新城市網(wǎng)格;更新城市網(wǎng)格時,如果省份變了,更新省份網(wǎng)格;更新省份網(wǎng)格時,如果國家變了,更新國家網(wǎng)格。

          這樣,我們按我們的分級 octaves 來進行 update 更新。

          onCenterChanged() {
            for (let i = 0; i < HexConfig.octaves; i++) {
              if (!this.checkCellChange(i)) {
                break;
              }
            }
          }

          在加上一個簡單的方向控制 ui 來控制主角移動,這些代碼比較基礎,就略過了。

          這時,我們已經(jīng)可以愉快地在我們的程序世界里逛街了。不過在完成這一步之后,我還實現(xiàn)了一個比較重要的優(yōu)化功能。

          6.共享節(jié)點

          雖然我們使用 lod,但是由于每個六邊形就有 3 個節(jié)點存在,地圖上的網(wǎng)格數(shù)量相當多。導致我們在初始化的時候卡了比較多的時間。這時,我想起了論壇上有一篇關于共享節(jié)點的帖子:

          文章鏈接:https://forum.cocos.org/t/topic/144350

          個人概括理解:共享節(jié)點就是不創(chuàng)建實際的節(jié)點,而是創(chuàng)建了 1 個節(jié)點加上 n-1 個自己定義的對象,這些對象里具備了渲染時的所有差異信息。

          用這個方式,我們能創(chuàng)建大量節(jié)點時的時間消耗。不過那篇文章是基于 2.x 的,跟我們 3.x 的模型的共享不能說是毫不搭邊,只能說是毛兒關系沒有。

          于是,我花時間啃了三天三夜的源碼,終于整出了一個大致的 3.x 模型的共享節(jié)點方案出來。代碼就不發(fā)了,沒啥特別的,并且我也沒有好好封裝,丑陋地一批。大致原理就是,ccc 的 meshrender 會在賦值了 mesh 后自動創(chuàng)建一個 model。這個 model 就是渲染時的目標對象。

          model 有個 node 和 transform 變量都指向當前的節(jié)點,在渲染時會用到node的矩陣信息。

          做了一個實驗場景,構造一萬個節(jié)點和使用共享節(jié)點時,編輯器中的耗時減少及其明顯。

          pc 網(wǎng)頁上也有一定提升:

          尚未進行的構想

          由于篇幅和時間的限制,本文到此為止。但是關于程序化生成地形,其實還有許多許多的東西可以探究。最后,我想將一些我沒有時間和精力去繼續(xù)探究,但是有了一些思路和線索的東西羅列一下,以后有時間優(yōu)化,也歡迎大佬們?nèi)L試:

          1. 使用高度+濕度定義區(qū)域類型:詳情可以參考以下文章:

          文章鏈接:https://indienova.com/indie-game-development/polygonal-map-generation-for-games-2/

          1. 根據(jù)主角視角方向,減少至少一半的網(wǎng)格

          這個其實挺好做的,只是我現(xiàn)在實在沒時間,倒騰這篇文章已經(jīng)肝了兩三個星期了。

          1. 程序化天空

          這是個大課題,做得好的話可以整出極其夢幻的效果,大家可以看看 shadertoy 上各種天體、星空的效果,如果把這些整到一個程序化世界的天空中,成品應該是會很震撼的。

          1. 地圖生物、物品

          按噪聲圖的計算方式,去給當前點獲得一個隨機值,然后按這個隨機值去確定當前點有什么生物或者物品即可。技術上沒有什么難度,并且是一個在實際游戲中很有使用性的方向。

          1. 滄海桑田

          thebookofshaders 中提到了用 fbm 來扭曲 fbm,構造出一種夢境版的扭動霧氣效果。根據(jù)類似的原理,其實我們可以給地形加上一個時間參數(shù),讓地形隨著時間慢慢變化。一個會變化的無限世界!

          1. 更好的噪聲圖

          啃了兩天iq大佬的文章,其實早就想好好看看了,只是一直沒有時間(絕對不是害怕讀英文)。目前有了一點點小收獲,但是還沒有實際產(chǎn)出,希望有一天能整出和大佬一個級別的地形表現(xiàn):

          1. 風格化

          我做的這個例子里的地形的渲染是按寫實的方向去的,但是由于技術力有限,并沒有多少真實感。其實很多時候,程序化生成的地形,也可以避開那些難點,不用一味朝著物理寫實的方向,反而能做出更好看的效果,這是我查資料的過程中看到的一個程序化地形的圖:

          寫在最后

          過幾天整理一下,打算把 demo 放到 Cocos Store 上,希望對有興趣的同學有所幫助。

          瀏覽 317
          3點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          3點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  1级片黄页网站 | 日韩草比 | 色色色五月天 在线播放 | ZZjiZZji亚洲日本少妇 | 2022最新毛片 |