当前位置: 首页 > news >正文

Unity地面交互效果——2、动态法线贴图实现轨迹效果

Unity引擎动态法线贴图制作球滚动轨迹

  大家好,我是阿赵。
  之前说了一个使用局部UV采样来实现轨迹的方法。这一篇在之前的基础上,使用法线贴图进行凹凸轨迹的绘制。

一、实现的目标

  先来回顾一下,上一篇最终我们已经绘制了一个轨迹的贴图
在这里插入图片描述

  可以思考一下,假如现在我绘制的不是黑白的遮罩,而是一张法线贴图,会怎样呢?比如这样:
在这里插入图片描述

  如果是这样,剩下的问题就非常简单了,使用局部的UV采样,然后正常的通过法线贴图读取法线方向,最后通过光照模型来表现出法线的凹凸感。
在这里插入图片描述

二、动态法线贴图的绘制和融合

  说起来好像很简单,但绘制灰度图简单,绘制法线贴图应该怎样做呢?
在这里插入图片描述

  在球的位置上,实际上我是用了这么一张法线贴图的,还是用顶部摄像机拍摄需要局部采样UV的范围。
  在球没有移动的时候,顶部摄像机拍摄到的应该是这样一个情况:
在这里插入图片描述

  除了球以外,这一片平整的纯色,是算出来的。
  这里涉及到一些法线贴图的计算方式。
  需要知道的是,法线贴图是一张RGB颜色的贴图,所以它的每一个通道的取值范围是0到1,但法线方向的取值范围是-1到1的,所以如果得到一张法线贴图,需要转换成法线方向时,需要对它乘以2再减1。然后想将一个法线方向转成RGB值,方法是把法线方向乘以0.5再加0.5。
  所以当一个正常朝向上方的法线方向是(0,0,1),它转换成RGB值之后,就是(0.5,0.5,1),也就是上图看到占了大部分面积的颜色了。
  由于我使用的是打一个正交摄像机在头顶的做法来渲染球所在的区域,这个摄像机通过layer过滤只看得到球所在的法线面片,所以除了球以外,其他区域应该是黑色的:
在这里插入图片描述

  想要把黑色的部分都填充成(0,0,1)法线方向的颜色,有2种方法:
1、把摄像机的颜色设置成这种颜色
在这里插入图片描述

2、在混合的shader里面判断,传入的图片是黑色的部分就填充成这张颜色。
  我这里是简单的使用第一种方式,修改摄像机的颜色。
  用上一篇说的方法,在移动的过程中,通过求出上一帧球的位置和当前球的位置作为偏移值,然后传入通过Graphics.Blit融合两帧的画面的材质球里面,就可以对两张法线贴图进行混合了。
  接下来需要混合两帧之间产生的法线贴图。
  混合法线的方法非常多,我常用的消耗比较低的法线混合方式有这么几种:
  假设n1和n2是需要混合的2个法线方向,n3是混合后的结果
1、线性混合
公式很简单:

float3 n3 = normalize(n1+n2);

在这里插入图片描述

这种方式混合法线,优点是实现简单,而且同一个法线贴图不停的叠加,效果都不会发生变化。缺点也很明显,会减淡本身法线贴图的特征,如果每帧都不停的和(0,0,1)混合,最终所有法线的特征都会被抹平了。
2、偏导混合
公式是:

float3 n3 = normalize( float3(n1.xy/n1.z+n2.xy/n2.z,1));

在这里插入图片描述

从效果上看,法线的特征保留会比线性混合要好很多。
如果把公式改一下,

float2 n0 = lerp(n1.xy/n1.z+n2.xy/n2.z,_blendVal);
float3 n3 = normalize( float3(n0,1));

还可以做出混合插值的效果。
3、Whiteout混合
和偏导的结果比较类似
公式:

n3 = normalize( float3( n1.xy+n2.xy,n1.z*n2.z));

在这里插入图片描述

三、绘制频率问题和解决

  虽然偏导和Whiteout的效果会比线性的好,但如果物体停留在同一个位置,每帧都不停的叠加同一个法线贴图,偏导和Whiteout都会对法线效果越来越加深,到最后就会出现和原来贴图比较偏差的效果,比如刚才那个球的法线如果每一帧不停的用Whiteout算法去叠加,最后就会变成这样
在这里插入图片描述

  为了解决这个问题,实际上是需要控制绘制的次数。基本思想很简单,就是物体移动的时候,并且移动的距离大于一定长度时,才会去绘制。
  这样,就出现了多种解决的方案:
1、还是用顶部摄像机,但只有主角移动的时候,才会调用Graphics.Blit方法去混合。这个方案的问题在于,如果只判断主角移动,那么产生这个法线贴图的主体也只能是主角,方便的其他角色是不能产生法线混合效果的。
2、不用顶部摄像机,而是每个可以产生轨迹的物体身上都挂上脚本,当物体移动的时候,主动的把自己的法线贴图通过Graphics.Blit混合到局部UV的法线贴图里面。这样做相当于每个角色有一个笔刷。不过这样做的问题是,需要逐个角色分别传入混合,还要计算每个角色相对于主角位置的偏移。如果场景里面的角色很多的情况下,这样做性能可能不是特别好。
3、还是用顶部摄像机,每个角色身上有一张法线贴图,只有当角色开始移动时,这张法线贴图才会变成激活可显示状态。那么,我们还是用顶部摄像机来渲染一张RenderTexture就够了,而这张RenderTexture看到的,只会是在绘制局部UV的范围内的正在移动的物体的法线贴图。
4、不管法线叠加变形的问题。实际上如果不是对混合后的法线要求特别精确,比如角色踩出的脚印要清晰到连鞋底都看得清,而只是需要一个大致的范围的话,我觉得直接叠加也无所谓,毕竟法线的大致方向是对的。

四、源码:

1、法线混合的Shader代码

