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}}
}

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

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

相关文章

NI‑9237国产化50 kS/s/ch,桥模拟输入,4通道C系列应变/桥输入模块

50 kS/s/ch,桥模拟输入,4通道C系列应变/桥输入模块 NI‑9237提供了所有的信号调理功能来实现多达四个基于桥的传感器的供电和测量。该模块提供通道间零相位延迟的应变或负载测量。它还具有60 VDC隔离和1,000 Vrms瞬态隔离,提供高…

java基础--多线程学习

写在前面: 多线程在面试中问的很多,之前没有过系统的学习,现在来进行一个系统的总结学习 文章目录 基础java多线程实现无参无返回值线程快速创建start和run方法的探讨run方法线程状态 有返回值线程线程池执行小结关于抛出异常的扩展 线程方…

云安全—kubelet攻击面

0x00 前言 虽然说总结的是kubelet的攻击面,但是在总结攻击面之前还是需要去了解kubelet的基本原理,虽然说我们浅尝即止,但是还是要以能给别人讲出来为基本原则。 其他文章: 云安全—K8s APi Server 6443 攻击面云安全—K8S API Server 未授…

解决【spring boot】Process finished with exit code 0的问题

文章目录 1. 复现错误2. 分析错误3. 解决问题 1. 复现错误 今天从https://start.spring.io下载配置好的spring boot项目: 启动后却报出如下错误: 即Process finished with exit code 0 2. 分析错误 Process finished with exit code 0翻译成中文进程已完…

USART HMI串口屏+GPS模块显示时间和经纬度

USART HMI串口屏GPS模块显示时间和经纬度 📍相关篇《基于u-box GPS模块通过串口指令调整输出信息》 📋在不使用其他单片机做数据中转处理情况下,利用USART HMI串口屏主动解析模式,来接收并解析GPS模块数据并显示,功能包…

k8s-调度约束

目录 工作机制 调度过程 指定调度节点 Kubernetes 是通过 List-Watch 的机制进行每个组件的协作,保持数据同步的,每个组件之间的设计实现了解耦。 用户是通过 kubectl 根据配置文件,向 APIServer 发送命令,在 Node 节点上面…

Linux 命令速查

Network ping ping -c 3 -i 0.01 127.0.0.1 # -c 指定次数 # -i 指定时间间隔 日志 一般存放位置: /var/log,包含:系统连接日志 进程统计 错误日志 常见日志文件说明 日志功能access-logweb服务访问日志acct/pacct用户命令btmp记录失…

打造中国汽车出海新名片,比亚迪亮相东京车展

作为全球知名的国际车展,东京车展向来都被业界人士誉为“亚洲汽车风向标”。2023年10月25日,第47届东京车展(自2023年更名为“日本移动出行展”)在东京国际展览中心如期揭幕。 作为中国车企的代表品牌,比亚迪携海豹、海…

8+双疾病+WGCNA+多机器学习筛选疾病的共同靶点并验证表达

今天给同学们分享一篇双疾病WGCNA多机器学习的生信文章“Shared diagnostic genes and potential mechanism between PCOS and recurrent implantation failure revealed by integrated transcriptomic analysis and machine learning”,这篇文章于2023年5月16日发表…

Springboot使用EasyExcel导入导出Excel文件

1&#xff0c;准备Excel文件和数据库表结果 2&#xff0c;导入代码 1&#xff0c;引入依赖 <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifac…

STM32—PWM开发SG90舵机

目录 PWM介绍 PWM输出模式&#xff1a; ​编辑PWM占空比&#xff1a; PWM周期与频率公式&#xff1a;​编辑 SG90舵机介绍 1. 什么是舵机 2. 怎么控制舵机 SG90舵机介绍实战 1. 在 SYS 选项里&#xff0c;将 Debug 设为 Serial Wire​编辑 2. 将 RCC 里的 HSE 设置为 …

代码随想录算法训练营第四十天丨 动态规划part03

343. 整数拆分 思路 看到这道题目&#xff0c;都会想拆成两个呢&#xff0c;还是三个呢&#xff0c;还是四个.... 来看一下如何使用动规来解决。 动态规划 动规五部曲&#xff0c;分析如下&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义 dp[i]…

JVM修炼印记之初识

文章目录 JVM认识JVM的功能常见JVMHotSpot的发展历程 JVM认识 Java虚拟机&#xff08;Java Virtual Machine&#xff0c;JVM&#xff09;是一个用于执行Java字节码的虚拟计算机。它是Java语言的核心&#xff0c;可以在不同的操作系统和硬件平台上运行Java程序。 JVM负责将Java…

【广州华锐互动】飞机诊断AR远程指导系统为工程师提供更多支持

随着科技的发展&#xff0c;飞机的维护工作也在不断进步。其中&#xff0c;AR&#xff08;增强现实&#xff09;技术的应用使得远程运维成为可能。本文将探讨AR在飞机诊断远程指导系统中的应用&#xff0c;以及它对未来航空维护模式的影响。 AR远程指导系统是一种使用增强现实技…

撕掉Hadoop标签,Cloudera未来可期吗?

Cloudera&#xff1a;大数据的弄潮儿 1、Cloudera发展史2、Cloudera问题出在哪里&#xff1f;3、参考文章 1、Cloudera发展史 说起Cloudera&#xff0c;就不得不提起Hadoop&#xff0c;Cloudera的过去就是Hadoop时代中的一个缩影。作为全球第一家也是最大一家Hadoop商业化公司&…

HarmonyOS鸿蒙原生应用开发设计- 隐私声明

HarmonyOS设计文档中&#xff0c;为大家提供了独特的隐私声明&#xff0c;开发者可以根据需要直接引用。 开发者直接使用官方提供的隐私声明内容&#xff0c;既可以符合HarmonyOS原生应用的开发上架运营规范&#xff0c;又可以防止使用别人的内容产生的侵权意外情况等&#xff…

[MySQL]——SQL预编译、动态sql

键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 一、SQL的预编译 &#x1f4d5;一条SQL语句的执行过程 &#x1f4d5;弊端 &#x1f4d5;预编译SQL的优势 &#x1f4d5;两种参数占位符 &#x1f4d5;小结 二、动态SQL &#x1f4d5;概念介绍&#xff1a; &#x1f4…

Opencv学习笔记(最近更新2023.11.1)

文章目录 棋盘格角点检测findChessboardCorners()亚像素角点检测cornerSubPix(&#xff09;棋盘格角点的绘制drawChessboardCorners()计算外参solvePnPRansac()旋转向量转旋转矩阵Rodrigues()鱼眼畸变矫正initUndistortRectifyMap()检测轮廓findContours()轮廓显示drawContours…

springboot2.x使用@RestControllerAdvice实现通用异常捕获

文章目录 demo地址实现效果引入基础类准备1.通用枚举与错误状态枚举2.定义通用返回结果3.自定义业务异常 统一异常捕获测试 demo地址 demo工程地址 实现效果 当我们输入1时&#xff0c;正常的返回通用的响应结果当我们输入2时&#xff0c;抛出异常&#xff0c;被捕获然后返回…

react-组件生命周期

一、生命周期阶段 官方文档&#xff1a;https://zh-hans.legacy.reactjs.org/docs/react-component.html React组件生命周期可分为三个阶段&#xff1a;挂载、更新、卸载 挂载&#xff1a;当组件实例被创建并插入 DOM 中时。其生命周期调用顺序如下&#xff1a; constructor()s…