【Unity 3D绳索】基于图形渲染的3D绳索模拟

这篇具有很好参考价值的文章主要介绍了【Unity 3D绳索】基于图形渲染的3D绳索模拟。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        前一段被要求模拟一根绳索,因为种种原因,笔者最后决定通过数学函数和Mesh模拟出一根绳索,具体的思路是首先利用Verlet函数模拟绳索的刚性,之后利用Mesh渲染圆柱体将绳索模拟出来。

        player.instance.transform.position,图形渲染,unity,游戏引擎

        首先,利用Verlet进行逻辑上的绳索创建,具体思路参考了这篇文章:

在Unity使用Verlet积分实现逼真的绳索 - 知乎

        不过这篇文章的作者是利用LineRenderer模拟的2D绳索,简单来讲就是将绳子看成许多节,每个节点都包含两个端点,端点会产生一个力来约束绳索,有了这些基础就可以模拟绳子的物理特性了。

player.instance.transform.position,图形渲染,unity,游戏引擎

/// <summary>
/// Verlet最小节点
/// </summary>
public class Particle
{
    public Vector3 position;
    public Vector3 oldPosition;
    public bool locked;
}

/// <summary>
/// 抓钩段长
/// </summary>

public class Stick
{
    public Particle particleA;
    public Particle particleB;
    public float length;

    public Stick(Particle a, Particle b)
    {
        particleA = a;
        particleB = b;
        length = (a.position - b.position).magnitude;
    }
}

        而因为需要建立在三维空间中,所以不采用LineRenderer,而是直接用一组Vector3来代表这些绳子的节点,在逻辑上将绳子模拟出来,以待后续的渲染。

public class Hook : MonoBehaviour
{
    [Header("节点数")]
    [Range(30, 100)]
    public float points = 80;
    [Header("半径")]
    public float lineRadius = 0.02f;
    [Header("重力")]
    [SerializeField] public float gravity = 1f;
    [Header("作用力")]
    [SerializeField] public int stiffness = 200;

    [SerializeField] private bool startPointLock; //锁定节点
    [SerializeField] private bool endPointLock;
    [SerializeField] private bool isfollow; //起始点随父物体移动

    private Vector3 _startPosition; //开始位置
    private Vector3 _endPosition; //结束位置

    public Material _material;
    private List<Vector3> _vector3s = new List<Vector3>();     //抓钩信息存储列表
    private List<Particle> _particles = new List<Particle>();
    private List<Stick> _sticks = new List<Stick>();
    private Dictionary<int, UnityEngine.Mesh> _meshMap = new Dictionary<int, UnityEngine.Mesh>();

    private bool _isExist = false; //是否已存在抓钩
    private int _index = 0; //抓钩节点计数

    /// <summary>
    /// 初始化
    /// </summary>
    public void Init(Vector3 startV3, Vector3 endV3)
    {
        _startPosition = startV3;
        _endPosition = endV3;
        Initialization();
        SetParticlesLockSet();
        _isExist = true;
    }

    private void FixedUpdate()
    {
        Simulation();
    }

    /// <summary>
    /// 抓钩节点设置
    /// </summary>
    private void Initialization()
    {
        _vector3s.Clear();
        _particles.Clear();
        _sticks.Clear();
        _meshMap.Clear();
        for (int i = 0; i <= points; i++)
        {
            float t = i / points;
            _vector3s.Add(Vector3.Lerp(_startPosition, _endPosition, t));
        }
        for (int i = 0; i < _vector3s.Count; i++)
        {
            _particles.Add(new Particle() { position = _vector3s[i], oldPosition = _vector3s[i] });
        }
        for (int i = 0; i < _particles.Count - 1; i++)
        {
            _sticks.Add(new Stick(_particles[i], _particles[i + 1]));
        }
    }

