Unity地面交互效果——1、局部UV采样和混合轨迹
大家好,我是阿赵。
这期开始,打算介绍一下地面交互的一些做法。
比如:
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 100Pass{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 texturefixed4 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 100Pass{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 texturehalf4 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 100Pass{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 texturehalf4 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}}
}
相关文章:

Unity地面交互效果——1、局部UV采样和混合轨迹
大家好,我是阿赵。 这期开始,打算介绍一下地面交互的一些做法。 比如: Unity引擎制作沙地实时凹陷网格的脚印效果 或者: Unity引擎制作雪地效果 这些效果的实现,需要基于一些基础的知识。所以这一篇先介绍一下简单…...

基于STM32的示波器信号发生器设计
**单片机设计介绍,基于STM32的示波器信号发生器设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序文档 六、 文章目录 一 概要 基于STM32的示波器信号发生器是一种高性能的电子仪器,用于测试和分析电路中的电信号。在该系统中&a…...

案例分析大汇总
案例分析心得 2018-2022年的案例分析考试内容汇总(近五年) 架构设计题型 软件系统建模 数据库 Web 系统设计 2018年 胖/瘦客户端 C/S 架构非功能性需求 数据流图DFDE-R图Essential Use Cases(抽象用例),Real Use Cases(基础用例)信息工…...
MVCC(Multi-Version Concurrency Control,多版本并发控制)
是一种数据库管理系统中常用的并发控制技术,用于处理多个事务同时访问数据库数据时的数据一致性和隔离性。MVCC的主要目标是允许多个事务并发执行,同时保持数据的一致性,避免数据丢失或不一致。 MVCC 的核心思想是为每个事务维护多个版本的数…...
嵌入式面试2(c相关)
目录 1.C语言中static、const、volatile关键字用法区别; static的用法(定义和用途) const的用法(定义和用途) volatile (英文意思为易变的) 作用和用法: 2.C语言中,const 和 static 的区别,c…...

基于SSM的n省出口基地公共信息服务平台设计与实现
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...

opencv dnn模块 示例(20) 目标检测 object_detection 之 yolor
文章目录 1、论文介绍1.1、YOLOR思想动机1.2、隐式知识学习1.2.1、隐式知识如何工作1.2.2、隐式知识统一网络建模 1.3、实验1.4、总结 2、测试2.1、opencv dnn2.1.1、代码2.1.2、结果 2.2、测试效率 YOLOR出自论文You Only Learn One Representation: Unified Network for Mult…...

【队列的顺序表示,链式表示】
文章目录 队列的表示和实现相关术语队列的表示链队的表示链队的定义链队的初始化销毁链队列 链队列的入队出栈 队列的表示和实现 相关术语 队列(Queue)是仅在表尾进行插入操作,在表头进行删除操作的线性表。表尾即an端,称为队尾…...
Pydantic 实践
1. 简介 pydantic 库是一种常用的用于数据接口 schema 定义与检查的库。 通过 pydantic 库,我们可以更为规范地定义和使用数据接口,这对于大型项目的开发将会更为友好。 当然,除了 pydantic 库之外,像是 valideer 库、marshmallo…...
获取pandas中的众数
pandas.DataFrame 也有一个 mode() 方法。 以下面的 pandas.DataFrame 为例。 df pd.DataFrame({‘col1’: [‘X’, ‘X’, ‘Y’, ‘X’], ‘col2’: [‘X’, ‘Y’, ‘Y’, ‘X’]}, index[‘row1’, ‘row2’, ‘row3’, ‘row4’]) print(df) col1 col2 row1 X X row2…...

SOLIDWORKS Simulation2024仿真10大新功能
SOLIDWORKS Simulation新增功能 1. 增强型轴承接头 •通过指定压缩、拉伸和弯曲的刚度,轻松创建自定义轴承接头。•通过向非线性和大型位移算例添加自定义条件,提高模拟精度。 优点:使用功能强大的接口,更轻松 、 更 准 确 地 设…...
Java程序设计2023-第二次上机练习
这里要用到一些面向对象的基本知识 目录 7-1 伪随机数 输入格式: 输出格式: 输入样例: 输出样例: 7-2 jmu-Java-03面向对象基础-01-构造方法与toString 1.编写无参构造函数: 2.编写有参构造函数 3.覆盖toString函数: 4.对每个属性生成setter…...
如何在 uniapp 里面使用 pinia 数据持久化 (pinia-plugin-persistedstate)
想要在 uniapp 里面使用 pinia-plugin-persistedstate 会遇到的问题就是 uniapp里面没有浏览器里面的 sessionStorage localStorage 这些 api。 我们只需要替换掉 pinia-plugin-persistedstate 默认的储存 api 就可以了。使用 createPersistedState 重新创建一个实例, 把里面的…...

智慧矿山AI算法助力护帮板支护监测,提升安全与效率
在智慧矿山AI算法系列中,护帮板支护监测是保障矿山安全和提高生产效率的重要环节。护帮板作为矿山支护体系中的重要组成部分,在矿山生产中起到了关键的作用。那么,护帮板在哪种状态下是正常打开的呢?本文将对此进行介绍。 护帮板的…...

shell中的运算
目录 1.运算符号 2.运算指令 练习 1.运算符号 运算符号意义加法-减法*乘法/除法%除法后的余数**乘方自加一- -自减一<小于<小于等于>大于>大于等于等于ji ->jji*j*i->jj*i/j/i->jj/i%j%i->jj%i 2.运算指令 (()) //((a12))let //let a12 …...

【Java 进阶篇】解决Java Web应用中请求参数中文乱码问题
在Java Web应用开发中,处理请求参数时经常会遇到中文乱码的问题。当浏览器向服务器发送包含中文字符的请求参数时,如果不正确处理,可能会导致乱码问题,使得参数无法正确解析和显示。本文将详细探讨Java Web应用中请求参数中文乱码…...

51单片机-点阵屏led
代码配置 这样就能选择每一列哪个亮了 进行位选,段清零,这样就不会影响多列同时了 实现动画 1、使用文字摸提取文件,提取图案的16进制表示数组 offest作为偏移量,count作为计时。count10,偏移量加1,就相当于得到下一…...

Angular-03:组件模板
各种学习后的知识点整理归纳,非原创! 组件模板 ① 数据绑定② 属性绑定③ 类名绑定④ 样式绑定⑤ 事件绑定⑥ 获取原生DOM对象6.1 在组件模板中获取6.2 在组件类中获取 ⑦ 双向数据绑定⑧ 内容投影8.1 select选择器8.2 单槽投影8.3 多槽投影 ⑨ 安全操作…...
mysql 操作慢查询日志
1、mysql 批量插入300w数据 CREATE PROCEDURE test_insert_200w() BEGINDECLARE i INT;SET i1;WHILE i<3000000 DOINSERT INTO shop_user (password, telephone, username) VALUES (admin, 15510304125, concat(admin, i));SET ii1;END WHILE; END; //执行sql call test_in…...
illuminate/database 使用 二
上一篇文章写怎么单独使用illuminate/database,这回讲下怎么整合到项目里使用。为此特意看了下laravel对其使用。本篇文章,参照laravel的使用,简单实现。 一 原理 laravel 里使用illuminate/config。 illuminate/config composer 地址&…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill
视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...