【UnityShader】使用Cubemap/Matcap制作玻璃

写在前面:写shader写累了就写写博客吧,每天都是充满bug的一天> <
使用版本:Unity3D 2019.4 (LTS) (LTS意思是长期支持,尽量选择LTS版本,兼容性比较好)

效果示意

Inspector

Inspectors

Glass

在这里插入图片描述
注:所有Texture均来自冯乐乐《UnityShader入门精要》项目配套源码1

代码示意

Glass.shader1

Shader "Glass"
{
    
    
    Properties
    {
    
    
        _BumpMap("Normal Map",2D) = "bump"{
    
    }        
        _Distortion("Distortion",range(0,100)) = 10
        //折射系数
        _RefractAmount("Refract Amount",range(0,1)) = 1
        [KeywordEnum(Cubemap, MatCap)] _Reflect("Reflect", Float) = 0
        _Cubemap("Environment Map",cube) = "_Skybox"{
    
    }

    }
        SubShader
    {
    
    
        Tags {
    
     "RenderType" = "Opaque" "Queue" = "Transparent" "LightMode" = "Always"}
        // GrabPass{"_RefractionTex"}

        Pass
        {
    
    
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile _REFLECT_CUBEMAP _REFLECT_MATCAP

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct appdata
            {
    
    
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f
            {
    
    
                float2 uv : TEXCOORD0;
                float4 pos : SV_POSITION;
                float4 scrPos : TEXCOORD4;
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;
            };

            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            samplerCUBE _Cubemap;
            float _Distortion;
            fixed _RefractAmount;
            sampler2D _RefractionTex;
            float4 _RefractionTex_TexelSize;

            v2f vert(appdata v)
            {
    
    
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);

                // o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
                o.uv = TRANSFORM_TEX(v.uv, _BumpMap);
                //得到屏幕采样坐标
                o.scrPos = ComputeGrabScreenPos(o.pos);

                float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                float3 worldBinormal = cross(worldTangent, worldNormal) * v.tangent.w;

                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
    
    
                float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
                float3x3 TtoW = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);

                fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                fixed3 tanNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
                fixed3 worldNormal = mul(TtoW, tanNormal);
                float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
                
                float2 offset = tanNormal.xy * _Distortion * _RefractionTex_TexelSize.xy;
                i.scrPos.xy += offset;
                fixed3 refractCol = tex2Dproj(_RefractionTex, i.scrPos).xyz;
                
                fixed3 reflectDir = reflect(-worldViewDir, worldNormal);
                fixed4 cubemapCol = texCUBE(_Cubemap, reflectDir);
                fixed4 matcapLookup = tex2D(_RefractionTex, viewNormal.xy * 0.5 + 0.5);
                fixed3 reflectCol;

                #if _REFLECT_CUBEMAP
                reflectCol = cubemapCol.rgb;

                #elif _REFLECT_MATCAP
                reflectCol = matcapLookup.rgb;

                #endif

                fixed3 color = refractCol * _RefractAmount + reflectCol * (1 - _RefractAmount);
                
                return fixed4(color,1.0);
            }
            ENDCG
        }
    }
}

CommandBufferBlurRefraction.cs2

using UnityEngine;
using UnityEngine.Rendering;
using System.Collections.Generic;

[ExecuteInEditMode]
public class CommandBufferBlurRefraction : MonoBehaviour
{
    
    
    private Camera m_Cam;
    // private Dictionary<Camera, CommandBuffer> m_Cameras = new Dictionary<Camera, CommandBuffer>();
    
    public void OnWillRenderObject()
    {
    
    

        var cam = Camera.current;
        if (!cam)
            return;

        cam.RemoveAllCommandBuffers();

        CommandBuffer buf = null;

        buf = new CommandBuffer();
        buf.name = "Grab screen and blur";

        //新建一张纹理,得到对应的纹理ID
        int screenCopyID = Shader.PropertyToID("_RefractionTex");
        //初始化这张纹理,其中纹理的大小为摄像机视野大小
        buf.GetTemporaryRT(screenCopyID, -1, -1, 0, FilterMode.Bilinear);
        //将当前的渲染目标纹理拷贝到你的纹理上
        buf.Blit(BuiltinRenderTextureType.CurrentActive, screenCopyID);

        buf.SetGlobalTexture("_RefractionTex", screenCopyID);

        buf.ReleaseTemporaryRT(screenCopyID);

        cam.AddCommandBuffer(CameraEvent.AfterSkybox, buf);
    }
}

