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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • This article is a record of my journey to learn Game Deve...
    蔡子聪阅读 3,750评论 0 9
  • 第一部分 1.请简述值类型与引用类型的区别答:区别: 1.值类型存储在内存栈中,引用类型数据存储在内存堆中,而内存...
    为什么你不觉得幸福啊阅读 681评论 0 1
  • 111. [动画系统]如何将其他类型的动画转换成关键帧动画? 动画->点缓存->关键帧 112. [动画]Unit...
    胤醚貔貅阅读 12,947评论 3 90
  • http://www.maiziedu.com/course/540/ 组件教程 prefabs教程http://...
    GZasplin阅读 1,173评论 0 0
  • 在老家,端午是个大节。端午的到来意味着高温将不留情面地覆盖这座小镇。 过去过端午节,要提前准备四天,五月初一开始撕...
    赶花人阅读 276评论 0 1