Unity Shader CubeMap(立方体贴图)

发表于2018-07-12
评论0 7.3k浏览
一般情况下使用的更多的还是2D纹理,当然还有其他更多的纹理类型在等着我们去学习,在本篇文章中介绍的纹理类型是将多个纹理组合起来映射到一个单一纹理,它就是cubemap。

一、CubeMap介绍

Cubemap是一个由六个独立的正方形纹理组成的集合,它将多个纹理组合起来映射到一个单一纹理。
基本上说CubeMap包含6个2D纹理,这每个2D纹理是一个立方体(cube)的一个面,也就是说它是一个有贴图的立方体。
CubeMap通常被用来作为具有反射属性物体的反射源。

你可能会奇怪这样的立方体有什么用?为什么费事地把6个独立纹理结合为一个单独的纹理,只使用6个各自独立的不行吗?这是因为cubemap有自己特有的属性,可以使用方向向量对它们索引和采样。

想象一下,我们有一个1×1×1的单位立方体,有个以原点为起点的方向向量在它的中心。方向向量的大小无关紧要,当方向向量向外延伸时,就会和立方体表面上的相应纹理发生相交,我们可以根据该交点进行采样。

Shader中对CubeMap采样

Shader提供了CubeMap的内置类型samplerCube,samplerCube和sampler2D一样,都是贴图,不同的是,需要使用textureCube进行采样,采样的时候需要传递规范化的方向向量而不是uv坐标。
texCUBE(_CubeMap, directionVec);

texCUBE会采样方向向量directionVec在CubeMap上的交点。
如果texCube采样时,传入模型中心到定点的向量,就可以将CubeMap纹理贴到模型上了。
Shader "Hidden/CubemapSampler"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;   
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float4 vertexLocal : TEXCOORD1;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }
            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, normalize(i.vertexLocal.xyz));
                return col;
            }
            ENDCG
        }
    }
}

Unity中如何制作CubeMap
  • 已经有6张2D纹理,直接创建CubeMap
  • Project-Create-Legacy-CubeMap

二、Reflect CubeMap(反射立方体纹理用于环境映射)

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

通常我们使用高光来模拟光滑物体对于点光源的反射效果,更进一步,高反光的物体通常可以在表面反射出周围的物体,这样的效果通过高光贴图就无法实现了,需要通过环境贴图CubeMap来实现。。

环境反射的原理很简单,一个光滑的物体表面可以根据我们观察的不同角度反射出不同位置的环境。即物体表面一点反射的颜色和该点的法线,观察视线和反射视线有关系。

环境映射原理


如果我们使用texCube函数对立方体纹理进行采样时,使用视线关于物体顶点法线的反射向量作为采样的方向向量就可以得到反射的效果。

反射方向的计算

L为入射光(顶点到光源)的单位法向量,N为顶点的单位法向量,R为反射光的单位法向量,V是观察方向。
R=2(N•L)N-L

推导过程:
L在N方向上的投影是|Lcosθ|=cosθ,那么投影矢量N’=Ncosθ
L+S = N’ = Ncosθ
R = L+2S = L+2(Ncosθ-L) = 2Ncosθ-L

简单计算,或者使用Cg的内置函数reflect(),该函数接受参数向量I和法线N,返回I关于N的反射向量。
float3 R = reflect(I,N);

Unity Shader实现Reflection CubeMap(基于CubeMap的反射效果)

步骤:
- 创建用于环境映射的立方体纹理(反射源)
- 用该立方体纹理作为天空盒子
- 使用worldRef(视线关于物体顶点法线的反射向量)作为采样的方向向量,对CubeMap采样作为物体的表面纹理
如果物体的纹理采集自与天空盒子同一个CubeMap,就会有一种环境映射的感觉。

反射CubeMap的Shader代码:
Shader "Hidden/CM       Reflect"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;           
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldRef : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
            };
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldRef = reflect(-o.worldViewDir,normalize(o.worldNormal));
                return o;
            }
            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, i.worldRef);
                return col;
            }
            ENDCG
        }
    }
}

反射效果展示

ps:“假”反射?
这种基于CubeMap反射效果的环境映射,并不是“真”反射,只是“模拟”环境反射,是“假”反射。如果我们改变了环境的天空盒子,物体的纹理并不会跟着改变,且不会反射出环境中的其他物体,只能反射天空盒子。


一个非常消耗效率的真反射实现方法

创建一个相机、RenderTexture将RenderTexture赋给相机的Render Target,再将RenderTexture赋给物体material的贴图。

三、Refract CubeMap(折射立方体纹理用于环境透视)

