shader實例(四十一)水上漂浮物

上一節學習了如何讓頂點動起來,形成水的效果,那問題又來了,有物體掉入水中怎么辦?這可不是直接掉在地上那么簡單哦(說簡單是因為直接使用Unity的剛體),是不是就得模擬漂浮物的效果呢?就好比下面這個效果:

[
shader實例(四十一)水上漂浮物
]

簡單的記錄幾個關鍵點:1.先獲取物體所包含的碰撞體,在碰撞體的范圍內生成若干浮力盒,然后根據浮力盒的位置,給當前位置施加推力。2.其中涉及到浮力的概念,當物體的浮力m_density小于水的浮力(設置1000)時,物體才會浮起來。
3.物體的阻力和角阻力,當物體在水中時,阻力相對較大,所以變化速度比較慢。

代碼如下:
using System;

using UnityEngine;

using System.Collections.Generic;

namespace Lin

{

[RequireComponent(typeof(Rigidbody))]  

public class BuoyancySetting : MonoBehaviour  

{  

    private CreateMesh m_water;  

    private Transform m_trans;  

    private Rigidbody m_rigidbody;  

    // 原代碼中碰撞體是集合  

    // 可以獲取子對象的碰撞體生成不規則的浮力盒  

    // 比如殘破的船只等  

    private Collider m_collider;  



    public float m_boxDensity = 2;              // 浮力盒的密度  

    private Bounds m_bounds;  

    private float m_boxSize = 0;                // 格子大小  

    private Vector3 m_boxCount = Vector3.one;   // XYZ軸各方向的格子個數  

    private BuoyancyBox[] m_buoyancyBoxs;       // 浮力盒集合  

    private struct BuoyancyBox  

    {  

        // 浮力盒在物體坐標的位置  

        public Vector3 Position;  

        // 是否在邊界  

        public bool IsOnColliderEdge;     

    }  



    // 物體密度,當物體密度小于水密度時才會浮起來  

    public float m_density = 750f;  

    // 流動時的阻力和角阻力,阻力越大運動越慢,越不明顯  

    public float m_inWaterDrag = 1;  

    public float m_inWaterAngularDrag = 1;  



    // 物體初始化時的阻力和角阻力  

    private float m_initDrag;                 

    private float m_initAngularDrag;  



    private void Start()  

    {  

        m_trans = transform;  

        m_water = GameObject.Find("water").GetComponent();  

        m_rigidbody = GetComponent();  

        m_collider = GetComponent();  



        m_initDrag = m_rigidbody.drag;  

        m_initAngularDrag = m_rigidbody.angularDrag;  



        InitBoxs();  

    }  

    // 初始化浮力盒  

    private void InitBoxs()  

    {  

        Quaternion originalRotation = m_trans.rotation;  

        Vector3 originalPosition = m_trans.position;  

        // 旋轉和位置歸零  

        m_trans.rotation = Quaternion.identity;  

        m_trans.position = Vector3.zero;  



        // 計算浮力盒大小  

        m_bounds.Encapsulate(m_collider.bounds);  

        m_boxSize = m_bounds.size.magnitude / m_boxDensity / 2;  

        // XYZ各方向的【浮力盒個數】=  邊界長度/單位大小  

        m_boxCount.x = Mathf.RoundToInt(m_bounds.size.x / m_boxSize) + 1;  

        m_boxCount.y = Mathf.RoundToInt(m_bounds.size.y / m_boxSize) + 1;  

        m_boxCount.z = Mathf.RoundToInt(m_bounds.size.z / m_boxSize) + 1;  



        m_buoyancyBoxs = SliceIntoVoxels().ToArray();  

        // 還原物體位置  

        m_trans.rotation = originalRotation;  

        m_trans.position = originalPosition;  



        m_boxSize = Mathf.Pow(m_bounds.size.x * m_bounds.size.y * m_bounds.size.z /  

                   (m_boxCount.x * m_boxCount.y * m_boxCount.z), 1f / 3f);  

    }  



    private List SliceIntoVoxels()  

