当前位置:   article > 正文

【UnityShader-URP】ParallaxMapping And Raymarching 视差贴图与光线步进--结合Rendering 20 详细讲解..(小白TA学习笔记)_shadergraph parallaxmapping

shadergraph parallaxmapping

前言:

自己在学习UnityShader的时候从自己规划的学习路程,要进入体积渲染。就要去掌握Raymarching。但是想到视差贴图”本就是采用了光线步进的简化版算法。会简单一些,然后啊就去啃Rendering 20 ,发现他写的好复杂好头痛,看了它的源码,对于一个小白来说,一堆宏,一堆定义。但是它原文是讲的真的好,自己花了很长时间去拆解理解,(当然知乎已经有大佬写了文章了)终于做出来了,!(放鞭炮!!)可能也会有后面的人学习,所以就把自己学这些的心路历程,踩到的坑和详细方法给仔细写下来,并且只汇总在一个Shader,不添加cginc,和shaderGUI。将它作为自己第一次发文章的里程碑吧。(小白可能讲的不是很正确,但是我会尽力把我理解的写出来!欢迎各位大佬纠错!)

Rendering 20 (catlikecoding.com)
知乎同步:

【UnityShader-URP】ParallaxMapping And Raymarching 视差贴图与光线步进--结合Rendering 20 详细讲解..(小白TA学习笔记) - 知乎 (zhihu.com)

介绍:

视差贴图是基于法线贴图的一个进阶版,在不增加模型表面顶点而用于更加明显的表达凹凸感的方法。可以用它做特效等。

视差凹陷

那么法线贴图和视差贴图的差别是什么呢?法线贴图改变的是光的行进,视差贴图依靠通过移动UV修改贴图在平面中制造了3D细节的假象.

那让我们开始吧!!!

1.视差贴图

1.1前期准备:

我们想把主贴图和法线贴图加上去

基础贴图

法线贴图

就先把URP模板添加后的放上主贴图和法向贴图。

  1. Shader "Unlit/ParallaxMapping"
  2. {
  3. Properties
  4. {
  5. _MainTex ("Texture", 2D) = "white"
  6. _NormalTex("NormalTex",2D) ="white"{}
  7. }
  8. SubShader
  9. {
  10. Tags
  11. {
  12. "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
  13. "RenderType"="Opaque"
  14. "Queue"="Geometry" //影响排序的顺序而已
  15. }
  16. LOD 100
  17. //主纹理
  18. Pass
  19. {
  20. //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
  21. Tags{"LightMode" = "UniversalForward"}
  22. HLSLPROGRAM
  23. #pragma vertex vert
  24. #pragma fragment frag
  25. //pragrams
  26. #pragma prefer_hlslcc gles
  27. #pragma exclude_renderers d3dll_9x
  28. #pragma target 2.0
  29. #pragma multi_compile_instancing
  30. //这个库也一定要装
  31. //......都是引用的内置的HLSL文件可以加一下然后了解
  32. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
  33. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
  34. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
  35. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
  36. struct Attributes
  37. {
  38. float4 vertex : POSITION;
  39. float3 normal:NORMAL;
  40. float2 uv : TEXCOORD0;
  41. };
  42. struct Varyings
  43. {
  44. float2 uv : TEXCOORD0;
  45. float3 worldNormal:TEXCOORD1;
  46. float4 vertex : SV_POSITION;
  47. };
  48. //主贴图
  49. sampler2D _MainTex;
  50. //法向贴图
  51. sampler2D _NormalTex;
  52. float4 _MainTex_ST;
  53. Varyings vert (Attributes v)
  54. {
  55. Varyings o;
  56. o.vertex = TransformObjectToHClip(v.vertex);
  57. o.worldNormal=TransformObjectToWorldNormal(v.normal);
  58. o.uv = TRANSFORM_TEX(v.uv, _MainTex);
  59. o.uv=v.uv;
  60. return o;
  61. }
  62. float4 frag (Varyings i) : SV_Target
  63. {
  64. // sample the texture
  65. float4 col = tex2D(_MainTex, i.uv);
  66. float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));//法线贴图要结合光信息的,先不急
  67. return col;
  68. }
  69. ENDHLSL
  70. }
  71. }
  72. }

