[Unity/URP] Create a simple water buoyancy effect

Simple water buoyancy production

This effect refers to the method of a certain up owner of station B. The implementation is relatively simple and the performance is relatively average. The main idea is to calculate the upward buoyancy of the corresponding draft volume by calculating the draft volume of the object in the water. But I There is no rotation effect added here, only up and down floating:

Calculation method: Archimedes' principle. If you forget, you can go back and read the high school textbook~~

First we need a method to find the volume of the object:

    //下面是一个方法的创建,这一个方法可以计算出水面上的不规则物体的体积,思路:向Mesh的包围盒里面打出多个点,最后是很多万个,之后统计留在Mesh里面的点占总的打出去的点的
    //百分之多少,就可以计算出物体的体积!!;
    //我们要将当前的gameobject设置为这一个物体,并创建一个矩阵和数组将在模型空间中进行计算,之后再转换世界坐标中并传入到数组中去;
    //同时不要忘了获取包围盒的中心;
    public float CalculateVolumn()
    {
        GameObject object1 = this.gameObject;//首先将场景中的模型设置为当前的模型;
        MeshCollider object1_mc = object1.GetComponent<MeshCollider>();//此处获取到顶点的组件;
        Vector3 object1_center = object1_mc.bounds.center;//获取到组件的顶点包围盒的中心;
        Matrix4x4 localToWorld = object1_mc.transform.localToWorldMatrix;//获取到物体将顶点转换到世界空间中的一个矩阵;
        Vector3[] vertices_object1 = object1_mc.GetComponent<MeshFilter>().mesh.vertices;//将获取到的顶点组件的顶点信息传入到数组当中;
        //这里会创建三个顶点个数长度的数组,并将顶点的xyz经过矩阵变换后移动到世界空间;
        float[] x = new float[vertices_object1.Length];
        float[] y = new float[vertices_object1.Length];
        float[] z = new float[vertices_object1.Length];
        
        //下面进行矩阵从模型空间转移到世界空间的转换;
        for (int i = 0; i < vertices_object1.Length; i++)
        {
            Vector3 world_v = localToWorld.MultiplyPoint3x4(vertices_object1[i]);//这一步将数组中的每一个顶点通过变换转移到世界空间中;
            //下面会将顶点的xyz传入到我们上面创建的三个float数组当中去;
            x[i] = world_v.x;
            y[i] = world_v.y;
            z[i] = world_v.z;
        }

        //下面要计算包围盒的长度,首先要进行大小的排序;
        Array.Sort(x);
        Array.Sort(y);
        Array.Sort(z);
        //大小的排序结束之后就可以直接进行包围盒长度的计算;
        float x_length = x[x.Length - 1] - x[0];
        float y_length = y[y.Length - 1] - y[0];
        float z_length = z[z.Length - 1] - z[0];
        
        //计算步长;
        float lerp_x = x_length / SamplerCount;
        float lerp_y = y_length / SamplerCount;
        float lerp_z = z_length / SamplerCount;
        //下面要计算打出的点中在Mesh中的点的个数;
        int pointCount = 0;
        for (int i = 0; i < SamplerCount; i++)
        {
            for (int j = 0; j < SamplerCount; j++)
            {
                for (int k = 0; k < SamplerCount; k++)
                {
                    //创建samplerDot来接受每一个打出的点;
                    Vector3 samplerDot = new Vector3(x[0] + i * lerp_x, y[0] + lerp_y * j, z[0] + lerp_z * k);
                    if (IsInCollider(object1_mc, object1_center, samplerDot))
                    {
                        pointCount++;//如果打出的点在包围盒的内部则将点数加一;
                    }
                }
            }
            
        }
        //跳出循环,下面是物体体积的计算;
        //计算模型的体积,单位为立方体;
        float volumn = (float)pointCount / (SamplerCount * SamplerCount * SamplerCount) * (x_length * y_length * z_length);
        Debug.Log("Volumn:"+volumn);//弄一个Log;
        return volumn;//最后返回体积;
    }