    {  

        List boxList = new List((int)(m_boxCount.x * m_boxCount.y * m_boxCount.z));  



        for (int ix = 0; ix < m_boxCount.x; ix++)  

        {  

            for (int iy = 0; iy < m_boxCount.y; iy++)  

            {  

                for (int iz = 0; iz < m_boxCount.z; iz++)  

                {  

                    float x = m_bounds.min.x + m_bounds.size.x / m_boxCount.x * (0.5f + ix);  

                    float y = m_bounds.min.y + m_bounds.size.y / m_boxCount.y * (0.5f + iy);  

                    float z = m_bounds.min.z + m_bounds.size.z / m_boxCount.z * (0.5f + iz);  

                    // 獲取當前每個格子的位置  

                    Vector3 point = new Vector3(x, y, z);  

                    // 如果格子的位置在包圍盒內,就添加到列表  

                    if (ColliderTools.IsPointInsideCollider(m_collider, point))  

                    {  

                        BuoyancyBox buoyancyBox;  

                        // 轉到物體坐標  

                        buoyancyBox.Position = m_trans.InverseTransformPoint(point);  

                        // 在包圍盒的邊緣  

                        buoyancyBox.IsOnColliderEdge = ColliderTools.IsPointAtColliderEdge(m_collider, point, m_boxSize);  



                        boxList.Add(buoyancyBox);  

                    }  

                }  

            }  

        }  

        return boxList;  

    }  



    private void FixedUpdate()  

    {  

        // 浮力增量大于0時,說明物體在水里,那么速度和旋轉的變換是很慢的  

        float m_boxUpDelta = 0;  



        // 比容就是密度的倒數  

        // 比容就是單位質量的體積                                

        float VFactor = 1f / m_density;  

        // 公式:重力G = g * 質量m  

        // 計算單位質量的體積所受重力,這里理解為單位體積所受的重力  

        float unitVG = -Physics.gravity.y * VFactor;  

        // 公式:體積 = 質量 / 密度  

        // 計算當前物體在水中的體積  

        float V = m_water._density * m_water._density * m_rigidbody.mass / m_water._density * Time.deltaTime;  

        // 總施加力 = 單位體積的重力 * 總體積  

        // 給每一個浮力盒的力  

        Vector3 _singleForce = Vector3.up * (unitVG * V / m_buoyancyBoxs.Length);  



        //  浮力盒個數  

        int boxLength = m_buoyancyBoxs.Length;  

        for (int i = 0; i < boxLength; i++)  

        {  

            // 獲取浮力盒在物體中的位置  

            Vector3 point = m_buoyancyBoxs[i].Position;  

            // 獲取浮力盒在世界坐標的位置  

            Vector3 wPoint = m_trans.TransformPoint(point);  

            // 獲取當前位置水面高度  

            float waterHeight = m_water.GetWaterLevel(wPoint.x, wPoint.z);  

            if (waterHeight != float.NegativeInfinity && (wPoint.y - m_boxSize / 1f < waterHeight))  

            {  

                // k = 1 浮力盒完全在水里面,k = 0 浮力盒完全在水外面  

                float k = (waterHeight - wPoint.y) / (2f * m_boxSize) + 0.5f;  

                if (k > 1f)  

                    k = 1f;  

                else if (k < 0f)  

                    k = 0f;  

                // 在水中浮力盒的個數  

                m_boxUpDelta += k;  

                // 計算最終施加給物體的力  

                Vector3 lastForce = k * _singleForce;  



                // 在水里面才需要添加一個力到剛體,使用質量  

                m_rigidbody.AddForceAtPosition(lastForce, wPoint, ForceMode.Impulse);  

            }  

        }  

        // 當浮力盒在水里面的時候,阻力+1,阻力變大,物體下降就變慢  

        // 當浮力盒都在水外面的時候,使用較小的阻力,物體下降就快  

        m_boxUpDelta /= boxLength;  

        m_rigidbody.drag = Mathf.Lerp(m_rigidbody.drag, m_boxUpDelta > 0.0001f ? m_initDrag + m_inWaterDrag : m_initDrag, 15f * Time.deltaTime);  

        m_rigidbody.angularDrag = Mathf.Lerp(m_rigidbody.angularDrag, m_boxUpDelta > 0.0001f ? m_initAngularDrag + m_inWaterAngularDrag : m_initAngularDrag, 15f * Time.deltaTime);  

    }  