透明材质,如水,玻璃,水晶,应该可以透视。这种环境的透视我们可以在对CubeMap采样时,使用视线关于物体法线的反射向量作为方向向量,就得到了环境透射的效果。
texCube(_CubeMap, refract(viewDir,normal,_RefractRatio));

_RefractRatio是折射相对系数,与物体的介质有关。
斯涅尔定律:入射角的正弦值与折射角的正弦值的比值为一定值,而此一定值跟入射与折射介质有关。
sinθ比值等于介质折射率n的比值
材质折射指数
空气1.00
1.33
1.309
玻璃1.52
宝石2.42

环境透视的实现原理

Unity Shader实现Refract CubeMap(基于CubeMap的折射效果)

折射CubeMap的Shader代码
Shader "Hidden/CMRefract"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
        _RefractRatio("Refract Ratio", Float) = 0.5
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;               
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldRef : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
                float4 vertexLocal : TEXCOORD4;
            };
            float _RefractRatio;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldRef = refract(-o.worldViewDir,normalize(o.worldNormal),_RefractRatio);
                return o;
            }
            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = texCUBE(_CubeMap, normalize(i.worldRef));
                return col;
            }
            ENDCG
        }
    }
}

折射效果展示

四、菲涅尔反射(同时考虑折射与反射)

菲尼尔现象是指光到达材质交界处时,一部分被反射,一部分被折射,即视线垂直于表面时,夹角越小,反射越明显。所有物体都有菲尼尔反射,只是强大大小不同。因此,菲尼尔反射是为了模拟真实世界中的这种光学现象。多少光发生反射,多少光发生折射,可以用菲涅尔公式进行计算。

实际世界的菲涅尔公式非常复杂,我们同样用一些近似公式来计算,如下面提到的Schlick菲涅尔近似公式和Empricial菲涅尔近似公式:
  • Schlick菲涅尔近似等式
//resnelBias 菲尼尔偏移系数  
//fresnelScale 菲尼尔缩放系数  
//fresnelPower 菲尼尔指数  
reflectFact = fresnelBias + fresnelScale*pow(1-dot(viewDir,N)),fresnelPower);  //系数:多少光发生折射多少光发生反射

  • Empricial菲涅尔近似等式:
//F0是反射系数用于控制菲涅尔的强度
reflectFact = F0 + (1-F0)*pow(1-dot(viewDir,N),5);

有了反射系数,我们就可以根据能量守恒计算反射光线和折射光线的强度了。
c(reflect) = reflect(viewDir,N)
c(refract) = reflect(viewDir,N,eatRadio)
C(fresnelFinal) = reflectFact * C(reflect) + (1-reflectFact)*C(refract);  //能量守恒

以Schlick菲涅尔公式为例实现Fresnel反射

CubeMap的菲涅尔反射Shader代码
Shader "Hidden/Fresnel_Schlick"
{
    Properties
    {
        _CubeMap("CubeMap", CUBE) = ""{}
        _RefractRatio("Refract Ratio", Float) = 0.5
        _FresnelScale("Fresnel Scale", Float) = 0.5
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct appdata
            {
                float4 vertex : POSITION;               
                float3 normal : NORMAL;
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldReflect : TEXCOORD1;
                float3 worldPos : TEXCOORD2;
                float3 worldViewDir : TEXCOORD3;
                float4 vertexLocal : TEXCOORD4;
                float3 worldRefract : TEXCOORD5;
            };
            float _RefractRatio;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertexLocal = v.vertex;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.worldNormal = mul(unity_ObjectToWorld, v.normal);
                o.worldViewDir =  normalize(_WorldSpaceCameraPos.xyz - o.worldPos.xyz);
                o.worldReflect = reflect(-o.worldViewDir,normalize(o.worldNormal));
                o.worldRefract = refract(-o.worldViewDir,normalize(o.worldNormal),_RefractRatio);
                return o;
            }
            float _FresnelScale;
            samplerCUBE _CubeMap;
            fixed4 frag (v2f i) : SV_Target
            {
                float4 fresnelReflectFactor = _FresnelScale + (1 - _FresnelScale)*pow(1-dot(i.worldViewDir,i.worldNormal), 5);
                fixed4 colReflect = texCUBE(_CubeMap, normalize(i.worldReflect));
                fixed4 colRefract = texCUBE(_CubeMap, normalize(i.worldRefract));
                fixed4 col = fresnelReflectFactor * colReflect + (1-fresnelReflectFactor) * colRefract;
                return col;
            }
            ENDCG
        }
    }
}

菲涅尔反射效果展示
来自:https://blog.csdn.net/v_xchen_v/article/details/79474193

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引