您當前的位置:首頁 > 體育

程式化河流後續——加入浮力系統

作者:由 就是愛折騰 發表于 體育時間:2020-05-21

前言

為了給程式化河流加入浮力系統,前面搞了個浮力外掛粗略研究了一下。也寫了一篇簡要的文章總結了下:

實際測試後,發現這個外掛並不能直接用到之前河流的網格上。效果上最明顯的是這個水效果不受水面傾斜的影響,如圖。

當水往下流時,物體還在原處漂浮,顯然違反自然規律。不過這個問題很好解決:直接給物體在水流方向再加一個力水流的推力就行了

。後期還遇到了一個問題,

由於這個水系統需要漂浮物碰撞體設定為Trigger型別。所以當漂浮物碰到河岸邊界時會衝出去(物體的碰撞型別是trigger),對於這個問題,我的解決辦法是透過自己寫些方法來限制漂浮物的運動。

程式化河流後續——加入浮力系統

水平面傾斜,物體還是再原處浮動

解決問題

首先附上之前建立程式化河流的文章的連結,可以先看下這篇:

為了解決第一個問題,我首先將浮力外掛的核心功能指令碼,也就是FloatingObject。cs和WaterVolume。cs提取出來。然後對WaterVolume。cs進行大幅簡化,去掉了網格頂點分析的部分。接著開始對FloatingObject。cs的核心部分FixUpdate進行修改:

水面法線和水面高度計算的簡化。

原始外掛計算水面的法線和水面的高度使用的水面網格資料來計算的,這裡偷懶(考慮到河流網格的頂點不會太多),直接從

體元的正上方一定距離向下發射射線,得到RaycastHit

,這裡面就已經包含了碰撞點和碰撞點的法線資訊了。這樣WaterVolume。cs中的很多程式碼就都可以去掉了。

//hit中就包含了法線和碰撞點

RaycastHit

hit

=

this

GetHitInfoOnWater

worldPoint

);

float

waterLevel

=

hit

point

y

//從水面上方垂直向下發射射線,得到和水面的交點

public

RaycastHit

GetHitInfoOnWater

Vector3

worldPoint

{

Vector3

_originUP

=

worldPoint

+

Vector3

up

*

1000

Ray

_rayDown

=

new

Ray

_originUP

Vector3

down

);

RaycastHit

hit

if

Physics

Raycast

_rayDown

out

hit

Mathf

Infinity

1

<<

LayerMask

NameToLayer

“Water”

)))

{

return

hit

}

return

hit

}

2。給物體新增推力。

在建立河流的網格的時候,我們給每個河流網格都添加了一個RiverInstance。用來儲存河流的基本資訊。

==============================

RiverInstance

cs

==============================

public

class

RiverSegInfo

{

public

float

LengthRadio

public

float

halfWidth

public

Vector3

flowDir

public

Vector3

center

}

================================

River

cs

========================================

//記錄河流寬度資訊

m_riverSegmentInfos

Add

new

RiverSegInfo

()

{

LengthRadio

=

float

i

/

_resultWayPoints

Count

//當前位置所處河流長度佔河流總長度比率

halfWidth

=

_halfRiverWidth

//當前位置和半寬

flowDir

=

_vetexOffset

normalized

//水流流向

center

=

_resultWayPoints

i

//河流路徑中心

}

);

//儲存每條河流的必要資料

m_riverInstance

riverSegmentInfos

=

this

m_riverSegmentInfos

//儲存每條河流的必要資料

m_riverInstance

riverUVWrapAmount

=

_uvWrap

//河流UVY大小

m_riverInstance

riverLength

=

_riverLength

//河流總長度

m_riverInstance

segmentAmount

=

_resultWayPoints

Count

-

1

//河流總節點

m_riverInstance

riverDepth

=

riverDepth

//河流深度

其中就包括指定河流位置河流的流向資訊flowDir ,河流網格UV。y的大小_uvWrap。由此,透過RaycastHit中的hit。texcoord。y資訊就可以反推出體元當前所在位置的水流流向flowDir 。

