Unity Shader - 实现类似镜面反射、水面扰动效果

前几天,家里出了一些问题,搞得心情很不好,面试我也取消了。
唉,反正那个伤心啊,不过,昨天处理好了。
所以说啊,家和万事兴。
加油加油!!!

所以心情好了,我又写博客了。

另外说一下:图形我今年2019.5才开始正式学习。
所以每个shader效果有些实现方法可能不是很好的,欢迎大神多多指点。

进入主题吧:

今天实现:Unity Shader - 实现类似镜面反射、水面扰动效果

Quad 效果

在这里插入图片描述

Cube 效果

在这里插入图片描述

思路

平面的过渡效果,可参考我之前写的一个,模型按平面渡效果:Unity Shader - 模型过滤效果
Noise 噪点实现类型水面可参考我之前写的:Unity Shader - Noise 噪点应用,实现类似流水的表面效果
模板缓存来只绘制镜面的内容:Unity Shader - 模板简单测试 - 实现镂空/遮罩、描边

上面两个效果组合,就差不多可以得到上面的效果了。

两个pass现实

第一个pass:绘制镜面模型

将模型的顶点按传进来的平面的法线、任一点,来对本体模型镜面顶点
这样再去绘制,就看起来又镜面的效果了
同时我们在镜像体绘制时,添加了纹理柏林噪点偏移UV,看起来有点像水流效果

但我们的镜面体的显示只能在那个地板模型中才绘制,所以我们给地板shader添加了Stencil处理(Stencil的功能实例看上面链接)

镜面详细讲解一下:

  • 首先我们需要将镜面的法线、任意平面点传入shader(*.cs脚本:下面将法线、坐标传入shader我列出来)
  • 然后利用平面信息将顶点镜像处理,具体看下图

在这里插入图片描述

第二个pass:绘制本体模型

绘制模型本体
但需要处理,上面Unity Shader - 模型过滤效果 的过渡效果,因为超过镜面底下,我们就不显示了(可以按需求来处理)
然后再对纹理UV扰动想水流似的,基本就这样就完了

将法线、坐标传入shader

// jave.lin 2019.08.15
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UpdateProps : MonoBehaviour
{
    private static int nHash;
    private static int pHash;

    public GameObject obj;
    public GameObject plane;

    private Material objMat;

    // Start is called before the first frame update
    void Start()
    {
        objMat = obj.GetComponent<MeshRenderer>().sharedMaterial;
        nHash = Shader.PropertyToID("n");
        pHash = Shader.PropertyToID("p");
    }

    // Update is called once per frame
    void Update()
    {
        // n==normal of plane
        // p==position of plane
        var n = plane.transform.up;
        var p = plane.transform.position;

        objMat.SetVector(nHash, n);
        objMat.SetVector(pHash, p);
    }
}

镜面的模板shader

// jave.lin 2019.08.15
Shader "Test/MirrorGroundStencil"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "Queue"="Geometry+1" "RenderType"="Opaque" }
        Stencil {
            Ref 1
            Comp Always
            Pass Replace
        }
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed4 _Color;
            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = tex2D(_MainTex, i.uv);
                return col * _Color;
            }
            ENDCG
        }
    }
}

需要绘制镜面效果的完整Shader

注释我写的相当详细了

