美文网首页
使用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