一、前言

在云艾尔登法环时,看到地面上的血迹时,发现某些地方脱离的地面,似乎是通过面片的方式实现的效果。但是同时某些,不过这种类型的血迹有道具的效果,估计是为了实现碰撞检测的功能才选择了面片的方式

而其他的战斗痕迹的效果似乎是通过贴花来实现的,贴花的方式多种多样。而在Unity中,有一种给官方文档提供代码的解决方案。这里就在这些代码的基础上做一个绘图的贴花效果,最终效果如图所示:

二、实现方式介绍

简单的来说就是通过发射一条射线与物体发生碰撞来获取物体的基本的信息,然后提取出碰撞处该物体的UV坐标点,然后进行一个计算得到物体对应Texture2D的像素信息,然后对这些像素进行一个颜色的替换,最后就可以得到一张贴花效果的Texture2D

这种方式的第一步就是需要通过发射一条射线,然后得到碰撞检测点的信息,这里用到的API为:

使用该API的返回结果是物体网格对应的UV坐标点,没有办法直接的去使用,需要先通过坐标转换,即通过UV坐标来获取到其Texture2D对应的像素点。在Unity中,我们知道UV坐标对应的范围为0到1,这样来说,只要将其与对应Texture2D的像素数量与UV坐标进行一个乘法计算就可以得到最后对应像素的下标位置

在得到检测位置的像素下表后,就可以根据被贴图的Texture2D的像素的宽高做一个计算,得到物体贴图的替换范围与下标,然后执行一遍遍历,对于所有替换的像素颜色一一对应,然后执行一个像素颜色的计算,做一个混合即可

三、实现过程

检测UV位置并替换像素颜色:

首先查阅Unity官方文档,得到射线检测UV坐标的代码,核心围绕RaycastHit 对应的API来得到检测的UV坐标并进行处理,代码如下:

public class ExampleClass : MonoBehaviour
{public Camera cam;void Start(){cam = GetComponent<Camera>();}void Update(){if (!Input.GetMouseButton(0))return;RaycastHit hit;if (!Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))return;Renderer rend = hit.transform.GetComponent<Renderer>();MeshCollider meshCollider = hit.collider as MeshCollider;if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)return;Texture2D tex = rend.material.mainTexture as Texture2D;Vector2 pixelUV = hit.textureCoord;pixelUV.x *= tex.width;pixelUV.y *= tex.height;tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.black);tex.Apply();}
}

然后在场景中创建一个Quad作为射线被检测的物体,但是同时需要注意,对于物体执行操作时,需要理解一个细节,就是物体只有在挂载网格碰撞体时候,才能够获取到对应物体的UV信息,具体的细节在官方文档中也有提到,如图:

创建完成物体后,需要通过一个材质来赋予该物体一张贴图,用来作为像素替换的贴图,我这里用了一张白色的图片,但是注意,在使用该图片时候,注意修改该图片的导入设置中的
Read/Write Enabled为开启状态,这样才可以进行后续的修改:

如果你测试这段代码,可能发现在点击后并没有发生什么变化,因为这一段代码只会对一个像素点执行替换操作,运行效果看起来并不明显。为了提升显示效果,这里可以先做一个简单的计算,来设计一个像素块作为替换的基本单元,以便于结果的观察。而计算方式为通过这个像素点的下表位置来计算出一个大小合适的方格区域,定义一个Vector2的属性,命名为replaceRange,然后修改像素替换区域的代码:

     for (int i = 0; i < replaceRange.x; i++){for (int j = 0; j < replaceRange.y; j++){tex.SetPixel((int)pixelUV.x+i- (int)replaceRange.x/2, (int)pixelUV.y+j-(int)replaceRange.y/2, Color.black);}}

然后运行整个场景,如果脚本执行成功的话,就可以看到正确的显示效果:

修改替换信息为图片信息:

上面我们对于每一个像素的颜色值进行替换时,使用的是指定的颜色数字。接下来就需要进行一定的扩展,将信息的提取方式修改为图片提取的方式。

同样定义一个Texture2D属性命名为:coverTex,然后提取这张Texture2D的信息,并覆盖掉对应点击点的像素信息,这里定义一个Draw方法来单独的处理这件事情:

 public void Draw(Texture2D orginTex,Texture2D coverTex,Vector2 pixelUV){for (int i = 0; i < coverTex.width; i++){for (int j = 0; j < coverTex.height; j++){Color colorOriginal = orginTex.GetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2);Color colorCover = coverTex.GetPixel(i, j);Color colorResult = colorCover * colorCover.a + (1 - colorCover.a) * colorOriginal;orginTex.SetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2, colorResult);}}}

注意,在上面的代码中,我们对于两个颜色值有一个简单的计算用来混合两个有透明通道的颜色值。假设颜色A要覆盖掉颜色B,这里使用的计算公式为:

  • A*A.a+(1-A.a)*B

通过上面的公式,可以简单的处理两个颜色的a通道的覆盖结果,也许这种方式不是很准确,但是对于完全透明的像素或者完全不透明的像素的混合还是比较有效的,这样就很方便 的处理不规则形状的贴花