1.2视差贴图原理讲解

为了突出凹凸感觉,就需要改变我们是视觉的感受

蓝色为高度信息,白色为纹理,黄色为视角方向

如上图0代表纹理最底下,1为最高处,蓝色为高度信息。模型的顶点信息其实一直没有变是0,而蓝色是表示为高度图的信息。当视线向下看的时候,采样的原点是在b处,而实际我们看到的信息确实在a处。所以说视角采样的像素信息应该是a处的像素,而不是b的像素。采用异从而给我们一种立体的感觉。

知道原理过后让我们开始吧

1.3构建TBN矩阵获得切线空间并且计算偏移坐标

先材质面板添加一下参数。

  1. [Header(ParallaxMap)]
  2. _ParallaxMap("ParallaxMap",2D) = "black"{}
  3. _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
  4. _Parallaxskew("skew",Range(0,15))=1 //偏离值

因为视差是由相对于观察者的透视投影引起的。所以我们要把纹理坐标移到这个位置,根据视角方向移动坐标。然而纹理坐标存在于切线空间中,所以为了去调整这些坐标,我们就需要切线空间中的视图方向。所以说我们就需要矩阵乘法也就是TBN去转换。

现在构造器里面声明法线,切线:

  1. struct Attributes
  2. {
  3. ....
  4. float4 tangent:TANGENT;
  5. float3 normal:NORMAL;
  6. .....
  7. };
  8. //声明一个TBN和TangentVS可以传参数
  9. struct Varyings
  10. {
  11. ......
  12. //切线转职矩阵
  13. float3x3 TBN:TEXCOORD3;
  14. float3 TangentVS:TEXCOORD8;
  15. .....
  16. };

在顶点着色器构建TBN矩阵

  1. Varyings vert (Attributes v)
  2. {
  3. .....
  4. //1.4.1准备好TBN
  5. float3x3 TBN = float3x3(
  6. v.tangent.xyz,
  7. cross(v.normal,v.tangent.xyz)* v.tangent.w, //副切线
  8. v.normal
  9. ) ;
  10. o.TBN=TBN;//
  11. .....
  12. }

再在顶点着色器求出TangentVDir(切线空间视图方向)

  1. o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex)); //摄像机视角乘以TBN矩阵获得切线空间视角
  2. o.TangentVS=normalize(o.TangentVS);

讲解:

1.在摄像机视角这里有一个细节。

它是运用的物体空间的摄像机位置,而不是世界空间的摄像机位置

我尝试过两种方法却有两种效果。效果却有略微差别

方法一:用世界空间转化为物体空间

  1. half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS); //用摄像机位置-物体世界空间的位置
  2. half3 VdirOS=TransformWorldToObject(VdirWS); //再用URP的矩阵转换为物体空间
  3. o.TangentVS=mul(TBN,VdirOS);

方法二:用untiy.CG的方法,这个需要拿出来

  1. //方法一 摄像机的物体视角
  2. inline float3 ObjSpaceViewDir( in float4 v )
  3. {
  4. float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
  5. return objSpaceCameraPos - v.xyz;
  6. }

方法二的效果:

而方法一挑动_Parallaxskew只是uv的移动而已。

但是最终效果方法方法二才是正确的,自己去查询一下和问了一下chatGPT,原因方法一是这种方法没有考虑物体自身的变换,如果物体有缩放、旋转或平移,计算出的视角方向可能不准确。

然后我们发现当视角下压至90度的时候,产生位移是最大的。而视角与法线(0,0,1)切空间中的视角方向等于表面法线(0,0,1),因此不会产生位移

所以视角越浅,投影越大,位移效应越大。

视角下压,发现扭曲会特别严重

1.4.基于高度的移动

用了高度图我们就可以让贴图看起来更高,我们使用视差贴图来缩放位移。采样地图,使用它的G通道作为高度,应用视差强度,并使用它来调节位移。

高度图

首先先采用贴图,我们可以创建一个函数获得高度图,方便我们后面的其他函数的调用。也方便来分类

  1. //获得高度的方法
  2. float GetParallaxHeight (float2 uv)
  3. {
  4. return tex2D(_ParallaxMap, uv.xy).g;
  5. }

