赞
踩
自己在学习UnityShader的时候从自己规划的学习路程,要进入体积渲染。就要去掌握Raymarching。但是想到视差贴图”本就是采用了光线步进的简化版算法。会简单一些,然后啊就去啃Rendering 20 ,发现他写的好复杂好头痛,看了它的源码,对于一个小白来说,一堆宏,一堆定义。但是它原文是讲的真的好,自己花了很长时间去拆解理解,(当然知乎已经有大佬写了文章了)终于做出来了,!(放鞭炮!!)可能也会有后面的人学习,所以就把自己学这些的心路历程,踩到的坑和详细方法给仔细写下来,并且只汇总在一个Shader,不添加cginc,和shaderGUI。将它作为自己第一次发文章的里程碑吧。(小白可能讲的不是很正确,但是我会尽力把我理解的写出来!欢迎各位大佬纠错!)
Rendering 20 (catlikecoding.com)
视差贴图是基于法线贴图的一个进阶版,在不增加模型表面顶点而用于更加明显的表达凹凸感的方法。可以用它做特效等。
视差凹陷
那么法线贴图和视差贴图的差别是什么呢?法线贴图改变的是光的行进,视差贴图依靠通过移动UV修改贴图在平面中制造了3D细节的假象.
那让我们开始吧!!!
我们想把主贴图和法线贴图加上去
基础贴图
法线贴图
就先把URP模板添加后的放上主贴图和法向贴图。
- Shader "Unlit/ParallaxMapping"
- {
- Properties
- {
-
- _MainTex ("Texture", 2D) = "white"
- _NormalTex("NormalTex",2D) ="white"{}
-
- }
- SubShader
- {
- Tags
- {
- "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
- "RenderType"="Opaque"
- "Queue"="Geometry" //影响排序的顺序而已
-
- }
- LOD 100
-
- //主纹理
- Pass
- {
-
- //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
- Tags{"LightMode" = "UniversalForward"}
-
-
- HLSLPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
- //pragrams
- #pragma prefer_hlslcc gles
- #pragma exclude_renderers d3dll_9x
- #pragma target 2.0
- #pragma multi_compile_instancing
- //这个库也一定要装
- //......都是引用的内置的HLSL文件可以加一下然后了解
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
-
- struct Attributes
- {
- float4 vertex : POSITION;
- float3 normal:NORMAL;
- float2 uv : TEXCOORD0;
- };
-
- struct Varyings
- {
- float2 uv : TEXCOORD0;
- float3 worldNormal:TEXCOORD1;
- float4 vertex : SV_POSITION;
- };
- //主贴图
- sampler2D _MainTex;
- //法向贴图
- sampler2D _NormalTex;
- float4 _MainTex_ST;
-
- Varyings vert (Attributes v)
- {
- Varyings o;
- o.vertex = TransformObjectToHClip(v.vertex);
- o.worldNormal=TransformObjectToWorldNormal(v.normal);
- o.uv = TRANSFORM_TEX(v.uv, _MainTex);
- o.uv=v.uv;
- return o;
- }
-
- float4 frag (Varyings i) : SV_Target
- {
- // sample the texture
- float4 col = tex2D(_MainTex, i.uv);
- float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));//法线贴图要结合光信息的,先不急
-
- return col;
- }
- ENDHLSL
- }
- }
- }
为了突出凹凸感觉,就需要改变我们是视觉的感受
蓝色为高度信息,白色为纹理,黄色为视角方向
如上图0代表纹理最底下,1为最高处,蓝色为高度信息。模型的顶点信息其实一直没有变是0,而蓝色是表示为高度图的信息。当视线向下看的时候,采样的原点是在b处,而实际我们看到的信息确实在a处。所以说视角采样的像素信息应该是a处的像素,而不是b的像素。采用异从而给我们一种立体的感觉。
知道原理过后让我们开始吧
先材质面板添加一下参数。
- [Header(ParallaxMap)]
- _ParallaxMap("ParallaxMap",2D) = "black"{}
- _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
- _Parallaxskew("skew",Range(0,15))=1 //偏离值
因为视差是由相对于观察者的透视投影引起的。所以我们要把纹理坐标移到这个位置,根据视角方向移动坐标。然而纹理坐标存在于切线空间中,所以为了去调整这些坐标,我们就需要切线空间中的视图方向。所以说我们就需要矩阵乘法也就是TBN去转换。
现在构造器里面声明法线,切线:
- struct Attributes
- {
- ....
- float4 tangent:TANGENT;
- float3 normal:NORMAL;
- .....
- };
- //声明一个TBN和TangentVS可以传参数
- struct Varyings
- {
- ......
- //切线转职矩阵
- float3x3 TBN:TEXCOORD3;
- float3 TangentVS:TEXCOORD8;
- .....
- };
在顶点着色器构建TBN矩阵
- Varyings vert (Attributes v)
- {
- .....
- //1.4.1准备好TBN
- float3x3 TBN = float3x3(
- v.tangent.xyz,
- cross(v.normal,v.tangent.xyz)* v.tangent.w, //副切线
- v.normal
- ) ;
- o.TBN=TBN;//传
- .....
- }
再在顶点着色器求出TangentVDir(切线空间视图方向)
- o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex)); //摄像机视角乘以TBN矩阵获得切线空间视角
- o.TangentVS=normalize(o.TangentVS);
讲解:
1.在摄像机视角这里有一个细节。
它是运用的物体空间的摄像机位置,而不是世界空间的摄像机位置
我尝试过两种方法却有两种效果。效果却有略微差别
方法一:用世界空间转化为物体空间
- half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS); //用摄像机位置-物体世界空间的位置
- half3 VdirOS=TransformWorldToObject(VdirWS); //再用URP的矩阵转换为物体空间
- o.TangentVS=mul(TBN,VdirOS);
方法二:用untiy.CG的方法,这个需要拿出来
- //方法一 摄像机的物体视角
- inline float3 ObjSpaceViewDir( in float4 v )
- {
- float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
- return objSpaceCameraPos - v.xyz;
- }
方法二的效果:
而方法一挑动_Parallaxskew只是uv的移动而已。
但是最终效果方法方法二才是正确的,自己去查询一下和问了一下chatGPT,原因方法一是这种方法没有考虑物体自身的变换,如果物体有缩放、旋转或平移,计算出的视角方向可能不准确。
然后我们发现当视角下压至90度的时候,产生位移是最大的。而视角与法线(0,0,1)切空间中的视角方向等于表面法线(0,0,1),因此不会产生位移
所以视角越浅,投影越大,位移效应越大。
视角下压,发现扭曲会特别严重
用了高度图我们就可以让贴图看起来更高,我们使用视差贴图来缩放位移。采样地图,使用它的G通道作为高度,应用视差强度,并使用它来调节位移。
高度图
首先先采用贴图,我们可以创建一个函数获得高度图,方便我们后面的其他函数的调用。也方便来分类
- //获得高度的方法
- float GetParallaxHeight (float2 uv)
- {
- return tex2D(_ParallaxMap, uv.xy).g;
- }
然后创建一个函数,设置视差偏转
- //设置ParallaxOffset(视差补偿)--之前的方法
- float2 ParallaxOffset (float2 uv, float2 viewDir)
- {
- float height = GetParallaxHeight(uv);
- height -= 0.5;
- height *= _ParallaxStrength;
- return viewDir * height;
- }
height -= 0.5; 为什么要减去0.5?
高度减去了0.5后,可以使低的区域也向下移动,而中间的区域保持在原来的位置,从而使效果更好。
但是这个_ParallaxStrength,还是只能在很低的强度,不然会炸。
效果,在强度0.036
在片段着色器调用方法
float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace);
目前使用的视差映射技术被称为带偏移限制的视差映射。我们只是使用视图方向的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);
细节贴图
标准着色器还简单地将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.7添加细节纹理
- float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace);
- i.uv.xy +=uvOffset;
- i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果
i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);
目的使之缩放应该相对于MainTex
效果:
加了纹理的效果
因为法线贴图影响的光源也是要使用TBN矩阵,法线贴图目的是影响光源。所以我们做一个半兰伯特光照来凸显效果。
- //添加法线贴图
- float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
- half3x3 TBN =i.TBN;
- half3 normalWS = normalize(mul(normalTex,TBN));
- half3 L =normalize(_MainLightPosition);
- half NdotL=normalize(dot(normalWS,L));
- half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
- //环境光
- float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
- halfdiffuse +=ambient;
- col.rgb *=halfdiffuse;
效果:
- Shader "Parallax Mapping"
- {
- Properties
- { [Header(ParallaxMap)]
- _ParallaxMap("ParallaxMap",2D) = "black"{}
- _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
- _Parallaxskew("skew",Range(0,15))=1
-
- [Header(Detail grid texture.)]
- _DetailTex("DetailGridTex",2D)="white"{}
- _MainTex ("Texture", 2D) = "white" {}
- [Header(NormalTex)]
- _NormalTex("NormalTex",2D) ="white"{}
-
-
- }
- SubShader
- {
- Tags
- {
- "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
- "RenderType"="Opaque"//表示渲染层级为不透明,实则没啥作用
- "Queue"="Geometry" //影响排序的顺序而已
-
- }
- LOD 100
-
- //主纹理
- Pass
- {
- //最关键的Tag
- //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
- Tags{"LightMode" = "UniversalForward"}
-
-
- HLSLPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
- //pragrams
- #pragma prefer_hlslcc gles
- #pragma exclude_renderers d3dll_9x
- #pragma target 2.0
- #pragma multi_compile_instancing
-
- //这个库也一定要装
- //......都是引用的内置的HLSL文件可以加一下然后了解
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
-
- struct Attributes
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- float3 normal:NORMAL;
- float4 tangent:TANGENT;
- };
-
- struct Varyings
- {
- float4 uv : TEXCOORD0;
-
- float4 vertex : SV_POSITION;
- float3 worldNormal:TEXCOORD1;
- float3 posWS:TEXCOORD2;
- //切线转职矩阵
- float3x3 TBN:TEXCOORD3;
- float3 TangentVS:TEXCOORD8;
- };
-
- sampler2D _MainTex;
- float4 _MainTex_ST;
- sampler2D _NormalTex;
- float4 _NormalTex_ST;
- //视差贴图
- sampler2D _ParallaxMap;
-
- float _ParallaxRefer;
- float _ParallaxStrength;
- float _Parallaxskew;
- sampler2D _DetailTex;
- float4 _DetailTex_ST;
-
-
-
- //方法一 摄像机的物体视角
- inline float3 ObjSpaceViewDir( in float4 v )
- {
- float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
- return objSpaceCameraPos - v.xyz;
- }
-
- //获得高度的方法
- float GetParallaxHeight (float2 uv)
- {
- return tex2D(_ParallaxMap, uv.xy).g;
- }
-
- //设置ParallaxOffset(视差补偿)--之前的方法
- float2 ParallaxOffset (float2 uv, float2 viewDir)
- {
- float height = GetParallaxHeight(uv);
- height -= 0.5;
- height *= _ParallaxStrength;
- return viewDir * height;
- }
-
-
-
-
-
-
- Varyings vert (Attributes v)
- {
- Varyings o;
- o.vertex = TransformObjectToHClip(v.vertex);
- o.worldNormal=TransformObjectToWorldNormal(v.normal);
- o.posWS=TransformObjectToWorld(v.vertex);
-
- o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
- o.uv.zw = TRANSFORM_TEX(v.uv,_DetailTex);
- //增加uv
-
-
- //其他TBN的写法
- //转置计算 本地转换为世界
- // o.worldTangent = TransformObjectToWorldDir(v.tangent);
- // //又v.tangent.w决定我们副切线的方向
- // float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
- // //由叉积计算出副切线cross (a,b)叉积运算
- // half3 worldBinormal = cross(o.worldNormal, o.worldTangent) * tangentSign;
- // //写法1
- // o.tSpace0 = float3(o.worldTangent.x,worldBinormal.x,o.worldNormal.x);
- // o.tSpace1 = float3(o.worldTangent.y,worldBinormal.y,o.worldNormal.y);
- // o.tSpace2 = float3(o.worldTangent.z,worldBinormal.z,o.worldNormal.z);
- //
- //1.4.1准备好TBN
- float3x3 TBN = float3x3(
- v.tangent.xyz,
- cross(v.normal,v.tangent.xyz)* v.tangent.w,
- v.normal
- ) ;
- o.TBN=TBN;
- //1.4.2.求出视线方向在切线空间下的一个坐标 ,并计算偏移坐标
- //方法1
- o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex));
- o.TangentVS=normalize(o.TangentVS);
- o.TangentVS.xy /= (o.TangentVS.z+_Parallaxskew);//1.5为了方便找到合适的偏移量
-
-
- // //方法2
- // half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS);
- // half3 VdirOS=TransformWorldToObject(VdirWS);
- // o.TangentVS=mul(TBN,VdirOS);
-
- return o;
- }
-
- float4 frag (Varyings i) : SV_Target
- {
- // sample the texture
- //1.4.2运用TangentVS
- half3 VdirTangentSpace =i.TangentVS;
- //half3 VdirTangentSpace = half3(dot(i.tSpace0,VdirOS),dot(i.tSpace1,VdirOS),dot(i.tSpace2,VdirOS));//取得到切线空间得值
- //.变化uv得切线
- VdirTangentSpace = normalize(VdirTangentSpace);
- i.uv.xy += VdirTangentSpace.xy * _ParallaxStrength;
-
-
- //1.5增加高度效果(后面写ParallaxOffset方法进行概括)
- float2 uvOffset = ParallaxOffset(i.uv.xy,VdirTangentSpace); //将这个偏转 化一个参数
- //1.7添加细节纹理
- i.uv.xy +=uvOffset;
- i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果
- float4 col = tex2D(_MainTex, i.uv);
-
- float4 DetailTex=tex2D(_DetailTex,i.uv.zw);
-
-
-
-
-
-
-
- col*=DetailTex*2;//增加一点亮度
-
- //添加法线贴图
- float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
- half3x3 TBN =i.TBN;
- half3 normalWS = normalize(mul(normalTex,TBN));
- half3 L =normalize(_MainLightPosition);
- half NdotL=normalize(dot(normalWS,L));
- half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
- //环境光
- float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
- halfdiffuse +=ambient;
- col.rgb *=halfdiffuse;
- return col;
-
- }
- ENDHLSL
- }
- }
-
- }
我们的视差效果的工作原理是,通过高度和体积发射一个视图光线,并确定它到达表面的位置。是通过在光线进入体的地方对高度图进行一次采样来实现。当采样的点和交点实际上具有相同的高度时,这才是正确的。如果我们能算出光线到达高度场的位置,那么我们就能总能找到真正的可见表面点。这不能用单个纹理样本完成。我们必须沿着视图光线小步移动,每次采样高度场,直到我们到达表面。这种技术被称为光线步进。
光线步进示意图
这是差别与ParallaxOffset另一种函数,是pro版本。创建一个函数
- float2 ParallaxRaymarching (float2 uv, float2 viewDir) {
- float2 uvOffset = 0; //将这个偏转 化一个参数
- float stepSize =0.1; //步数是10布,1/10
- float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
- float stepHeight =1;//归一化
- float surfaceHeight =GetParallaxHeight(uv);
- }
(1.) float2 uvDelta =viewDir *(stepSize*_ParallaxStrength);
用视差强度,我们可以调整每一步采样的高度。
(2.) float stepSize =0.1;
因为单位长度是1,结果=1/步数,步数越多最后呈现出来的效果更好,但是对性能的要求也会更高
编译步进循环
我们会想到两个循环,但是用While,编辑器警告告诉我们在循环中使用了梯度指令。这指的是我们循环中的纹理采样。GPU必须弄清楚要使用哪个mipmap级别,为此它需要比较相邻片段的使用过的UV坐标。只有当所有片段执行相同的代码时,它才能这样做。这对我们的循环来说是不可能的,因为它可以提前终止,每个片段可能不同。因此编译器将展开循环,这意味着它将始终执行所有九个步骤,而不管我们的逻辑是否建议我们可以提前停止
编译失败是因为编译器无法确定循环的最大迭代次数。它不知道这个最多等于9。让我们明确一点,把while循环变成for循环,这样就有了限制。
- for (int i =1;i<10 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
- {
- uvOffset -=uvDelta;
- stepHeight-=stepSize;
- surfaceHeight=GetParallaxHeight(uv+uvOffset);
- }
然后用这个去替换,ApplyParallax
效果图
这种基本的射线行进方法最适合陡视差映射。效果的质量由我们的样品分辨率决定。有些方法根据视角使用可变数量的步骤。更浅的角度需要更多的步骤,因为光线更长。
提高质量的明显方法是增加样本的数量
float stepSize =0.02; //步长是0.0.2 1/50
用50步去
- for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
- {
- uvOffset -=uvDelta;
- stepHeight-=stepSize;
- surfaceHeight=GetParallaxHeight(uv+uvOffset);
- }
整个代码
- //raymarching方法创建一个新函数--采用光线步进的方法
- float2 ParallaxRaymarching (float2 uv,float2 viewDir)
- { float2 uvOffset = 0; //将这个偏转 化一个参数
- float stepSize =0.02; //步长是0.1
- float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
- float stepHeight =1;
- float surfaceHeight =GetParallaxHeight(uv);
- //循环使用梯度
- for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
- {
- uvOffset -=uvDelta;
- stepHeight-=stepSize;
- surfaceHeight=GetParallaxHeight(uv+uvOffset);
- }
- return uvOffset;
- }
50步骤的效果
提高质量的一种方法是对光线实际照射到表面的位置进行差值,上一步我们在水面上,下一步我们就在水面下了。在这两步之间的某个地方,射线一定击中了表面。
射线点和面点对定义了两条线段。因为射线和表面碰撞,这两条线相交。因此,如果我们跟踪前一步,我们可以在循环后执行直线相交。我们可以用这个信息来近似真实的交点。
原理图
在迭代过程中,我们必须跟踪之前的UV偏移量,步长高度和表面高度。最初,这些等于第一个样本在循环之前的值
- float2 prevUVOffset = uvOffset;
- float prevStepHeight = stepHeight;
- float prevSurfaceHeight = surfaceHeight;
- for ( ... )
- {
- prevUVOffset = uvOffset;
- prevStepHeight = stepHeight;
- prevSurfaceHeight = surfaceHeight;
-
- …
- }
循环之后,我们计算直线相交的位置。我们可以使用它来在前一个和最后一个UV偏移之间进行插值。
- float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
- float difference = surfaceHeight - stepHeight;//之后的
- float t = prevDifference / (prevDifference + difference); //求出插值的因子
- uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算
完整:
- //跟踪之前的uv值
- float2 prevUVOffset = uvOffset;
- float prevStepHeight = stepHeight;
- float prevSurfaceHeight = surfaceHeight;
- //循环使用梯度
- for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
- {
- //记录之前的uv偏移
- prevUVOffset = uvOffset;
- prevStepHeight = stepHeight;
- prevSurfaceHeight = surfaceHeight;
- //记录之后的uv偏移
- uvOffset -=uvDelta;
- stepHeight-=stepSize;
- surfaceHeight=GetParallaxHeight(uv+uvOffset);
- }
- //进行插值
- float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
- float difference = surfaceHeight - stepHeight;//之后的
- float t = prevDifference / (prevDifference + difference); //求出插值的因子
- uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算
效果对比。Step为10的时候。
插值之前 (10步)
插值之后(10步)
是不是很明显啦!!
做完了捏(放鞭炮!!)哗啦啦啦啦
通过在两个步骤之间进行线性插值,我们假设表面在它们之间是直线的。然而,情况往往并非如此。为了更好地处理不规则的高度场,我们必须搜索两个步骤之间的实际交点。或者至少离目标更近一点。
完成循环后,不要使用最后一个偏移量,而是将偏移量调整到最后两个步骤之间的中间位置。对该点的高度进行采样。如果我们最终在表面以下,移动偏移量的四分之一回到前一个点并再次采样。如果我们最终在地表以上,向前移动四分之一到最后一点并再次采样。它最适合浮雕映射方法。每走一步,距离就减半,直到到达目的地。
在我们的例子中,我们只是这样做固定的次数,达到一个期望的解决方案。在一步中,我们总是在最后两点的中间,即0.5处结束。通过两个步骤,我们最终得到0.25或0.75。通过三个步骤,它是0.125、0.375、0.625或0.875。等等
当然这种方法,我并没有去尝试,有兴趣的朋友可以去原文理解然后去试试。
总之这就是自己的学习路程和具体的总结,实现了还不做的效果。当然!自己也是一个学习者,也是按照一个学习者的思维来写的文章。肯定其中也有考虑不完全的内容,第一次写文章肯定也有结构的疏忽而且可能有很多错别字。希望各位佬们可以理解。!谢谢喵!
总代码:
- Shader "Parallax Mapping"
- {
- Properties
- { [Header(ParallaxMap)]
- _ParallaxMap("ParallaxMap",2D) = "black"{}
- _ParallaxStrength("ParallaxStrength",Range(0,1)) = 0.3
- _Parallaxskew("skew",Range(0,15))=1
-
- [Header(Detail grid texture.)]
- _DetailTex("DetailGridTex",2D)="white"{}
- _MainTex ("Texture", 2D) = "white" {}
- [Header(NormalTex)]
- _NormalTex("NormalTex",2D) ="white"{}
-
-
- }
- SubShader
- {
- Tags
- {
- "RenderPipeline"="UniversalPipeline"//渲染管线设置为URP,告诉引擎是给通用渲染管线用的 就是在管线设置的情况下会有影响
- "RenderType"="Opaque"//表示渲染层级为不透明,实则没啥作用
- "Queue"="Geometry" //影响排序的顺序而已
-
- }
- LOD 100
-
- //主纹理
- Pass
- {
- //最关键的Tag
- //"LightMode"是指指定不同光照模式的关键字,而"UniversalForward"是指通用的前向渲染模式。
- Tags{"LightMode" = "UniversalForward"}
-
-
- HLSLPROGRAM
-
- #pragma vertex vert
- #pragma fragment frag
- //pragrams
- #pragma prefer_hlslcc gles
- #pragma exclude_renderers d3dll_9x
- #pragma target 2.0
- #pragma multi_compile_instancing
-
- //这个库也一定要装
- //......都是引用的内置的HLSL文件可以加一下然后了解
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
- #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
- #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
-
- struct Attributes
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- float3 normal:NORMAL;
- float4 tangent:TANGENT;
- };
-
- struct Varyings
- {
- float4 uv : TEXCOORD0;
-
- float4 vertex : SV_POSITION;
- float3 worldNormal:TEXCOORD1;
- float3 posWS:TEXCOORD2;
- //切线转职矩阵
- float3x3 TBN:TEXCOORD3;
- float3 TangentVS:TEXCOORD8;
- };
-
- sampler2D _MainTex;
- float4 _MainTex_ST;
- sampler2D _NormalTex;
- float4 _NormalTex_ST;
- //视差贴图
- sampler2D _ParallaxMap;
-
- float _ParallaxRefer;
- float _ParallaxStrength;
- float _Parallaxskew;
- sampler2D _DetailTex;
- float4 _DetailTex_ST;
-
-
-
- //方法一 摄像机的物体视角
- inline float3 ObjSpaceViewDir( in float4 v )
- {
- float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
- return objSpaceCameraPos - v.xyz;
- }
-
- //获得高度的方法
- float GetParallaxHeight (float2 uv)
- {
- return tex2D(_ParallaxMap, uv.xy).g;
- }
-
- //设置ParallaxOffset(视差补偿)--之前的方法
- float2 ParallaxOffset (float2 uv, float2 viewDir)
- {
- float height = GetParallaxHeight(uv);
- height -= 0.5;
- height *= _ParallaxStrength;
- return viewDir * height;
- }
-
- //raymarching方法创建一个新函数--采用光线步进的方法
- float2 ParallaxRaymarching (float2 uv,float2 viewDir)
- { float2 uvOffset = 0; //将这个偏转 化一个参数
- float stepSize =0.02; //步长是0.1
- float2 uvDelta =viewDir *(stepSize*_ParallaxStrength); //用视差强度,我们可以调整每一步采样的高度
- float stepHeight =1;
- float surfaceHeight =GetParallaxHeight(uv);
- //跟踪之前的uv值
- float2 prevUVOffset = uvOffset;
- float prevStepHeight = stepHeight;
- float prevSurfaceHeight = surfaceHeight;
- //循环使用梯度
- for (int i =1;i<50 && stepHeight>surfaceHeight;i++) //可能会导致性能不好但是是学习无所谓了
- {
- //记录之前的uv偏移
- prevUVOffset = uvOffset;
- prevStepHeight = stepHeight;
- prevSurfaceHeight = surfaceHeight;
- //记录之后的uv偏移
- uvOffset -=uvDelta;
- stepHeight-=stepSize;
- surfaceHeight=GetParallaxHeight(uv+uvOffset);
- }
- //进行插值
- float prevDifference = prevStepHeight - prevSurfaceHeight;//之前的
- float difference = surfaceHeight - stepHeight;//之后的
- float t = prevDifference / (prevDifference + difference); //求出插值的因子
- uvOffset = lerp(prevUVOffset, uvOffset, t);//插值计算
- return uvOffset;
- }
-
-
-
-
-
- Varyings vert (Attributes v)
- {
- Varyings o;
- o.vertex = TransformObjectToHClip(v.vertex);
- o.worldNormal=TransformObjectToWorldNormal(v.normal);
- o.posWS=TransformObjectToWorld(v.vertex);
-
- o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
- o.uv.zw = TRANSFORM_TEX(v.uv,_DetailTex);
- //增加uv
-
-
- //其他TBN的写法
- //转置计算 本地转换为世界
- // o.worldTangent = TransformObjectToWorldDir(v.tangent);
- // //又v.tangent.w决定我们副切线的方向
- // float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
- // //由叉积计算出副切线cross (a,b)叉积运算
- // half3 worldBinormal = cross(o.worldNormal, o.worldTangent) * tangentSign;
- // //写法1
- // o.tSpace0 = float3(o.worldTangent.x,worldBinormal.x,o.worldNormal.x);
- // o.tSpace1 = float3(o.worldTangent.y,worldBinormal.y,o.worldNormal.y);
- // o.tSpace2 = float3(o.worldTangent.z,worldBinormal.z,o.worldNormal.z);
- //
- //1.4.1准备好TBN
- float3x3 TBN = float3x3(
- v.tangent.xyz,
- cross(v.normal,v.tangent.xyz)* v.tangent.w,
- v.normal
- ) ;
- o.TBN=TBN;
- //1.4.2.求出视线方向在切线空间下的一个坐标 ,并计算偏移坐标
- //方法1
- o.TangentVS=mul(TBN,ObjSpaceViewDir(v.vertex));
- o.TangentVS=normalize(o.TangentVS);
- o.TangentVS.xy /= (o.TangentVS.z+_Parallaxskew);//1.5为了方便找到合适的偏移量
-
-
- // //方法2
- // half3 VdirWS=normalize(_WorldSpaceCameraPos-o.posWS);
- // half3 VdirOS=TransformWorldToObject(VdirWS);
- // o.TangentVS=mul(TBN,VdirOS);
-
- return o;
- }
-
- float4 frag (Varyings i) : SV_Target
- {
- // sample the texture
- //1.4.2运用TangentVS
- half3 VdirTangentSpace =i.TangentVS;
- //half3 VdirTangentSpace = half3(dot(i.tSpace0,VdirOS),dot(i.tSpace1,VdirOS),dot(i.tSpace2,VdirOS));//取得到切线空间得值
- //.变化uv得切线
- VdirTangentSpace = normalize(VdirTangentSpace);
- i.uv.xy += VdirTangentSpace.xy * _ParallaxStrength;
-
-
- //1.5增加高度效果(后面写ParallaxOffset方法进行概括)
- float2 uvOffset = ParallaxRaymarching(i.uv.xy,VdirTangentSpace); //将这个偏转 化一个参数
- //1.7添加细节纹理
- i.uv.xy +=uvOffset;
- i.uv.zw +=uvOffset*(_DetailTex_ST.xy / _MainTex_ST.xy);//使之细节贴图意义对应的。然后有了扭转效果
- float4 col = tex2D(_MainTex, i.uv);
-
- float4 DetailTex=tex2D(_DetailTex,i.uv.zw);
-
-
-
-
-
-
-
- col*=DetailTex*2;//增加一点亮度
-
- //添加法线贴图
- float3 normalTex = UnpackNormal(tex2D(_NormalTex,i.uv));
- half3x3 TBN =i.TBN;
- half3 normalWS = normalize(mul(normalTex,TBN));
- half3 L =normalize(_MainLightPosition);
- half NdotL=normalize(dot(normalWS,L));
- half3 halfdiffuse = max(0.3,NdotL*0.5+0.5);//确定阴影效果
- //环境光
- float3 ambient =UNITY_LIGHTMODEL_AMBIENT;
- halfdiffuse +=ambient;
- col.rgb *=halfdiffuse;
- return col;
-
- }
- ENDHLSL
- }
- }
-
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。