和上一篇差不多的,只是修改一下片段着色器就行

			half4 frag(v2f i) : SV_Target{//当前帧传入的法线笔刷half4 col = tex2D(_MainTex, i.uv);half3 curNormal = col.rgb * 2 - 1;//上一帧绘制的法线贴图half4 lastCol = tex2D(_lastTex, i.uv - _offset);half3 lastNormal = lastCol.rgb * 2 - 1;			//默认的法线方向float3 defaultNormal = float3(0,0,1);//用于保持边缘是默认法线方向的遮罩half4 maskCol = tex2D(_maskTex, i.uv);//使用whiteout法线混合float3 finalNormal = normalize(half3(curNormal.xy + lastNormal.xy, curNormal.z*lastNormal.z));//计算边缘遮罩finalNormal = normalize(finalNormal * maskCol.r + _defaultDir.xyz*(1 - maskCol.r));//转RGB颜色finalNormal = finalNormal * 0.5 + 0.5;return half4(finalNormal, 1);}

2、地面上的局部UV采样包含法线贴图的Shader

Shader "azhao/GroundFootStepNormalTex"
{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)_NormalTex("Normal Tex", 2D) = "black"{}_normalScale("normalScale", Range(-5 , 5)) = 0_normalFootStepScale("normalFootStepScale", Range(-5 , 5)) = 0_specColor("SpecColor",Color) = (1,1,1,1)_shininess("shininess", Range(1 , 100)) = 1_specIntensity("specIntensity",Range(0,1)) = 1_ambientIntensity("ambientIntensity",Range(0,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;sampler2D _NormalTex;float4 _NormalTex_ST;float _normalScale;float _normalFootStepScale;float4 _specColor;float _shininess;float _specIntensity;float _ambientIntensity;struct appdata{float4 pos	: POSITION;float2 uv  : TEXCOORD0;float3 normal:NORMAL;float3 tangent:TANGENT;};struct v2f{float4 pos : SV_POSITION;float3 worldPos	: TEXCOORD0;float2 uv  : TEXCOORD1;float2 footstepUV : TEXCOORD2;float3 worldNormal : TEXCOORD3;float3 worldTangent :TEXCOORD4;float3 worldBitangent : TEXCOORD5;};half3 UnpackScaleNormal(half4 packednormal, half bumpScale){half3 normal;//由于法线贴图代表的颜色是0到1,而法线向量的范围是-1到1//所以通过*2-1,把色值范围转换到-1到1normal = packednormal * 2 - 1;//对法线进行缩放normal.xy *= bumpScale;//向量标准化normal = normalize(normal);return normal;}//获取HalfLambert漫反射值float GetHalfLambertDiffuse(float3 worldPos, float3 worldNormal){float3 lightDir = UnityWorldSpaceLightDir(worldPos);float NDotL = saturate(dot(worldNormal, lightDir));NDotL = NDotL * 0.5 + 0.5;return NDotL;}//获取BlinnPhong高光float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal){float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));float3 halfDir = normalize((viewDir + _WorldSpaceLightPos0.xyz));float specDir = max(dot(normalize(worldNormal), halfDir), 0);float specVal = pow(specDir, _shininess);return specVal;}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));o.worldNormal = UnityObjectToWorldNormal(i.normal);o.worldTangent = UnityObjectToWorldDir(i.tangent);o.worldBitangent = cross(o.worldNormal, o.worldTangent);return o;}fixed4 frag (v2f i) : SV_Target{//采样漫反射贴图的颜色half4 col = tex2D(_MainTex, i.uv*_MainTex_ST.xy + _MainTex_ST.zw);//计算法线贴图的UVhalf2 normalUV = i.uv * _NormalTex_ST.xy + _NormalTex_ST.zw;//采样法线贴图的颜色half4 normalCol = tex2D(_NormalTex, normalUV);fixed4 footstepCol = tex2D(_footstepTex, i.footstepUV);fixed3 footstepRGB = UnpackScaleNormal(footstepCol, _normalFootStepScale).rgb;half3 normalVal = UnpackScaleNormal(normalCol, _normalScale).rgb;//normalVal = footstepRGB;normalVal = normalize(normalVal + footstepRGB);//构建TBN矩阵float3 tanToWorld0 = float3(i.worldTangent.x, i.worldBitangent.x, i.worldNormal.x);float3 tanToWorld1 = float3(i.worldTangent.y, i.worldBitangent.y, i.worldNormal.y);float3 tanToWorld2 = float3(i.worldTangent.z, i.worldBitangent.z, i.worldNormal.z);//通过切线空间的法线方向和TBN矩阵,得出法线贴图代表的物体世界空间的法线方向float3 worldNormal = float3(dot(tanToWorld0, normalVal), dot(tanToWorld1, normalVal), dot(tanToWorld2, normalVal));//用法线贴图的世界空间法线,算漫反射half diffuseVal = GetHalfLambertDiffuse(i.worldPos, worldNormal);//diffuseVal = clamp(diffuseVal, 0.5, 1);//用法线贴图的世界空间法线,算高光角度half3 specCol = _specColor * GetBlinnPhongSpec(i.worldPos, worldNormal)*_specIntensity;//最终颜色 = 环境色+漫反射颜色+高光颜色half3 finalCol = UNITY_LIGHTMODEL_AMBIENT * _ambientIntensity + saturate(col.rgb*diffuseVal) + specCol;return half4(finalCol, 1);}ENDCG}}
}

相关文章:

Unity地面交互效果——2、动态法线贴图实现轨迹效果

Unity引擎动态法线贴图制作球滚动轨迹 大家好,我是阿赵。   之前说了一个使用局部UV采样来实现轨迹的方法。这一篇在之前的基础上,使用法线贴图进行凹凸轨迹的绘制。 一、实现的目标 先来回顾一下,上一篇最终我们已经绘制了一个轨迹的贴图…...

视频剪辑达人教您:如何运用嵌套合并技巧制作固定片尾

在视频剪辑的过程中,嵌套合并技巧是一种非常实用的技术,可以帮助您将多个素材叠加在一起,制作出更加丰富多彩的视频。本文将由视频剪辑达人为您详细介绍如何运用云炫AI智剪嵌套合并技巧制作固定片尾,让您的视频剪辑水平更上一层楼…...

【腾讯云 TDSQL-C Serverless 产品体验】TDSQL-C MySQL Serverless最佳实践

一、引言: 随着云计算技术的不断发展,越来越多的企业开始选择将自己的数据库部署在云上,以更好了的支持企业数字化转型以及业务创新,在这个过程中,很多客户会遇到这样一个问题,业务会存在高峰期和低谷期&a…...

SQLyog连接数据库报plugin caching_sha2_password could not be loaded......解决方案

问题描述 问题分析 因为MySQL新版默认使用caching_sha2_password作为身份验证的插件,而旧版本使用的是mysql_native_password。当出现plugin caching_sha2_password could not be loaded报错,我们更换为旧版本 如何解决 先使用cmd命令登录MySQL&a…...

linux应急排查

常用命令 查看登录用户和活动 whoami:显示当前登录用户的用户名。 w:显示当前登录到系统上的用户列表和他们正在执行的命令。 last:显示最近登录到系统的用户列表、登录时间和来源IP地址。 ps aux:列出当前正在运行的所有进程&…...

Apache POI及easyExcel读取及写入excel文件

目录 1.excel 2.使用场景 3.Apache POI 4.easyExcel 5.总结 1.excel excel分为两版,03版和07版。 03版的后缀为xls,最大有65536行。 07版的后缀为xlsx,最大行数没有限制。 2.使用场景 将用户信息导出到excel表格中。 将excel中的数…...

为什么写作

1记录生活,表达自己的想法和情感,提高沟通能力。 2年轻的时候就有写作的意愿,一直未动笔。 3想突破自己看看自己能写到什么程度。锻炼自己更好组织思路,提高逻辑思维能力。 4给自己的生活增添一些爱好,更好地理解和…...

python基于VGG19实现图像风格迁移

目录 1、原理 2、代码实现 1、原理 图像风格迁移是一种将一张图片的内容与另一张图片的风格进行合成的技术。 风格(style)是指图像中不同空间尺度的纹理、颜色和视觉图案,内容(content)是指图像的高级宏观结构。 实…...

BoredHackerBlog: Cloud AV RT日记

目录 信息搜集 WEB漏洞攻击 拿shell 信息搜集 首先ifconfig查看自己IP, netdiscover查看同网段下主机 第三个应该是目标靶机。用nmap查看靶机开放端口: 开放22和8080,看看8080开的啥服务 WEB漏洞攻击 看到让我们输入邀请码。有输入框的第…...

数据结构之“初窥门径”

目录 前言: 一,数据结构起源 二,基本概念和术语 2.1数据 2.2数据元素 2.3数据项 2.4数据对象 2.5数据结构 三,逻辑结构与物理结构 3.1逻辑结构 3.1.1集合结构 3.1.2线性结构 3.1.3树形结构 3.1.4图形结构 3.2物理结…...

css:transform实现平移、旋转、缩放、倾斜元素

目录 文档语法示例旋转元素 transform-rotate旋转过渡旋转动画 参考文章 文档 https://developer.mozilla.org/zh-CN/docs/Web/CSS/transform 语法 /* Keyword values */ transform: none;/* Function values */ transform: matrix(1, 2, 3, 4, 5, 6); transform: translate…...

如何理解AutoGPT

AutoGPT和GPT-4都是OpenAI公司的产品。AutoGPT是一个实验性开源应用程序,展示了GPT-4语言模型的能力。GPT-4是OpenAI研发的人工智能语言模型。 AutoGPT在GitHub主页上有151k星(151k星代表了151,000个用户点赞了该项目),AutoGPT获…...

【网络知识必知必会】聊聊网络层IP协议

文章目录 前言IP 协议格式总结 前言 在之前的博文中, 我们聊过了传输层中的两个重点协议 TCP 和 UDP, 本文我们再来聊聊网络层中的一个协议IP, 简单认识一下 IP 协议格式. IP 协议与 TCP 协议的复杂度也不妨多让, 不过我们在这里只是简单的聊一聊 IP 协议的报文格式就行, 毕竟…...

66. 加一

给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。 最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外,这个整数不会以零开头。 示例 1: 输入: digits …...

逻辑(css3)_强制不换行

需求 如上图做一个跑马灯数据,时间、地点、姓名、提示文本字数都不是固定的。 逻辑思想 个人想法是给四个文本均设置宽度,不然会出现不能左对齐的现象。 此时四个文本均左对齐, 垂直排列样式也比较好看,但是出现一个缺点&#…...

营收净利双降、股价下跌四成,敷尔佳带伤闯关“双11”

今年双11预售已经开启,敷尔佳在天猫、抖音等电商平台火热营销;营销热业绩冷,敷尔佳的三季报不及预期。 10月23日,哈尔滨敷尔佳科技发展有限公司(下称“敷尔佳”,301371SZ)公布2023年三季报,其三季度营收净…...

C语言KR圣经笔记 2.8自增和自减 2.9位运算 2.10赋值

2.8 自增和自减操作符 C提供了两个不同寻常的操作符,用于对变量进行自增和自减。自增操作符对操作数加上1,而自减操作符 -- 对操作数减去1。我们已经频繁使用 对变量进行自增,如: if (c \n)nl; 不寻常之处在于 和 -- 既能用作…...

PHP的Excel导出与导入

下载地址(注意php版本大于7.3可能会报错) GitHub - PHPOffice/PHPExcel: ARCHIVED 解压 1、导出 Excel $data[[name>a,age>11],[name>b,age>22],[name>d,age>33], ]; $fileds["name">"名称","age"…...

Ubuntu自建git服务器

Ubuntu 安装 gitlab-ce sudo apt-get update sudo apt-get install gitlab-ce 安装成功 sudo apt-get install gitlab-ce 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 下列【新】软件包将被安装:gitlab-ce 升…...

【面试专题】并发编程篇①

📃个人主页:个人主页 🔥系列专栏:Java面试专题 1.线程和进程的区别 线程和进程都是操作系统中的概念,它们的主要区别如下: 资源分配:进程是操作系统中的资源分配的基本单位,每个进程…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Netty从入门到进阶(二)

二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

RSS 2025|从说明书学习复杂机器人操作任务:NUS邵林团队提出全新机器人装配技能学习框架Manual2Skill

视觉语言模型(Vision-Language Models, VLMs),为真实环境中的机器人操作任务提供了极具潜力的解决方案。 尽管 VLMs 取得了显著进展,机器人仍难以胜任复杂的长时程任务(如家具装配),主要受限于人…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...

React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?

系列回顾: 在上一篇《React核心概念:State是什么?》中,我们学习了如何使用useState让一个组件拥有自己的内部数据(State),并通过一个计数器案例,实现了组件的自我更新。这很棒&#…...