知识点

Unity Shader自定义材质面板的小技巧3

适用场景

仅仅想在材质面板里定义少数变量,不想多写一个C#函数从头自定义材质面板的时候。

提到的drawer

//in Properties
[KeywordEnum(Cubemap, MatCap)] _Reflect("Reflect", Float) = 1

……

//in Pass
#pragma multi_compile _REFLECT_CUBEMAP _REFLECT_MATCAP

//in frag
#if _REFLECT_CUBEMAP
reflectCol = cubemapCol.rgb;

#elif _REFLECT_MATCAP
reflectCol = matcapLookup.rgb * cubemapCol.rgb;

#endif

Unity官方文档原文:

KeywordEnum allows you to choose which of a set of shader keywords to enable. It displays a float as a popup menu, and the value of the float determines which shader keyword Unity enables. Unity enables a shader keyword with the name . Up to 9 names can be provided.(uppercase property name)_(uppercase enum value name)

大意就是,这个drawer画了一个选项框,最多支持9个选项,并且可以用浮点数初始化选项。浮点数默认从零开始。
在Properties中初始化后,我们还要在Pass中引入选项,这样才能在后文中定义选择该选项时Shader的行为,具体格式如上。
在本shader中,我在片元着色器上使用#if和#elif来控制选择不同选项时shader的不同行为。最后记得#endif,不然会出现Syntax Error。

使用CommandBuffer来抓取RT

原本我是使用了GrabPass,但这个在实际项目中是禁止使用的。换成了CommandBuffer后效果也是一样的。参考的CommandBuffer功能太多,在帮助之下删成了上文那个样子o> <o
捋一下思路就是,获取相机->清除相机buffer->新建一个临时纹理并初始化,将抓到的贴图装进去->拷贝临时纹理到指定纹理上->新建相机缓冲区并载入->清除临时纹理
这时候就有人问了,如果不载入指定纹理会发生什么呢?我也不知道qaq,试了一下效果都是一样的。

Matcap(未完待续)

关于Shader,Cubemap的部分在《UnityShader入门精要》中有详细讲解,这里不再复述,主要是Matcap的部分,作为第一次接触的方法,详细了解一下。
我对Matcap的理解是,它其实就是一种采样方式,根据法线的xy分量在贴图中选取对应的点来渲染。注意要把法线从模型空间转移到观察空间,再取观察空间中法线的xy分量进行计算。如果使用法线贴图的话,流程就是切线空间->模型空间->观察空间,代码如下:

fixed3 tanNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
fixed3 worldNormal = mul(TtoW, tanNormal);
float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);

Matcap的采样方式如下,之所以viewNormal.xy * 0.5 + 0.5是为了将法线分量范围从[-1,1]转换为[0,1],也就是uv坐标的范围。

fixed4 matcapLookup = tex2D(_RefractionTex, viewNormal.xy * 0.5 + 0.5);

当然,我的代码还是有问题的,问题在于Mapcap如果是用的长方体的话,法线在同一个面是相同的,只能采样到同一个位置,看起来就是纯色的,所以需要用到曲率,但我现在还没有时间,等到有时间了再更新~先mark一个解决办法在这里

结束语:居然写了三个小时……一边写一边捋思路真的太难了。有空把之前写过的也捋一下吧,不然真的会忘qaq


  1. https://github.com/candycat1992/Unity_Shaders_Book ↩︎ ↩︎

  2. https://blog.csdn.net/Jaihk662/article/details/113780524 ↩︎

  3. https://blog.csdn.net/candycat1992/article/details/51417965 ↩︎

猜你喜欢

转载自blog.csdn.net/alicelise/article/details/124353900
今日推荐