    /// <summary>
    /// 抓钩节点锁定设置
    /// </summary>
    private void SetParticlesLockSet()
    {
        if (startPointLock)
        {
            _particles[0].locked = true;
        }
        if (endPointLock)
        {
            _particles[_particles.Count - 1].locked = true;
        }
        if (isfollow)
        {
            _particles[0].locked = true;
        }
    }

    /// <summary>
    /// 抓钩特性赋值
    /// </summary>
    private void Simulation()
    {
        //遍历
        for (int i = 0; i < _particles.Count; i++)
        {
            Particle p = _particles[i];
            if (p.locked == false)
            {
                Vector3 temp = p.position;
                //Verlet积分
                p.position = p.position + (p.position - p.oldPosition) + Time.fixedDeltaTime * Time.fixedDeltaTime * new Vector3(0, -gravity, 0);
                p.oldPosition = temp;
            }
        }

        //迭代次数,控制刚性
        for (int i = 0; i < stiffness; i++)
        {
            for (int j = 0; j < _sticks.Count; j++)
            {
                Stick stick = _sticks[j];

                Vector3 delta = stick.particleB.position - stick.particleA.position;
                float deltaLength = delta.magnitude;
                float diff = (deltaLength - stick.length) / deltaLength;
                if (stick.particleA.locked == false)
                    stick.particleA.position += 0.5f * diff * delta;
                if (stick.particleB.locked == false)
                    stick.particleB.position -= 0.5f * diff * delta;
            }
        }

        if (isfollow && _particles.Count > 0)
        {
            _particles[0].position = Player.instance.transform.position;
        }
    }
}

        接下来便是考虑如何渲染3D的绳索了,最优的方案如下,利用所得节点渲染不平行的柱体,这是以下这篇Unreal的绳索文档思路,不过这个文章并没有提供源码,所有笔者准备改一种更简单的方法。

https://docs.unrealengine.com/4.27/en-US/Basics/Components/CableComponent/

player.instance.transform.position,图形渲染,unity,游戏引擎

        将两个节点之间渲染成不平行圆柱体实现起来比较麻烦,而渲染成平行的就简单很多了,但随之面临的问题是不平行的圆柱体之间会有缝隙在绳索弯曲的时候,但利用积分的思想,假设绳子上的节点越多,那么绳子间的缝隙影响就会越小,当节点够多时 (目前尝试的,其实50-80个的时候基本就看不到了,前提时绳子不要太粗),就看不到缝隙了。

