Unity地面交互效果——1、局部UV采样和混合轨迹

news2024/9/24 11:21:51

  大家好,我是阿赵。
  这期开始,打算介绍一下地面交互的一些做法。
比如:

Unity引擎制作沙地实时凹陷网格的脚印效果

或者:

Unity引擎制作雪地效果

  这些效果的实现,需要基于一些基础的知识。所以这一篇先介绍一下简单的局部UV采样,然后映射纹理到地面的做法。
  大概需要实现的效果是这个视频的前半部分:

Unity曲面细分制作雪地效果

一、轨迹的绘制

  看这段视频的前半部分。可以看到,球在移动的过程中,在地面产生了移动的轨迹
在这里插入图片描述

  这个效果可能很多朋友都会做,一般的做法是计算球的坐标相对于整个地面的位置,然后拾像素绘制在地面的遮罩贴图上面。
  不过这种做法会有一个问题,假如地面很大的时候,通过一张和整个地面匹配UV的遮罩贴图来绘制轨迹,那么这张遮罩贴图的分辨率需要多大,才能显示足够的精度呢?比如一个4096米4096米的地面,就算我们用一张40964096的贴图做遮罩,那么每平方米的面积,才占一个像素,明显是绘制不出这么清晰的轨迹图形的。
  其实我们没有必要去绘制整张贴图,只需要局部绘制就好了
在这里插入图片描述
在这里插入图片描述

  绘制这一个小局部,然后通过局部UV采样的方式,把这个贴图叠加到大贴图上面去。
  这时候,就需要给Shader传入一个范围,让Shader知道,这个局部UV,最终占整个地面UV的多少。
地面的Shader代码是这样的:

Shader "azhao/GroundFootStep"
{
    Properties
    {
		_MainTex("Texture", 2D) = "white" {}
		_Color("Color", Color) = (1,1,1,1)
		_centerPos("CenterPos", Vector) = (0,0,0,0)
		_footstepRect("footstepRect",Vector) = (0,0,0,0)
		_footstepTex("footstepTex",2D) = "gray"{}
		_footstepColor("footstepColor",Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag


            #include "UnityCG.cginc"
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;

			uniform float3 _centerPos;

			float4 _footstepRect;
			sampler2D _footstepTex;
			float4 _footstepColor;
			struct appdata
			{
				float4 pos	: POSITION;
				float2 uv  : TEXCOORD0;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float3 worldPos	: TEXCOORD0;
				float2 uv  : TEXCOORD1;
				float2 footstepUV : TEXCOORD2;
			};


			float RemapUV(float min, float max, float val)
			{
				return (val - min) / (max - min);
			}
			
			v2f vert(appdata i)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(i.pos);
				o.worldPos = mul(unity_ObjectToWorld,i.pos.xyz);
				o.uv = i.uv*_MainTex_ST.xy+ _MainTex_ST.zw;
				o.footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, o.worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, o.worldPos.z));
				return o;
			}
			
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv)*_Color;
				fixed4 footstepCol = tex2D(_footstepTex, i.footstepUV);
				fixed3 footstepRGB = _footstepColor.rgb;
				fixed3 finalRGB = col.rgb*(1 - footstepCol.a) + footstepRGB * footstepCol.a;
				fixed4 finalCol = fixed4(saturate(finalRGB), 1);
                return finalCol;
				return col;
            }
            ENDCG
        }
    }
}

  从代码可以看出footstepRect是一个很关键的东西,它告诉了Shader,需要绘制轨迹的范围在哪里。然后通过RemapUV方法,拿这个范围和当前的顶点世界坐标去计算出,当前的点该占整体UV的实际位置。
  这个footstepRect其实是C#动态算出来的,根据角色所在的坐标和半径,算出来一个范围。
C#的代码大概是这样:

Vector3 pos = role.transform.position;
mat.SetVector("_centerPos", pos);
mat.SetFloat("_maxVal", radius);
mat.SetVector("_footstepRect", new Vector4(pos.x - radius, pos.z - radius, pos.x + radius, pos.z + radius));

  其实就是中心点加减半径而已。
  这个做法的优点是,只需要局部绘制一张贴图,就能达到比较清晰的轨迹图形
  缺点是,只能在一定范围内显示,超出了footstepRect范围,轨迹就会消失了。

