渲染纹理的玻璃效果

1. 玻璃效果基本原理

要实现玻璃效果,可能会联想到使用透明相关知识来进行制作。透明固然可以制作出玻璃透明的效果,但是它在许多地方有所缺陷,比如:

  • 透明无法表现出复杂的光学效果,玻璃不仅仅是透明的,它还具有反射、折射等光学效果,使用透明无法简单的实现这些效果
  • 透明物体往往会遇到深度排序问题,渲染顺序不正确时,会导致视觉错误

使用 渲染纹理 来制作玻璃效果,基本原理是:
渲染玻璃效果物体之前,先获取到当前屏幕图像,将当前屏幕图像存储在渲染纹理之中,之后在真正处理玻璃效果物体时,再利用该渲染纹理来实现 透明、折射 等等效果。

该过程中并不会使用混合,而是直接进行颜色相乘或相加来进行颜色叠加

 总结:在渲染玻璃效果之前,先捕获当前屏幕内容并保存到一张渲染纹理当中,在之后的Shader处理中利用该渲染纹理进行采样,参与最终的颜色计算,实现各种玻璃效果

这里需要用到:特殊渲染通道 GrabPass、内置函数 ComputeGrabScreenPos、 模拟折射的自定义计算规则

特殊渲染通道 GrabPass

GrabPass 的作用是捕获当前屏幕上已经渲染的内容,并将其存储到一张纹理中
它需要包含在SubShader语句块中
它的用法有两种:
(1)大括号中什么都不写,默认会把屏幕内容写入一个叫做 _GrabTexture 的纹理变量中
直接在CG语句中声明_GrabTexture 纹理变量即可直接使用抓取的渲染纹理

(2)大括号中写入自定义变量名,会把对应屏幕内容写入该自定义纹理变量中
在CG语句中声明对应纹理变量即可使用抓取的渲染纹理

内置函数 ComputeGrabScreenPos

该内置函数可以用于计算屏幕空间位置,传入顶点的裁剪空间位置,返回一个 float4 结果,该float4中的内容分别代表:

  • X:屏幕空间X坐标
  • Y:屏幕空间Y坐标
  • Z:裁剪空间深度值,一般表示顶点距离摄像机的相对深度
  • W:裁剪空间的W分量,通常用于透视除法,即 X或Y/W 后 X或Y的范围将在 0~1之间

我们可以利用该函数得到顶点相对屏幕的坐标,从而从捕获的渲染纹理中进行采样

模拟折射的自定义计算规则

为了模拟出玻璃折射的效果,我们一般不会使用立方体纹理中采样,我们往往会自定义一些计算规则,来模拟计算出折射的效果。
总体的设计思路,就是在对捕获纹理进行采样时,进行一些偏移计算。

2、如何让玻璃效果对象滞后渲染 

在实现玻璃效果之前,需要先捕获当前屏幕内容并保存到一张渲染纹理当中,那么要保证玻璃效果对象后面的内容正确渲染,我们必须保证玻璃对象能够滞后渲染,想要让一个对象滞后渲染,那么可以使用渲染标签Tags中的 渲染队列Queue

因此对于玻璃效果对象,虽然它本质上是一个不透明物体吗,但是我们完全可以将它的渲染队列设置为 Transparent(透明的),保证它晚于 背景队列、几何队列、透明测试队列 之后再进行渲染,这时我们捕获的屏幕内容,将包含这些更早渲染的内容信息,便可以利用GrabPass捕获到相对正确的内容了

