zoukankan      html  css  js  c++  java
  • 第九章 高级纹理(2)

    1. 反射

    使用了反射效果的物体通常看起来就像镀了层金属。想要模拟反射效果很简单,我们只需要通过入射光线的方向和表面法线方向来计算反射方向,再利用反射方向对立方体纹理采样即可。
    在学习完本节后,我们可以得到类似下图的效果:
    在这里插入图片描述
    (1)首先我们声明了3个新的属性:

    Properties{
    _Color("Color Tint",Color)=(1,1,1,1)
    _ReflectColor("Reflection Color",Color)=(1,1,1,1)
    _ReflectAmount("Reflect Amount",Range(0,1))=1
    _Cubemap("Reflection Cubemap",Cube)="_Skybox"{}
    }
    

    其中_ReflectColor用于控制反射颜色,_ReflectAmount用于控制这个材质的反射程度,而_Cubemap就是用于模拟反射的环境映射纹理。
    (2)我们在顶点着色器中计算了该顶点处的反射方向,这是通过使用CG的reflect函数来实现的:

    v2f vert(a2v v){
    v2f o;
    o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    o.worldNormal=UnityObjectToWorldNormal(v.normal);
    o.worldPos=mul(_Object2World,v.vertex).xyz;
    o.worldViewDir=UnityWorldSpaceViewDir(o.worldPos);
    //Compute the reflect dir in world space
    o.worldRef1=reflect(-o.worldViewDir,o.worldNormal);
    TRANSFER_SHADOW(o);
    return o;
    }
    

    物体反射到摄像机中的光线方向,可以由光路可逆的原则来反向求得。也就是说,我们可以计算视角方向关于顶点法线的反射方向来求得入射光线的方向。
    (3)在片元着色器中,利用反射方向来对立方体纹理采样:

    fixed4 frag(v2f i):SV_Target{
    fixed3 worldNormal=normalize(i.worldNormal);
    fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 worldViewDir=normalize(i.worldViewDir);
    fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
    //Use the reflect dir in world space to access the cubemap
    fixed3 reflection=texCUBE(_Cubemap,i.worldRef1).rbg*_ReflectColor.rgb;
    UNITY_LIGHTMODEL_AMBIENT(atten,i,i.worldPos);
    //Mix the diffuse color with the reflected color
    fixed3 color =ambient+lerp(diffuse,reflection,_ReflectAmount)*atten;
    return fixed(color,1.0);
    }
    

    对立方体纹理的采样需要使用CG的texCUBE函数。注意到,在上面的计算中,我们在采样时并没有对i.worldRef1进行归一化操作。这是因为,用于采样的参数仅仅是作为方向变量传递给texCUBE函数的,因此我们没有必要进行一次归一化的操作。然后我们使用_ReflectAmount来混合漫反射颜色和反射颜色,并和光照相加后返回。
    在上面的计算中,我们选择在顶点着色器中计算反射方向。当然我们也可以选择在片元着色器中进行计算,这样得到的效果更佳细腻。但是,对绝大多数人来说这种差别往往是可以忽略不计的,因此出于性能方面的考虑,我们选择在顶点着色器中计算反射方向。

    2. 折射

    折射的物理原理比反射复杂一些。我们在初中物理就已经接触过折射的定义:当光线从一种介质(例如空气)斜射入另一种介质(例如玻璃)时,传播方向一般会发生改变。当给定入射角时,我们可以使用斯涅尔定律(Snell’s law)来计算反射角。当光从介质1沿着和表面法线夹角为θ1的方向斜射入介质2时,我们可以使用如下公式计算折射光线与法线的夹角θ2:
    在这里插入图片描述
    其中η1和η2分别是两个介质的折射率(index of refraction)。折射率是一项重要的物理常量,例如真空的折射率是1,而玻璃的折射率一般是1.5。下图给出了这些变量之间的关系。
    在这里插入图片描述
    通常来说,当得到折射方向后我们就会直接使用它来对立方体纹理进行采样,但这是不符合物理规律的。对一个透明物体来说,一种更精确的模拟方法需要计算两次折射——一次是当光线进入它的内部时,另一次则是从它内部射出时。但是想要在实时渲染中模拟出第二次折射方向是比较复杂的,而且仅仅模拟一次得到的效果从视觉上看起来“也挺像那么回事的”。正如我们之前提到的——图形学第一准则“如果它看起来是对的,那么它就是对的”。因此,在实时渲染中,我们通常仅模拟第一次折射。
    在学习完本节后,我们可以得到类似下图的效果:
    在这里插入图片描述
    (1)首先,我们声明了4个新属性:

    Properties{
    _Color{"Color Tint",Color}={1,1,1,1}
    _RefractColor("Refraction Color",Color)={1,1,1,1}
    _RefractAmount("Refraction Amount",Range(0,1))=1
    _RefractRatio("Refraction Ratio",Range(0.1,1))=0.5
    _Cubemap("Refraction Cubemap",Cube)="_Skybox"{}
    }
    

    其中_RefractColor、_RefractAmount和_RefractAmount和前面控制反射时使用的属性类似,除此之外,我们还使用了一个属性_RefractRatio,我们需要使用该属性得到不同介质的透射比,以此来计算折射方向。
    (2)在顶点着色器中,计算折射方向:

    v2f vert(a2v v){
    v2f o;
    o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    o.worldNormal=UnityObjectToWorldNormal(v.normal);
    o.worldPos=mul(_Object2World,v.vertex).xyz;
    o.worldViewDir=UnityWorldSpaceViewDir(o.worldPos);
    //Compute the refract dir in world space
    o.worldRefr=refract(-normalize(o.worldViewDir),normalize(o.worldNormal),_RefractRatio);
    TRANSFER_SHADOW(o);
    return o;
    };
    

    我们使用CG的refract函数来计算折射方向。它的第一个参数即为入射光线方向,它必须是归一化后的矢量;第二个参数是表面法线,法线方向同样是要归一化后的;第三个参数是入射光线所在介质的折射率和折射光线所在介质的折射率之间的比值,例如如果光是从空气射到玻璃表面,那么这个参数应该是空气的折射率和玻璃的折射率之间的比值,即1/1.5。它的返回值就是计算而得的折射方向,它的模则等于入射光线的模。
    (3)然后,我们在片元着色器中使用折射方向对立方体纹理进行采样:

    fixed4 frag(v2f i):SV_Target{
    fixed3 worldNormal=normalize(i.worldNormal);
    fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 worldViewDir=normalize(i.worldViewDir);
    fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
    fixed3 diffuse=_LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
    //Use the refract dir in world space to access the cubemap
    fixed3 refraction = texCUBE(_Cubemap,i.worldRefr).rgb*_RefractColor.rgb;
    UNITY_LIGHTMODEL_ATTENUATION(atten,i,i.worldPos);
    //Mix the diffuse color with the refract color
    fixed3 color=ambient+lerp(diffuse,refraction,_RefractAmount)*atten;
    return fixed4(color,1.0);
    }
    

    同样,我们也没有对i.worldRefr进行归一化操作,因为对立方体纹理的采样只需要提供方向即可。最后,我们使用_RefractAmount来混合漫反射颜色和折射颜色,并和环境光照相加后返回。

    3. 菲涅尔反射

    在实时渲染中,我们经常会使用菲涅尔反射(Fresnel reflection)来根据视角方向控制反射过程。通俗地讲,菲涅尔反射描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或散射。被反射的光和入射光之间存在一定的比率关系,这个比率关系可以通过菲涅尔等式进行计算。一个经常使用的例子是,当你站在湖边,直接低头看脚边的水面时,你会发现水几乎是透明的,你可以直接看到水底的小鱼和石子;但是当你抬头看远处的水面时,会发现几乎看不到水下的情景,而只能看到水面反射的环境。这就是所谓的菲涅尔效果。事实上,不仅仅是水、玻璃这样的反光物体具有菲涅尔效果,几乎任何物体都或多或少包含了菲涅尔效果,这是基于物理的渲染中非常重要的一项高光反射计算因子。
    那么,我们如何计算菲涅尔反射呢?这就需要使用菲涅尔等式。真实世界的菲涅尔等式是非常复杂的,但在实时渲染中,我们通常会使用一些近似公式来计算。其中一个著名的计算公式就是Schlick菲涅尔近似等式:
    在这里插入图片描述
    其中,F0是一个反射系数,用于控制菲涅尔反射的强度,v是视角方向,n是表面法线。另一个应用比较广泛的等式是Empricial菲涅尔近似等式:
    在这里插入图片描述
    其中,bias、scale和power是控制项。
    使用上面的菲涅尔近似等式,我们可以在边界处模拟反射光强和折射光强/漫反射光强之间的变化。在许多车漆、水面等材质渲染中,我们会经常使用菲涅尔反射来模拟更加真实的反射效果。
    在本节中,我们将使用Schlick菲涅尔近似等式来模拟菲涅尔反射。在本节最后我们可以得到类似下图的效果。注意图中在模型边界处的反射现象。
    在这里插入图片描述
    (1)首先,我们在Properties语义块中声明了用于调整菲涅尔反射的属性以及反射使用的Cubemap:

    Properties{
    _Color("Color Tint",Color)=(1,1,1,1)
    _FresnelScale("Fresnel Scale",Range(0,1))=0.5
    _Cubemap("Reflection Cubemap",Cube)="_Skybox"{}
    }
    

    (2)在顶点着色器中计算世界空间下的法线方向、视角方向和反射方向:

    v2f vert(a2v v){
    v2f o;
    o.pos=mul(UNITY_MATRIX_MVP,v.vertex);
    o.worldNormal=(v.Normal,(float3×3)_World2Object);
    o.worldPos=mul(_Object2World,v.vertex).xyz;
    o.worldViewDir=UnityWorldSpaceViewDir(o.worldPos);
    o.worldRef1=reflect(-o.worldViewDir,o.worldNormal);
    TRANSFER_SHADOW(o);
    return o;
    }
    

    (3)在片元着色器中计算菲涅尔反射,并使用结果值混合漫反射光照和反射光照:

    fixed4 frag(v2f i):SV_Target{
    fixed3 worldNormal=normalize(i.worldNormal);
    fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
    fixed3 worldViewDir=normalize(i.worldViewDir);
    fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
    UNITY_LIGHTMODEL_AMBIENT(atten,i,i.worldPos);
    fixed3 reflection = texCUBE(_Cubemap,i.worldRefl).rgb;
    fixed fresnel=_FresnelScale+(1-_FresnelScale)*pow(1-dot(worldViewDir,worldNormal),5);
    fixed3 diffuse = _LightColor0.rgb*_Color.rgb*max(0,dot(worldNormal,worldLightDir));
    fixed3 Color=ambient+lerp(diffuse,reflection,saturate(fresnel))*atten;
    return fixed4(color,1.0);
    }
    

    在上面的代码中,我们使用Schlick菲涅尔近似等式来计算fresnel变量,并使用它来混合漫反射光照和反射光照。一些实现也会直接把fresnel和反射光照相乘后叠加到漫反射光照上,模拟边缘光照的效果。
    当我们把_FresnelScale调节到1时,物体将完全反射Cubemap中的图像;当_Fresnel为0时,则是一个具有边缘光照效果的漫反射物体。

  • 相关阅读:
    Codeforces Round #649 (Div. 2) D. Ehab's Last Corollary
    Educational Codeforces Round 89 (Rated for Div. 2) E. Two Arrays
    Educational Codeforces Round 89 (Rated for Div. 2) D. Two Divisors
    Codeforces Round #647 (Div. 2) E. Johnny and Grandmaster
    Codeforces Round #647 (Div. 2) F. Johnny and Megan's Necklace
    Codeforces Round #648 (Div. 2) G. Secure Password
    Codeforces Round #646 (Div. 2) F. Rotating Substrings
    C++STL常见用法
    各类学习慕课(不定期更新
    高阶等差数列
  • 原文地址:https://www.cnblogs.com/xiegaosen/p/12009745.html
Copyright © 2011-2022 走看看