然后创建一个函数,设置视差偏转

  1. //设置ParallaxOffset(视差补偿)--之前的方法
  2. float2 ParallaxOffset (float2 uv, float2 viewDir)
  3. {
  4. float height = GetParallaxHeight(uv);
  5. height -= 0.5;
  6. height *= _ParallaxStrength;
  7. return viewDir * height;
  8. }

height -= 0.5; 为什么要减去0.5?

高度减去了0.5后,可以使低的区域也向下移动,而中间的区域保持在原来的位置,从而使效果更好。

但是这个_ParallaxStrength,还是只能在很低的强度,不然会炸。

效果,在强度0.036

在片段着色器调用方法

float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace); 

1.5 正确地设置投影偏移值

目前使用的视差映射技术被称为带偏移限制的视差映射。我们只是使用视图方向的XY部分,它的最大长度是1。纹理偏移是有限的。效果可以给出不错的结果,但不能代表正确的透视投影。

我们必须缩放视图方向矢量,使它的Z分量变成1,我们通过除以它自己的Z分量来做。因为我们以后不需要用到Z,我们只需要用X和Y除以Z。因为高度差为1,根据相似三角形,xy/z得到正确的偏移

o.TangentVS.xy /= o.TangentVS.z;//1.5为了方便找到合适的偏移量

然后把__Parallaxskew,参数放入。

o.TangentVS.xy /= (o.TangentVS.z+_Parallaxskew);

1.6 Detail UV(用个纹理来明显我们的效果)和简化

细节贴图

标准着色器还简单地将UV偏移量添加到细节UV中,存储在UV插值器的ZW组件中。讲uv变成float4

   float4 uv : TEXCOORD0;

然后存在zw分量

    o.uv.zw = TRANSFORM_TEX(v.uv,_DetailTex);

再去片段着色器调用

float4 DetailTex=tex2D(_DetailTex,i.uv.zw);

设置5,5。设置uvoffset添加纹理细节

  1. //1.7添加细节纹理
  2. float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace);
  3. i.uv.xy +=uvOffset;
  4. i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果

i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);

目的使之缩放应该相对于MainTex

效果:

加了纹理的效果

1.7细节的增加

因为法线贴图影响的光源也是要使用TBN矩阵,法线贴图目的是影响光源。所以我们做一个半兰伯特光照来凸显效果。

  1. //添加法线贴图
  2. float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
  3. half3x3 TBN =i.TBN;
  4. half3 normalWS = normalize(mul(normalTex,TBN));
  5. half3 L =normalize(_MainLightPosition);
  6. half NdotL=normalize(dot(normalWS,L));
  7. half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
  8. //环境光
  9. float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
  10. halfdiffuse +=ambient;
  11. col.rgb *=halfdiffuse;

效果:

0.0036的扰动