// jave.lin 2019.08.15
Shader "Test/PerlinNoiseMirror" {
    Properties {
        [NoScaleOffset] _MainTex ("MainTex", 2D) = "white" {}               // 主纹理
        [NoScaleOffset] _NoiseTex ("NoiseTex", 2D) = "white" {}             // 噪点图
        _NoiseScaleX ("NoiseScaleX", Range(0, 1)) = 0.1                     // 水平噪点放大系数
        _NoiseScaleY ("NoiseScaleY", Range(0, 1)) = 0.1                     // 垂直放大系数
        _NoiseSpeedX ("NoiseSpeedX", Range(0, 10)) = 1                      // 水平扰动速度
        _NoiseSpeedY ("NoiseSpeedY", Range(0, 10)) = 1                      // 垂直扰动速度
        _NoiseBrightOffset ("NoiseBrightOffset", Range(0, 0.9)) = 0.25      // 噪点图整体的数值偏移
        _NoiseFalloff ("NoiseFalloff", Range(0, 1)) = 1                     // 扰动衰减

        _MirrorRange ("MirrorRange", Range(0, 3)) = 1                       // 镜面范围(最大范围,超出该范围就不反射)
        _MirrorAlpha ("MirrorAlpha", Range(0, 1)) = 1                       // 镜面图像不透明度
        _MirrorFadeAlpha ("_MirrorFadeAlpha", Range(0,1)) = 0.5             // 镜面范围值边缘位置的不透明度,如果调整为0,意思越接近该最大范围的透明就越接近该值:0
    }
    CGINCLUDE
    #include "UnityCG.cginc"
    #include "Lighting.cginc"
    #include "AutoLight.cginc"
    struct appdata {
        float4 vertex : POSITION;
        float2 uv : TEXCOORD0;
        float3 normal : NORMAL;
    };
    struct v2f {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
        float3 wPos : TEXCOORD1;
    };
    struct v2f_m {
        float4 vertex : SV_POSITION;
        float2 uv : TEXCOORD0;
        float4 normal : TEXCOORD1;
        float4 wPos : TEXCOORD2;
    };
    sampler2D _MainTex;
    sampler2D _NoiseTex;
    fixed _NoiseScaleX, _NoiseScaleY;
    fixed _NoiseSpeedX, _NoiseSpeedY;
    fixed _NoiseBrightOffset;
    fixed _NoiseFalloff;
    float _MirrorRange, _MirrorAlpha, _MirrorFadeAlpha;
    float3 n, p; // 镜面法线,镜面任意点
    v2f vert_normal (appdata v) {
        v2f o;
        o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
        o.vertex = UnityObjectToClipPos(v.vertex);
        o.uv = v.uv;
        return o;
    }
    fixed4 frag_normal (v2f i) : SV_Target {
        float3 dir = i.wPos.xyz - p;                // 平面与插值点的指向
        half d = dot(dir, n);                       // 与反向镜面的距离
        if (d < 0) discard;                         // 如果平面背面,那就丢弃

        return tex2D(_MainTex, i.uv);
    }
    v2f_m vert_mirror (appdata v) {
        v2f_m o;

        o.wPos = mul(unity_ObjectToWorld, v.vertex);

        float3 nn = -n;                 // 法线反向
        float3 dp = o.wPos.xyz - p;     // 平面点与世界空间的点的向量(即:从平面的点指向世界空间点的方向)
        half nd = dot(n, dp);           // 计算出点与平面的垂直距离
        o.wPos.xyz += nn * (nd * 2);    // 将垂直距离反向2倍的距离,就是镜像的位置
        
        o.vertex = mul(unity_MatrixVP, o.wPos);
        o.normal.xyz = UnityObjectToWorldNormal(v.normal);

        fixed t = nd / _MirrorRange;       // 将位置与镜面最大范围比利作为fade alpha的插值系数
        fixed a = lerp(_MirrorAlpha, _MirrorAlpha * _MirrorFadeAlpha, t);
        o.normal.w = a;     // 透明度我们存于o.normal.w
        o.wPos.w = nd;      // 距离存于o.wPos.w
        o.uv = v.uv;
        
        return o;
    }
    fixed4 frag_mirror (v2f_m i) : SV_Target {
        if (i.wPos.w > _MirrorRange) discard;       // 超过镜像范围也丢弃
        if (i.normal.w <= 0) discard;               // 透明度为0丢弃

        float3 dir = i.wPos.xyz - p;                // 平面与插值点的指向
        half d = dot(dir, n);                       // 与反向镜面的距离
        if (d > 0) discard;                         // 如果超过了平面,那就丢弃

        fixed2 ouvxy = fixed2( // 噪点图采样,用于主纹理的UV偏移的
            tex2D(_NoiseTex, i.uv + fixed2(_Time.x * _NoiseSpeedX, 0)).r,
            tex2D(_NoiseTex, i.uv + fixed2(0, _Time.x * _NoiseSpeedY)).r);
        ouvxy -= _NoiseBrightOffset; // 0~1 to ==> -_NoiseBrightOffset~ 1 - _NoiseBrightOffset
        ouvxy *= fixed2(_NoiseScaleX, _NoiseScaleY);    // 扰动放大系数
        
        float scale = i.wPos.w / _MirrorRange;          // 用距离来作为扰动衰减
        scale = lerp(scale, 1, (1 - _NoiseFalloff));    // 距离越近扰动越是衰减(即:与镜面距离越近,基本是不扰动的,所以我们可以看到边缘与镜面的像素是吻合的)
        ouvxy *= scale;
        
        fixed4 col = tex2D(_MainTex, i.uv + ouvxy);     // 加上扰动UV后再采样主纹理
        return fixed4(col.rgb, i.normal.w);
    }
    ENDCG
    SubShader {
        Tags { "Queue"="Geometry+2" "RenderType"="Opaque" }
        Pass {
            Cull front
            ZTest Always
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha
            Stencil {
                Ref 1
                Comp Equal
            }
            CGPROGRAM
            #pragma vertex vert_mirror
            #pragma fragment frag_mirror
            ENDCG
        }
        Pass {
            CGPROGRAM
            #pragma vertex vert_normal
            #pragma fragment frag_normal
            ENDCG
        }
    }
}

Source Project

TestShaderToy_NoiseAndMirror 提取码: j1kh 复制这段内容后打开百度网盘手机App,操作更方便哦

打开:PerlinNoiseMirror.unity 场景即可

(今天要去探亲,我就过几天再回来学习shader,然后再博客)

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页