如何用Unity制作逼真的自然場景

      作者:直尚教育
      2020-03-26
      509

      游戲中的風(fēng)景越真實(shí)、自然,玩家的代入感也就越強(qiáng)。那如何用Unity制作逼真的自然場景? 學(xué)習(xí)群:233763660

      游戲中的風(fēng)景越真實(shí)、自然,玩家的代入感也就越強(qiáng)。那如何用Unity制作逼真的自然場景?本文將分析Unity官方制作的演示短片——《Book of the Dead》中,風(fēng)與植物的交互原理,幫助大家制作出更自然的風(fēng)景。



      《Book of the Dead》是由Unity官方制作的一個(gè)演示短片,其中有大量的植物,不僅渲染上有著照片級(jí)的真實(shí)感,而且風(fēng)與植物的交互也非常的自然。其所有的自然資源都是來自照片掃描技術(shù),而且使用了HDRP高清渲染管線,場景在Unity Asset Store上可以下載得到。本文主要分析短片中風(fēng)與植物交互的原理。



      一、支持的植物結(jié)構(gòu)


      場景中,對(duì)于風(fēng)與植物交互的模擬,支持三種結(jié)構(gòu):


      Hierachy Pivot:層次嵌套Pivot,用于模擬樹或者其他有多重層次結(jié)構(gòu)的植物


      Single Pivot Color:單Pivot,用于模擬草


      Procedural Animation:程序動(dòng)畫,用于模擬浮萍等無pivot的植物



      對(duì)于樹的模擬最為復(fù)雜,它屬于Hierachy Pivot結(jié)構(gòu),最多支持3個(gè)層次嵌套:


      主干,連接地面


      Level 0分支,連接著主干


      Level 1分支,連接著Leval 0分支



      二、代碼入口


      本文重點(diǎn)分析Hierachy Pivot結(jié)構(gòu)的實(shí)現(xiàn)原理。風(fēng)與植物的交互一般用程序頂點(diǎn)動(dòng)畫實(shí)現(xiàn),隨意找到一棵樹的shader,順藤摸瓜可以在VS中找到如下代碼:


      可以看到,每個(gè)頂點(diǎn)的uv3通道中存的是pivot信息,即該頂點(diǎn)受哪些pivot影響。


      #if USE_VEGETATION_ANIM


          float3 positionWS = GetAbsolutePositionWS(positionRWS);


          APPLY_VEGETATION_ANIM_TIMENUDGE(positionWS, normalWS, input.uv3/*pivotData*/, input.color.rgb/*pivotColor*/, GetObjectAbsolutePositionWS(), time.x);


          positionRWS = GetCameraRelativePositionWS(positionWS);


      #endif


      注意的是,這里的uv3是float3類型。


      struct AttributesMesh


      {


      //...


      //forest-begin: Added vertex animation


      #if defined(_ANIM_SINGLE_PIVOT_COLOR) || defined(_ANIM_HIERARCHY_PIVOT)


          float3 uv3          : TEXCOORD3;


      //...


      };


      Hierachy Pivot結(jié)構(gòu)的植物,最終調(diào)用的是AnimateVegetationHierarchyPivot。


      #if defined(USE_VEGETATION_ANIM) && defined(_ANIM_SINGLE_PIVOT_COLOR)


          #define APPLY_VEGETATION_ANIM_TIMENUDGE(worldPos, normalWorld, pivotData, pivotColor, objectRoot, timeNudge) { AnimateVegetationSinglePivot(worldPos, normalWorld, pivotData, pivotColor, timeNudge); }


      #elif defined(USE_VEGETATION_ANIM) && defined(_ANIM_HIERARCHY_PIVOT)


          #define APPLY_VEGETATION_ANIM_TIMENUDGE(worldPos, normalWorld, pivotData, pivotColor, objectRoot, timeNudge) { AnimateVegetationHierarchyPivot(worldPos, normalWorld, pivotData, pivotColor, objectRoot, timeNudge); }


      #elif defined(USE_VEGETATION_ANIM) && defined(_ANIM_PROCEDURAL_BRANCH)


          #define APPLY_VEGETATION_ANIM_TIMENUDGE(worldPos, normalWorld, pivotData, pivotColor, objectRoot, timeNudge)  { AnimateVegetationProceduralBranch(worldPos, normalWorld, objectRoot, timeNudge); }


      三、PivotData解碼


      pivotData是float3類型,先用asuint轉(zhuǎn)成uint3,一共是32x3=96個(gè)bit,分成兩段前后48bit,分別存Pivot0和Pivot1的信息,分別用UnpackPivot0和UnpackPivot1解出來。


          uint3 packedData =asuint(pivotData);


          float3 pivotPos0, pivotPos1, pivotFwd0, pivotFwd1;


          bool pivotEnabled0 =UnpackPivot0(packedData, pivotPos0, pivotFwd0);


          bool pivotEnabled1 =UnpackPivot1(packedData, pivotPos1, pivotFwd1);


      Pivot0和Pivot1剛好是對(duì)稱排列。



      接下來分析Pivot0是如何解碼出來的,48bit里面,高32bit存Pivot Pos,低16位存Pivot Fwd,細(xì)節(jié)如下圖所示。最終解出來的Pos是模型空間的坐標(biāo),樹的建模應(yīng)該是樹干的根在模型空間的原點(diǎn),Pos.x和Pos.z是有正負(fù)的,而樹只能向上長,于是Pos.y必然是大于0。對(duì)于一般的樹而言垂直方向范圍一般大于水平方向的范圍,于是用12bit保存Pos.y的值,稍微比x和z多2個(gè)bit的精度。



      滿足packedData.y & 0xFFFF0000時(shí),即高16位有值時(shí),代表有Pivot0的信息,才需要解析。


      // Needs to match shader packing in baking tool


      bool UnpackPivot0(uint3 packedData, inout float3 pivotPos0, inout float3 pivotFwd0) {


          if(packedData.y & 0xFFFF0000) {


              pivotPos0.x = UnpackFixedToSFloat(packedData.x, 8.f, 10, 22);


              pivotPos0.y = UnpackFixedToUFloat(packedData.x, 32.f, 12, 10);


              pivotPos0.z = UnpackFixedToSFloat(packedData.x, 8.f, 10, 0);


              pivotFwd0.x = UnpackFixedToSFloat(packedData.y, 1.f, 8, 24);


              pivotFwd0.z = UnpackFixedToSFloat(packedData.y, 1.f, 7, 17);


              pivotFwd0.y = sqrt(1.f - saturate(dot(pivotFwd0.xz, pivotFwd0.xz))) * (((packedData.y >> 16) & 1) ? 1.f : -1.f);


              pivotFwd0 = normalize(pivotFwd0);


              return true;


          }


          return false;


      }


      其中Pos.x用UnpackFixedToSFloat解出來,10bit實(shí)際存的是百分比[0, 1],由于x可能是負(fù)數(shù),編碼時(shí)把[-1, 1]映射到[0, 1],于是這里把[0, 1]反映射回[-1, 1],再乘以傳入的range,可以看出Pos.x的范圍是[-8f, 8f]。從其他硬編碼的參數(shù)可以看出,樹的建模尺寸是長寬16x16,高是32。


      float UnpackFixedToSFloat(uint val, float range, uint bits, uint shift) {


          const uint BitMask = (1 << bits) - 1;


          val = (val >> shift) & BitMask;


          float fval = val / (float)BitMask;


          return (fval * 2.f - 1.f) * range;


      }


      Fwd是分支(樹干或者樹枝)的方向,由于是單位向量,所以只存了x和z分量,y分量可以通過公式反算出來,開方后丟失了符號(hào)信息,于是用1位存符號(hào)。


      pivotFwd0.x = UnpackFixedToSFloat(packedData.y, 1.f, 8, 24);


              pivotFwd0.z = UnpackFixedToSFloat(packedData.y, 1.f, 7, 17);


              pivotFwd0.y = sqrt(1.f - saturate(dot(pivotFwd0.xz, pivotFwd0.xz))) * (((packedData.y >> 16) & 1) ? 1.f : -1.f);


              pivotFwd0 = normalize(pivotFwd0);


      四、整體流程


      偽代碼如下所示,有點(diǎn)跟骨骼動(dòng)畫類似,頂點(diǎn)受骨骼的變換影響,而每個(gè)骨骼會(huì)受其父骨骼的變換影響,最終頂點(diǎn)受骨骼的級(jí)聯(lián)變換影響。樹的頂點(diǎn)至少受主干的影響,因?yàn)槿魏雾旤c(diǎn)肯定要么是屬于主干或者屬于其他分支,而其他分支必然直接或間接連著主干,最復(fù)雜的情況是頂點(diǎn)在level1分支上,level1分支連著level0分支,level分支連著主干,需要計(jì)算累計(jì)變換。


      //任何頂點(diǎn)肯定是在主干上或連接著主干


          計(jì)算主干受風(fēng)力影響導(dǎo)致的旋轉(zhuǎn);


          旋轉(zhuǎn)作用于頂點(diǎn)pos和normal;


          if (有pivot0信息)//主干連接著level0分支


          {


              計(jì)算level0分支受風(fēng)力影響導(dǎo)致的旋轉(zhuǎn);


              旋轉(zhuǎn)作用于頂點(diǎn)pos和normal;


              if (有pivot1信息)//level0分支連接著level1分支


              {


                  計(jì)算level1分支受風(fēng)力影響導(dǎo)致的旋轉(zhuǎn);


                  旋轉(zhuǎn)作用于頂點(diǎn)pos和normal;


              }


          }


      4.1 主干受風(fēng)影響


      對(duì)于每個(gè)枝干受風(fēng)吹后彎曲程度,由如下變量控制,整體的彎曲程度可以由Wind Elasticity Lvl x系列變量控制,其中Lvl B是主干。



      樹被風(fēng)吹有個(gè)特點(diǎn),離地面越遠(yuǎn)部分,被吹彎曲的越厲害,所以會(huì)有個(gè)縮放系數(shù)來控制旋轉(zhuǎn)量,地面處為0(樹根),離地面越遠(yuǎn)的部分這個(gè)縮放系數(shù)越大。有種預(yù)烘培做法,是用頂點(diǎn)模型空間的y除以整個(gè)樹的高度,計(jì)算出縮放系數(shù)并把它烘到點(diǎn)色或者其他通道上,這里的做法是運(yùn)行時(shí)計(jì)算,用變量_WindRangeLvlB調(diào)節(jié)受風(fēng)的范圍,它是模型空間的量,其實(shí)跟預(yù)烘培的效果差不多。lvBElasticity是最終的彈性縮放系數(shù)。


      //主干風(fēng)力影響代碼


          float lvBRelativeObjectScale = mul(GetActualObject2World(), float4(0, _WindRangeLvlB, 0, 0)).y;


          float3 windFwd = GetWindDirection(objectRoot);


          float3 lvBBaseGustWind = GetTreeBaseGustWind(objectRoot, timeNudge);


          float3 lvBPos = objectRoot;


          //主干fwd直接取模型空間y軸方向


          float3 lvBFwd = float3(0, 1, 0); //TODO: grab from rotation matrix


          float lvBElasticity = _WindElasticityLvlB;


          float lvBDistScale = saturate((worldPos.y - objectRoot.y) / lvBRelativeObjectScale);


          lvBElasticity *= lvBDistScale;


      對(duì)于風(fēng)吹草的模擬一般在頂點(diǎn)加上風(fēng)力方向的偏移就可以得到比較好的效果,因?yàn)橐话悴荻急容^矮小,但是對(duì)于樹這種比較高的復(fù)雜結(jié)構(gòu),用草的方式模擬會(huì)有種樹被拉扯變長的感覺,所以一般的方案是用旋轉(zhuǎn)代替頂點(diǎn)偏移。


      lvBWindAxis為旋轉(zhuǎn)軸,windFwd與lvBFwd如果同向或者反向時(shí)候,主干應(yīng)該是不會(huì)旋轉(zhuǎn),后面的枝干level 0做了這種情況的修正,主干這里可能從設(shè)計(jì)上就不會(huì)有垂直于地面的風(fēng)向吧。


      然后就是把旋轉(zhuǎn)作用到頂點(diǎn)的pos和normal上,旋轉(zhuǎn)的錨點(diǎn)是世界空間下的objectRoot,應(yīng)該是模型空間的原點(diǎn)。


          float lvBWindRotAngle = lvBBaseGustWind.x * lvBElasticity;


          //對(duì)旋轉(zhuǎn)角度進(jìn)行l(wèi)og2衰減


          lvBWindRotAngle = log2(1.f + abs(lvBWindRotAngle)) * sign(lvBWindRotAngle);


          float3 lvBWindAxis = cross(lvBFwd, windFwd);


          float4 lvBWindQuat = QuaternionFromAxisAngle(lvBWindAxis, lvBWindRotAngle);


          worldPos = QuaternionRotatePointAbout(worldPos, lvBPos, lvBWindQuat);


          worldNrm = QuaternionRotateVector(worldNrm, lvBWindQuat);


      4.2 支干Level0受風(fēng)影響


      邏輯基本與主干差不多,不同的地方是lv0Fwd的方向用lv0BaseGustWind和lvBDistScale做了調(diào)整,猜測是為了讓彎曲旋轉(zhuǎn)更自然。


      旋轉(zhuǎn)角度lv0WindRotAngle根據(jù)windFwd和lv0Fwd的是否平行,進(jìn)行了相應(yīng)的衰減。


          //當(dāng)lv0BaseGustWind.y為0時(shí),平行風(fēng),旋轉(zhuǎn)軸為y軸模擬更自然


          lv0Fwd.y *= lv0BaseGustWind.y * lvBDistScale;


          lv0Fwd = normalize(lv0Fwd);


          float3 lv0WindAxis = cross(windFwd, lv0Fwd);


          float3 lv0WindRight = cross(windFwd, lv0WindAxis);


          float lv0PerpendicularFactor = dot(lv0Fwd, lv0WindRight);


          float lv0AngleFactor = lv0PerpendicularFactor * lv0PerpendicularFactor;


          lv0AngleFactor *= sign(lv0PerpendicularFactor);


          lv0WindRotAngle *= lv0AngleFactor;


      4.3 支干Level1受風(fēng)影響


      Level1是樹最外層的部分了,整體流程與LevelB和Level0差不多,另外多了一些撲動(dòng)的處理,樹的外端末枝(樹葉或者小樹枝)被風(fēng)吹的時(shí)候往往是有較劇烈的搖晃,而且呈一定的隨機(jī)周期性運(yùn)動(dòng),這部分計(jì)算出來的是頂點(diǎn)偏移,并在最后的旋轉(zhuǎn)前先加到頂點(diǎn)坐標(biāo)上。


          float vertexFlutterPhase = dot(worldPos, _WindFlutterPhase);


          float windFlutterCos = cos(WIND_PI2 * (_WindTime + timeNudge + vertexFlutterPhase) / (_WindTreeFlutterGustVariancePeriod * _WindFlutterPeriodScale));


          float windFlutterStrength = lv1Elasticity * _WindFlutterElasticity * _WindFlutterScale * (_WindTreeFlutterStrength + saturate((max(0.f, lv0BaseGustWind.z) - _WindTreeFlutterGustStrengthOffset) / _WindTreeFlutterGustStrengthScale) * _WindTreeFlutterGustStrength);


          float3 lv1WindAxis = cross(windFwd, lv1Fwd);


          worldPos += lv1WindAxis * windFlutterCos * windFlutterStrength;


      文 | Kirk


      騰訊互動(dòng)娛樂 工程師




      直線網(wǎng)公眾號(hào),第一時(shí)間學(xué)習(xí)最新教程,看最新行業(yè)動(dòng)態(tài)!!


      0
      0
      分享到:

      0

      喜歡他,就推薦他上首頁吧^_^

      推薦閱讀

      ×

      賽事服務(wù)聯(lián)系方式

      0371-86068866

      4008887269

      cndesign@163.com

      好的,我知道了

      官方微信

      聯(lián)系我們

      • QQ:33143335 QQ:1904200230
      • 電話:18569912460
      • 投稿:cndesign@163.com
      • 地址:鄭州市國家大學(xué)科技園東區(qū)9號(hào)樓2層

      版權(quán)信息

        移動(dòng) Android 版 豫 ICP 備16038122號(hào)-2 豫公網(wǎng)安備 41019702002261號(hào)

      主站蜘蛛池模板: 中文字幕亚洲一区二区三区| 无码精品视频一区二区三区| 亚洲日韩精品无码一区二区三区| 亚洲一区二区影院| 国产91精品一区| 亚洲欧美日韩一区二区三区 | 久久亚洲一区二区| 一区二区三区在线观看视频| 精品欧美一区二区在线观看| 亚洲国产视频一区| 国产午夜精品一区理论片| 国产成人高清视频一区二区| 精品3d动漫视频一区在线观看| 国产微拍精品一区二区| 欧美人妻一区黄a片| 人妻精品无码一区二区三区| 亚洲高清日韩精品第一区| 色偷偷av一区二区三区| 99久久精品国产一区二区成人| 亚洲AV综合色一区二区三区| 在线精品亚洲一区二区小说| 91精品一区国产高清在线| 亚洲综合国产一区二区三区| 久久精品一区二区三区日韩 | 天天躁日日躁狠狠躁一区| 精品无码国产一区二区三区51安 | 国产自产V一区二区三区C| 日韩精品无码一区二区三区四区 | 精品一区二区久久久久久久网站| 本免费AV无码专区一区| 国产成人精品无码一区二区| 亚洲AV日韩AV天堂一区二区三区 | 成人区精品人妻一区二区不卡| 成人乱码一区二区三区av| 人妻少妇一区二区三区| 老熟妇仑乱视频一区二区| 日本一道高清一区二区三区| 精品视频午夜一区二区| 东京热人妻无码一区二区av| 一区二区三区亚洲| 色一情一乱一区二区三区啪啪高|