美文网首页
使用Unity的HDRP实现水滴效果

使用Unity的HDRP实现水滴效果

作者: SunnyDecember | 来源:发表于2020-03-14 15:17 被阅读0次

      最近公司裁了一个技术美术,然而现在用人之际,才发现缺一个技术美术,便把我推到火坑上,美术不够前端来凑?
      目前HDRP的shader资料非常少,国外有一些shader graph教程,无奈我不太喜欢这种通过编写节点和拉线来完成一个shader,哪怕只是简单几行的HLSL代码,也要拉N个节点来拼凑。好吧,手动撸一个水滴及地面的水波纹shader效果,需要结合特效:


      完整shader如下:

    /* 
    *Author : SunnyDecember
    */
    
    Shader "Weather/RainDrop"
    {
        Properties
        {
            _MainTex("MainTex", 2D) = "white" {}
            _Brightness("Brightness", Range(0, 2)) = 1
            _MainColor("MainColor", COLOR) = (1, 1, 1, 1)
            _NormalTex("NormalTex", 2D) = "bump" {}
            _CutoutTex("CutoutTex", 2D) = "white" {}
            _Distortion("Distortion", Range(0.5, 5)) = 1
        }
    
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
        #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
        #include "Packages/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl"
        #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"
    
        CBUFFER_START(RainDorp)
        real _Brightness;
        real4 _MainColor;
        real4 _NormalTex_ST;
        real4 _CutoutTex_ST;
        real4 _MainTex_ST;
        real _Distortion;
        real4 _ColorPyramidTexture_TexelSize;
        CBUFFER_END
       
        TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
        TEXTURE2D(_NormalTex); SAMPLER(sampler_NormalTex);
        TEXTURE2D(_CutoutTex); SAMPLER(sampler_CutoutTex);
        SAMPLER(sampler_ColorPyramidTexture);
        ENDHLSL
    
        SubShader
        {
            Tags { "Queue"="Transparent" "RenderType"="Transparent"}
            Lighting Off
    
            //HDRP不支持GrabPass ! ! ! 抠脚。
            //GrabPass{ }
            
            Pass
            {
                ZWrite Off
                Blend SrcAlpha OneMinusSrcAlpha
                HLSLPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                struct VertexInput
                {
                    float4 vertex : POSITION;
                    float4 texcoord : TEXCOORD0;
                    float4 color : COLOR;
                };
    
                struct VertexOutput
                {
                    float4 clipPosition : SV_POSITION;
                    half2 uv : TEXCOORD0;
                    real4 grabUV : TEXCOORD1;
                    real2 normalUV : TEXCOORD2;
                    real2 cutoutUV : TEXCOORD3;
                    real4 color : TEXCOORD4;
                };
    
                VertexOutput vert(VertexInput v)
                {
                    VertexOutput o;
                   
                    float3 positionWS = TransformObjectToWorld(v.vertex);
                    o.clipPosition = TransformWorldToHClip(positionWS);
                    o.uv = v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
                    o.color = v.color;
                   
                    //屏幕UV
                    #if UNITY_UV_STARTS_AT_TOP
                    float scale = -1;
                    #else
                    float scale = 1;
                    #endif
                   
                    o.grabUV.xy = (float2(o.clipPosition.x, o.clipPosition.y * scale) + o.clipPosition.w) * 0.5;
                    o.grabUV.zw = o.clipPosition.zw;
                   
                    //获取法线图UV
                    o.normalUV = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
                    o.cutoutUV = v.texcoord.xy * _CutoutTex_ST.xy + _CutoutTex_ST.zw;
                   
                    return o;
                }
    
                float4 frag (VertexOutput IN) : SV_Target
                {
                    real4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
                    real2 bump = UnpackNormal(SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, IN.normalUV)).rg;
                    real4 cutoutColor = SAMPLE_TEXTURE2D(_CutoutTex, sampler_CutoutTex, IN.cutoutUV);
                   
                    //水折射
                    real2 offset = bump * _Distortion * IN.grabUV.z;
                    IN.grabUV.xy += offset;
                    real4 grabColor = SAMPLE_TEXTURE2D_X_LOD(_ColorPyramidTexture, sampler_ColorPyramidTexture, IN.grabUV.xy / IN.grabUV.w, 0);
                    albedo *= _Brightness * _MainColor * IN.color.r;
                   
                    return float4(grabColor.rgb + albedo.rgb, cutoutColor.a * _MainColor.a);
                }
                ENDHLSL
            }
        }
           FallBack Off
    }
    
    

    雨滴使用的图:


    地面波纹使用的图:


    1.声明Unity的属性

      水滴和地面的水波纹都是使用同一个shader,只是使用的纹理不一样,主要实现了水的折射。定义shader的属性,和普通Unity版本一致的写法:

        Properties
        {
            _MainTex("MainTex", 2D) = "white" {}
            _Brightness("Brightness", Range(0, 2)) = 1
            _MainColor("MainColor", COLOR) = (1, 1, 1, 1)
            _NormalTex("NormalTex", 2D) = "bump" {}
            _CutoutTex("CutoutTex", 2D) = "white" {}
            _Distortion("Distortion", Range(0.5, 5)) = 1
        }
    

      _Brightness 为控制水珠的亮度。_MainColor为水珠颜色。_NormalTex为法线图,非常重要,水的折射就靠她来模拟了。_CutoutTex用于控制水珠的透明度,这个shader其实是作用在面片上,我们希望面片周围的多余像素不显示,请观察她的纹理_____。_Distortion控制水扭曲的程度。

    2.引入HLSL文件及声明变量

        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl"
        #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
        #include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
        #include "Packages/com.unity.shadergraph/ShaderGraphLibrary/Functions.hlsl"
        #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"
    
        CBUFFER_START(RainDorp)
        real _Brightness;
        real4 _MainColor;
        real4 _NormalTex_ST;
        real4 _CutoutTex_ST;
        real4 _MainTex_ST;
        real _Distortion;
        real4 _ColorPyramidTexture_TexelSize;
        CBUFFER_END
       
        TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
        TEXTURE2D(_NormalTex); SAMPLER(sampler_NormalTex);
        TEXTURE2D(_CutoutTex); SAMPLER(sampler_CutoutTex);
        SAMPLER(sampler_ColorPyramidTexture);
        ENDHLSL
    

      使用HLSLINCLUDE / ENDHLSL ,包裹住HLSL文件。把变量定义在CBUFFER_START / CBUFFER_END中,只是为了放在缓冲区,这里没什么值得深究的。在纹理名字后加上“_ST”,获取纹理的Tiling和offset值,在inspector面板有显示此值。这里的real类型,要么是half要么是float,在Common.hlsl文件中有声明。在这里贴出简化后的代码:

    #if REAL_IS_HALF
    #define real half
    #define real2 half2
    #define real3 half3
    #define real4 half4
    ...
    #else
    
    #define real float
    #define real2 float2
    #define real3 float3
    #define real4 float4
    ...
    #endif
    

      TEXTURE2D(名字) / SAMPLER(名字)声明纹理和她的采样器,对应于普通unity版本的sampler2D。注意,在SAMPLER中的声明,需要在纹理名字前面加上 “sampler”。_ColorPyramidTexture是unity声明的变量,所以在此我们只需要她的采样器就好了,她的作用是获取屏幕像素,普通版本的GrabPass在HDRP中无法使用了,用_ColorPyramidTexture代替之。

    3.命令及结构体

    Tags { "Queue"="Transparent" "RenderType"="Transparent"}
    ZWrite Off
    Blend SrcAlpha OneMinusSrcAlpha
    HLSLPROGRAM
    
    struct VertexInput
    {
        float4 vertex : POSITION;
        float4 texcoord : TEXCOORD0;
        float4 color : COLOR;
    };
    
    struct VertexOutput
    {
        float4 clipPosition : SV_POSITION;
        half2 uv : TEXCOORD0;
        real4 grabUV : TEXCOORD1;
        real2 normalUV : TEXCOORD2;
        real2 cutoutUV : TEXCOORD3;
        real4 color : TEXCOORD4;
    };
    

      我们使用的几种渲染队列值分别为:

    • Background
      值为1000。使用此队列的物体,会率先渲染,通常用于渲染背景及天空盒。
    • Geometry
      值为2000。此队列用得最多,用于渲染不透明物体。
    • AlphaTest
      值为2450。
    • Transparent
      值为3000。用于半透明物体的渲染。此时Background和Geometry已经渲染完毕,物体可以混合他们的颜色实现半透明效果。
    • Overlay
      值为4000。最后的叠加效果。

      在渲染物体时候,Unity会先对他们进行排序,规则是按照物体的网格中心点离摄像机的距离远近排列。Background,Geometry和AlphaTest均是从近到远排序,也就是先渲染距离近摄像机的物体。为什么需要排序呢?主要是为了避免同一个像素点的重复绘制。Transparent和Overlay是从远到近的规则排列后渲染,因为远的物体必须要先渲染,近的物体才能和他们做颜色混合,呈现半透明颜色。所以呢,这两个队列会有重复绘制,性能稍低一点。
      我们希望不透明物体的颜色已经写入颜色缓冲后,才开始渲染水滴,此时水滴颜色可以混合颜色缓冲来达到半透明效果,可以解释的了能透过水波纹看到地面砖,因此用透明队列"Queue"="Transparent"。RenderType是给shader replacement使用的,标记的RenderType可用于写入摄像机深度纹理或者灯光的深度图。
      每个片元会经过深度测试,深度测试通过了,把深度记录到深度缓冲中,并写入颜色到颜色缓冲。但是,通常半透明物体不会写入深度值的,ZWrite Off。原因1,半透明物体是从远到近渲染,远的物体不会遮挡近的物体。原因2,如果开启深度写入,会使得看不到半透明物体的背面,因为深度值被离摄像机更近的面覆盖了。

    4.顶点函数

    VertexOutput vert(VertexInput v)
    {
        VertexOutput o;
    
        float3 positionWS = TransformObjectToWorld(v.vertex);
        o.clipPosition = TransformWorldToHClip(positionWS);
        o.uv = v.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
        o.color = v.color;
    
        //屏幕UV
    #if UNITY_UV_STARTS_AT_TOP
        float scale = -1;
    #else
        float scale = 1;
    #endif
    
        o.grabUV.xy = (float2(o.clipPosition.x, o.clipPosition.y * scale) + o.clipPosition.w) * 0.5;
        o.grabUV.zw = o.clipPosition.zw;
    
        //获取法线图UV
        o.normalUV = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
        o.cutoutUV = v.texcoord.xy * _CutoutTex_ST.xy + _CutoutTex_ST.zw;
    
        return o;
    }
    

      UNITY_UV_STARTS_AT_TOP可判断uv坐标开始位置在纹理的左上还是左下。openGL在左下,DX的在左上并且沿y方向往下逐渐减小。经过TransformWorldToHClip转换后的clipPosition坐标,此时在裁剪坐标系下,他的xyz范围都在(-w,w)之间。加上clipPosition.w在乘上0.5,此时的x和y范围被转换到(0,w)之间了,而zw不需要转换。这么做的原因,是为片元函数做准备,在片元函数需要把他的xy除以w来转换到(0,1)范围,也就是所谓的NDC空间吧。为什么不直接在顶点函数直接除以w?而且在片元函数才做这步呢?因为顶点函数之后会做插值,我们希望这个插值是线性的,否则就不准确了(这和gamma,linear是一样的道理)。待插值完毕后,我们在片元函数才除以w。

    5.片元函数

    float4 frag (VertexOutput IN) : SV_Target
    {
        real4 albedo = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
        real2 bump = UnpackNormal(SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, IN.normalUV)).rg;
        real4 cutoutColor = SAMPLE_TEXTURE2D(_CutoutTex, sampler_CutoutTex, IN.cutoutUV);
                    
        //水折射
        real2 offset = bump * _Distortion * IN.grabUV.z;
        IN.grabUV.xy += offset;
        real4 grabColor = SAMPLE_TEXTURE2D_X_LOD(_ColorPyramidTexture, sampler_ColorPyramidTexture, IN.grabUV.xy / IN.grabUV.w, 0);
        albedo *= _Brightness * _MainColor * IN.color.r;
                    
        return float4(grabColor.rgb + albedo.rgb, cutoutColor.a * _MainColor.a);
    }
    

      分别对_MainTex,_NormalTex,_CutoutTex采样得到相应的颜色值。我们对法线的xy偏移值并叠加到grabUV上以模拟折射,让grabUV除以他的w值,此时grabUV的xy范围是(0,1)。我们算grabUV的xy的目的,其实是在计算水的每个片元映射到屏幕的哪个位置,通过对这个屏幕位置的偏移后,再去对屏幕颜色采样,以模仿水折射效果,当雨滴打在地砖时候,会观察到地砖颜色有偏移。而我们希望水的折射和深度有关,远近的折射不一样,在计算offset时候乘上了grabUV.z值。普通版本的Unity的grabpass在HDRP中不能使用了, 在HDRP中可使用_ColorPyramidTexture来获取屏幕颜色,对屏幕颜色采样后得到grabColor,加上反射率albedo可以达到我们想要的效果。为了不显示多余的颜色,我们在A通道乘上cutoutColor.a。_CutoutTex图的目的是剔除多余的颜色而已。

    Author : SunnyDecember
    Date : 2020.3.14
    原文

    相关文章

      网友评论

          本文标题:使用Unity的HDRP实现水滴效果

          本文链接:https://www.haomeiwen.com/subject/wueorhtx.html