Shader入门精要学习记录七

立方体纹理 Cubemap

在图形学中,立方体纹理(Cubemap)是环境映射(Enviroment Mapping)的一种实现方法。环境映射可以模拟物体周围的环境,而使用了环境映射的物体可以看起来像镀了层金属一样反射出周围的环境。

立方体纹理一共包含了6张图片,对应立方体的六个面。
如何对立方体纹理采样:和之前使用二维纹理坐标不同,对立方体纹理采样我们需要提供一个三维的纹理坐标,这个三维纹理坐标表示了我们在世界空间下的一个3D方向。这个方向矢量从立方体的中心出发,当它向外部延伸时就会和立方体的6个纹理之一发生相交,而采样得到的结果就是由该交点计算出来的。
这里写图片描述

使用立方体纹理的好处在于:它的实现简单快速,而且得到的效果也比较好,但它有一个缺点,例如当场景中引入了新的物体光源,或者物体发生移动时,我们就需要重新生成立方体纹理。除此之外,立方体纹理也仅可以反射环境,但不能反射使用了该立方体纹理的物体本身。这是因为,立方体纹理不能模拟多次反射的结果,例如两个金属球互相反射的情况。由于这样的原理,想要得到令人信服的渲染效果,我们应该尽量对凸面体而不要对凹面体使用立方体纹理(因为凹面体会反射自身)

立方体纹理在实时渲染中有很多应用,最常见的是用于天空盒以及环境映射。

天空盒子

天空盒子(Skybox)是游戏中用于模拟背景的一种方法。天空盒子这个名字包含两个信息:
它是用来模拟天空的,它是一个盒子。 当我们在场景中使用了天空盒子时,整个场景就被包围在一个立方体内。这个立方体的每个面使用的技术就是立方体纹理映射技术。
注意 纹理设置 Wrap Mode 设置为Clamp 防止在接缝处出现不匹配。

创建用于环境映射的立方体纹理

除了天空盒,立方体纹理最常见的用处就是用于环境映射,通过这种方法,我们可以模拟出金属质感的材质。

反射

使用了反射效果的物体通常看起来就像镀了层金属,想要模拟反射效果很简单,我们只需要通过入射光线的方向和表面法线方向来计算反射方向,再利用反射方向对立方体纹理采样即可。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/GM_CubeMap"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CubeMap("CubeMap",Cube)="_Skybox"{}
        _refelectionAmount("Reflection",Range(0,1))=1
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

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

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos:TEXCOORD1;
                fixed3 worldNormal: TEXCOORD2;
                float3 worldReflect :TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            samplerCUBE _CubeMap;
            fixed _refelectionAmount;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal=UnityObjectToWorldNormal(v.normal);
                o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                fixed3 viewDir=UnityWorldSpaceViewDir(o.worldPos);

                o.worldReflect=reflect(-viewDir,o.worldNormal);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 reflection=texCUBE(_CubeMap,i.worldReflect);
                return lerp(col,reflection,_refelectionAmount);
            }
            ENDCG
        }
    }
}
  • 物体反射到摄像机中的光线方向,可以由光路可逆的原则来反向求得。也就是说,我们可以计算视角方向关于顶点法线的反射方向来求得入射光线的方向。
  • 对立方体纹理的采样需要使用CG的texCUBE函数。注意到,在上面的计算中,我们在采样时并没有对i,worldRelection进行归一化操作,这是因为,用于采样的参数仅仅是作为方向变量传递给texCUBE函数的,因此我们没有必要进行一次归一化的操作。然后,我们使用_refelectionAmount来混合颜色。
  • 在上面的计算我们选择在顶点着色器中计算反射方向。当然,我们也可以选择在片元着色器中计算,这样得到的效果更细腻。但是,对于大多数人来讲这种差别是可以忽略不计的。出于性能考虑,我们选择在顶点着色器中计算反射方向。

折射

根据物理上对光线折射的定义: 当光线从一种介质到斜射入另一种介质时,传播方向会发生改变,当给定入射角时,我们可以使用斯涅尔定律(Snell’s Law)来计算反射角。


Shader "Unlit/GM_CubeMap"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CubeMap("CubeMap",Cube)="_Skybox"{}
        _refelectionAmount("Reflection",Range(0,1))=1
        _refractRatio("refractRatio",Range(0.1,1))=0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

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

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos:TEXCOORD1;
                fixed3 worldNormal: TEXCOORD2;
                float3 worldReflect :TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            samplerCUBE _CubeMap;
            fixed _refelectionAmount;
            fixed _refractRatio;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal=UnityObjectToWorldNormal(v.normal);
                o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                fixed3 viewDir=UnityWorldSpaceViewDir(o.worldPos);

            //  o.worldReflect=reflect(-viewDir,o.worldNormal);
                o.worldReflect=refract(-normalize(-viewDir),normalize(o.worldNormal),_refractRatio);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
            //  fixed4 reflection=texCUBE(_CubeMap,i.worldReflect);
                fixed4 refraction=texCUBE(_CubeMap,i.worldReflect);
                return lerp(col,refraction,_refelectionAmount);
            }
            ENDCG
        }
    }
}