==============================

RiverInstance

cs

======================================

//RiverInstance類方法

public

RiverSegInfo

GetFlowSegmentInfo

float

UVY

{

if

UVY

>

0

&&

riverSegmentInfos

Count

>

0

{

float

_lengthUVYRadio

=

UVY

/

riverUVWrapAmount

float

_lengthRadioPerSegment

=

1f

/

segmentAmount

foreach

var

segInfo

in

riverSegmentInfos

{

if

Mathf

Abs

segInfo

LengthRadio

-

_lengthUVYRadio

<

_lengthRadioPerSegment

*

2

{

// Debug。Log(segInfo。LengthRadio + “ ” + _lengthUVYRadio + “ ” + segInfo。center);

return

segInfo

}

}

}

return

null

}

================================

FloatObject

cs

=================================

//由hit。textureCoord。y反推水流流向flowDirection

segmentInfo

=

river

GetFlowSegmentInfo

hit

textureCoord

y

);

Vector3

flowDirection

=

segmentInfo

flowDir

有了水流流向,配合該位置水面網格的法線我們就可以計算水流推力了。這裡我使用(1。2 -Dot(N,y))作為因子來調整推力大小(水面傾斜度越大,推力越大,水完全水平時,依然會緩慢向水流方向前進),最終得到推力currentVoxelWaterForce。最後將推力與原來的浮力疊加。至此推力新增完成。物體開始沿著河流移動。

//這裡1。2f - ndotUp是確保當物體處於完全水平的水面時也能向水流方向運動

currentVoxelWaterForce

=

1。2f

-

ndotUp

*

flowDirection

*

water

baseWaterPushForceRadio

Quaternion

surfaceRotation

=

Quaternion

FromToRotation

this

water

transform

up

surfaceNormal

+

flowDirection

)。

normalized

);

//沉入水中越少,朝水面法線偏移越厲害,抖動的越厲害

surfaceRotation

=

Quaternion

Slerp

surfaceRotation

Quaternion

identity

submergedFactor

);

Vector3

finalVoxelForce

=

surfaceRotation

*

((

forceAtSingleVoxel

+

currentVoxelWaterForce

*

submergedFactor

);

//新增力

this

rigidbody

AddForceAtPosition

finalVoxelForce

worldPoint

);

程式化河流後續——加入浮力系統

白線即為水流推力方向

3。解決物體衝出河流的問題。

物體衝出河流的問題是必然的,因為地形碰撞體其實已經完全沒起作用了。這裡我用兩個方式來解決了一下(這兩個方式都不是精確的,只是大概達到了效果)。

首先在物體的OnTriggerEnter被觸發時,當檢測到物體是與地形網格碰撞。我將給物體在碰撞的瞬間給物體設定一個反彈速度

從而讓物體反彈回來並繼續往下游前進。如下圖:當前物體運動速度為V,水流推力為W,於是給物體新增的力即為VW

程式化河流後續——加入浮力系統

protected

virtual

void

OnTriggerEnter

Collider

other

{

//……

this

rigidbody

velocity

=

currentVoxelWaterForce

-

rigidbody

velocity

*

boundRadio

}

}

單純這樣還是不能解決問題,物體偶爾還是會鑽出去。於是後面又加了一個操作:

持續給物體新增一個拽向河流中心的力,力的大小根據物體偏離河流中心的大小來調整

。透過挑取合適的因子,終於達到了可以接受的效果。

//防止物體衝出河道

if

segmentInfox

!=

null

){

//與河中心的偏移值

Vector3

offset

=

transform

position

-

segmentInfox

center

+

river

riverDepth

*

Vector3

down

);

///將物體拉回河流中心

this

rigidbody

AddForce

(-

offset

*

centerDragForceRadio

ForceMode

Impulse

);

}

最後

