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

前言:

自己在学习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模板添加后的放上主贴图和法向贴图。

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

1.2视差贴图原理讲解

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

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

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

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

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

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

[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),因此不会产生位移

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

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

1.4.基于高度的移动

用了高度图我们就可以让贴图看起来更高,我们使用视差贴图来缩放位移。采样地图,使用它的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); 

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

效果:

加了纹理的效果

1.7细节的增加

因为法线贴图影响的光源也是要使用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;

效果:

0.0036的扰动

1.8阶段性代码

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

2.Raymarching光线步进

2.1.原理分析

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

光线步进示意图

2.2视差函数 ParallaxRaymarching

这是差别与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

效果图

2.3使用更多步

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

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

         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步骤的效果

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

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

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

原理图

在迭代过程中,我们必须跟踪之前的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步)

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

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

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

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

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

3.总结

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

总代码:

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
        }
        }
  
        }
  • 27
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值