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

你可能感兴趣的:(shader实例(四十一)水上漂浮物)