我们使用了CG的refract函数来计算折射方向。它的第一个参数即为入射光线的防线,它必须是归一化后的矢量:第二个参数是表面法线,同样必须是归一化后的,第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值。它的返回值就是计算而得的折射方向,它的模等于入射光线的模。

菲涅尔反射

在实时渲染中,我们经常会使用菲涅尔反射来根据视角方向控制反射程序,通俗地讲,菲涅尔反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部发生折射或散射。 被反射的光和入射光存在一定的比率关系,这个比率关系可以通过菲涅尔等式进行计算。

这里写图片描述

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Unlit/GM_CubeMap"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _CubeMap("CubeMap",Cube)="_Skybox"{}
        _refelectionAmount("Reflection",Range(0,1))=1
        _refractRatio("refractRatio",Range(0.1,1))=0.5

        _fresnelScale("FresnelScale",Range(0,1))=0.5
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

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

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 worldPos:TEXCOORD1;
                fixed3 worldNormal: TEXCOORD2;
                float3 worldViewDir: TEXCOORD3;
                float3 worldReflect :TEXCOORD4;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            samplerCUBE _CubeMap;
            fixed _refelectionAmount;
            fixed _refractRatio;
            fixed _fresnelScale;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.worldNormal=UnityObjectToWorldNormal(v.normal);
                o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                o.worldViewDir=UnityWorldSpaceViewDir(o.worldPos);

                o.worldReflect=reflect(-o.worldViewDir,o.worldNormal);
                //o.worldReflect=refract(-normalize(-viewDir),normalize(o.worldNormal),_refractRatio);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv);
                fixed4 reflection=texCUBE(_CubeMap,i.worldReflect);
                fixed3 worldNormal=normalize(i.worldNormal);
                fixed3 worldViewDir=normalize(i.worldViewDir);
                fixed fresnel= _fresnelScale+(1-_fresnelScale)*pow((1-dot(worldViewDir,worldNormal)),5);

            //  fixed4 refraction=texCUBE(_CubeMap,i.worldReflect);
                return lerp(col,reflection,saturate(fresnel));
            }
            ENDCG
        }
    }
}

fixed fresnel= _fresnelScale+(1-_fresnelScale)*pow((1-dot(worldViewDir,worldNormal)),5);
注意公式。当_fresnelScale为0时,得到的是一个具有边缘效果的物体。

渲染纹理

摄像机的渲染结果会输出到颜色缓冲中,并显示到我们的屏幕上,现在的GPU允许我们把整个三维场景渲染到一个中间缓冲中,即渲染目标纹理(Render Target Texture,RTT),而不是传统的帧缓冲或后背缓冲。与之相关的是多重渲染目标(Multiple Render Target,MRT),这种技术指的是GPU允许我们把场景同时渲染到多个目标纹理中,而不再需要为每个渲染目标纹理单独渲染完整的场景。延迟渲染就是使用多重渲染目标的一个应用。
Unity 为渲染目标纹理定义了一种专门的纹理类型-渲染纹理(Render Texture)。 在Unity中使用渲染纹理通常有两种方式: 一种方式是在Project目录下创建一个渲染纹理,然后把某个摄像机的纹理设置成该渲染纹理,这样以来该摄像机的渲染结果就会实时更新到渲染纹理中,而不会显示在屏幕上,使用这种方法,我们还可以选择渲染纹理的分辨率、滤波等纹理属性。 另一种方式是在屏幕后处理时使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像,Unity会把这个屏幕图像放到一张和屏幕分辨率等同的渲染纹理中,我们可以在自定义的Pass中把它们当成普通的纹理来处理,从而实现各种屏幕特效。

镜子效果

参见书 注意uv.x需要翻转 uv.x=1-uv.x.

玻璃效果

在Unity中,我们还可以在UnityShader中使用一种特殊的Pass来完成获取屏幕图像的目的,这就是GrabPass。当我们在Shader中定义了一个GrabPass后,Unity会把当前屏幕的图像绘制在一张纹理中,以便我们在后续的pass中访问它。我们通常会使用GrabPass来实现诸如玻璃等透明材质的模拟,与使用简单的透明混合不同,使用GrabPass 可以让我们对该物体后面的图像进行更复杂的处理,例如使用法线来模拟折射效果,而不再是简单的和原屏幕颜色进行混合。

需要注意的是,在使用GrabPass的时候,我们需要格外小心物体的渲染队列设置。正如之前所说,GrabPass通常用于渲染透明物体,尽管代码里并不包含混合指令,但我们往往仍然需要把物体的渲染队列设置成透明队列 (”Queue”=”Transparent”)。这样才可以保证当前渲染该物体时,所有的不透明物体都已经被绘制在屏幕上,从而获取正确的屏幕图像。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值