UnityShader18.1:立方体贴图(下)

接上文:https://blog.csdn.net/Jaihk662/article/details/113248074

四、菲涅尔反射

菲涅尔反射(Fresnel Reflection)就是同时考虑反射和漫反射,或是说同时考虑反射和折射:当光照到物体上时,一部分发生反射,一部分进入物体内部发生折射和散射。当你站在湖边低头看脚边的水面时,会发现水几乎是透明的,可以很清晰的看到湖底,但是当你抬头看远处的水面时,就几乎看不到任何水下的场景。几乎任何物体都多多少少包含了菲涅尔反射的现象

现实中的菲涅尔反射计算非常的复杂,因此往往采用如下的近似方式:

F_{\text {schlick }}(\mathbf{v}, \mathbf{n})=F_{0}+\left(1-F_{0}\right)(1-\mathbf{v} \cdot \mathbf{n})^{5}

其中 F_{0} 为反射系数,\mathbf{v} 代表视角方向,\mathbf{n} 代表表面法线

下面为叠加反射和漫反射的 Shader:

Shader "Jaihk662/Fresnel1"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1, 1, 1, 1)
        _FresnelScale ("FresnelScale", Range(0, 1)) = 0.5
        _Cubemap ("ReflectionCubemap", Cube) = "_Skybox" {}
    }
    SubShader
    {
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }
 
            CGPROGRAM
            #pragma vertex vert             //声明顶点着色器的函数
            #pragma fragment frag           //声明片段着色器的函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed _FresnelScale;
            samplerCUBE _Cubemap;

            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float3 wPos: TEXCOORD0;
                float3 wNormal: TEXCOORD1;
                float3 wViewDir: TEXCOORD2;
                float3 wReflect: TEXCOORD3;
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.wViewDir = normalize(UnityWorldSpaceViewDir(o.wPos));
                o.wReflect = reflect(-o.wViewDir, o.wNormal);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 diffuse = _LightColor0.rgb * _DiffuseColor.rgb * (0.5 * dot(i.wNormal, wLightDir) + 0.5);            
                fixed3 reflection = texCUBE(_Cubemap, i.wReflect).rgb;
 
                fixed fresnel = saturate(_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(i.wViewDir, i.wNormal), 5));
                return fixed4(ambient + lerp(diffuse, reflection, fresnel), 1.0); 
            } 
            ENDCG
        }
    }
    FallBack "Specular"
}

可以看出,垂直视角下漫反射的效果明显,而在倾斜视角下,反射效果明显

类似的可以实现叠加反射和折射的 Shader:

Shader "Jaihk662/Fresnel1"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1, 1, 1, 1)
        _FresnelScale ("FresnelScale", Range(0, 1)) = 0.5
        _RefractRatio ("RefractiRatio", Range(0.1, 1)) = 0.5
        _CubeMap ("ReflectionCubeMap", Cube) = "_Skybox" {}
    }
    SubShader
    {
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }
 
            CGPROGRAM
            #pragma vertex vert             //声明顶点着色器的函数
            #pragma fragment frag           //声明片段着色器的函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed _FresnelScale;
            fixed _RefractRatio;
            samplerCUBE _CubeMap;

            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float3 wPos: TEXCOORD0;
                float3 wNormal: TEXCOORD1;
                float3 wViewDir: TEXCOORD2;
                float3 wReflect: TEXCOORD3;
                float3 wRefract: TEXCOORD4;
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.wNormal = normalize(UnityObjectToWorldNormal(v.normal));
                o.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                o.wViewDir = normalize(UnityWorldSpaceViewDir(o.wPos));
                o.wReflect = reflect(-o.wViewDir, o.wNormal);
                o.wRefract = refract(-o.wViewDir, o.wNormal, _RefractRatio);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(i.wPos));

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;      
                fixed4 reflection = texCUBE(_CubeMap, i.wReflect);
                fixed4 refraction = texCUBE(_CubeMap, i.wRefract);
 
                fixed fresnel = saturate(_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(i.wViewDir, i.wNormal), 5));
                return fixed4(ambient, 1) + fresnel * reflection + (1 - fresnel) * refraction;              //此处应考虑能量守恒,而非使用插值
            } 
            ENDCG
        }
    }
    FallBack "Specular"
}

五、动态环境映射

在上一节中实现了假反射,也就是物体仅能反射天空盒,而无法反射周边的其它的物体

可以尝试在不考虑性能的情况下实现以下动态环境映射,也就是实现“真”反射

1):动态生成立方体贴图:

可以依靠 Unity 自带的函数 Camera.RenderToCubemap(cubemap) 来生成以当前物体为中心,周围场景的天空盒:

Unity 官网上给出了一个很经典的例子如下:

using UnityEngine;
using UnityEditor;
using System.Collections;

public class RenderCubemapWizard : ScriptableWizard
{
    public Transform renderFromPosition;
    public Cubemap cubemap;

    void OnWizardUpdate()
    {
        string helpString = "Select transform to render from and cubemap to render into";
        bool isValid = (renderFromPosition != null) && (cubemap != null);
    }

    void OnWizardCreate()
    {
        // create temporary camera for rendering
        GameObject go = new GameObject("CubemapCamera");
        go.AddComponent<Camera>();
        // place it on the object
        go.transform.position = renderFromPosition.position;
        go.transform.rotation = Quaternion.identity;
        // render into cubemap
        go.GetComponent<Camera>().RenderToCubemap(cubemap);

        // destroy temporary camera
        DestroyImmediate(go);
    }

    [MenuItem("GameObject/Render into Cubemap")]
    static void RenderCubemap()
    {
        ScriptableWizard.DisplayWizard<RenderCubemapWizard>(
            "Render cubemap", "Render!");
    }
}

这是一份工具代码,应用后,就可以在这里找到自己的“编辑器”:

点击 Render 就可以将图象渲染到上面设置的立方体纹理中,其中立方体纹理的创建如下:

不过需要注意的是,这个立方体需要设置可读,不然会出现报错“Unable to render into cubemap, make sure it's marked as 'Readable'”

2):渲染到 RT

根据 1),我们可以得到一个很暴力的实现方法,对于每一帧每一个物体都动态生成对应的立方体贴图,并将其作为前面的 Shader 中的 CubeMap

当然在实现过程中需要用到渲染纹理(RenderTexture),这部分就在后面介绍了

还是能得到一个很不错的效果的

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/113406085