c#教程​https://www.cnblogs.com/Yesi/p/15829200.html

有些游戏中的战斗痕迹的效果会通过贴花来实现的,贴花的方式多种多样。而在Unity中,有一种给官方文档提供代码的解决方案。本文将这些代码的基础上做一个绘图的贴花效果,感兴趣的童鞋可以参考一下

目录

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

一、前言

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

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

二、实现方式介绍

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

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

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

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

三、实现过程

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

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,然后修改像素替换区域的代码:

1

2

3

4

5

6

7

8

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方法来单独的处理这件事情:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

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方向分别进行判断,同时为了保证斜率,得到最大偏差的方向进行等距的插值,具体的逻辑代码为:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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平铺方面与贴图的缓存方面都有很大的挑战,还是建议尝试一下其他方式,最后,贴上完整的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

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);

                

            }

        }

    }

}

<font face="Arial, Verdana, sans-serif"><span style="white-space: normal;"> </span></font>

以上就是Unity 实现贴花效果的制作教程的详细内容

https://blog.csdn.net/xinzhilinger/article/details/121421282

Unity 实现贴花效果的制作教程相关推荐

  1. 软件测试自学毛笔字纹身,ps纹身教程_photoshop给人物添加纹身效果实例制作教程...

    摘要 腾兴网为您分享:photoshop给人物添加纹身效果实例制作教程,智学网,找乐助手,有道英语,易推广等软件知识,以及安卓测试软件,星露谷物语,店宝宝软件,乐播投屏软件,光棍证,查查看,国家工作人 ...

  2. ps国画效果案例制作教程和思路介绍

    内容提要:本文讲述PS国画效果的制作步骤和制作国画效果思路供借鉴使用.对PS感兴趣的朋友可加ps学习交流群:142574315 绘制并拥有一幅国画一直以来都是我的梦想,遗憾的是自己并没有美术功底,此时 ...

  3. FLASH水波纹效果动画制作教程图文自编

    这个教程很简单也好学,希望对初步认识FLASH的动画爱好者有帮助,废话少说,下面我们开始制作 教程吧 教程准备,准备一张带水的风景图片为例,其他没有什么需求 一,首先打开FLASH软件8.0,这个版本 ...

  4. Unity Shader 贴花效果(一)

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

  5. Unity 3D,小地图,制作教程

    笔者的Unity 3D课程已经结束,这里记录下几个比较常用的功能教程并分享给大家(适用的版本是Unity 3D 5.6.5). 接下来我们说一说小地图的制作,具体思路为:在角色头顶创建一个摄像机,并选 ...

  6. Unity中子弹反弹效果的制作

    最近在做一个射击游戏,需要做子弹的反弹效果:即当子弹碰撞到物体的时候可以向光线反射一样改变方向射击. 本来是想着可以利用unity自带的物理效果加力加碰撞来做,可是效果不好,不知道是我调的参数不对还是 ...

  7. CSS:模拟下雪效果动画制作教程

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

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

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

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

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

最新文章

  1. JSON字符串的解析
  2. hdu 5092 线裁剪(纵向连线最小和+输出路径)
  3. 【合并区间】排序 + 双指针
  4. 办公室心理学-九型人格导图
  5. unity全栈开发是什么意思_unity游戏公司面试问题总结
  6. 10 步让你成为更优秀的程序员
  7. java 可逆的加密算法_java实现AES可逆加密算法
  8. Adidas、金拱门、KFC、乐天玛特,零售巨头的选址秘诀都在数据里了
  9. 【解放日报】除了CEO首席执行官,你了解CIO吗?
  10. Java变异出现错误:No enclosing instance of type XXX is accessible
  11. mysql数据库备份和还原的命令_Mysql数据库备份和还原常用的命令
  12. hadoop中的合并(Combine)与归并(Merge)
  13. vscode之添加到右键快捷方式
  14. UIImageView三种方式 和 位置分布
  15. 如何在服务器运行脚本精灵,脚本精灵循环方式教程
  16. Apex里面的retainAll
  17. FPGA学习篇之计数器
  18. 数学物理方程课程感想
  19. Graphql是什么
  20. 嵌入式C语言之零碎知识

热门文章

  1. 微信支付宝个人收款将于2022年3月1日起被禁止用于经营收款
  2. SANGFOR SCSA——协议基础
  3. html table tb左对齐,标签之美五——网页表格的设计
  4. Eclipse+tomcat开发j2ee经典入门例子
  5. Celery (1)- 安装配置及使用
  6. C语言 打印5阶魔方阵
  7. HTML期末学生大作业-班级校园我的校园网页设计与实现html+css+javascript
  8. 云计算概念及Linux系统详解
  9. 调用MapReduce对文件各个单词出现的次数进行统计
  10. 扇贝编程可以用c语言吗,扇贝编程手机版下载_扇贝编程app下载 v1.1.41 - 87G手游网...