The following are settings for other variables:

    //先声明一些变量,包括在水里1阻力,旋转的阻力,空中的阻力,空中旋转的阻力,以及密度水面的高度等等;
    private int SamplerCount = 100;//这里是每一个方向会选取100个点,总共会选取100万个点;
    public float density = 1f;//物体的密度;
    public float volumn;//体积大小;
    public float underWaterDrag = 3f;//水里的阻力大小;
    public float underWaterAngularDrag = 1f;//旋转的阻力;
    public float airDrag = 0f;//空气的阻力;
    public float airAngularDrag = 0.05f;//空气中旋转的阻力;
    public float waterHeight = 0f; //水面的高度;
    
    Rigidbody m_Rigidbody;//创建一个Rigidbody对象,用来获取到场景中的物体;
    bool underWater;//用来确定是否在水下;
    MeshRenderer mesh;//创建mesh来获取到场景中物体的所有面;

The following are the Start and FixedUpdate functions. Here we focus on the following FixedUpdate functions:

The difference between Update and FixedUpdate:

-->Update(): Executed every frame, the code is executed N times per second, but because the time of each frame of the computer is not stable, the time interval of each execution is different.

-->FixedUpdate() (physical frame execution): The execution order is after Update, executed every frame, N times per second, but the time of each frame is fixed, that is, the time between each frame is the same

Therefore, if you use Update() to write the physical relationship of an object (such as collision, etc.), there will be some problems if the object receives uneven force in each frame (such as 2D collision causing picture jitter), so use FixedUpdate when it comes to physical relationships. () is better.

Below is the code for the two functions:

void Start()
    {
        m_Rigidbody = GetComponent<Rigidbody>();//获取到场景对象并传入到m_Rigidbody对象中;
        mesh = GetComponent<MeshRenderer>();//获取到物体的面,并存储到mesh对象里面;
        volumn = CalculateVolumn();//通过下面定义的CalculateVolumn方法来获取到物体的体积;
    }

    void FixedUpdate()
    {
        //每一帧的时间相同,可以使得物理受力更加的均匀,这里主要是进行浮力相关的计算;
        //计算m = p*V;mass即为m;
        m_Rigidbody.mass = volumn * Mathf.Clamp(density, 0, density);//m = pV的计算,此处将变量density限制在了0到density之间;
        var difference = transform.position.y - mesh.bounds.size.y / 2 - waterHeight;
        if (difference < 0)
        {
            //给物体一个向上的浮力;
            m_Rigidbody.AddForceAtPosition(Vector3.up * volumn * 9.81f * Mathf.Clamp(Mathf.Abs(difference),0,mesh.bounds.size.y),transform.position,ForceMode.Force);
            //判断是否在水下:
            if (!underWater)
            {
                underWater = true;
                SwitchState(true);
            }
            else if(underWater)
            {
                underWater = false;
                SwitchState(false);
            }
        }
    }

There are also some additional functions:

 void SwitchState(bool isUnderWater)
    {
        if (isUnderWater)
        {
            //修改阻力参数为水下参数;
            m_Rigidbody.drag = underWaterDrag;
            m_Rigidbody.angularDrag = underWaterAngularDrag;
        }
        else
        {
            //如果不在水下则修改成为空气的阻力参数;
            m_Rigidbody.drag = airDrag;
            m_Rigidbody.angularDrag = airAngularDrag;
        }
    }

    //用于判断打出的点是否在包围盒的内部,如果是则返回True;
    //这里传入的是顶点的组件,包围盒的中心,以及每一个打出的顶点位置;
    bool IsInCollider(MeshCollider other, Vector3 center, Vector3 point)
    {
        //这一个函数会对场景中的所有物体进行获取并投射射线,如果透射出的射线属于other则返回false,因为如果物体打出的点是在内部则不会有射线;

        Vector3 direction = center - point;//获取射线方向;
        RaycastHit[] hits = Physics.RaycastAll(point, direction);//获取所有场景中的射线并传入到数组hits当中;
        foreach (RaycastHit hit in hits)
        {
            if (hit.collider == other)
            {
                //对于射线的数组如果判断为改other对象的射线,则返回false;
                return false;
            }
        }

        return true;//否则返回false;
    }