1.8阶段性代码

  1. Shader "Parallax Mapping"
  2. {
  3. Properties
  4. { [Header(ParallaxMap)]
  5. _ParallaxMap("ParallaxMap",2D) = "black"{}
  6. _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
  7. _Parallaxskew("skew",Range(0,15))=1
  8. [Header(Detail grid texture.)]
  9. _DetailTex("DetailGridTex",2D)="white"{}
  10. _MainTex ("Texture", 2D) = "white" {}
  11. [Header(NormalTex)]
  12. _NormalTex("NormalTex",2D) ="white"{}
  13. }
  14. SubShader
  15. {
  16. Tags
  17. {
  18. "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
  19. "RenderType"="Opaque"//表示渲染层级为不透明,实则没啥作用
  20. "Queue"="Geometry" //影响排序的顺序而已
  21. }
  22. LOD 100
  23. //主纹理
  24. Pass
  25. {
  26. //最关键的Tag
  27. //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
  28. Tags{"LightMode" = "UniversalForward"}
  29. HLSLPROGRAM
  30. #pragma vertex vert
  31. #pragma fragment frag
  32. //pragrams
  33. #pragma prefer_hlslcc gles
  34. #pragma exclude_renderers d3dll_9x
  35. #pragma target 2.0
  36. #pragma multi_compile_instancing
  37. //这个库也一定要装
  38. //......都是引用的内置的HLSL文件可以加一下然后了解
  39. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
  40. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
  41. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
  42. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
  43. struct Attributes
  44. {
  45. float4 vertex : POSITION;
  46. float2 uv : TEXCOORD0;
  47. float3 normal:NORMAL;
  48. float4 tangent:TANGENT;
  49. };
  50. struct Varyings
  51. {
  52. float4 uv : TEXCOORD0;
  53. float4 vertex : SV_POSITION;
  54. float3 worldNormal:TEXCOORD1;
  55. float3 posWS:TEXCOORD2;
  56. //切线转职矩阵
  57. float3x3 TBN:TEXCOORD3;
  58. float3 TangentVS:TEXCOORD8;
  59. };
  60. sampler2D _MainTex;
  61. float4 _MainTex_ST;
  62. sampler2D _NormalTex;
  63. float4 _NormalTex_ST;
  64. //视差贴图
  65. sampler2D _ParallaxMap;
  66. float _ParallaxRefer;
  67. float _ParallaxStrength;
  68. float _Parallaxskew;
  69. sampler2D _DetailTex;
  70. float4 _DetailTex_ST;
  71. //方法一 摄像机的物体视角
  72. inline float3 ObjSpaceViewDir( in float4 v )
  73. {
  74. float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
  75. return objSpaceCameraPos - v.xyz;
  76. }
  77. //获得高度的方法
  78. float GetParallaxHeight (float2 uv)
  79. {
  80. return tex2D(_ParallaxMap, uv.xy).g;
  81. }
  82. //设置ParallaxOffset(视差补偿)--之前的方法
  83. float2 ParallaxOffset (float2 uv, float2 viewDir)
  84. {
  85. float height = GetParallaxHeight(uv);
  86. height -= 0.5;
  87. height *= _ParallaxStrength;
  88. return viewDir * height;
  89. }
  90. Varyings vert (Attributes v)
  91. {
  92. Varyings o;
  93. o.vertex = TransformObjectToHClip(v.vertex);
  94. o.worldNormal=TransformObjectToWorldNormal(v.normal);
  95. o.posWS=TransformObjectToWorld(v.vertex);
  96. o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
  97. o.uv.zw = TRANSFORM_TEX(v.uv,_DetailTex);
  98. //增加uv
  99. //其他TBN的写法
  100. //转置计算 本地转换为世界
  101. // o.worldTangent = TransformObjectToWorldDir(v.tangent);
  102. // //又v.tangent.w决定我们副切线的方向
  103. // float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
  104. // //由叉积计算出副切线cross (a,b)叉积运算
  105. // half3 worldBinormal = cross(o.worldNormal, o.worldTangent) * tangentSign;
  106. // //写法1
  107. // o.tSpace0 = float3(o.worldTangent.x,worldBinormal.x,o.worldNormal.x);
  108. // o.tSpace1 = float3(o.worldTangent.y,worldBinormal.y,o.worldNormal.y);
  109. // o.tSpace2 = float3(o.worldTangent.z,worldBinormal.z,o.worldNormal.z);
  110. //
  111. //1.4.1准备好TBN
  112. float3x3 TBN = float3x3(
  113. v.tangent.xyz,
  114. cross(v.normal,v.tangent.xyz)* v.tangent.w,
  115. v.normal
  116. ) ;
  117. o.TBN=TBN;
  118. //1.4.2.求出视线方向在切线空间下的一个坐标 ,并计算偏移坐标
  119. //方法1
  120. o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex));
  121. o.TangentVS=normalize(o.TangentVS);
  122. o.TangentVS.xy /= (o.TangentVS.z+_Parallaxskew);//1.5为了方便找到合适的偏移量
  123. // //方法2
  124. // half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS);
  125. // half3 VdirOS=TransformWorldToObject(VdirWS);
  126. // o.TangentVS=mul(TBN,VdirOS);
  127. return o;
  128. }
  129. float4 frag (Varyings i) : SV_Target
  130. {
  131. // sample the texture
  132. //1.4.2运用TangentVS
  133. half3 VdirTangentSpace =i.TangentVS;
  134. //half3 VdirTangentSpace = half3(dot(i.tSpace0,VdirOS),dot(i.tSpace1,VdirOS),dot(i.tSpace2,VdirOS));//取得到切线空间得值
  135. //.变化uv得切线
  136. VdirTangentSpace = normalize(VdirTangentSpace);
  137. i.uv.xy += VdirTangentSpace.xy * _ParallaxStrength;
  138. //1.5增加高度效果(后面写ParallaxOffset方法进行概括)
  139. float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace); //将这个偏转 化一个参数
  140. //1.7添加细节纹理
  141. i.uv.xy +=uvOffset;
  142. i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果
  143. float4 col = tex2D(_MainTex, i.uv);
  144. float4 DetailTex=tex2D(_DetailTex,i.uv.zw);
  145. col*=DetailTex*2;//增加一点亮度
  146. //添加法线贴图
  147. float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
  148. half3x3 TBN =i.TBN;
  149. half3 normalWS = normalize(mul(normalTex,TBN));
  150. half3 L =normalize(_MainLightPosition);
  151. half NdotL=normalize(dot(normalWS,L));
  152. half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
  153. //环境光
  154. float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
  155. halfdiffuse +=ambient;
  156. col.rgb *=halfdiffuse;
  157. return col;
  158. }
  159. ENDHLSL
  160. }
  161. }
  162. }

