水面渲染-浮力的一種實現

在Github發現一個頗有意思的項目github.com/dbrizov/Unity-WaterBuoyancy,git

這個項目基於unity遊戲引擎開發,爲水體增長了浮力這一物理要素。儘管浮力的實現代碼只有短短一百多行,但多了浮力的水面彷彿有了靈魂,這就是遊戲開發技術的魅力啊。github

本身寫的水面渲染

浮力定義

那浮力是怎麼實現,咱們回顧下浮力的定義。c#

漂浮於流體(液體或氣體)表面或浸沒於流體之中的物體,受到各方向流體靜壓力的向上協力。其大小等於被物體排開流體的重力。ide

簡單來講,浮力方向與重力相反,它的大小等於物體排開液體的重力。知道了浮力的原理,那麼就很容易在水面渲染中實現它,咱們只須要知道物體浸入水中的體積,水的密度,就很容易計算出浮力了。this

體積的計算

物體整體積的計算

首先咱們嘗試計算物體的整體積,這裏咱們假設物體是實心的。lua

咱們知道遊戲的物體的本質是網格,而網格本質是由多個三角形面近似成的幾何體code

因此只須要獲取物體的網格信息中的三角形面片數據,計算網格每個三角形面片和物體中心點所構成三角錐的體積,三角錐總和即是物體的整體積orm

/// <summary>
/// 計算體積
/// </summary>
private void CalualateVolume()
{
    MeshFilter mf = GetComponent<MeshFilter>();
    Mesh mesh = mf.mesh;
    float volume = 0f;
    Vector3[] vertices = mesh.vertices;
    int[] triangles = mesh.triangles;
    for (int i = 0; i < mesh.triangles.Length; i += 3)
    {
        Vector3 p1 = vertices[triangles[i + 0]];
        Vector3 p2 = vertices[triangles[i + 1]];
        Vector3 p3 = vertices[triangles[i + 2]];
        Vector3 a = p1 - p2;
        Vector3 b = p1 - p3;
        Vector3 c = p1 - Vector3.zero;

        volume += (Vector3.Dot(a, Vector3.Cross(b, c))) / 6f;

    }

    m_Volume = Mathf.Abs(volume) * transform.localScale.x * transform.localScale.y * transform.localScale.z;
}

計算物體浸入水中部分的體積

物體的整體積計算成功了,那如何計算物體浸入水中的部分呢,此次咱們可不能經過計算三角錐的方法來計算了,由於物體實際被水面截斷了,三角錐可能存在少量浸入水中,大部分露出水面的狀況。遊戲

dbrizov/Unity-WaterBuoyancy項目提出體素這個概念,它把一個物體量化成均勻分佈的點,只須要計算浸入水中的點的數目,即可近似獲得物體浸入水中部分的體積ip

獲取體素列表
private void CalualateVoxels()
    {
        Quaternion initialRotation = this.transform.rotation;
        this.transform.rotation = Quaternion.identity;
        Bounds bounds = m_Bounds;
        this.voxelSize.x = bounds.size.x / VoxelSize;
        this.voxelSize.y = bounds.size.y / VoxelSize;
        this.voxelSize.z = bounds.size.z / VoxelSize;
        List<Vector3> voxels = new List<Vector3>( VoxelSize * VoxelSize * VoxelSize);

        for (int j = 0; j < VoxelSize; j++)
        {
            for (int i = 0; i < VoxelSize; i++)
            {
                for (int k = 0; k < VoxelSize; k++)
                {
                    float pX = bounds.min.x + this.voxelSize.x * (0.5f + i);
                    float pY = bounds.min.y + this.voxelSize.y * (0.5f + j);
                    float pZ = bounds.min.z + this.voxelSize.z * (0.5f + k);

                    Vector3 point = new Vector3(pX, pY, pZ);
                    if (IsPointInsideCollider(point))
                    {
                        voxels.Add(this.transform.InverseTransformPoint(point));
                    }
                }
            }
        }

        transform.rotation = initialRotation;

        m_Voxels = voxels.ToArray();
    }

    private bool IsPointInsideCollider(Vector3 point)
    {
        float rayLength = m_Bounds.size.magnitude;
        Ray ray = new Ray(point, m_Collider.transform.position - point);
        RaycastHit hit;

        if (Physics.Raycast(ray, out hit, rayLength))
        {
            if (hit.collider == m_Collider)
            {
                return false;
            }
        }

        return true;
    }

浮力實現1.0版

咱們首先計算物體徹底浸入水中所受到浮力,再平攤到每個體素上,物體受到的總浮力即是浸入水中體素所受到浮力之和

總浮力的計算

int len = m_Voxels.Length;
float submergedVolume = 0f;
Vector3 force = water.Density * m_Volume * -Physics.gravity / m_Voxels.Length;//單個體素受到的浮力
for (int i = 0; i < len; i++)
{
    Vector3 worldPoint = transform.TransformPoint(m_Voxels[i]);

    float submergedFactor = 0;

    if (worldPoint.y < water.transform.position.y)
    {
        submergedVolume += 1;
    }

}
m_Rigidbody.AddForce(force * submergedVolume);

表現效果

浮力實現1.0版

這裏已經初步實現了浮力,但膠囊幾何體出現了不天然的直立,咱們但願物體能和水面交互,產生旋轉等效果

浮力實現2.0版

咱們但願物體會天然的旋轉,那麼物體所受力的方向不能只是簡單的垂直向上。

dbrizov/Unity-WaterBuoyancy是這樣實現的

Vector3 worldPoint = transform.TransformPoint(m_Voxels[i]);
 
float submergedFactor = 0;

if (worldPoint.y < water.transform.position.y)
{
    submergedFactor = 1;
    submergedVolume += submergedFactor;
}
 
Vector3 surfaceNormal = water.GetSurfaceNormal(worldPoint);
Quaternion surfaceRotation = Quaternion.FromToRotation(water.transform.up, surfaceNormal);
surfaceRotation = Quaternion.Slerp(surfaceRotation, Quaternion.identity, submergedFactor);

Vector3 finalVoxelForce = surfaceRotation * force * submergedFactor;
m_Rigidbody.AddForceAtPosition(finalVoxelForce, worldPoint);

Debug.DrawLine(worldPoint, worldPoint + finalVoxelForce.normalized, Color.blue);

它把物體所受的浮力平攤到每一個點上,而每一個點受到的浮力方向應該是與水面法線相同,因此須要一個四元數矯正力的方向

最後的結果是這樣的

完整代碼

https://github.com/IceLanguage/LinHowe_WaterRendering/blob/master/Assets/Scripts/Componets/FloatingObject.cs

相關文章
相關標籤/搜索