Unity地面交互效果——4、制作地面凹陷轨迹

  大家好,我是阿赵。
  上一篇介绍了曲面细分着色器的基本用法和思路,这一篇在曲面细分的基础上,制作地面凹陷的轨迹效果。

一、思路分析

  这次需要达到的效果是这样的:
在这里插入图片描述

  从效果上看,这个凹陷在地面下的轨迹,里面有法线变化的效果,然后地表模型也是真实的发生了凹陷变化。所以其实就是之前说到的法线混合轨迹和曲面细分的综合应用。
在这里插入图片描述

  曲面细分的等级实际上是根据自己的需要去调整的
在这里插入图片描述

  做到这么高的细分也可以,但实际上也没太大必要。
  之前已经介绍过法线轨迹的混合,然后有了曲面细分之后,剩下的事情就是把两者结合起来,并且做一个顶点偏移效果了。顶点偏移,实际上也是通过绘制出来的轨迹做偏移,比如我现在有一张黑白的图片,轨迹就绘制在上面,然后对贴图进行局部采样,最后把某些色值范围内的顶点做一个高度的偏移,就做出了这个凹陷的效果了。

二、贴图通道的利用

  还记得最开始的一篇的内容吗?我是使用一个顶视正交摄像机去拍摄这个范围内的轨迹的。
在这里插入图片描述

  这里的这个法线贴图,实际上只用到了RGB三个通道而已,还有一个A通道可以用。于是,可以把需要实现高度偏移的黑白图片,记录在这张贴图的A通道。
在这里插入图片描述

  这样,只需要打一个摄像机,渲染一张RenderTexture,就同时实现了法线贴图和顶点偏移2种效果了。
  值得注意的是,我这里只把需要凹陷的地方刷成了白色,然后不需要凹陷的地方默认是黑色。这样做的好处是计算简单,只需要根据A通道的0-1去决定凹陷的深度就行了。不过有时候,做轨迹是不止凹陷的,不如一个球在沙地上移动,实际上轨迹的边缘还会凸起来的。如果要实现这种效果,那么就要把平地的Alpha值设置成0.5,然后大于0.5的部分是凸起,小于0.5的部分是凹陷。

三、Shader实现

1、顶点片段着色器里的实现:

Shader "azhao/VFGround"
{Properties{_MainTex("Texture", 2D) = "white" {}_Color("Color", Color) = (1,1,1,1)_centerPos("CenterPos", Vector) = (0,0,0,0)_minDis("minVal", Float) = 0_maxDis("maxVal", Float) = 20_factor("factor", Float) = 15_footstepRect("footstepRect",Vector) = (0,0,0,0)_footstepTex("footstepTex",2D) = "gray"{}_height("height" ,Float) = 0.3_NormalTex("Normal Tex", 2D) = "black"{}_normalScale("normalScale", Range(-1 , 1)) = 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//在正常的vertex和fragment之间还需要hull和domain,所以在这里加上声明#pragma hull hullProgram#pragma domain domainProgram#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;float4 _MainTex_ST;fixed4 _Color;uniform float _minDis;uniform float _maxDis;uniform float3 _centerPos;uniform float _factor;float4 _footstepRect;sampler2D _footstepTex;float _height;sampler2D _NormalTex;float4 _NormalTex_ST;float _normalScale;float4 _specColor;float _shininess;float _specIntensity;float _ambientIntensity;struct a2v{float4 pos	: POSITION;float2 uv  : TEXCOORD0;float3 normal:NORMAL;float3 tangent:TANGENT;};struct v2t{				float2 uv  : TEXCOORD0;float2 footstepUV : TEXCOORD1;float4 worldPos	: TEXCOORD2;float3 worldNormal : TEXCOORD3;float3 worldTangent :TEXCOORD4;float3 worldBitangent : TEXCOORD5;};struct t2f{float4 clipPos:SV_POSITION;float2 uv: TEXCOORD0;				float2 footstepUV:TEXCOORD1;float4 worldPos:TEXCOORD2;float3 worldNormal : TEXCOORD3;float3 worldTangent :TEXCOORD4;float3 worldBitangent : TEXCOORD5;};struct TessOut{float2 uv  : TEXCOORD0;float4 worldPos	: TEXCOORD1;float2 footstepUV:TEXCOORD2;float3 worldNormal : TEXCOORD3;float3 worldTangent :TEXCOORD4;float3 worldBitangent : TEXCOORD5;};struct TessParam{float EdgeTess[3]	: SV_TessFactor;//各边细分数float InsideTess : SV_InsideTessFactor;//内部点细分数};float RemapUV(float min, float max, float val){return (val - min) / (max - min);}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;}//获取Lambert漫反射值float GetLambertDiffuse(float3 worldPos, float3 worldNormal){float3 lightDir = UnityWorldSpaceLightDir(worldPos);float NDotL = saturate(dot(worldNormal, lightDir));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;}v2t vert(a2v i){v2t o;o.worldPos = mul(unity_ObjectToWorld,i.pos);o.uv = i.uv;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;}//在hullProgram之前必须设置这些参数,不然会报错[domain("tri")]//图元类型,可选类型有 "tri", "quad", "isoline"[partitioning("integer")]//曲面细分的过渡方式是整数还是小数[outputtopology("triangle_cw")]//三角面正方向是顺时针还是逆时针[outputcontrolpoints(3)]//输出的控制点数[patchconstantfunc("ConstantHS")]//对应之前的细分因子配置阶段的方法名[maxtessfactor(64.0)]//最大可能的细分段数//vert顶点程序之后调用,计算细分前的三角形顶点信息TessOut hullProgram(InputPatch<v2t, 3> i, uint idx : SV_OutputControlPointID){TessOut o;o.worldPos = i[idx].worldPos;o.uv = i[idx].uv;o.footstepUV = i[idx].footstepUV;o.worldNormal = i[idx].worldNormal;o.worldTangent = i[idx].worldTangent;o.worldBitangent = i[idx].worldBitangent;return o;}//指定每个边的细分段数和内部细分段数TessParam ConstantHS(InputPatch<v2t, 3> i, uint id : SV_PrimitiveID){TessParam o;float4 worldPos = (i[0].worldPos + i[1].worldPos + i[2].worldPos) / 3;float smoothstepResult = smoothstep(_minDis, _maxDis, distance(worldPos.xz, _centerPos.xz));float fac = max((1.0 - smoothstepResult)*_factor, 1);//由于我这里是根据指定的中心点和半径范围来动态算细分段数,所以才有这个计算,不然可以直接指定变量来设置。o.EdgeTess[0] = fac;o.EdgeTess[1] = fac;o.EdgeTess[2] = fac;o.InsideTess = fac;return o;}//在domainProgram前必须设置domain参数,不然会报错[domain("tri")]//细分之后,把信息传到frag片段程序t2f domainProgram(TessParam tessParam, float3 bary : SV_DomainLocation, const OutputPatch<TessOut, 3> i){t2f o;				//线性转换float2 uv = i[0].uv * bary.x + i[1].uv * bary.y + i[2].uv * bary.z;o.uv = uv;o.footstepUV = i[0].footstepUV * bary.x + i[1].footstepUV * bary.y + i[2].footstepUV * bary.z;float4 footstepCol = tex2Dlod(_footstepTex, float4(o.footstepUV, 0, 0.0));float addVal = footstepCol.a*_height;float4 worldPos = i[0].worldPos * bary.x + i[1].worldPos * bary.y + i[2].worldPos * bary.z;worldPos.y = worldPos.y - addVal;o.worldPos = worldPos;o.clipPos = UnityWorldToClipPos(worldPos);o.worldNormal = i[0].worldNormal * bary.x + i[1].worldNormal * bary.y + i[2].worldNormal * bary.z;o.worldTangent = i[0].worldTangent * bary.x + i[1].worldTangent * bary.y + i[2].worldTangent * bary.z;o.worldBitangent = i[0].worldBitangent * bary.x + i[1].worldBitangent * bary.y + i[2].worldBitangent * bary.z;return o;}fixed4 frag (t2f i) : SV_Target{//            // sample the texture//float2 mainUV = i.uv*_MainTex_ST.xy + _MainTex_ST.zw;//            fixed4 col = tex2D(_MainTex, mainUV)*_Color;//fixed4 footstepCol = tex2D(_footstepTex, i.footstepUV);//fixed3 footstepRGB = fixed3(footstepCol.r - 0.5, footstepCol.g - 0.5, footstepCol.b - 0.5);//footstepRGB = footstepRGB * footstepCol.a;//fixed4 finalCol = col;// fixed4(saturate(col.rgb + footstepRGB), 1);//            return finalCol;//采样漫反射贴图的颜色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*footstepCol.a, _normalScale).rgb;//得到切线空间的法线方向half3 normalVal = UnpackScaleNormal(normalCol, _normalScale).rgb;//normalVal = footstepRGB;//normalVal = normalize(normalVal + footstepRGB);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 = GetLambertDiffuse(i.worldPos, worldNormal);diffuseVal = clamp(diffuseVal, 0.6, 1);diffuseVal = pow(diffuseVal, 1.5);//用法线贴图的世界空间法线,算高光角度half3 specCol = _specColor * GetBlinnPhongSpec(i.worldPos, worldNormal)*_specIntensity;//最终颜色 = 环境色+漫反射颜色+高光颜色half3 finalCol = UNITY_LIGHTMODEL_AMBIENT * _ambientIntensity + saturate(col.rgb*diffuseVal) + specCol;return half4(finalCol,1);}ENDCG}}
}

值得注意的地方:
1.为了获得细分后的顶点做偏移,所以顶点偏移的过程是写在domainProgram
2.在顶点类程序里面如果需要采样贴图,并不是使用tex2D方法,而是使用tex2Dlod方法

2、Surface着色器里的实现:

Shader "azhao/SurfaceGround"
{Properties{_MainTex("MainTex", 2D) = "white" {}_NormalTex("NormalTex", 2D) = "white" {}_minDis("minVal", Float) = 0_maxDis("maxVal", Float) = 15_factor("factor", Float) = 1_height("height",float) = 0_centerPos("centerPos", Vector) = (0,0,0,0)		_footstepRect("footstepRect", Vector) = (0,0,0,0)		_footstepTex("footstepTex", 2D) = "black" {}		[HideInInspector] _texcoord("", 2D) = "white" {}[HideInInspector] __dirty("", Int) = 1}SubShader{Tags{ "RenderType" = "Opaque"  "Queue" = "Geometry+0" }Cull BackCGPROGRAM#include "UnityStandardUtils.cginc"#pragma target 4.6#pragma surface surf Standard keepalpha addshadow fullforwardshadows vertex:vertexDataFunc tessellate:tessFunction struct Input{float2 uv_texcoord;float3 worldPos;};sampler2D _NormalTex;float4 _NormalTex_ST;sampler2D _footstepTex;float4 _footstepRect;sampler2D _MainTex;float4 _MainTex_ST;float _minDis;float _maxDis;float4 _centerPos;float _factor;float _height;float RemapUV(float min, float max, float val){return (val - min) / (max - min);}float4 tessFunction( appdata_full v0, appdata_full v1, appdata_full v2 ){float4 vertex = (v0.vertex + v1.vertex + v2.vertex) / 3;float3 worldPos = mul( unity_ObjectToWorld, vertex );float smoothstepResult = smoothstep( _minDis , _maxDis , distance(_centerPos.xz, worldPos.xz));float tessVal = max( ( ( 1.0 - smoothstepResult ) * _factor ) , 0.1 );return tessVal;}void vertexDataFunc( inout appdata_full v ){float3 worldPos = mul(unity_ObjectToWorld, v.vertex);float temp_output_1_0_g1 = _footstepRect.x;float temp_output_1_0_g2 = _footstepRect.y;float2 footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, worldPos.z));float4 tex2DNode = tex2Dlod(_footstepTex, float4(footstepUV, 0, 0.0));float offset = tex2DNode.a * _height*-0.01;v.vertex.y += offset;}void surf( Input i , inout SurfaceOutputStandard o ){float2 uv_NormalTex = i.uv_texcoord * _NormalTex_ST.xy + _NormalTex_ST.zw;float3 worldPos = i.worldPos;float temp_output_1_0_g1 = _footstepRect.x;float temp_output_1_0_g2 = _footstepRect.y;float2 footstepUV = float2(RemapUV(_footstepRect.x, _footstepRect.z, worldPos.x), RemapUV(_footstepRect.y, _footstepRect.w, worldPos.z));o.Normal = BlendNormals( UnpackNormal( tex2D( _NormalTex, uv_NormalTex ) ) , UnpackNormal( tex2D( _footstepTex, footstepUV) ) );float2 uv_MainTex = i.uv_texcoord * _MainTex_ST.xy + _MainTex_ST.zw;o.Albedo = tex2D( _MainTex, uv_MainTex ).rgb;o.Alpha = 1;}ENDCG}Fallback "Diffuse"
}

  用Surface写,过程会简单很多,毕竟不需要自己去声明曲面细分的各个步骤,也不需要自己写光照模型和法线贴图的计算。不过我觉得如果能用顶点片段程序去实现一下,会对这个过程更了解一些。

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

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

相关文章

平面扫描(Plane-sweeping)深度体会

先看文章 三维重建之平面扫描算法&#xff08;Plane-sweeping&#xff09;_plane sweeping_小玄玄的博客-CSDN博客 Plane Sweeping | 平面扫描 - 知乎 (zhihu.com) 注意平面Dm,这是其中一个平面&#xff0c;平面上有一个M点&#xff0c;这个点也再物体上。所以会被摄像机看到…

C++多态基础

文章目录 1.多态概念2.多态使用3.多态析构4.多态隐藏5.多态原理5.1.单类继承5.1.1.问题一&#xff1a;非指针或引用无法调用多态5.1.2.问题二&#xff1a;同类对象共用虚表5.1.3.问题三&#xff1a;子类对象拷贝父类对象虚表5.1.4.问题四&#xff1a;打印虚表地址和虚表内容 5.…

Linux文件系统的功能规划

对于运行的进程来说&#xff0c;内存就像一个纸箱子&#xff0c;仅仅是一个暂存数据的地方&#xff0c;而且空间有限。如果我们想要进程结束之后&#xff0c;数据依然能够保存下来&#xff0c;就不能只保存在内存里&#xff0c;而是应该保存在外部存储中。就像图书馆这种地方&a…

centos7.9 postgresql 16.0 源码安装部署

postgresql 16.0 源码安装部署 环境准备 系统主机名IP地址centos7.9postgres192.168.200.56 软件准备 postgresql-16.0.tar.gz https://ftp.postgresql.org/pub/source/v16.0/postgresql-16.0.tar.gz依赖安装 yum -y install systemd-devel readline readline-devel zlib-devel…

Spring Cloud智慧工地源码,利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术开发,微服务架构

智慧工地系统充分利用计算机技术、互联网、物联网、云计算、大数据等新一代信息技术&#xff0c;以PC端&#xff0c;移动端&#xff0c;设备端三位一体的管控方式为企业现场工程管理提供了先进的技术手段。让劳务、设备、物料、安全、环境、能源、资料、计划、质量、视频监控等…

【神经网络】【GoogleNet】

1、引言 卷积神经网络是当前最热门的技术&#xff0c;我想深入地学习这门技术&#xff0c;从他的发展历史开始&#xff0c;了解神经网络算法的兴衰起伏&#xff1b;同时了解他在发展过程中的**里程碑式算法**&#xff0c;能更好的把握神经网络发展的未来趋势&#xff0c;了解神…

vue3+ts 项目遇到的问题和bug

1.router中使用pinia报错 pinia.mjs:1709 Uncaught Error: [&#x1f34d;]: "getActivePinia()" was called but there was no active Pinia. Are you trying to use a store before calling "app.use(pinia)"? See https://pinia.vuejs.org/core-concep…

[Kettle] Excel输入

Excel文件采用表格的形式&#xff0c;数据显示直观&#xff0c;操作方便 Excel文件采用工作表存储数据&#xff0c;一个文件有多张不同名称的工作表&#xff0c;分别存放相同字段或不同字段的数据 数据源 物理成绩(Kettle数据集2).xls https://download.csdn.net/download/H…

【TES745D】青翼自研基于复旦微的FMQL45T900全国产化ARM核心模块(100%国产化)

板卡概述 TES745D是一款基于上海复旦微电子FMQL45T900的全国产化ARM核心板。该核心板将复旦微的FMQL45T900&#xff08;与XILINX的XC7Z045-2FFG900I兼容&#xff09;的最小系统集成在了一个87*117mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&…

【ChatGPT】人工智能的下一个前沿

&#x1f38a;专栏【ChatGPT】 &#x1f33a;每日一句&#xff1a;慢慢变好,我是,你也是 ⭐欢迎并且感谢大家指出我的问题 文章目录 一、引言 二、ChatGPT的工作原理 三、ChatGPT的主要特点 四、ChatGPT的应用场景 五、结论与展望 ​​​​​​​ 一、引言 随着人工智能技…

深度学习入门

全连接批量归一化 目的是&#xff1a;只有一个学习率&#xff0c; 通过归一化&#xff0c;让所有的 x i x_i xi​具有一样的分布&#xff0c;则对每个参数 w i w_i wi​梯度的作用是相当的实现是&#xff1a;实际上是在全连接中增加了两个节点 γ \gamma γ, β \beta β 卷积…

【Linux】程序地址空间回顾

我们的地址空间划分成如下几个区域&#xff1a; 代码区&#xff0c;字符常量区&#xff0c;已初始化全局变量区&#xff0c;未已初始化全局数据区&#xff0c;堆区&#xff0c;栈区&#xff1b;其中有代码区至栈区的代码地址依次增大。 局部变量是具有临时性的局部变量&#x…