2.Raymarching光线步进

2.1.原理分析

我们的视差效果的工作原理是,通过高度和体积发射一个视图光线,并确定它到达表面的位置。是通过在光线进入体的地方对高度图进行一次采样来实现。当采样的点和交点实际上具有相同的高度时,这才是正确的。如果我们能算出光线到达高度场的位置,那么我们就能总能找到真正的可见表面点。这不能用单个纹理样本完成。我们必须沿着视图光线小步移动,每次采样高度场,直到我们到达表面。这种技术被称为光线步进。

光线步进示意图

2.2视差函数 ParallaxRaymarching

这是差别与ParallaxOffset另一种函数,是pro版本。创建一个函数

  1. float2 ParallaxRaymarching (float2 uv, float2 viewDir) {
  2. float2 uvOffset = 0; //将这个偏转 化一个参数
  3. float stepSize =0.1; //步数是10布,1/10
  4. float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
  5. float stepHeight =1;//归一化
  6. float surfaceHeight =GetParallaxHeight(uv);
  7. }

(1.) float2 uvDelta =viewDir *(stepSize*_ParallaxStrength);

用视差强度,我们可以调整每一步采样的高度。

(2.) float stepSize =0.1;

因为单位长度是1,结果=1/步数,步数越多最后呈现出来的效果更好,但是对性能的要求也会更高

编译步进循环