二、绘制轨迹的手段

  绘制轨迹,其实就是连贯的把某个笔刷的像素复制到一张图片上。这个应该不是很难理解的概念。
  上面的例子,球是一个笔刷,它移动的时候,它所在的位置会产生一个圆形的笔刷,通过连续每帧的覆盖,就形成了一个轨迹。
在这里插入图片描述

  如果绘制的间隔拉大一点,看到的情况大概是这样的。
  那么问题来了,球移动的时候,上面说到,相对于地表贴图的footstepRect,是会变化的,所以说,我们不能直接把球的笔刷印到之前的那张图去。
比如上一张图的位置是在这里
在这里插入图片描述

  下一张图的位置就变成了这里
在这里插入图片描述

  留意看左下角的球,它在世界中的位置是一直没有变化的,但在这个footstepRect的局部里面,它的相对位置是变化了的。
  下面来说一下具体的做法。

1、通过摄像机绘制RenderTexture

在这里插入图片描述

  这里为了渲染一张顶视图,我是打了一个摄像机在运动的球的上方,然后摄像机跟随这球移动。
  需要注意的是,摄像机一定要是正交的,然后通过控制orthographicSize参数,可以准确的绘制符合footstepRect的范围。最后,给这个摄像机的targetTexture赋予一张RenderTexture,作为输出。

2、通过偏移来叠加上一张图

  刚才那个RenderTexture是每帧都会渲染一次的。我们需要2张RenderTexture,一张是上一次留下的,一张是这一帧渲染出来的。
  接下来就是把两张RenderTexture,通过Graphics.Blit方法合并在一起。由于Graphics.Blit方法是可以传入一个材质球的,所以可以通过写一个Shader来混合2张贴图。具体的方式是,计算上一帧和当前帧角色所在位置的偏移,然后用偏移来控制上一帧的贴图的UV采样,再把两张贴图合并在一起就可以了。

3、合并的Shader

Shader "azhao/DrawFootstep"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_lastTex("lastTex",2D) = "black"{}
		_offset("offset",Vector) = (0,0,0,0)

    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			sampler2D _lastTex;
			float2 _offset;


            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                // sample the texture
				half4 col = saturate(tex2D(_MainTex, i.uv));
				half3 curRGB = col.rgb * 2 - 1;
				half4 lastCol = saturate(tex2D(_lastTex, i.uv - _offset));
				float lastAlpha = lastCol.a;

				half3 lastRGB = lastCol.rgb*2-1;

				half mr = lastRGB.r*lastAlpha;

				if (col.a >0)
				{
					if (curRGB.r > 0)
					{
						if (lastAlpha == 0)
						{
							mr = curRGB.r;
						}
					}
					else if (curRGB.r < 0)
					{
						mr = min(curRGB.r,mr);
					}
				}
				else
				{
					mr = lastRGB.r;
				}
				mr = (mr + 1) / 2;
				float alpha = max(col.a, lastAlpha);
				half3 mixRGB = half3(mr, mr, mr);
				half3 finalRGB = mixRGB;
                return half4(finalRGB, alpha);

            }
            ENDCG
        }
    }
}

三、细节问题

  第一步绘制轨迹通过局部UV坐标采样,和地表的贴图纹理混合。这里会存在一个问题。通过第二步绘制出来的轨迹贴图,是Clamp平铺方式的
在这里插入图片描述

  这意味着,超出了UV的0到1范围的坐标,会直接采样了0或者1的UV。具体的表现是这样的:
在这里插入图片描述

  这个黑线,其实就是到边缘了,所以超出的部分,都会是黑的
在这里插入图片描述

  为了解决这个问题,可以加一个渐变的遮罩叠加
在这里插入图片描述

  把UV接近0和1的地方都变成纯黑色,这样就不会出现Clamp平铺的问题,也可以让接近边缘的地方不会有一个很硬的消失,而是稍微柔软的过渡。
在这里插入图片描述
在这里插入图片描述

  所以用于绘制轨迹混合的shader会变成这样:

Shader "azhao/DrawFootstep"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
		_lastTex("lastTex",2D) = "black"{}
		_offset("offset",Vector) = (0,0,0,0)
		_maskTex("maskTex",2D) = "white"{}
		_reduceVal("reduceVal",float) = 0.001
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
			sampler2D _lastTex;
			float2 _offset;
			sampler2D _maskTex;
			float _reduceVal;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;
            }

            half4 frag (v2f i) : SV_Target
            {
                // sample the texture
				half4 col = saturate(tex2D(_MainTex, i.uv));
				half3 curRGB = col.rgb * 2 - 1;
				half4 lastCol = saturate(tex2D(_lastTex, i.uv - _offset));
				float lastAlpha = saturate(lastCol.a - _reduceVal);
				half4 maskCol = tex2D(_maskTex, i.uv);
				half3 lastRGB = lastCol.rgb*2-1;

				half mr = lastRGB.r*lastAlpha;

				if (col.a >0)
				{
					if (curRGB.r > 0)
					{
						if (lastAlpha == 0)
						{
							mr = curRGB.r;
						}
					}
					else if (curRGB.r < 0)
					{
						mr = min(curRGB.r,mr);
					}
				}
				else
				{
					mr = lastRGB.r;
				}
				mr = (mr + 1) / 2;
				float alpha = max(col.a, lastAlpha)*maskCol.r;
				half3 mixRGB = half3(mr, mr, mr);
				half3 finalRGB = mixRGB * maskCol.rgb;
                return half4(finalRGB, alpha);

            }
            ENDCG
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1134253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型(LLM)在电商推荐系统的探索与实践

本文对LLM推荐的结合范式进行了梳理和讨论&#xff0c;并尝试将LLM涌现的能力迁移应用在推荐系统之中&#xff0c;利用LLM的通用知识来辅助推荐&#xff0c;改善推荐效果和用户体验。 背景 电商推荐系统&#xff08;Recommend System&#xff0c;RecSys&#xff09;是一种基于…

C++-openssl-aes-加密解密

hmac Hash-based Message Authentication Code MAC 定义&#xff1a; Message Authentication Code 一种确认完整性并进行认证的技术。 1.openssl基本版 加密解密 #include "openssl/rand.h" #include "openssl/md5.h" #include "openssl/hmac.h…

5G技术在职业教育领域的应用:产生巨变的技术

5G技术在职业教育领域的应用&#xff1a;产生巨变的技术 职业教育领域正面临着前所未有的挑战和机遇。随着5G技术的快速发展和普及&#xff0c;其高速度、低延迟、大容量和连接数的特性给职业教育带来了革命性的改变。本文将深入探讨5G技术在职业教育领域的应用场景、技术原理和…

Android Studio错误修复Connect to repo.maven.apache.org:443

环境 名称版本操作系统Windows10(64位)AndroidStudio2022.3.1 Patch 2 前言 最近更新了AndroidStudio编写程序的时候发现gradle时老是报read time out错误提示 分析 当出现这个警告时&#xff0c;你应该猜到这是一个连接不上的问题(Connect to repo.maven.apache.org:443)&…

huoshan device_id和iid生成记录学习分析

huoshan 和 douyin 作为字节系的产品&#xff0c;device_id 和 iid是抓包经常遇到的字段&#xff0c;代表了设备的概念。 还是熟悉的接口&#xff0c;像 device_register &#xff0c;app_alert_check 和 app_notice_status 都需要请求一遍。 这些接口跑完一遍&#xff0c;设…

【强烈推荐】视频转gif、图片拼gif,嘎嘎好用,免费免费真的免费,亲测有效,无效过来打我

问题描述 最近遇到一个需求是需要将视频生成gif&#xff0c;这个看上去不是很难&#xff0c;所以有了以下的解决办法 解决办法 首先想到的当然是自己写一个&#xff0c;用了两套代码&#xff1a; from moviepy.editor import *# 读取视频文件 video_clip VideoFileClip(&quo…

若依微服务上传图片文件代理配置

在使用若依微服务文件上传时候,文件上传成功会上传到D:/ruoyi/uploadPath目录下。默认使用9300端口进行访问图片文件,现在我想把它代理到80端口应该怎么做呢? 配置前:http://localhost:9300/statics/2023/09/24/test.jpg 配置后:http://localhost/statics/2023/09/24/test…

Kafka KRaft模式探索

1.概述 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。其核心组件包含Producer、Broker、Consumer&#xff0c;以及依赖的Zookeeper集群。其中Zookeeper集群是Kafka用来负责集群元数据的管理、控制器的选举等。 2.内容…

StableDiffusion学习

模型推荐 majicMIX realistic 麦橘写实(写实近景人像): https://civitai.com/models/43331/majicmix-realisticDreamShaper(各种画风, 比较全能): https://civitai.com/models/4384/dreamshaperLOFI (更精致人像面部): https://civitai.com/models/9052?modelVersionId146253…

