Unity实现喷漆效果

本文实例为大家分享了Unity实现喷漆效果展示的具体代码,供大家参考,具体内容如下

喷漆功能

**应用场景:**如墙上的标语贴花,汽车上的喷漆等。

选择方案:

1、当然实现方法各式各异,最最最简单,也是最“不堪入目”的方法是直接给一个面片,然后获取喷漆位置,加上一个要喷漆表面法线方向的偏移,作为最终面片放置位置,当然,不要忘了设置面片的方向。这种方法虽然说简单,但是效果并不理想,会出经常现与其他物体穿插的情况,如果游戏中曲面太多,那么这个方案基本没法看。
2、对于个别特殊的需求来讲,比如说人物身上的纹身,完全可以用一个shader里实现,此方法仅限于一个贴花对应一个物体,如果是一对多的情况,请看后边这两种。
3、有一种简易的方法是用Projector,这种方法实现较为简单,不多说。
4、接下来说一种动态生成网格方案,也较为常用,接下来就详细说说这种方案。

实现思路:

喷漆的网格是根据场景中所喷位置的物体的网格动态生成的,喷漆的时候,获取规定范围内的物体,再用一个立方体(也可以用球体)去截取这些物体的Mesh,从而构造新的网格,将喷漆渲染在这个Mesh就OK了。

代码实现:

首先,我们需要一个获取规定范围内MeshRenderer的函数:

public GameObject[] GetAffectedObjects(Bounds bounds, LayerMask affectedLayers)
{
 MeshRenderer[] renderers = FindObjectsOfType<MeshRenderer>();
 List<GameObject> objects = new List<GameObject>();
 foreach (Renderer r in renderers)
 {
 if (!r.enabled) continue;
 if ((1 << r.gameObject.layer & affectedLayers.value) == 0) continue;
 if (r.GetComponent<Decal>() != null) continue;

 if (bounds.Intersects(r.bounds))
 {
  objects.Add(r.gameObject);
 }
 }
 return objects.ToArray();
}

然后拿到这些GameObject去做裁剪,裁剪函数:

public void BuildDecal(GameObject affectedObject, bool isLast)
{
 Mesh affectedMesh = affectedObject.GetComponent<MeshFilter>().sharedMesh;
 if (affectedMesh == null) return;

 //这里预存了已获取物体的vertices和triangles,减少了不必要的GC
 Vector3[] vertices = GetVertexList(affectedObject);
 int[] triangles = GetTriangleList(affectedObject);

 //目标顶点转换到当前物体的模型空间
 Matrix4x4 matrix = this.transform.worldToLocalMatrix*affectedObject.transform.localToWorldMatrix;
 //将主要计算移入异步
 Loom.RunAsync(() =>
 {
 for (int i = 0; i < triangles.Length; i += 3)
 {
  int i1 = triangles[i];
  int i2 = triangles[i + 1];
  int i3 = triangles[i + 2];

  Vector3 v1 = matrix.MultiplyPoint(vertices[i1]);
  Vector3 v2 = matrix.MultiplyPoint(vertices[i2]);
  Vector3 v3 = matrix.MultiplyPoint(vertices[i3]);

  Vector3 side1 = v2 - v1;
  Vector3 side2 = v3 - v1;
  Vector3 normal = Vector3.Cross(side1, side2).normalized;

  if (Vector3.Angle(-Vector3.forward, normal) >= maxAngle) continue;

  DecalPolygon poly = new DecalPolygon(v1, v2, v3);

  //用立方体裁剪
  poly = DecalPolygon.ClipPolygon(poly, right);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly, left);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly, top);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly, bottom);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly, front);
  if (poly == null) continue;
  poly = DecalPolygon.ClipPolygon(poly, back);
  if (poly == null) continue;

  AddPolygon(poly, normal);
 }

 if (isLast)
 {
  RenderDecal();
 }
 });
}

DecalPolygon构建了新的三角形(这里注意顶点的空间变换),然后分别用立方体的每一个面去做裁剪,转换成数学算法,其实是判面与面的关系,具体实现:

/// <summary>
/// 两面相交裁剪
/// </summary>
public static DecalPolygon ClipPolygon(DecalPolygon polygon, Plane plane)
{
 //相交为True
 bool[] positive = new bool[9];
 int positiveCount = 0;

 for (int i = 0; i < polygon.vertices.Count; i++)
 {
 positive[i] = !plane.GetSide(polygon.vertices[i]); //不在裁剪面正面,说明有相交
 if (positive[i]) positiveCount++;
 }

 if (positiveCount == 0)
 return null; //全都在裁剪面正面面,不相交
 if (positiveCount == polygon.vertices.Count) return polygon; //全都在裁剪面反面,完全相交

 DecalPolygon tempPolygon = new DecalPolygon();

 for (int i = 0; i < polygon.vertices.Count; i++)
 {
 int next = i + 1;
 next %= polygon.vertices.Count;

 if (positive[i])
 {
  tempPolygon.vertices.Add(polygon.vertices[i]);
 }

 if (positive[i] != positive[next])
 {
  Vector3 v1 = polygon.vertices[next];
  Vector3 v2 = polygon.vertices[i];

  Vector3 v = LineCast(plane, v1, v2);
  tempPolygon.vertices.Add(v);
 }
 }

 return tempPolygon;
}

OK,到这里已经为新的Mesh准备好了所有的数据,接下来将计算好的数据移步到主线程做渲染:

public void RenderDecal()
{
 //主线程渲染
  Loom.QueueOnMainThread(() =>
 {
 if (sprite == null || Renderer == null||filter==null)
    {
      return;
    }
    //生成uv信息
    GenerateTexCoords(0, sprite);
    //距离偏移
 Push(pushDistance);

 Mesh mesh = CreateMesh();
 if (mesh != null) {
  mesh.name = "DecalMesh";
  filter.mesh = mesh;
  Renderer.material = material;
  Renderer.enabled = true;
 }
 });
}

这样,一个喷漆功能就做好了,有几点需要注意是的是:

1.GC的控制

示例:Vector3[] vertices = mesh.vertices;
注意这里不是简单的内存引用,而是会申请新的内存,所以这样的临时变量会造成GC,当物体的顶点上十几K,甚至几十K的时候,这样的GC是吃不消的!为了尽量避免这样的情况,可以做一次预存处理,对没有检测过物体的顶点和三角形数据进行保存,下次用的时候直接取,从而取代mesh.vertices;

2.计算量的问题

还是出于性能的考虑,当与之裁剪的Mesh顶点数太多,在主线程for循环几十K次,不出意外PC端也会卡顿,所以异步是一个较好的选择。复杂的裁剪计算交给其他线程,计算好主线程直接拿数据做渲染;

3.效果问题

