Unity3D教程:如何利用Shader实现钻石渲染效果
一些游戏中会有一些类似钻石发光的项目,那么这类钻石渲染的效果一般要如何实现呢,下面就给大家介绍下在Unity3D中是通过Shader实现钻石渲染效果的教程,希望可以帮到。
一、概述
人们喜欢一切闪闪发光的东西,例如钻石。下面试着用shader来实现钻石的渲染。先来看看我们最终的实现效果:
然后,我们来看看一束光照射到钻石表面时都发生了什么。
如上图所示,绿线表示法线,赭黄是光线在钻石内部的路线。在上图中,从外部射入的光线总共经过了四次变换(实际中不止四次)。在第一处,发生了折射和反射,在这里光线是从光疏介质射入光密介质;在第二处,同样发生了折射和反射,但是这里的折射是光线从光密介质射入光疏介质;在第三处,由于入射角大于全反射的临界角,发生了全反射,并没有折射产生;在第四处,光线垂直射出钻石,当然这里会有色散的现象,然后最终光线进入我们的眼睛。
二、预备知识
1、折射定律
光从真空进入某种介质发生折射时,入射角i的正弦与折射角r的正弦之比,等于这种介质的折射率n。写成公式如下:
对于光从介质1进入另一种不同折射率的介质2时,有如下公式成立:
在cg语言中有提供一个内置函数refract来用于计算折射,其声明如下:
1 | float3 refract(float3 I, float3 N, float etaRatio); |
前置条件:光从介质1进入介质2.
参数含义:I是入射方向(这里的入射方向一般是指从摄像机位置射向顶点位置的方向。);N是法线;etaRatio表示。
2、反射
反射相信大家都很熟悉,这里直接略过定义来看公式:
1 | float3 reflect(float3 I, float3 N); |
其中参数的定义与折射相同。
3、全反射
全反射在我们的实现中并没有体现出来,但是在这里我仍然想提一下。
光由光密介质射到光疏介质时,只产生反射而不产生折射的现象,称之为全反射。
钻石本身并不会发光,而它之所以这么的闪,是因为钻石的全反射临界角只有大约24度,即在钻石内部,入射角超过24度的光线被锁在了钻石内,少有光线逸出,这就是为什么钻石如此闪耀的原因。
三、渲染方法
1、首先,准备一张钻石内部朝外观察的cubemap,我们将它命名为RefractTex,如下图
通常,漂亮的渲染结果总是需要美术提供制作精良的贴图,这里也不例外。让美术使用3DMax或者Maya来制作此cubemap。一张cubemap一般需要使用摄像机渲染六次,上下左右前后,渲染时摄像机的fov应该设置为90度。上述截图是我使用3DMax的mental ray渲染制作的,我对mental ray是完全不熟悉,相信专攻材质的美术师可以制造出更好的贴图来。
当然,你还需要一张正常的环境贴图,我们将之命名为EnvTex:
2、渲染宝石的背面,计算一次折射,使用折射向量作为参数在EvnTex中取出相应的颜色值。
在shaderlab中,要渲染物体的背面,我们只需在Pass中声明下列语句:
1 | Cull Front |
如果在Pass中未做声明,则默认是Cull Back。
然后我们选择在世界坐标系中计算折射:
1 2 3 | Float3 view =normalize(_WorldSpaceCameraPos.xyz - worldPos); float3refractVector = refract(-viewVector, normal, 1/2.4f); |
并从EvnTex中取出颜色做个伽马矫正:
1 2 3 | float3refractColor = texCUBE(_EvnTex, refractVector); refractColor =pow(refractColor, 2.2f); |
下图是我们这次运算的收获:
图1:RefractColor_Back
有时候它会显得过亮,一般而言,背后射入的光线一部分发生了反射,一部分则折射入宝石内部,所以我们需要将它乘以一个系数调低亮度,比如0.3。
3、同样在背面渲染时,我们计算一次反射,然后用反射向量到RefractTex中获取颜色值:
1 2 3 | float3reflectVector = reflect(-viewVector, normal); float3reflectColor = texCUBE(_RefractTex, reflectVector_)*_Color.xyz; |
然后我们得到如下图的结果:
图2:ReflectColor_Back
4、接着回到正面渲染,我们依然计算折射,并从RefractTex中取出颜色值:(代码同上)
图3:RefractColor_Front
5、准备一张环境光cubemap以及一张色散cubemap,我们分别命名为EnvLightTex、DispersionTex:
图4:环境光EnvLightTex
图5:色散图DispersionTex
6、在正面渲染中计算反射,从环境贴图EnvTex中取出颜色值:(代码同上)
图6:ReflectColor_Front
7、取反射向量作为参数,从EnvLightTex以及DispersionTex中取出环境光亮度以及色散值,并且在两者之间做差值计算:(这里也可以再做一次fresnel的计算)
1 2 3 4 5 | float3 envColor= texCUBE(_EnvLightTex, reflectVector); float3dispersion = texCUBE(_DispersionTex, reflectVector); envColor =lerp(envColor, dispersion*envColor, 0.25f); |
图7:envColor
8、计算高光,公式使用普通的镜面高光公式即可:
1 2 3 4 5 | float3 lightDir =normalize(_WorldSpaceLightPos0.xyz); floatRL = saturate(dot(reflectVector, lightDir)); float3specularColor = _SpecularPower.y*pow(RL, _SpecularPower.x)*_LightColor0.xyz; |
图8:SpecularColor
9、将上述计算结果按下列公式合并:
1 2 3 4 5 | Float3 back =(RefractColor_Back + ReflectColor_Back); Float3 front =RefractColor_Front+envColor*ReflectColor_Front*_ReflectStrength; Float3 result =back + front + SpecularColor; |
我们就可以得到最终宝石的渲染效果了,当然由于环境贴图偏橙红色,这里宝石经过折射反射后也会带点橙红色:
图9:钻石渲染效果
10、当然,可以加入屏幕后期特效“耀斑”,让钻石看起来更闪:
参考资料:Phat Lewt: Drawing a Diamond
以上就是利用Shader实现钻石渲染效果的全部内容,希望对大家有帮助。