MSQL系列(九) Mysql实战-Join算法底层原理

Mysql实战-Join算法底层原理 前面我们讲解了BTree的索引结构&#xff0c;及Mysql的存储引擎MyISAM和InnoDB,今天我们来详细讲解下Mysql的查询连接Join的算法原理 文章目录 Mysql实战-Join算法底层原理1.Simple Nested-Loop Join 简单嵌套循环2.Block Nested-Loop Join 块嵌套…

查找算法-二分查找法(Binary Search)

目录 查找算法-二分查找法&#xff08;Binary Search&#xff09; 1、说明 2、算法分析 3、C代码 查找算法-二分查找法&#xff08;Binary Search&#xff09; 1、说明 如果要查找的数据已经事先排好序了&#xff0c;就可以使用二分查找法来进行查找。二分查找法是将数据…

HackTheBox - Starting Point -- Tier 0 ---Preignition

文章目录 一 题目二 实验过程 一 题目 Tags Network、Programming、RDP、Reconnaissance、Weak Credentials译文&#xff1a;网络、编程、RDP、侦察、凭证薄弱Connect To attack the target machine, you must be on the same network.Connect to the Starting Point VPN us…

【面试经典150 | 链表】合并两个有序链表

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;递归方法二&#xff1a;迭代 写在最后 Tag 【递归】【迭代】【链表】 题目来源 21. 合并两个有序链表 题目解读 合并两个有序链表。 解题思路 一种朴素的想法是将两个链表中的值存入到数组中&#xff0c;然后对数组…

16、window11+visual studio 2022+cuda+ffmpeg进行拉流和解码(RTX3050)

基本思想:需要一个window11 下的gpu的编码和解码代码,逐开发使用,先上个图 几乎0延迟的,使用笔记本的显卡 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.0\extras\demo_suite>deviceQuery.exe deviceQuery.exe Starting...CUDA Device Query (Runtime API…

Xcode自定义快捷键

一、新建脚本 1. 编写脚本 把脚本sh文件保存在安全的目录&#xff0c;不会被删除 我这里主要是两个常用的&#xff1a; 1.打开终端: xcode-terminal.sh #!/bin/shif [ -n "$XcodeProjectPath" ]; then open -a Terminal "$XcodeProjectPath"/.. elseo…

macOS鼠标管理操作增强BetterMouse简体中文

BetterMouse是一款专为Mac用户设计的鼠标增强工具&#xff0c;旨在帮助用户更好地掌握和管理鼠标操作。它提供了全局鼠标手势、高度可定制的鼠标设置选项以及一些有用的鼠标增强功能&#xff0c;如鼠标放大镜、鼠标轨迹和应用程序切换功能。这些功能可以大大提高用户的工作效率…

Redis桌面管理工具:Redis Desktop Manager for Mac

Redis Desktop Manager是一款非常实用的Redis管理工具&#xff0c;它不仅提供了方便易用的图形用户界面&#xff0c;还支持多种Redis数据结构&#xff0c;可以帮助用户轻松地完成Redis数据库的管理工作。 以下是一些推荐Redis Desktop Manager的理由&#xff1a; 多平台支持&a…

2021-arXiv-The Power of Scale for Parameter-Efficient Prompt Tuning

2021-arXiv-The Power of Scale for Parameter-Efficient Prompt Tuning Paper: https://arxiv.org/abs/2104.08691 Code: https://github.com/google-research/ text-to-text-transfer-transformer/ blob/main/released_checkpoints.md# lm-adapted-t511lm100k 在这项工作中&…

如何编辑pdf?推荐福昕高级pdf编辑器

这里写目录标题 安装教程1.双击FoxitPhantomPDF941_L10N_Setup.exe安装2.打开FiX UZ1文件夹 复制plugins文件夹和FoxitPhantomPDF.exe到安装目录中替换3. 双击Express2BusinessFix New.reg导入注册表 如何复制页面如何修改pdf的内容福昕高级pdf编辑器安装包 【Note】学校要求加…

http代理IP它有哪些应用场景?如何提升访问速度?

随着互联网的快速发展&#xff0c;越来越多的人开始关注网络速度和安全性。其中&#xff0c;代理IP技术作为一种有效的网络加速和安全解决方案&#xff0c;越来越受到人们的关注。那么&#xff0c;http代理IP有哪些应用场景&#xff1f;又如何提升访问速度呢&#xff1f; 一、h…