player.instance.transform.position,图形渲染,unity,游戏引擎

        所以说,接下来只要对各个节点进行圆柱体绘制就行了:

    private void LateUpdate()
    {
        if (_isExist)
        {
            StartCoroutine(HookRendering(true));
        }
        Rendering();
    }
    
    /// <summary>
    /// 绘制抓钩
    /// </summary>
    private void Rendering()
    {
        for (int i = 0; i < _index; i++)
        {
            DrawCylinder(_particles[i].position, _particles[i + 1].position);
        }
    }

    /// <summary>
    /// 绘制抓钩携程,true为延伸,false为收缩
    /// </summary>
    /// <param name="isExtend"></param>
    /// <returns></returns>
    IEnumerator HookRendering(bool isExtend)
    {
        endPointLock = isExtend;
        _isExist = false;
        if (isExtend)
        {
            while (_index < _particles.Count - 1)
            {
                _index++;
                yield return new WaitForSeconds(0.01f);
            }
            //Player.instance.MoveControl(_endPosition, true);
            //StartCoroutine(HookRendering(false));
        }
        else
        {
            while (_index > 0)
            {
                _index--;
                yield return new WaitForSeconds(0.01f);
            }
        }
    }    
    /// <summary>
    /// Mesh绘制
    /// </summary>
    /// <param name="a"></param>
    /// <param name="b"></param>
    private void DrawCylinder(Vector3 a, Vector3 b)
    {
        if (isNaN(a) || isNaN(b)) { return; }

        float length = (a - b).magnitude;

        if ((a - b).magnitude > 0.001f)
        {
            Graphics.DrawMesh(GetCylinderMesh(length),
                              Matrix4x4.TRS(a,
                                            Quaternion.LookRotation(b - a),
                                            new Vector3(transform.lossyScale.x, transform.lossyScale.x, 1)),
                              _material,
                              gameObject.layer,
                              null, 0, null, true);
        }
    }

    /// <summary>
    /// Mesh获取
    /// </summary>
    /// <param name="length"></param>
    /// <returns></returns>
    private UnityEngine.Mesh GetCylinderMesh(float length)
    {
        const float CYLINDER_MESH_RESOLUTION = 0.1f;
        int lengthKey = Mathf.RoundToInt(length * 100 / CYLINDER_MESH_RESOLUTION);

        UnityEngine.Mesh mesh;
        if (_meshMap.TryGetValue(lengthKey, out mesh))
        {
            return mesh;
        }

        mesh = new UnityEngine.Mesh();
        mesh.hideFlags = HideFlags.DontSave;

        List<Vector3> verts = new List<Vector3>();
        List<Color> colors = new List<Color>();
        List<int> tris = new List<int>();

        Vector3 p0 = Vector3.zero;
        Vector3 p1 = Vector3.forward * length;
        int _cylinderResolution = 12;
        float _cylinderRadius = lineRadius;
        for (int i = 0; i < _cylinderResolution; i++)
        {
            float angle = (Mathf.PI * 2.0f * i) / _cylinderResolution;
            float dx = _cylinderRadius * Mathf.Cos(angle);
            float dy = _cylinderRadius * Mathf.Sin(angle);

            Vector3 spoke = new Vector3(dx, dy, 0);

            verts.Add(p0 + spoke);
            verts.Add(p1 + spoke);

            colors.Add(Color.white);
            colors.Add(Color.white);

            int triStart = verts.Count;
            int triCap = _cylinderResolution * 2;

            tris.Add((triStart + 0) % triCap);
            tris.Add((triStart + 2) % triCap);
            tris.Add((triStart + 1) % triCap);

            tris.Add((triStart + 2) % triCap);
            tris.Add((triStart + 3) % triCap);
            tris.Add((triStart + 1) % triCap);
        }

        mesh.SetVertices(verts);
        mesh.SetIndices(tris.ToArray(), MeshTopology.Triangles, 0);
        mesh.RecalculateBounds();
        mesh.RecalculateNormals();
        mesh.UploadMeshData(true);

        _meshMap[lengthKey] = mesh;

        return mesh;
    }
    private bool isNaN(Vector3 v)
    {
        return float.IsNaN(v.x) || float.IsNaN(v.y) || float.IsNaN(v.z);
    }

最后,如果这篇文章帮助到你,麻烦点个赞吧。文章来源地址https://www.toymoban.com/news/detail-861582.html