将上面的颜色快的方式替换掉,可以观察一下效果:

当我们使用到一张圆形的贴图后,我们就可以看到成功的执行了替换

运行时使用复制贴图:

如果我们直接使用本体的贴图来修改材质,就会发现本地的资源也被执行了修改,这样会造成下次进入游戏,整个贴图的状态也不会刷新。为了避免这个问题,可以在每次执行像素替换时,复制一份贴图来作为被贴画的材质贴图,不过这里就不进行演示,可以在自己的项目中,根据需要来决定是否执行该操作

修改帧检测断触问题:

上面的一个代码,有一个特点,就是可以通过连续的绘制来做出一些图案,有一些类似于江南白景图游戏中抽卡前的绘制效果,但是通过上面的代码来实现时,就会发现如果鼠标移动的过快,相邻的两个绘制点之间会产生空隙,如图所示:

为了解决这样一个问题,这里在每一帧执行绘制之后都缓存本帧的UV坐标,同时在绘制时与上一帧的UV坐标进行距离对比,如果超出一定的距离。就在中间执行插值的操作

同时为了保证性能,需要固定距离的执行插值操作,为了简化计算,将两帧坐标的距离分为XY方向分别进行判断,同时为了保证斜率,得到最大偏差的方向进行等距的插值,具体的逻辑代码为:

     if (!isClick){Draw(tex, coverTex, pixelUV);catchPos = pixelUV;isClick = true;}else{if (Vector2.Distance(pixelUV, catchPos) < coverTex.width / 4){Draw(tex, coverTex, pixelUV);}else{Vector2 pixelCatchUV = catchPos;float lerpNum=0;float interval = 1 / (Mathf.Max(Mathf.Abs(pixelUV.x - pixelCatchUV.x), Mathf.Abs(pixelUV.y - pixelCatchUV.y)) / (coverTex.width/4));while (lerpNum<=1){lerpNum += interval;catchPos = Vector2.Lerp(pixelCatchUV, pixelUV, InterpolationCalculation(lerpNum));Draw(tex, coverTex, catchPos);}catchPos = pixelUV;Draw(tex, coverTex, catchPos);}}

执行代码的显示结果为:

总结

从实现过程中面临的一些问题来看,这种贴画效果的实现限制条件很多,性能表现上也是比较差的,适合做一些局部的贴画效果实现,比如百景图的抽卡绘制的效果

而若想实现全局的效果,在UV平铺方面与贴图的缓存方面都有很大的挑战,还是建议尝试一下其他方式,最后,贴上完整的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class Test : MonoBehaviour
{public Camera cam;public Texture2D coverTex;private Texture2D catchTexture;private Vector2 catchPos;private bool isFirst=true;private bool isClick = false;void Awake(){Application.targetFrameRate = 200;}void Update(){if (Input.GetKeyDown(KeyCode.Space)){isFirst = true;}DrawSticker();}public void DrawSticker(){if (!Input.GetMouseButton(0)){isClick = false;return;}            RaycastHit hit;if (!Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out hit))return;Renderer rend = hit.transform.GetComponent<Renderer>();MeshCollider meshCollider = hit.collider as MeshCollider;if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)return;if (isFirst){if (catchTexture == null){catchTexture = rend.material.mainTexture as Texture2D;}rend.material.mainTexture = Instantiate(catchTexture);isFirst = false;}Texture2D tex = rend.material.mainTexture as Texture2D;Vector2 pixelUV = hit.textureCoord;pixelUV.x *= tex.width;pixelUV.y *= tex.height;if (!isClick){Draw(tex, coverTex, pixelUV);catchPos = pixelUV;isClick = true;}else{if (Vector2.Distance(pixelUV, catchPos) < coverTex.width / 4){Draw(tex, coverTex, pixelUV);}else{Vector2 pixelCatchUV = catchPos;float lerpNum=0;float interval = 1 / (Mathf.Max(Mathf.Abs(pixelUV.x - pixelCatchUV.x), Mathf.Abs(pixelUV.y - pixelCatchUV.y)) / (coverTex.width/4));while (lerpNum<=1){lerpNum += interval;catchPos = Vector2.Lerp(pixelCatchUV, pixelUV, InterpolationCalculation(lerpNum));Draw(tex, coverTex, catchPos);}catchPos = pixelUV;Draw(tex, coverTex, catchPos);}}tex.Apply();}float InterpolationCalculation(float num){return 3 * Mathf.Pow(num, 2) - 2 * Mathf.Pow(num, 3);}public void Draw(Texture2D orginTex,Texture2D coverTex,Vector2 pixelUV){for (int i = 0; i < coverTex.width; i++){for (int j = 0; j < coverTex.height; j++){Color colorOriginal = orginTex.GetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2);Color colorCover = coverTex.GetPixel(i, j);Color colorResult = colorCover * colorCover.a + (1 - colorCover.a) * colorOriginal;orginTex.SetPixel((int)pixelUV.x + i - (int)coverTex.width / 2, (int)pixelUV.y + j - (int)coverTex.height / 2, colorResult);}}}}