我们会想到两个循环,但是用While,编辑器警告告诉我们在循环中使用了梯度指令。这指的是我们循环中的纹理采样。GPU必须弄清楚要使用哪个mipmap级别,为此它需要比较相邻片段的使用过的UV坐标。只有当所有片段执行相同的代码时,它才能这样做。这对我们的循环来说是不可能的,因为它可以提前终止,每个片段可能不同。因此编译器将展开循环,这意味着它将始终执行所有九个步骤,而不管我们的逻辑是否建议我们可以提前停止
编译失败是因为编译器无法确定循环的最大迭代次数。它不知道这个最多等于9。让我们明确一点,把while循环变成for循环,这样就有了限制。

  1. for (int i =1;i<10 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
  2. {
  3. uvOffset -=uvDelta;
  4. stepHeight-=stepSize;
  5. surfaceHeight=GetParallaxHeight(uv+uvOffset);
  6. }

然后用这个去替换,ApplyParallax

效果图

2.3使用更多步

这种基本的射线行进方法最适合陡视差映射。效果的质量由我们的样品分辨率决定。有些方法根据视角使用可变数量的步骤。更浅的角度需要更多的步骤,因为光线更长。

提高质量的明显方法是增加样本的数量

         float stepSize =0.02; //步长是0.0.2 1/50

用50步去

  1. for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
  2. {
  3. uvOffset -=uvDelta;
  4. stepHeight-=stepSize;
  5. surfaceHeight=GetParallaxHeight(uv+uvOffset);
  6. }

整个代码

  1. //raymarching方法创建一个新函数--采用光线步进的方法
  2. float2 ParallaxRaymarching (float2 uv,float2 viewDir)
  3. { float2 uvOffset = 0; //将这个偏转 化一个参数
  4. float stepSize =0.02; //步长是0.1
  5. float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
  6. float stepHeight =1;
  7. float surfaceHeight =GetParallaxHeight(uv);
  8. //循环使用梯度
  9. for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
  10. {
  11. uvOffset -=uvDelta;
  12. stepHeight-=stepSize;
  13. surfaceHeight=GetParallaxHeight(uv+uvOffset);
  14. }
  15. return uvOffset;
  16. }

50步骤的效果

2.4图层间插值(提高质量)

提高质量的一种方法是对光线实际照射到表面的位置进行差值,上一步我们在水面上,下一步我们就在水面下了。在这两步之间的某个地方,射线一定击中了表面。

射线点和面点对定义了两条线段。因为射线和表面碰撞,这两条线相交。因此,如果我们跟踪前一步,我们可以在循环后执行直线相交。我们可以用这个信息来近似真实的交点。

原理图

在迭代过程中,我们必须跟踪之前的UV偏移量,步长高度和表面高度。最初,这些等于第一个样本在循环之前的值

  1. float2 prevUVOffset = uvOffset;
  2. float prevStepHeight = stepHeight;
  3. float prevSurfaceHeight = surfaceHeight;
  4. for ( ... )
  5. {
  6. prevUVOffset = uvOffset;
  7. prevStepHeight = stepHeight;
  8. prevSurfaceHeight = surfaceHeight;
  9. }

循环之后,我们计算直线相交的位置。我们可以使用它来在前一个和最后一个UV偏移之间进行插值。

  1. float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
  2. float difference = surfaceHeight - stepHeight;//之后的
  3. float t = prevDifference / (prevDifference + difference); //求出插值的因子
  4. uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算

完整:

  1. //跟踪之前的uv值
  2. float2 prevUVOffset = uvOffset;
  3. float prevStepHeight = stepHeight;
  4. float prevSurfaceHeight = surfaceHeight;
  5. //循环使用梯度
  6. for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
  7. {
  8. //记录之前的uv偏移
  9. prevUVOffset = uvOffset;
  10. prevStepHeight = stepHeight;
  11. prevSurfaceHeight = surfaceHeight;
  12. //记录之后的uv偏移
  13. uvOffset -=uvDelta;
  14. stepHeight-=stepSize;
  15. surfaceHeight=GetParallaxHeight(uv+uvOffset);
  16. }
  17. //进行插值
  18. float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
  19. float difference = surfaceHeight - stepHeight;//之后的
  20. float t = prevDifference / (prevDifference + difference); //求出插值的因子
  21. uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算

效果对比。Step为10的时候。

插值之前 (10步)

插值之后(10步)

是不是很明显啦!!
做完了捏(放鞭炮!!)哗啦啦啦啦

2.5(可选).图层间搜索(更加积分思想)---处理不规则的高度场--优化的算法

通过在两个步骤之间进行线性插值,我们假设表面在它们之间是直线的。然而,情况往往并非如此。为了更好地处理不规则的高度场,我们必须搜索两个步骤之间的实际交点。或者至少离目标更近一点。
完成循环后,不要使用最后一个偏移量,而是将偏移量调整到最后两个步骤之间的中间位置。对该点的高度进行采样。如果我们最终在表面以下,移动偏移量的四分之一回到前一个点并再次采样。如果我们最终在地表以上,向前移动四分之一到最后一点并再次采样。它最适合浮雕映射方法。每走一步,距离就减半,直到到达目的地。

在我们的例子中,我们只是这样做固定的次数,达到一个期望的解决方案。在一步中,我们总是在最后两点的中间,即0.5处结束。通过两个步骤,我们最终得到0.25或0.75。通过三个步骤,它是0.125、0.375、0.625或0.875。等等

当然这种方法,我并没有去尝试,有兴趣的朋友可以去原文理解然后去试试。

3.总结

总之这就是自己的学习路程和具体的总结,实现了还不做的效果。当然!自己也是一个学习者,也是按照一个学习者的思维来写的文章。肯定其中也有考虑不完全的内容,第一次写文章肯定也有结构的疏忽而且可能有很多错别字。希望各位佬们可以理解。!谢谢喵!

总代码:

  1. Shader "Parallax Mapping"
  2. {
  3. Properties
  4. { [Header(ParallaxMap)]
  5. _ParallaxMap("ParallaxMap",2D) = "black"{}
  6. _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
  7. _Parallaxskew("skew",Range(0,15))=1
  8. [Header(Detail grid texture.)]
  9. _DetailTex("DetailGridTex",2D)="white"{}
  10. _MainTex ("Texture", 2D) = "white" {}
  11. [Header(NormalTex)]
  12. _NormalTex("NormalTex",2D) ="white"{}
  13. }
  14. SubShader
  15. {
  16. Tags
  17. {
  18. "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
  19. "RenderType"="Opaque"//表示渲染层级为不透明,实则没啥作用
  20. "Queue"="Geometry" //影响排序的顺序而已
  21. }
  22. LOD 100
  23. //主纹理
  24. Pass
  25. {
  26. //最关键的Tag
  27. //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
  28. Tags{"LightMode" = "UniversalForward"}
  29. HLSLPROGRAM
  30. #pragma vertex vert
  31. #pragma fragment frag
  32. //pragrams
  33. #pragma prefer_hlslcc gles
  34. #pragma exclude_renderers d3dll_9x
  35. #pragma target 2.0
  36. #pragma multi_compile_instancing
  37. //这个库也一定要装
  38. //......都是引用的内置的HLSL文件可以加一下然后了解
  39. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
  40. #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
  41. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
  42. #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
  43. struct Attributes
  44. {
  45. float4 vertex : POSITION;
  46. float2 uv : TEXCOORD0;
  47. float3 normal:NORMAL;
  48. float4 tangent:TANGENT;
  49. };
  50. struct Varyings
  51. {
  52. float4 uv : TEXCOORD0;
  53. float4 vertex : SV_POSITION;
  54. float3 worldNormal:TEXCOORD1;
  55. float3 posWS:TEXCOORD2;
  56. //切线转职矩阵
  57. float3x3 TBN:TEXCOORD3;
  58. float3 TangentVS:TEXCOORD8;
  59. };
  60. sampler2D _MainTex;
  61. float4 _MainTex_ST;
  62. sampler2D _NormalTex;
  63. float4 _NormalTex_ST;
  64. //视差贴图
  65. sampler2D _ParallaxMap;
  66. float _ParallaxRefer;
  67. float _ParallaxStrength;
  68. float _Parallaxskew;
  69. sampler2D _DetailTex;
  70. float4 _DetailTex_ST;
  71. //方法一 摄像机的物体视角
  72. inline float3 ObjSpaceViewDir( in float4 v )
  73. {
  74. float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
  75. return objSpaceCameraPos - v.xyz;
  76. }
  77. //获得高度的方法
  78. float GetParallaxHeight (float2 uv)
  79. {
  80. return tex2D(_ParallaxMap, uv.xy).g;
  81. }
  82. //设置ParallaxOffset(视差补偿)--之前的方法
  83. float2 ParallaxOffset (float2 uv, float2 viewDir)
  84. {
  85. float height = GetParallaxHeight(uv);
  86. height -= 0.5;
  87. height *= _ParallaxStrength;
  88. return viewDir * height;
  89. }
  90. //raymarching方法创建一个新函数--采用光线步进的方法
  91. float2 ParallaxRaymarching (float2 uv,float2 viewDir)
  92. { float2 uvOffset = 0; //将这个偏转 化一个参数
  93. float stepSize =0.02; //步长是0.1
  94. float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
  95. float stepHeight =1;
  96. float surfaceHeight =GetParallaxHeight(uv);
  97. //跟踪之前的uv值
  98. float2 prevUVOffset = uvOffset;
  99. float prevStepHeight = stepHeight;
  100. float prevSurfaceHeight = surfaceHeight;
  101. //循环使用梯度
  102. for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
  103. {
  104. //记录之前的uv偏移
  105. prevUVOffset = uvOffset;
  106. prevStepHeight = stepHeight;
  107. prevSurfaceHeight = surfaceHeight;
  108. //记录之后的uv偏移
  109. uvOffset -=uvDelta;
  110. stepHeight-=stepSize;
  111. surfaceHeight=GetParallaxHeight(uv+uvOffset);
  112. }
  113. //进行插值
  114. float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
  115. float difference = surfaceHeight - stepHeight;//之后的
  116. float t = prevDifference / (prevDifference + difference); //求出插值的因子
  117. uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算
  118. return uvOffset;
  119. }
  120. Varyings vert (Attributes v)
  121. {
  122. Varyings o;
  123. o.vertex = TransformObjectToHClip(v.vertex);
  124. o.worldNormal=TransformObjectToWorldNormal(v.normal);
  125. o.posWS=TransformObjectToWorld(v.vertex);
  126. o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
  127. o.uv.zw = TRANSFORM_TEX(v.uv,_DetailTex);
  128. //增加uv
  129. //其他TBN的写法
  130. //转置计算 本地转换为世界
  131. // o.worldTangent = TransformObjectToWorldDir(v.tangent);
  132. // //又v.tangent.w决定我们副切线的方向
  133. // float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
  134. // //由叉积计算出副切线cross (a,b)叉积运算
  135. // half3 worldBinormal = cross(o.worldNormal, o.worldTangent) * tangentSign;
  136. // //写法1
  137. // o.tSpace0 = float3(o.worldTangent.x,worldBinormal.x,o.worldNormal.x);
  138. // o.tSpace1 = float3(o.worldTangent.y,worldBinormal.y,o.worldNormal.y);
  139. // o.tSpace2 = float3(o.worldTangent.z,worldBinormal.z,o.worldNormal.z);
  140. //
  141. //1.4.1准备好TBN
  142. float3x3 TBN = float3x3(
  143. v.tangent.xyz,
  144. cross(v.normal,v.tangent.xyz)* v.tangent.w,
  145. v.normal
  146. ) ;
  147. o.TBN=TBN;
  148. //1.4.2.求出视线方向在切线空间下的一个坐标 ,并计算偏移坐标
  149. //方法1
  150. o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex));
  151. o.TangentVS=normalize(o.TangentVS);
  152. o.TangentVS.xy /= (o.TangentVS.z+_Parallaxskew);//1.5为了方便找到合适的偏移量
  153. // //方法2
  154. // half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS);
  155. // half3 VdirOS=TransformWorldToObject(VdirWS);
  156. // o.TangentVS=mul(TBN,VdirOS);
  157. return o;
  158. }
  159. float4 frag (Varyings i) : SV_Target
  160. {
  161. // sample the texture
  162. //1.4.2运用TangentVS
  163. half3 VdirTangentSpace =i.TangentVS;
  164. //half3 VdirTangentSpace = half3(dot(i.tSpace0,VdirOS),dot(i.tSpace1,VdirOS),dot(i.tSpace2,VdirOS));//取得到切线空间得值
  165. //.变化uv得切线
  166. VdirTangentSpace = normalize(VdirTangentSpace);
  167. i.uv.xy += VdirTangentSpace.xy * _ParallaxStrength;
  168. //1.5增加高度效果(后面写ParallaxOffset方法进行概括)
  169. float2 uvOffset = ParallaxRaymarching(i.uv.xy,VdirTangentSpace); //将这个偏转 化一个参数
  170. //1.7添加细节纹理
  171. i.uv.xy +=uvOffset;
  172. i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果
  173. float4 col = tex2D(_MainTex, i.uv);
  174. float4 DetailTex=tex2D(_DetailTex,i.uv.zw);
  175. col*=DetailTex*2;//增加一点亮度
  176. //添加法线贴图
  177. float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
  178. half3x3 TBN =i.TBN;
  179. half3 normalWS = normalize(mul(normalTex,TBN));
  180. half3 L =normalize(_MainLightPosition);
  181. half NdotL=normalize(dot(normalWS,L));
  182. half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
  183. //环境光
  184. float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
  185. halfdiffuse +=ambient;
  186. col.rgb *=halfdiffuse;
  187. return col;
  188. }
  189. ENDHLSL
  190. }
  191. }
  192. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/101827
推荐阅读
相关标签
  

闽ICP备14008679号