到了这里,关于【Unity 3D绳索】基于图形渲染的3D绳索模拟的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Unity3D中实现Player的第一人称视角

    效果,上面为Scene场景,下面为Game场景 0创建地形,当然可以先简单的创建一个空白的Terrain。这里我已经对地形进行了初步的编辑和渲染。 1.在Hierarchy视图中右键创建一个胶囊体(Capsule)作为Player,添加好后重置胶囊体的位置,并且调整胶囊体在一个合适的位置。  2.将Main

    2023年04月08日
    浏览(20)
  • Unity3d_Rewired官方文档翻译:概念(一):InputManager、Players、Actions

    仅翻译了官方文档中的Essentials(要点)、Concepts(概念)两部分,这是文档中最重要的部分,理解了这两部分的内容应该足以让你将Rewired运用到你的项目中,之后再去阅读文档的其他部分也能更容易理解。 斜体加下划线部分为添加的注解,非官方文档内容。若你发现有翻译

    2024年02月02日
    浏览(15)
  • 计算机视觉与图形学-神经渲染专题-pi-GAN and CIPS-3D

    《pi-GAN: Periodic Implicit Generative Adversarial Networks for 3D-Aware Image Synthesis 》 摘要 我们见证了3D感知图像合成的快速进展,利用了生成视觉模型和神经渲染的最新进展。然而,现有的方法在两方面存在不足:首先,它们可能缺乏底层的3D表示,或者依赖于视图不一致的渲染,从而合

    2024年02月14日
    浏览(22)
  • Unity GPU Instancing合批_如何基于单个的实体修改材质参数

    最近在做DOTS的教程,由于DOTS(版本1.0.16)目前不支持角色的骨骼动画,我们是将角色的所有动画数据Baker到一个纹理里面,通过修改材质中的参数AnimBegin,AnimEnd来决定动画播放的起点和终点,材质参数AnimTime记录当前过去的动画时间。但是在做大规模战斗控制的时候,有10000+的小

    2024年01月21日
    浏览(15)
  • Unity绳子插件QuickRopes使用方法(让你快速创建你想要的绳索效果)

    QuickRope插件介绍 从插件的名字就可以知道,QuickRope,使用此插件可以让开发者快速创建绳子、锁链等链条的工具,并可以模拟这些绳子的物理属性,自动生成绳子,可以编辑绳子属性,创建一条你想要的绳子。 插件demo演示说明 demo1:Edit Rope Renderer 点击左侧列表按钮即可看到

    2023年04月08日
    浏览(21)
  • 【Unity3D】线段渲染器LineRenderer

    1 LineRenderer 简介         LineRenderer 组件用于绘制线段,可以调整线段条数、端点坐标、颜色、宽度等属性,其属性面板如下: Materials :线段材质,最好设置为 Default-Line; Positions-Size :线段端点个数; Positions-Element :线段端点值; Width :线段宽度,可以是不等宽的;

    2023年04月08日
    浏览(19)
  • Unity 之 Material (材质)渲染3D对象的重要组件

    在Unity中,Material(材质)是一种用于渲染3D对象的重要组件。Material定义了对象的外观,包括其颜色、纹理、光照属性和反射等。以下是关于Material的详细介绍: 创建Material : 要创建一个Material,通常需要一个着色器(Shader)以及一个或多个纹理。您可以通过以下步骤来创建

    2024年02月08日
    浏览(10)
  • Unity3D学习记录01:URP渲染管线以及3D游戏场景设置

    以下内容所使用的版本均为Unity2022.3 先在 Window-Package Manager-Unity Registry 里面搜索添加Universal RP   Unity中,创建渲染管线的方式为Asset文件夹下右键 Create-Readering-URP Asset(with Universal Asset) 会创建以下两个Pipeline:  接着在图中的设置里添加这两个渲染管线(Project Setting在Edit窗口下

    2024年02月08日
    浏览(17)
  • 计算机视觉与图形学-神经渲染专题-第一个基于NeRF的自动驾驶仿真平台

    如今,自动驾驶汽车可以在普通情况下平稳行驶,人们普遍认识到,真实的 传感器模拟将在通过模拟解决剩余的极端情况方面发挥关键作用 。为此,我们提出了一种基于神经辐射场(NeRF)的自动驾驶模拟器。与现有作品相比,我们的作品具有三个显着特点:(1) 实例感知

    2024年02月12日
    浏览(15)
  • 控制renderQueue解决NGUI与Unity3D物体渲染顺序问题

    NGUI与Unity3D物体渲染顺序问题,做过UI的各位应该都遇到过。主要指的是UI与Unity制作的特效、3D人物等一同显示时的层次问题。 由于UI与特效等都是以transparent方式渲染,而Unity与NGUI在管理同是透明物体的render queue时实际上互相没有感知,于是引出排序问题。现在介绍以render

    2024年02月13日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包