我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

一、前言

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

而其他的战斗痕迹的效果似乎是通过贴花来实现的,贴花的方式多种多样。而在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坐标进行距离对比,如果超出一定的距离。就在中间执行插值的操作

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


		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 实现贴花效果的制作教程的详细内容,更多关于Unity的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

强大的CSS:模拟下雪效果动画制作教程

下雪效果只是一类效果的名称,可以是红包雨等一些自由落体的运动效果,本文就是用纯css模拟下雪的效果,更多效果大家可以自行发挥。1.前言由于公司产品的活动,需要模拟类似下雪的效果。浏览器实现动画无非css3和canvas(还有gif),对比下
2023-06-03

uni-app制作小程序实现左右菜单联动效果

这篇文章主要介绍了uni-app制作小程序实现左右菜单联动效果,实现步骤和思路都很简单,今天通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
2022-11-16

如何使用CSS制作手风琴效果的实现步骤

手风琴效果是一个常见的网页展示效果,通过收缩和展开不同内容块,使网页更加美观和交互性。在本文中,我们将介绍如何使用CSS制作手风琴效果,并提供具体的代码示例。实现手风琴效果的基本原理是使用CSS的过渡(transition)和动画(anim
2023-10-21

如何使用CSS制作跑马灯效果的实现步骤

跑马灯效果是一种常见的前端特效,在网页中显示连续滚动的文字或图片,给页面增添了一些动感和活力。本文将介绍如何使用CSS来实现跑马灯效果的具体步骤,并提供相应的代码示例供参考。步骤一:创建HTML结构首先,我们需要在HTML中创建用来实现跑马
2023-10-21

如何使用CSS制作倒计时效果的实现步骤

如何使用CSS制作倒计时效果的实现步骤倒计时效果是网页开发中常见的一个功能,可以为用户呈现倒计时的动态效果,给人以紧迫感和期待感。本文将介绍如何使用CSS来实现倒计时效果,并给出详细的实现步骤和代码示例。实现步骤如下:步骤一:HTML结构搭
2023-10-26

实现微信小程序中的手势操作效果

实现微信小程序中的手势操作效果,需要具体代码示例随着微信小程序的不断发展,手势操作已经成为了许多小程序中常见的功能。手势操作为用户提供了更加直观、便捷的操作方式,使用户体验更为流畅。下面将介绍如何在微信小程序中实现手势操作效果,并提供具体的
实现微信小程序中的手势操作效果
2023-11-21

怎么用uni-app制作小程序实现左右菜单联动效果

这篇文章主要介绍“怎么用uni-app制作小程序实现左右菜单联动效果”,在日常操作中,相信很多人在怎么用uni-app制作小程序实现左右菜单联动效果问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么用uni-
2023-07-04

Java图像处理教程之正片叠底效果的实现

前言本文主要给大家介绍了关于利用Java如何实现正片叠底效果的方法,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。正片叠底,在Photoshop中是一种混合模式,简单的说就是可以让2个图层的内容融合起来。PS中最佳展示用例
2023-05-31

使用CSS实现响应式图片画廊效果的教程

使用CSS实现响应式图片画廊效果的教程在现代网页设计中,响应式设计已成为一个重要的考虑因素。而通过使用CSS实现响应式图片画廊效果,可以使图片在不同设备上自动适应并呈现最佳效果。本文将分享一个简单而实用的教程,并提供具体的代码示例。一、HT
使用CSS实现响应式图片画廊效果的教程
2023-11-21

利用CSS实现响应式图片轮播效果的教程

利用CSS实现响应式图片轮播效果的教程随着移动设备的普及和技术的进步,响应式网站设计已成为当今设计趋势之一。在设计过程中,图片轮播是常见的元素之一,它可以有效地向用户展示多张图片的信息。本教程将分享如何使用CSS实现响应式图片轮播效果,并提
利用CSS实现响应式图片轮播效果的教程
2023-11-21

如何使用CSS制作滑出效果的导航栏的实现步骤

导航栏是网页设计中常见的元素,它可以让用户方便地导航到网站的不同页面。在许多网站中,滑出效果的导航栏具有更加现代和时尚的外观。本文将介绍如何使用CSS来制作这种滑出效果的导航栏,并提供具体的代码示例。实现步骤如下:创建HTML结构首先,我们
2023-10-21

CSS布局教程:实现平面转换效果的最佳方法

引言:在现代网页设计中,引入各种动画和过渡效果能够增加用户体验,提升页面的交互性。其中,平面转换效果是一种常见且流行的效果之一,通过它可以实现元素在平面上的旋转、翻转等视觉变换效果。本文将介绍实现平面转换效果的最佳CSS布局方法,同时给出具
2023-10-21

使用CSS实现响应式图片自动轮播效果的教程

随着移动设备的普及,网页设计需要考虑到不同终端的设备分辨率和屏幕尺寸等因素,以实现良好的用户体验。在实现网站的响应式设计时,常常需要使用到图片轮播效果,以展示多张图片在有限的可视窗口中的内容,同时也能够增强网站的视觉效果。本文将介绍如何使用
使用CSS实现响应式图片自动轮播效果的教程
2023-11-21

如何使用CSS制作水平滚动的新闻栏效果的实现步骤

在网页设计中,为了增加新闻内容的展示效果和用户体验,经常会使用水平滚动的新闻栏效果。本文将介绍使用CSS实现水平滚动新闻栏的具体步骤,并提供代码示例供参考。创建HTML结构首先,在HTML中创建一个div容器,用来包裹新闻内容。例如:
2023-10-21

如何使用CSS制作滚动加载的图片展示效果的实现步骤

随着网页技术的发展,滚动加载已成为一种常见的图片展示方式。通过使用CSS,我们可以实现一个具有滚动加载功能的图片展示效果,让网页在用户滚动的同时自动加载新的图片,提升用户体验。下面将介绍一种实现滚动加载图片展示效果的具体步骤,并提供相应的代
2023-10-21

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录