Shader "ShaderProj/6/GlassBase"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Cube ("Cube", Cube) = "" {}
        //折射程度 0~1 0代表完全反射(完全不折射)1代表完全折射(透明效果 相当于光全部进入了内部)
        _RefractAmount ("RefractAmount", Range(0, 1)) = 1
    }
    SubShader
    {
        //将渲染队列改为透明的 目的是让玻璃对象 滞后渲染
        //能够捕获到之前正确的屏幕图像
        Tags { "RenderType"="Opaque" "Queue"="Transparent"}

        //使用它来捕获当前屏幕内容 并存储到默认的渲染纹理变量中
        GrabPass{}

        Pass
        {
            Tags {"LightMode" = "ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 grabPos:TEXCOORD0; //用于存储从屏幕图像中采样的坐标(顶点相对于屏幕的位置)
                float2 uv:TEXCOORD1;
                float3 worldRefl:TEXCOORD2;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            samplerCUBE _Cube;
            float _RefractAmount;
            //GrabPass默认存储的纹理变量 这个是规则
            sampler2D _GrabTexture;

            v2f vert (appdata_base v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.pos);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
                float3 worldViewDir = UnityWorldSpaceViewDir(worldPos);
                o.worldRefl = reflect(-worldViewDir, worldNormal);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 mainTex = tex2D(_MainTex, i.uv);
                //将反射颜色和主纹理颜色进行叠加
                fixed4 reflColor = texCUBE(_Cube, i.worldRefl) * mainTex;

                //折射相关的颜色
                //其实就是从抓取的 屏幕渲染纹理中进行采样 参与计算
                //抓取纹理中的颜色信息 相当于是这个玻璃对象后面的颜色

                // 自定义折射规则
                float2 offset = 1 - _RefractAmount;
                i.grabPos.xy = i.grabPos.xy - offset / 10;

                //利用透视除法 将屏幕坐标转换到 0~1范围内 然后再进行采样
                fixed2 screenUV = i.grabPos.xy / i.grabPos.w;
                fixed4 grabColor = tex2D(_GrabTexture, screenUV);

                float4 color = lerp(reflColor, grabColor, _RefractAmount);
                    
                return color;
            }
            ENDCG
        }
    }
}

 3、带法线纹理的玻璃效果

对 Cube 加上法线向量后,需要修改反射向量计算规则,由于法线需要从法线纹理中获取,因此需要将反射向量的计算放入到片元着色器中

利用切线空间法线来计算折射偏移

(1)加入一个控制折射扭曲程度的新属性_Distortion 取值范围可以大一些
(2)利用切线空间下法线来计算偏移值,加入两行关键代码
第一行:float2 offset = tangentNormal.xy * _Distortion;
使用切线空间下法线的xy* 扭曲值得到一个偏移量,代表光线经过法线方向扰动后的偏移程度,确定光线折射的方向和强度

第二行:屏幕坐标.xy = offset * 屏幕坐标.z + 屏幕坐标.xy;
用偏移量和屏幕空间深度值相乘,模拟出真实的折射效果,深度值越大(即距离相机越远),折射效果越明显。 这样可以实现近大远小的效果,使得物体在不同深度上的折射效果有所差异

Shader "ShaderProj/6/GlassRefraction"
{
     Properties
    {
        _MainTex("MainTex", 2D) = ""{}
        _BumpMap("BumpMap", 2D) = ""{}
        _Cube("Cubemap", Cube) = ""{}
        _RefractAmount("RefractAmount", Range(0,1)) = 1
        //控制折射扭曲程度的变量
        _Distortion("Distortion", Range(0,10)) = 0
    }
    SubShader
    {
        Tags{"RenderType"="Opaque" "Queue"="Transparent"}
        GrabPass{}

        Pass
        {
            Tags{"LightMode"="ForwardBase"}
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            samplerCUBE _Cube;
            float _RefractAmount;
            sampler2D _GrabTexture;
            float _Distortion;

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 grabPos:TEXCOORD0;
                float4 uv:TEXCOORD1;
                float4 TtoW0:TEXCOORD3;
                float4 TtoW1:TEXCOORD4;
                float4 TtoW2:TEXCOORD5;
            };

            v2f vert(appdata_full v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.pos);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
                //计算反射光向量
                float3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 worldTangent = UnityObjectToWorldDir(v.tangent);
                float3 worldBinormal = cross(normalize(worldTangent), normalize(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);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                float4 packedNormal = tex2D(_BumpMap, i.uv.zw);
                float3 tangentNormal = UnpackNormal(packedNormal);
                float3 worldNormal = float3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal));

                fixed4 mainTex = tex2D(_MainTex, i.uv);
                float3 refl = reflect(-viewDir, worldNormal);
                fixed4 reflColor = texCUBE(_Cube, refl) * mainTex;
                
                //折射相关的颜色
                float2 offset = tangentNormal.xy * _Distortion;
                i.grabPos.xy = offset*i.grabPos.z + i.grabPos.xy; 

                fixed2 screenUV = i.grabPos.xy / i.grabPos.w;
                fixed4 grabColor = tex2D(_GrabTexture, screenUV);

                float4 color = lerp(reflColor, grabColor, _RefractAmount);

                return color;
            }

            ENDCG
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值