Unity 贴花效果的实现相关推荐

  1. 【游戏开发小技】Unity中实现Dota里的角色技能地面贴花效果(URP | ShaderGraph | Decal)

    本文最终效果 文章目录 一.前言 二.环境准备 1.URP环境准备 2.技能范围图案 二.方案一:写Shader实现 1.Shader脚本:UrpDecal.shader 2.材质球 3.创建Cube ...

  2. 【游戏开发小技】Unity中实现Dota里的角色技能地面贴花效果(URP ShaderGraph Decal)

    本文最终效果 文章目录 一.前言 二.环境准备 1.URP环境准备 2.技能范围图案 二.方案一:写Shader实现 1.Shader脚本:UrpDecal.shader 2.材质球 3.创建Cube ...

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

    c#教程​https://www.cnblogs.com/Yesi/p/15829200.html ​ 有些游戏中的战斗痕迹的效果会通过贴花来实现的,贴花的方式多种多样.而在Unity中,有一种给官方 ...

  4. Unity Shader 实现简单的贴花效果(二)

    这个月进入了找不倒工作的焦虑状态,花了两周时间去学OpenGL,发现以前课上的讲的内容过于浅显,也加深了对渲染管线的了解也算是相当不错的吧. 话不多说,先上最初的效果图吧. 本效果考了该篇博客Unit ...

  5. Unity镜子效果的实现(无需镜子Shader)

    Unity镜子效果制作教程 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人! (拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享 ...

  6. Unity 毛玻璃效果(UGUI)—高斯模糊

    因为Unity 提供了GrabPass,可以在Shader中很方便的拿到Panel下面的图像,即_GrabTexture. 注:添加优化.原本的搞死模糊效果达不到苹果的那种细腻效果,修改参数即可. 修 ...

  7. [cesium] 数字孪生 | 超图 | UE4 | Unity | 基于三维GIS + 游戏引擎结合示例 | UE4 和 Unity的效果展示

    说明 因为公司的产品规划,最近在研究数字孪生方向的产品,看到51word的云渲染路线三维GIS+游戏引擎跨界融合: 不过没多久看到超图官网已经出了相应的拓展插件今天准备试试.之前有关注过cesium有 ...

  8. Unity UGUI 效果 之 UI 元素 多边形UI (例如雷达图,圆形,不规则多边形 UI等)显示 的简单实现的几种方法整理

    Unity UGUI 效果 之 UI 元素 多边形UI (例如雷达图,圆形,不规则多边形 UI等)显示 的简单实现的几种方法整理 目录 Unity UGUI 效果 之 UI 元素 多边形UI (例如雷 ...

  9. Unity Shader 贴花效果(一)

    本文实现的是一个Mesh Decal方法的贴花方案,参考了本篇博文链接: unity的贴花方案.链接的文章是转载的,我并没有找到原文地址.本篇文章主要是学习和自己的理解为主. 先看未贴花之前的效果 这 ...

最新文章

  1. python tqdm包_python tqdm库的使用
  2. 在visualstudio中使用Qt
  3. Python3.x的mysqlclient的安装、Python操作mysql,python连接MySQL数据库,python创建数据库表,带有事务的操作,CRUD
  4. scrapy 搜索关键字_Scrapy 新浪微博搜索爬虫
  5. 光遇服务器维护都在干什么,光遇:全图毕业后该做什么?老玩家建议,这四件事不做会后悔...
  6. 【01】《正则表达式必知必会》(已看)(仅存放)
  7. 前端学习(3257):js高级教程(1)准备
  8. ajax的访问 WebService 的方法
  9. 剖析:3D游戏建模的千奇百变,带你快速入门
  10. 蚂蚁战配基金已售罄两只 累计关注人数超500万
  11. 在honeypack中启用HMR
  12. 用VC++建立Service服务应用程序
  13. 怎样从外网访问自己的HTTP服务器
  14. 从另一个视角看待逻辑回归
  15. 防火墙软件 - IPCop
  16. OpenCV编译、安装、测试
  17. java我的世界光影推荐_最棒的7款我的世界光影水反效果包
  18. siamfc代码解读_SiamFC算法改进思路
  19. 我的世界服务器标记家位置,我的世界:你真的会看藏宝图吗?学会用标记,位置一次就找对...
  20. 网络操作系统项目教程----Windows server 2003篇----打印机安装与配置

热门文章

  1. 普罗米修斯监控结合Java服务进行工作
  2. iphone11返回上一级手势怎么设置_iPhone11怎么返回上一级-使用教程
  3. 【趋势分析合集】含M-K检验、Spearman秩次相关检验、Kendall秩次相关检验等
  4. 高德地图通过地图点击复制经纬度数据 ——限定地图围栏范围内
  5. iPhoneX 底部Tabbar布局错误
  6. 《小狗钱钱》读后感及总结的一点方法
  7. 帆软10jar包升级
  8. Ubuntu删除无用缓存及垃圾文件【转】
  9. STM32外设系列—DHT11
  10. wxPython+cefPython3: 高DPI缩放导致显示异常