Unity5之后加入新的渲染方式 PBR。PBR是一种着色和渲染技术,用于更精确的描述光如何与物体表面互动。PBS(Physically Based Shading)在有一些地方也叫PBR(Physically Based Rendering),是一个基于物体表面材质属性的着色方法。与之前的Blinn-Phong等算法不同。PBS通过对物体表面的材质属性与周围光照信息来进行着色计算。PBS着色系统中,一个物体不仅受到光源的影响,还会受到周围环境的影响。 这会使得整个场景更加真实。很多端游(洛奇英雄传),手游(楚留香)都有染色系统的存在。时装染色过程中支持自由调节色彩、饱和度以及明暗度,所以每一位少侠染色过后的衣服都是独一无二的,每一件外观都能改造出不同风格,走在街上再也不怕撞衫啦。

PBR模型

PBS有一个大前提,就是它能够满足光能传播过程中的能量守衡。能量守衡体现在三个方面。

1、一个对象反射出来的光照信息,不可能超过它接受到的信息。也就是说,全反射是一个物体的极限。

2、一个物体越光亮,那么它的颜色信息应该越少。(可以看出,refection 和 diffuse 应该是一个插值关系)

3、一个物体越平滑,那么它的高亮点会越小,越亮。

很多BRDF模型里的计算,比如微面元法线分布函数(GGXTerm)、微面元遮挡函数(SmithJointGGXVisibilityTerm)、菲涅耳反射(FresnelTerm), 我们直接使用了UnityStandardBRDF.cginc里已经为我们实现好的函数。

BRDF处理镜面反射的公式:

其中:

微面元法线分布函数 D(h):GGX

alpha = roughness * roughness,roughness是粗糙度,roughness= 1-smoothness

微面元遮挡函数 G(l,v,h)

Smith-Schlick,在Smith近似下G(l,v,h) = g(l)*g(v)

k是α基于几何函数是针对直接光照还是针对IBL光照的重映射(Remapping),, 很多的时候我们直接这里直接传roughness

菲涅尔方程 F(v,h):UE4对Schlick的一个近似

Schlick近似实现:

下面以一个自己实现的 pbr 例子,例子下载地址,点击这里

关于 pbr shader的实现这里就不再赘述了,工程附带的 readme 推导公式、源码都给的很详细。本文主要是通过此例子来验证 pbr 的一些特性。

选中材质,开启 OpenDebug 选项。通过输出参数控制来调试 pbr 的各种效果。当然这些只是在编辑器里预览,游戏运行时,可以通过 material.DisableKeyword()来关闭此选项,来避免不必要的计算。

我们都知道PBR 材质金属性越强,反射的光越少,因为大部分光都被金属吸收了,转化为热能或者说电能。
为了验证 pbr 这一特性,我们 debugmode 选中 Diffuse,如下图所示,拖拽 Matillic 属性来改变材质的金属性发现:matallic 值越大,材质的颜色越暗;反之,材质的颜色越亮。

下面我们再验证 PBR 材质的能量守恒性质:

我们把 Debug Mode 选中 None, 不要开启边缘发光效果,我们拖拽 Gloss 选项来改变材质的光滑度。通过滑动,我们可以发现,Gloss 越小,漫反射(高光部分)区域越大,但光线锐度越小;Gloss 越大,虽然高光区域越小,但光线的亮度越高,锐度越犀利。

更多的调试选项这里就不一一列举了,比如查看材质发现的方向,PBR 公式计算的过程中 法线分布函数、微平面遮挡系数、Fresnel 现象等等,等等都可以通过 DebugMode 来调试。

然而,尽管实现了 pbr 的特性,往往却不能满足美术或者说策划大大们的需求,这些需求往往并不是更真实的着色,比如说我们在展示 avatar 的时候,需要一圈外发光效果。有时候外发光的颜色使我们场景里主光的颜色,有时外发光的颜色是某一个指定的颜色。

在材质的选项中,我们没有勾选 SpecialRimColor,外发光的颜色为主光的颜色,当我们勾选之后,可以指定一个特定的外发光颜色。效果如下图所示:

处理半透明有多重方式,主要是 AlphaTest 和 AlphaBlend 两种。在例子的Example_ALPHA的 scene 中,我们给了四种 alpha 混合方式:Opaque、 Cutout、 CutoutTansparent、 Transparent四种裁剪方式。

Opaque 的渲染队列是Geometry, RenderType 是Opaque 效果是右下角,没有透明度也没有Alpha裁剪

Cutout 的渲染队列是AlphaTest,RenderType 是Opaque 效果如左下角,没有透明度但有 Alpha 裁剪

CutoutTransparent 渲染队列是Transparent, RenderType 是TransparentCutout, 有透明度也有 Alha 裁剪

Transparent 的渲染队列是Transparent, RenderType 是Transparent, 有透明度 Alpha混合处理

结语:我们可以使用 PBR,当然也可以再之基础上添加更多的效果,艺术的大脑是无限的。

染色系统

网易《楚留香》手游实现的染色系统。

为了实现类似的效果,我们在手游在还原端游的同时,也加入了染色系统。下面是一个自己实现的染色例子,感兴趣的读者,可以前往下载地址,点击这里。Unity里打开Rendering/Art/Example_ROLE场景即可。实现的效果具体参考下图:

染色系统的实现不再基于对纹理简单的采样, 而是程序里自定义颜色。shader的属性里设置了R,G,B 三个通道的颜色,可以通过材质Inspector窗口自定义颜色。piexl shader中去混合这些颜色。在这个例子当中,我们只是定义了是三个通道,往往并不能满足策划们的需求。

在实际情况中,我们通过uv划分,来支持更多的染色区域。 比如说uv.y 在[1,2]区间可以染色成一种颜色,在uv.y 在[2,3]区间还可以染成另外一种颜色,

类似的原理来支持更多的颜色混合。在我们正在研发的手游中,定义了五个通道(RGB三个通道+2个uv区分)来实现混合的效果。由于游戏还在研发中,这里就不多赘诉了。

至于颜色混合原码,这里贴出颜色混合的部位核心代码,至于完整的代码,可以去前面贴出的github地址前往下载:


float3 diffuseColor1 = 
        (_ColorR.rgb * texColor.r * _ColorR.a +
         _ColorG.rgb * texColor.g * _ColorG.a + 
         _ColorB.rgb * texColor.b * _ColorB.a) * _Color.rgb * float(8);

float2 newuv= float2(i.uv0.x-1,i.uv0.y);
float4 newColor = tex2D(_MainTex,TRANSFORM_TEX(newuv, _MainTex));
float3 diffuseColor2 = (newColor.rgb * _Color.rgb);

float uvlow = step(i.uv0.x, 1); 
float uvhigh = 1 - uvlow;
float3 diffuseColor = diffuseColor1 * uvlow + diffuseColor2 * uvhigh;
float alpha = (_ColorR.a + _ColorG.a + _ColorB.a) * 0.7 + uvhigh * 0.3;

使用这套染色系统,对mesh有一定的要求,需要诸如衣服颜色这些固定颜色的部位使用R,G,B中的一种颜色,里面只有灰度变化。对于像皮肤肉色这种变化的且追求细节的部位,纹理绑定的uv.x区间需要超出1,这部分区域我们不再混合颜色,而是直接对原纹理进行采样。

读者感兴趣的话,可以通过工具QUVEditor uv工具查看。unity的QUVEditor可以在这里下载