透過上面的操作,河流的浮力系統終於改造完成了。最終結果:

程式化河流後續——加入浮力系統

Git:

最後貼下FloatObject。cs完整的FixUpdate程式碼,方便對照。

protected

virtual

void

FixedUpdate

()

{

if

this

water

!=

null

&&

this

voxels

Length

>

0

{

//將總共的浮力分到每個體元

Vector3

forceAtSingleVoxel

=

this

CalculateMaxBuoyancyForce

()

/

this

voxels

Length

Bounds

bounds

=

this

collider

bounds

//每個體元的高度

float

voxelHeight

=

bounds

size

y

*

this

normalizedVoxelSize

float

submergedVolume

=

0f

RiverSegInfo

segmentInfo

=

null

for

int

i

=

0

i

<

this

voxels

Length

i

++)

{

Vector3

worldPoint

=

this

transform

TransformPoint

this

voxels

i

]);

//獲取水深度

RaycastHit

hit

=

this

GetHitInfoOnWater

worldPoint

);

float

waterLevel

=

hit

point

y

//體元的深度(體元底部到水面)

float

deepLevel

=

waterLevel

-

worldPoint

y

+

voxelHeight

/

2f

);

// 0 - 完全出了水面 1 - 完全沉入水中

float

submergedFactor

=

Mathf

Clamp

deepLevel

/

voxelHeight

0f

1f

);

submergedVolume

+=

submergedFactor

//水面的法線

Vector3

surfaceNormal

=

hit

normal

//水對物體的推力計算

float

ndotUp

=

Mathf

Clamp01

Vector3

Dot

surfaceNormal

Vector3

up

));

segmentInfo

=

river

GetFlowSegmentInfo

hit

textureCoord

y

);

if

segmentInfo

!=

null

{

Vector3

flowDirection

=

segmentInfo

flowDir

//這裡1。2f - ndotUp是確保當物體處於完全水平的水面時也能向水流方向運動

currentVoxelWaterForce

=

1。2f

-

ndotUp

*

flowDirection

*

water

baseWaterPushForceRadio

Quaternion

surfaceRotation

=

Quaternion

FromToRotation

this

water

transform

up

surfaceNormal

+

flowDirection

)。

normalized

);

//沉入水中越少,朝水面法線偏移越厲害,抖動的越厲害

surfaceRotation

=

Quaternion

Slerp

surfaceRotation

Quaternion

identity

submergedFactor

);

Vector3

finalVoxelForce

=

surfaceRotation

*

((

forceAtSingleVoxel

+

currentVoxelWaterForce

*

submergedFactor

);

//新增力

this

rigidbody

AddForceAtPosition

finalVoxelForce

worldPoint

);

Debug

DrawLine

worldPoint

worldPoint

+

finalVoxelForce

normalized

Color

blue

);

Debug

DrawLine

worldPoint

worldPoint

+

flowDirection

);

}

}

submergedVolume

/=

this

voxels

Length

// 0 - object is fully out of the water, 1 - object is fully submerged

this

rigidbody

drag

=

Mathf

Lerp

this

initialDrag

this

dragInWater

submergedVolume

);

this

rigidbody

angularDrag

=

Mathf

Lerp

this

initialAngularDrag

this

angularDragInWater

submergedVolume

);

RaycastHit

hitx

=

this

GetHitInfoOnWater

transform

position

);

RiverSegInfo

segmentInfox

=

river

GetFlowSegmentInfo

hitx

textureCoord

y

);

//防止物體衝出河道

if

segmentInfox

!=

null

){

Vector3

offset

=

transform

position

-

segmentInfox

center

+

river

riverDepth

*

Vector3

down

);

this

rigidbody

AddForce

(-

offset

*

centerDragForceRadio

ForceMode

Impulse

);

Debug

DrawLine

transform

position

,(

segmentInfox

center

+

river

riverDepth

*

Vector3

down

));

}

}

}

標簽: 物體  河流  CS  worldPoint  水面