    private void OnDrawGizmos()  

    {  

        Gizmos.DrawIcon(transform.position, "DynamicWater/BuoyancyForce.png");  

        if (!Application.isEditor || m_buoyancyBoxs == null)  

        {  

            return;  

        }  

        Vector3 gizmoSize = Vector3.one * m_boxSize;  

        Gizmos.color = new Color(Color.yellow.r, Color.yellow.g, Color.yellow.b, 0.5f);  



        foreach (var p in m_buoyancyBoxs)  

        {  

            Gizmos.DrawCube(transform.TransformPoint(p.Position), gizmoSize);  

        }  

        Gizmos.color = Color.red;  

        Gizmos.DrawSphere(rigidbody.worldCenterOfMass, m_boxSize / 2f);  

    }  

}  



public static class ColliderTools  

{  

    public static bool IsPointInsideCollider(Collider collider, Vector3 point)  

    {  

        RaycastHit hit;  

if !UNITY_FLASH

        if (collider is TerrainCollider)  

        {  

            if (!collider.Raycast(new Ray(point, Vector3.up), out hit, collider.bounds.size.y))  

            {  

                return false;  

            }  

        }  

        else  

endif

            if (collider is MeshCollider && !((MeshCollider)collider).convex)  

            {  

                if (!IsPointInsideMeshCollider(collider, point))  

                {  

                    return false;  

                }  

            }  

            else  

            {  

                Vector3 direction = collider.bounds.center - point;  

                float directionMagnitude = direction.magnitude;  

                if (directionMagnitude > 0.01f &&  

                    collider.Raycast(new Ray(point, direction.normalized), out hit, directionMagnitude))  

                {  

                    return false;  

                }  

            }  



        return true;  

    }  



    public static bool IsPointInsideMeshCollider(Collider collider, Vector3 point)  

    {  

        Vector3[] directions = { Vector3.up, Vector3.down, Vector3.left, Vector3.right, Vector3.forward, Vector3.back };  



        foreach (var ray in directions)  

        {  

            RaycastHit hit;  

            if (collider.Raycast(new Ray(point - ray * 1000f, ray), out hit, 1000f) == false)  

            {  

                return false;  

            }  

        }  



        return true;  

    }  



    public static bool IsPointAtColliderEdge(Collider collider, Vector3 point, float tolerance)  

    {  

        RaycastHit hit;  



        tolerance *= 0.71f; // Approximately 1/sqrt(2)  

        Vector3 direction = collider.bounds.center - point;  

        Vector3 directionNormalized = direction.normalized;  



        bool result = direction != Vector3.zero &&  

                      collider.Raycast(new Ray(point - directionNormalized * tolerance, directionNormalized),  

                                       out hit, tolerance);  



        return result;  

    }  

}  

}

注:可以設置物體密度和阻力達到其他效果,比如

shader實例(四十一)水上漂浮物

shader實例(四十一)水上漂浮物
以上例子需要上一節內容才能運行:
http://blog.sina.com.cn/s/blog_89d90b7c0102vpa3.html

學習來源:
https://www.assetstore.unity3d.com/#/content/10382

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • This article is a record of my journey to learn Game Deve...
    蔡子聰閱讀 3,850評論 0 9
  • 第一部分 1.請簡述值類型與引用類型的區別答:區別: 1.值類型存儲在內存棧中,引用類型數據存儲在內存堆中,而內存...
    為什么你不覺得幸福啊閱讀 696評論 0 1
  • 111. [動畫系統]如何將其他類型的動畫轉換成關鍵幀動畫? 動畫->點緩存->關鍵幀 112. [動畫]Unit...
    胤醚貔貅閱讀 13,147評論 3 90
  • http://www.maiziedu.com/course/540/ 組件教程 prefabs教程http://...
    GZasplin閱讀 1,230評論 0 0
  • 在老家,端午是個大節。端午的到來意味著高溫將不留情面地覆蓋這座小鎮。 過去過端午節,要提前準備四天,五月初一開始撕...
    趕花人閱讀 288評論 0 1