Unity3D教程:如何利用Shader实现钻石渲染效果

发表于2016-10-21
评论4 1.64w浏览

一些游戏中会有一些类似钻石发光的项目,那么这类钻石渲染的效果一般要如何实现呢,下面就给大家介绍下在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度。上述截图是我使用3DMaxmental 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);

下图是我们这次运算的收获:


1RefractColor_Back

    

有时候它会显得过亮,一般而言,背后射入的光线一部分发生了反射,一部分则折射入宝石内部,所以我们需要将它乘以一个系数调低亮度,比如0.3

3、同样在背面渲染时,我们计算一次反射,然后用反射向量到RefractTex中获取颜色值:

1
2
3
float3reflectVector = reflect(-viewVector, normal);
 
float3reflectColor = texCUBE(_RefractTex, reflectVector_)*_Color.xyz;

然后我们得到如下图的结果:


2ReflectColor_Back


4、接着回到正面渲染,我们依然计算折射,并从RefractTex中取出颜色值:(代码同上)


3RefractColor_Front


5、准备一张环境光cubemap以及一张色散cubemap,我们分别命名为EnvLightTexDispersionTex:

 

4:环境光EnvLightTex


5:色散图DispersionTex


6、在正面渲染中计算反射,从环境贴图EnvTex中取出颜色值:(代码同上)


6ReflectColor_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);


7envColor


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;


8SpecularColor


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实现钻石渲染效果的全部内容,希望对大家有帮助。

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