zoukankan      html  css  js  c++  java
  • 深入解读Job system(2)

    https://mp.weixin.qq.com/s/vV4kqorvMtddjrrjmOxQKg

    上一篇文章中,我们讲解了Job System的基础知识,本文将以网格变形项目为示例,讲解Job System的使用。

    该项目中,我们将程序化生成一个平面,然后使用鼠标点击输入来生成球体,然后球体会在平面上产生凹槽,该功能可以用于实现脚印的效果。此项目只是使用Unity的Job System来实现高效网格变形的一个开端。

    访问代码

    本文代码你可以在GitHub上查看:

    https://github.com/itsKristin/Jobified-Meshdeformation

    DeformableMesh.cs

    首先编写生成平面的代码。创建一个C#脚本,命名为DeformableMesh。我们将加入using Unity.Collections声明,因为我们需要使用NativeArrays和Unity.Jobs,而且作业要继承自IJobParalelFor。

    我们要定义几个变量,用来帮助定义程序化生成平面的大小、作用力和半径,在变形部分时会用到这些变量,我们还在Awake函数缓存了所有需要用于渲染网格的信息。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Unity.Collections;
    using Unity.Jobs;

    [RequireComponent(typeof(MeshFilter),(typeof(MeshRenderer)))]
    public class DeformableMesh : MonoBehaviour 
    {
     [Header("Size Settings:")]
     [SerializeField] float verticalSize;
     [SerializeField] float horizontalSize;

     [Header("Material:")]
     [SerializeField] Material meshMaterial;

     [Header("Indentation Settings:")]
     [SerializeField] float force;
     [SerializeField] float radius;

     Mesh mesh;
     MeshFilter meshFilter;
     MeshRenderer meshRenderer;
     MeshCollider meshCollider;

     //网格信息

     Vector3[] vertices;
     Vector3[] modifiedVertices;
     int[] triangles;

     Vector2 verticeAmount;

     void Awake() 
     {
       meshRenderer = GetComponent<MeshRenderer>();
       meshFilter = GetComponent<MeshFilter>();
       meshFilter.mesh = new Mesh();
       mesh = meshFilter.mesh;
       GeneratePlane();
     }

    仔细观察代码以及注释内容,了解如何通过代码程序化生成平面。

    /*网格是由顶点和三角形构建的,基本上由其中的三个顶点构建。我们首先处理顶点的位置。

    顶点需要Vector3数组,因为它们在世界空间中拥有3D位置。数组的长度取决于所生成平面的大小。

    简单来说,可以想象平面顶部有网格覆盖,每个网格区域的每个角都需要一个顶点,相邻区域可以共享同一个角。因此,在每个维度中,顶点的数量需要比区域的数量多1。*/

    void GeneratePlane()
    {
     vertices = new Vector3[((int)horizontalSize + 1) * 
     ((int)verticalSize + 1)];
     Vector2[] uv = new Vector2[vertices.Length];

      /*现在使用嵌套的for循环相应地定位顶点*/

     for(int z = 0, y = 0; y <= (int)verticalSize; y++)
     {
       for(int x = 0; x <= (int)horizontalSize; x++, z++)
       {
         vertices[z] = new Vector3(x,0,y);
         uv[z] = new Vector2(x/(int)horizontalSize,
         y/(int)verticalSize);
       }
     }

      /*我们已经生成并定位了顶点,应该开始生成合适的网格。

      首先设置这些顶点为网格顶点*/

     mesh.vertices = vertices;

      /*我们还需要确保我们的顶点和修改的顶点在一开始就相互匹配*/

     modifiedVertices = new Vector3[vertices.Length];
     for(int i = 0; i < vertices.Length; i++)
     {
       modifiedVertices[i] = vertices[i];
     }
     mesh.uv = uv;

      /*网格此时还不会出现,因为它没有任何三角形。我们会通过循环构成三角形的点来生成三角形,这些三角形的标签会进入int类型的triangles数组中*/

     triangles = new int[(int)horizontalSize * 
     (int)verticalSize * 6];

     for(int t = 0, v = 0, y = 0; y < (int)verticalSize; y++, v++)
     {
       for(int x = 0; x <(int)horizontalSize; x++, t+= 6, v++)
       {
         triangles[t] = v;
         triangles[t + 3] = triangles[t + 2] = v + 1; 
         triangles[t + 4] = triangles[t + 1] = v + (int)horizontalSize + 1;
         triangles[t + 5] = v + (int)horizontalSize + 2;
       }
     }

      /*最后,我们需要将三角形指定为网格三角形,然后重新计算法线,确保得到正确的光照效果*/

     mesh.triangles = triangles;
     mesh.RecalculateNormals();
     mesh.RecalculateBounds();
     mesh.RecalculateTangents();

      /*我们还需要碰撞体,从而能够使用物理系统检测交互*/

     meshCollider = gameObject.AddComponent<MeshCollider>();
     meshCollider.sharedMesh = mesh;

      //我们需要设置网格材质,以避免出现难看的红色平面

     meshRenderer.material = meshMaterial;
    }

    我们使用了不同的方法进行碰撞检测,MouseInput脚本会触发一个协程,该协程会在平面上创建圆形球体并留下凹槽。

    void OnCollisionEnter(Collision other) {
       if(other.contacts.Length > 0)
       {
        Vector3[] contactPoints = new Vector3[other.contacts.Length];
         for(int i = 0; i < other.contacts.Length; i++)
         {
           Vector3 currentContactpoint = other.contacts[i].point;
           currentContactpoint = transform.InverseTransformPoint(currentContactpoint);
           contactPoints[i] = currentContactpoint;
         }
         IndentSnow(force,contactPoints);
       }
     }

     public void AddForce(Vector3 inputPoint)
     {
       StartCoroutine(MarkHitpointDebug(inputPoint));
     }

     IEnumerator MarkHitpointDebug(Vector3 point)
     {
       GameObject marker = GameObject.CreatePrimitive(PrimitiveType.Sphere);
       marker.AddComponent<SphereCollider>();
       marker.AddComponent<Rigidbody>();
       marker.transform.position = point;
       yield return new WaitForSeconds(0.5f);
       Destroy(marker);
     }

    现在来到了重点部分,调度作业。我们将在这部分可视化说明了解调度作业的方法的重要性。

    第一个代码段是个调度作业的方法,可以复制该代码段到自己的项目中,然而它执行的效果不如预期的高效。原因很简单,我们可能会使用到IJobParalelFor,但并没有让作业并行执行,因为我们会在调度后马上调用Complete, 这样就会导致执行还是需要一个一个的来。

    public void IndentSnow(float force, Vector3[] worldPositions)
     {
       NativeArray<Vector3> contactpoints = new NativeArray<Vector3>
       (worldPositions, Allocator.TempJob);
       NativeArray<Vector3> initialVerts = new NativeArray<Vector3>
     (vertices, Allocator.TempJob);
     NativeArray<Vector3> modifiedVerts = new NativeArray<Vector3>
    (modifiedVertices, Allocator.TempJob);
     
     IndentationJob meshIndentationJob = new IndentationJob
    {
          contactPoints = contactpoints,
          initialVertices = initialVerts,
          modifiedVertices = modifiedVerts,
          force = force,
          radius = radius
     };

     JobHandle indentationJobhandle = meshIndentationJob.Schedule(initialVerts.Length,initialVerts.Length);
     indentationJobhandle.Complete();
     
       contactpoints.Dispose();
       initialVerts.Dispose();
       modifiedVerts.CopyTo(modifiedVertices);
       modifiedVerts.Dispose();

       mesh.vertices = modifiedVertices;
       vertices = mesh.vertices;
       mesh.RecalculateNormals();
     }

    现在查看下图性能分析器。

    仔细注意到上图中的工作线程,你会看到所有线程中的等待时间,这是因为我们没有相应地调度作业。希望上图能清楚告诉你调度的重要性。

    下面我们来进行正确的调度作业。

    后面的代码段中,我们会创建一个类,它将帮助我保存本地数组和作业句柄。我会跟踪已创建的每个作业,然后在Update中从循环代码完成它。

    在调度要执行的作业前,我们定义了一些变量,下面的代码段中我们没有使用Vector3的常规数组,而是使用了NativeArray<Vector3>。NativeArrays中添加了Job System命名空间,从而确保能够安全地处理多线程代码。

    如前文所说,这些数组和常规数组不同,因为你必须定义一个分配器。这基本上是NativeArrays持续性和分配过程的数值。这些数组还不会受到垃圾收集过程的影响,因此它们和本地代码相似,所以你需要手动除去或释放这些数组。

    void IndentSnow(float force, Vector3[] worldPositions,ref HandledResult newHandledResult)
     {

       newHandledResult.contactpoints = new NativeArray<Vector3>
       (worldPositions, Allocator.TempJob);
       newHandledResult.initialVerts = new NativeArray<Vector3>
     (vertices, Allocator.TempJob);
       newHandledResult.modifiedVerts = new NativeArray<Vector3>
    (modifiedVertices, Allocator.TempJob);
     
     IndentationJob meshIndentationJob = new IndentationJob
    {
          contactPoints = newHandledResult.contactpoints,
          initialVertices = newHandledResult.initialVerts,
          modifiedVertices = newHandledResult.modifiedVerts,
          force = force,
          radius = radius
     };

     JobHandle indentationJobhandle = meshIndentationJob.Schedule(newHandledResult.initialVerts.Length,newHandledResult.initialVerts.Length);
     
       newHandledResult.jobHandle = indentationJobhandle;

       scheduledJobs.Add(newHandledResult);
     }

     void CompleteJob(HandledResult handle)
     {
       scheduledJobs.Remove(handle);

       handle.jobHandle.Complete();
     
       handle.contactpoints.Dispose();
       handle.initialVerts.Dispose();
       handle.modifiedVerts.CopyTo(modifiedVertices);
       handle.modifiedVerts.Dispose();

       mesh.vertices = modifiedVertices;
       vertices = mesh.vertices;
       mesh.RecalculateNormals();
         
     }
    }

    struct HandledResult
    {
     public JobHandle jobHandle;
     public NativeArray<Vector3> contactpoints;
     public NativeArray<Vector3> initialVerts;
     public NativeArray<Vector3> modifiedVerts;
    }

    最后,性能分析器会告诉新代码的效率明显高了很多。

    IndentationJob.cs

    最后需要编写IndentationJob.cs,该代码是执行作业的struct。作为作业,它也继承自IJob接口,本示例中是IJobParallelFor,它最后会对网格变形产生影响,因为想要让它在每个作业多次运行,我们将调用作业的执行函数,调用次数等于网格顶点的数量。

    你编写的每个作业都必须拥有Execute()函数,因为你需要通过该函数添加自定义代码到作业中。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using Unity.Collections;
    using Unity.Jobs;

    public struct IndentationJob : IJobParallelFor {

     public NativeArray<Vector3> contactPoints;
     public NativeArray<Vector3> initialVertices;
     public NativeArray<Vector3> modifiedVertices;

     public float force;
     public float radius;

     public void Execute(int i)
     {
       for(int c = 0; c < contactPoints.Length; c++)
       {
         Vector3 pointToVert = (modifiedVertices[i] - contactPoints[c]);
         float distance = pointToVert.sqrMagnitude;

         if(distance < radius)
         {
           Vector3 newVertice = initialVertices[i] + Vector3.down * (force);
           modifiedVertices[i] = newVertice;
         }
       }
     }
    }

    在Execute()函数中,我们在顶点和特定在碰撞球体时缓存contactPoints变量中循环,然后比较半径大小,如果符合条件,我们会给顶点添加负作用力值,从而造成下图中的凹槽。顺便一提,如果作用力为负,顶点会上升而不是下沉。

    小结

    本文将以网格变形项目为示例,讲解Job System的使用就介绍到这里,希望大家学以致用,熟练掌握Job System。Unity更多内容介绍尽在Unity官方中文论坛(UnityChina.cn)!

  • 相关阅读:
    Java程序:从命令行接收多个数字,求和并输出结果
    大道至简读后感
    大道至简第一章读后感Java伪代码
    Creating a SharePoint BCS .NET Connectivity Assembly to Crawl RSS Data in Visual Studio 2010
    声明式验证超时问题
    Error message when you try to modify or to delete an alternate access mapping in Windows SharePoint Services 3.0: "An update conflict has occurred, and you must re-try this action"
    Upgrading or Redeploying SharePoint 2010 Workflows
    Upgrade custom workflow in SharePoint
    SharePoint 2013中Office Web Apps的一次排错
    How to upgrade workflow assembly in MOSS 2007
  • 原文地址:https://www.cnblogs.com/nafio/p/9794286.html
Copyright © 2011-2022 走看看