由于新生成的喷漆Mesh是由原有物体的mesh裁剪所得的,而这两个Mesh位置是重叠在一起的,两个完全重叠的面,如果其他因变量也相同的情况下,让计算机渲染,计算机也不知道该先渲染哪个,这样就出现z-fighting的问题。所以加一个Push()方法,将新Mesh的顶点沿当前顶点的法线方向挤出一点距离,这样就实现了一个喷漆功能。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • unity使用射线实现贴花系统

    本文实例为大家分享了Unity使用射线实现贴花系统,供大家参考,具体内容如下 老规矩,直接上代码: using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; /// <summary> /// 贴花系统 /// </summary> public class Applique_ZH: MonoBehaviour { [H

  • Unity实现喷漆效果

    本文实例为大家分享了Unity实现喷漆效果展示的具体代码,供大家参考,具体内容如下 喷漆功能 **应用场景:**如墙上的标语贴花,汽车上的喷漆等. 选择方案: 1.当然实现方法各式各异,最最最简单,也是最"不堪入目"的方法是直接给一个面片,然后获取喷漆位置,加上一个要喷漆表面法线方向的偏移,作为最终面片放置位置,当然,不要忘了设置面片的方向.这种方法虽然说简单,但是效果并不理想,会出经常现与其他物体穿插的情况,如果游戏中曲面太多,那么这个方案基本没法看. 2.对于个别特殊的需求来讲,比

  • unity实现玻璃效果

    本文实例为大家分享了unity实现玻璃效果的具体代码,供大家参考,具体内容如下 一.使用Cubemap,做一个假反射 shader代码如下: Shader "Custom/glassShader" { Properties { _MainColor("Main Color",Color)=(1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _Cube("Cube&qu

  • unity实现流光效果

    本文实例为大家分享了unity实现流光效果的具体代码,供大家参考,具体内容如下 1.通过一些简单效果可以让我们更好的去理解shader,具体都在代码注释中: Shader "Unlit/MoveLightImage" { Properties { //主纹理 _MainTex ("Texture", 2D) = "white" {} //灯光纹理 _LightTex("Light Texture",2D)="whit

  • Unity实现瞄准镜效果

    本文实例为大家分享了Unity实现瞄准镜效果的具体代码,供大家参考,具体内容如下 using UnityEngine; using System.Collections; public class TelesopicView : MonoBehaviour { public float zoomLevel = 2.0f; public float zoomInSpeed = 100.0f; public float zoomOutSpeed = 100.0f; private float ini

  • Unity 实现贴花效果的制作教程

    目录 一.前言 二.实现方式介绍 三.实现过程 检测UV位置并替换像素颜色: 修改替换信息为图片信息: 运行时使用复制贴图: 修改帧检测断触问题: 总结 一.前言 在云艾尔登法环时,看到地面上的血迹时,发现某些地方脱离的地面,似乎是通过面片的方式实现的效果.但是同时某些,不过这种类型的血迹有道具的效果,估计是为了实现碰撞检测的功能才选择了面片的方式 而其他的战斗痕迹的效果似乎是通过贴花来实现的,贴花的方式多种多样.而在Unity中,有一种给官方文档提供代码的解决方案.这里就在这些代码的基础上做一

  • unity制作瞄准镜效果

    使用unity制作瞄准镜,供大家参考,具体内容如下 一.创建场景 在Hierarchy窗口中使用Plane和Cube创建场景,并调整摄像机位置 二.使用步骤 1.在Hierarchy窗口中右击在UI中选择Image 2.将瞄准镜素材拖入Image的检视试图中Soure Image中 结果如下: (注意在导入图片时将图片类型更改为下图所示,更改完之后点击Apply) 3.调整瞄准镜位置 三.新建代码 代码如下(示例): using UnityEngine; using System.Collect

  • Unity实现跑马灯效果的示例代码

    目录 一.效果 二.需要动画插件DOTween 三.脚本 1.每个格子上的脚本文件 2.管理脚本文件 一.效果 二.需要动画插件DOTween 下载地址 三.脚本 1.每个格子上的脚本文件 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using DG.Tweening; public class MarqueeUIItem : MonoBe

  • 详解Unity安卓共享纹理

    概述 本文的目的是实现以下的流程: Android/iOS native app 操作摄像头 -> 获取视频流数据 -> 人脸检测或美颜 -> 传输给 Unity 渲染 -> Unity做出更多的效果(滤镜/粒子) 简单通信 在之前的博客里已经说到,Unity 和安卓通信最简单的方法是用 UnitySendMessage 等 API 实现. Android调用Unity: //向unity发消息 UnityPlayer.UnitySendMessage("Main Cam

  • 详解Unity使用ParticleSystem粒子系统模拟药水在血管中流动(粒子碰撞)

    一.前言 点关注不迷路,持续输出Unity干货文章. 嗨,大家好,我是新发. 之前我写了一篇Unity流体模拟的文章:<Unity流体模拟,支持粒子系统,支持流体碰撞交互(Obi Fluid插件使用教程)> 然后有同学私信我,问我能否做药剂打入血管的效果. 这个嘛,用ObiFluid可以做,不过我今天要尝试的,是用另一种方式:直接使用Unity的粒子系统来做. 二.最终效果 最终效果如下,效果虽然一般,不过制相对简单很多. 本文Demo工程已上传到CodeChina,感兴趣的同学可自行下载学习

  • 游戏开发Unity2D图片任意形状破碎裂片效果展示

    目录 一.前言 二.效果演示 三.Demo工程下载 四.操作步骤 1.牙图片:SrpiteRenderer 2.碎裂:Explodable 3.多边形碰撞体组件:PolygonCollider2D 4.生成碎片:Generate Fragments 5.点击碎裂:ExplodeOnClick 6.碎裂爆破:ExplosionForce 7.碎了再碎:ExplodableFragments 结束语 一.前言 点关注不迷路,持续输出Unity干货文章. 嗨,大家好,我是新发. 2014年的某一天,我

随机推荐