All the code is as follows:

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;


[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(Rigidbody))]
public class buoyancy : MonoBehaviour
{
    //先声明一些变量,包括在水里1阻力,旋转的阻力,空中的阻力,空中旋转的阻力,以及密度水面的高度等等;
    private int SamplerCount = 100;//这里是每一个方向会选取100个点,总共会选取100万个点;
    public float density = 1f;//物体的密度;
    public float volumn;//体积大小;
    public float underWaterDrag = 3f;//水里的阻力大小;
    public float underWaterAngularDrag = 1f;//旋转的阻力;
    public float airDrag = 0f;//空气的阻力;
    public float airAngularDrag = 0.05f;//空气中旋转的阻力;
    public float waterHeight = 0f; 
    
    Rigidbody m_Rigidbody;//创建一个Rigidbody对象,用来获取到场景中的物体;
    bool underWater;//用来确定是否在水下;
    MeshRenderer mesh;//创建mesh来获取到场景中物体的所有面;

    void Start()
    {
        m_Rigidbody = GetComponent<Rigidbody>();//获取到场景对象并传入到m_Rigidbody对象中;
        mesh = GetComponent<MeshRenderer>();//获取到物体的面,并存储到mesh对象里面;
        volumn = CalculateVolumn();//通过下面定义的CalculateVolumn方法来获取到物体的体积;
    }

    void FixedUpdate()
    {
        //每一帧里面都会进行调用,这里主要是进行浮力相关的计算;
        //计算m = p*V;mass即为m;
        m_Rigidbody.mass = volumn * Mathf.Clamp(density, 0, density);//m = pV的计算,此处将变量density限制在了0到density之间;
        var difference = transform.position.y - mesh.bounds.size.y / 2 - waterHeight;
        if (difference < 0)
        {
            //给物体一个向上的浮力;
            m_Rigidbody.AddForceAtPosition(Vector3.up * volumn * 9.81f * Mathf.Clamp(Mathf.Abs(difference),0,mesh.bounds.size.y),transform.position,ForceMode.Force);
            if (!underWater)
            {
                underWater = true;
                SwitchState(true);
            }
            else if(underWater)
            {
                underWater = false;
                SwitchState(false);
            }
        }
    }

    void SwitchState(bool isUnderWater)
    {
        if (isUnderWater)
        {
            //修改阻力参数为水下参数;
            m_Rigidbody.drag = underWaterDrag;
            m_Rigidbody.angularDrag = underWaterAngularDrag;
        }
        else
        {
            //如果不在水下则修改成为空气的阻力参数;
            m_Rigidbody.drag = airDrag;
            m_Rigidbody.angularDrag = airAngularDrag;
        }
    }

    //用于判断打出的点是否在包围盒的内部,如果是则返回True;
    //这里传入的是顶点的组件,包围盒的中心,以及每一个打出的顶点位置;
    bool IsInCollider(MeshCollider other, Vector3 center, Vector3 point)
    {
        //这一个函数会对场景中的所有物体进行获取并投射射线,如果透射出的射线属于other则放回false,因为如果物体打出的点是在内部则不会有射线;

        Vector3 direction = center - point;//获取射线方向;
        RaycastHit[] hits = Physics.RaycastAll(point, direction);//获取所有场景中的射线并传入到数组hits当中;
        foreach (RaycastHit hit in hits)
        {
            if (hit.collider == other)
            {
                //对于射线的数组如果判断为改other对象,则返回false;
                return false;
            }
        }

        return true;//否则返回false;
    }


