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

目录
  • 一、前言
  • 二、实现方式介绍
  • 三、实现过程
    • 检测UV位置并替换像素颜色:
    • 修改替换信息为图片信息:
    • 运行时使用复制贴图:
    • 修改帧检测断触问题:
  • 总结

一、前言

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

而其他的战斗痕迹的效果似乎是通过贴花来实现的,贴花的方式多种多样。而在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的资料请关注我们其它相关文章!

(0)

相关推荐

  • Unity使用鼠标旋转物体效果

    本文实例为大家分享了Unity使用鼠标旋转物体效果的具体代码,供大家参考,具体内容如下 了解完基础知识后,然我们来做个小程序练习一下 1.在Main Camera下新建一个Cube 然后调整一下Cube的位置,把他放置在相机前方 2.给Cube挂载脚本 using System.Collections; using System.Collections.Generic; using UnityEngine; public class CubeControlScript : MonoBehavio

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

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

  • Unity使用LineRender实现签名效果

    本文为大家分享了Unity制作签名功能的具体代码,供大家参考,具体内容如下 前言:项目中需要做一个签名的功能,同时需要两个两个屏幕进行显示,但是都是在UI上,从网上查了大量资料. 找到两种方法: 1.修改图片像素点  但是是马赛克效果,不满足需求 2.使用LineRenderer 的3D签名制作出2D效果 改像素点: 先上代码 using System.Collections; using System.Collections.Generic; using UnityEngine; using

  • Unity Shader实现模糊效果

    本文实例为大家分享了Unity Shader实现模糊效果的具体代码,供大家参考,具体内容如下 今天分享一个超简单实现模糊效果的方法,先上图: 核心代码就这句: 注意要在3.0以上的版本才能使用 在采样后做偏移采样再叠加,效果与下面的代码类似: float4 frag(v2f o):SV_TARGET{ fixed4 color = tex2D(_MainTex,o.uv);//,float2(_Scale,_Scale),float2(_Scale,_Scale) float2 uv1= o.u

  • unity实现方向盘转动效果

    本文实例为大家分享了unity实现方向盘转动效果的具体代码,供大家参考,具体内容如下 效果 手指或鼠标拖动方向盘旋转,有角度限制,松手后自动回转. 代码 将代码添加到方向盘Image上. 注意需要赋值Canvas. using UnityEngine; using UnityEngine.EventSystems; public class SteeringWheel : MonoBehaviour,IDragHandler,IEndDragHandler { public Canvas Can

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

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

  • 精心挑选的12款优秀的基于jQuery的手风琴效果插件和教程

    当你想在有限的页面空间内展示多个内容片段的时候,手风琴(Accordion)效果就显得非常有用,它可以帮助你以对用户非常友好的方式实现多个内容片段之间的切换.借助流行的 jQuery 框架,只需很少的代码就可以实现精美的手风琴效果,帮助你的网站吸引更多用户的眼球. Elegant Accordion with jQuery and CSS3 首先推荐的这款插件是基于 jQuery 和 CSS3 实现的优雅的鼠标悬停手风琴效果. 制作教程 在线演示 Vertical Sliding Accordi

  • Unity Shader实现线框效果的制作步骤

    先上图看看效果: 下面详细分享一下制作步骤吧: 一.首先模型本身需要特殊处理 二.编写Shader shader "Giraffe/Wireframe" { properties{ _Color("Color",Color) = (1.0,1.0,1.0,1.0) _EdgeColor("Edge Color",Color) = (1.0,1.0,1.0,1.0) _EdgeColor2("Edge Color",Color)

  • 分享10篇优秀的jQuery幻灯片制作教程及应用案例

    幻灯片效果是常用的内容展示方式之一,这是一种在有限的网页空间内展示系列项目时非常好的方法.今天要给大家分享的是10篇非常棒的 jQuery 幻灯片教程及16个优秀的 jQuery 幻灯片应用案例. 10篇 jQuery 幻灯片制作教程Create Beautiful jQuery slider tutorial jQuery Plugin – Feature List Create an Image Rotator with Description (CSS/jQuery) Moving Box

  • 40款非常棒的jQuery 插件和制作教程(系列一)

    本文向大家分享40个实用的 jQuery 插件以及制作教程. Parallax Slider with jQuery ( 演示| 下载 ) 带立体效果的 jQuery 幻灯片插件,很酷! Merging Image Boxes with jQuery ( 演示| 下载 ) 非常酷的带消融效果的 jQuery 相册插件,太靓了! Sweet Thumbnails Preview Gallery ( 演示| 下载 ) 带缩略图预览效果的 jQuery 相册插件 Portfolio Zoom Slid

  • 40款非常棒的jQuery 插件和制作教程(系列二)

    jQuery 以其插件众多.独特.轻量以及支持大规模的网站开发闻名.本文继续向大家分享实用的 jQuery 插件,可以根据您的项目需要来选择使用.<<前一篇:40款非常棒的 jQuery 插件和制作教程(系列一)>> Client Testimonials Powered by PHP, XML and jQuery ( 演示| 下载 ) 结合 jQuery 和  XML 制作的客户感言插件,吸引更多用户使用你的产品. Coding a Rotating Image Slidesh

  • Unity实现喷漆效果

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

  • PHP保姆级API制作教程,不会剁手

    方法一:直接返回图片 第一步:首先你得把图片的链接一行一行的排版放到txt文档,如下: 第二步:使用如下php代码如下所示 <?php //存有美图链接的文件名img.txt $filename = "img.txt"; if (!file_exists($filename)) { die('文件不存在'); } //从文本获取链接 $pics = []; $fs = fopen($filename, "r"); while (!feof($fs)) { $l

  • PHP保姆级API制作教程,不会剁手

    方法一:直接返回图片 第一步:首先你得把图片的链接一行一行的排版放到txt文档,如下: 第二步:使用如下php代码如下所示 <?php //存有美图链接的文件名img.txt $filename = "img.txt"; if (!file_exists($filename)) { die('文件不存在'); } //从文本获取链接 $pics = []; $fs = fopen($filename, "r"); while (!feof($fs)) { $l

  • javascript 弹出层居中效果的制作

    问题:做一个带拖动的弹出层效果(像提示框那种) 先写了一半,明天继续奋斗: javascript 弹出层居中效果的制作 * { padding:0; margin:0; list-style:none; } body { font-family:Verdana, Geneva, sans-serif; font-size:14px; } #a { width:300px; height:80px; border:5px solid #d3d3d3; background-color:#f7f7f

随机推荐