    //下面是一个方法的创建,这一个方法可以计算出水面上的不规则物体的体积,思路:向Mesh的包围盒里面打出多个点,最后是很多万个,之后统计留在Mesh里面的点占总的打出去的点的
    //百分之多少,就可以计算出物体的体积!!;
    //我们要将当前的gameobject设置为这一个物体,并创建一个矩阵和数组将在模型空间中进行计算,之后再转换世界坐标中并传入到数组中去;
    //同时不要忘了获取包围盒的中心;
    public float CalculateVolumn()
    {
        GameObject object1 = this.gameObject;//首先将场景中的模型设置为当前的模型;
        MeshCollider object1_mc = object1.GetComponent<MeshCollider>();//此处获取到顶点的组件;
        Vector3 object1_center = object1_mc.bounds.center;//获取到组件的顶点包围盒的中心;
        Matrix4x4 localToWorld = object1_mc.transform.localToWorldMatrix;//获取到物体将顶点转换到世界空间中的一个矩阵;
        Vector3[] vertices_object1 = object1_mc.GetComponent<MeshFilter>().mesh.vertices;//将获取到的顶点组件的顶点信息传入到数组当中;
        //这里会创建三个顶点个数长度的数组,并将顶点的xyz经过矩阵变换后移动到世界空间;
        float[] x = new float[vertices_object1.Length];
        float[] y = new float[vertices_object1.Length];
        float[] z = new float[vertices_object1.Length];
        
        //下面进行矩阵从模型空间转移到世界空间的转换;
        for (int i = 0; i < vertices_object1.Length; i++)
        {
            Vector3 world_v = localToWorld.MultiplyPoint3x4(vertices_object1[i]);//这一步将数组中的每一个顶点通过变换转移到世界空间中;
            //下面会将顶点的xyz传入到我们上面创建的三个float数组当中去;
            x[i] = world_v.x;
            y[i] = world_v.y;
            z[i] = world_v.z;
        }

        //下面要计算包围盒的长度,首先要进行大小的排序;
        Array.Sort(x);
        Array.Sort(y);
        Array.Sort(z);
        //大小的排序结束之后就可以直接进行包围盒长度的计算;
        float x_length = x[x.Length - 1] - x[0];
        float y_length = y[y.Length - 1] - y[0];
        float z_length = z[z.Length - 1] - z[0];
        
        //计算步长;
        float lerp_x = x_length / SamplerCount;
        float lerp_y = y_length / SamplerCount;
        float lerp_z = z_length / SamplerCount;
        //下面要计算打出的点中在Mesh中的点的个数;

        int pointCount = 0;
        for (int i = 0; i < SamplerCount; i++)
        {
            for (int j = 0; j < SamplerCount; j++)
            {
                for (int k = 0; k < SamplerCount; k++)
                {
                    //创建samplerDot来接受每一个打出的点;
                    Vector3 samplerDot = new Vector3(x[0] + i * lerp_x, y[0] + lerp_y * j, z[0] + lerp_z * k);
                    if (IsInCollider(object1_mc, object1_center, samplerDot))
                    {
                        pointCount++;//如果打出的点在包围盒的内部则将点数加一;
                    }
                }
            }
            
        }
        //跳出循环,下面的体积的计算;
        //计算模型的体积,单位为立方体;
        float volumn = (float)pointCount / (SamplerCount * SamplerCount * SamplerCount) * (x_length * y_length * z_length);
        Debug.Log("Volumn:"+volumn);//弄一个Log;
        return volumn;//最后返回体积;
    }
}

Finally, add a Rigidbody and script to the object during operation. After running, adjust the waterHeight to the appropriate height to see the buoyancy effect, as shown in the figure:

Adjusting Density can make objects float or sink.

Some additions to other functions

Rigidbody.AddForce

Rigidbody.AddForceAtPosition

Guess you like

Origin blog.csdn.net/2201_75303014/article/details/129368064