基于 Blinn-Phong 的高性能 Shader,支持阴影和环境反射

COCOS

共 9602字,需浏览 20分钟

 ·

2023-08-30 14:24

引言:社区高产大户孙二喵同学今天给大家带来了全套的传统光照模型 Shader,并集成了 Cocos Creator 的光照、阴影和环境反射能力,让你在渲染效果和性能之间自由权衡。


正文开始

Cocos Creator 引擎的 3D 渲染功能,从一开始就支持了标准的现代化渲染流程,如 PBR材质、HDR 渲染等等。但对于一些算力紧张或者对发热耗电控制严格的平台,我们需要一些更为低功耗的渲染方式来降低渲染开销。基于这个需求,我基于传统光照模型重写了一套高性能的 Cocos Shader,并且能够利用 Cocos Creator 内置的光照、阴影和环境反射能力。希望能给有需要的开发者带来帮助。

  • 体验地址:
http://learncocos.com/light
  • 源码地址:

https://store.cocos.com/app/detail/5256

在计算机图形学中,光照模型(Lighting Model)用于模拟物体表面在光线照射下的反射效果。本文使用 Cocos Creator 来演示常用的光照模型的效果和示例代码(使用GLSL语言)。

85b978b9869dd27f8098fddb395326df.webp


Unlit(无光照)

无光照模型并不考虑光照影响,只将物体的颜色或纹理直接渲染到屏幕。这种模型适用于不需要光照影响的场景,例如广告牌或者地面指引等特效。

f5c96e6b3bf9d6fa3f9e1cf1b233bf40.webp

实现代码如下:

    
    
      void main()
{
   vec4 o = mainColor; //材质颜色
   return CCFragOutput(o);
}


Lambert(兰伯特)

兰伯特光照模型是一种描述漫反射的光照模型,其假设物体表面对光的反射不依赖于观察者的位置。这种模型常用于模拟非金属、非镜面的物体表面。

044a91221a5bc53faf52b8aa1fde3b10.webp


实现代码如下:

    
    
      void Lambert(inout vec4 diffuseColor,in vec3 normal)
{
   vec3 N = normalize(normal);
   vec3 L = normalize(cc_mainLitDir.xyz * -1.0);
   float NL = max(dot(N, L), 0.0);
   vec3 diffuse = NL * (diffuseColor.rgb * cc_mainLitColor.xyz);
   vec3 ambient = cc_ambientGround.rgb * diffuseColor.rgb * cc_ambientSky.w;
   diffuseColor.rgb = ambient + diffuse;
}


Half Lambert(半兰伯特)

半兰伯特光照模型是兰伯特光照模型的一个变体,它改变了对光线反射的解释,使得在光线与法线成 90 度角时,反射强度为 0.5 而非 0 ,从而使阴影部分不那么暗,这里做了下优化增加了 diffuseWrap 的参数,用  pow(NL * diffuseWrap + (1.-diffuseWrap),2.0)  代替  pow(NL *0.5 +00.5,2.) 。此光照模型常用于卡通或非真实感渲染。

396b35f31813a0cccfd7d383ebafadf1.webp

相比兰伯特模型,半兰伯特模型的阴影部分不那么暗(下图左侧),我们也可以通过 diffuseWrap 去控制暗部的阴影强度。

af9f958c111c0c893c9c0cf82ca3daec.webp

实现代码如下:

    
    
      void HalfLambert(inout vec4 diffuseColor,in vec3 normal)
{
   vec3 N = normalize(normal);
   vec3 L = normalize(cc_mainLitDir.xyz * -1.0);
   float NL = max(dot(N, L), 0.0);
   vec3 diffuse = pow(NL * diffuseWrap + (1.-diffuseWrap),2.0) * (diffuseColor.rgb * cc_mainLitColor.xyz);
   vec3 ambient = cc_ambientGround.rgb * diffuseColor.rgb * cc_ambientSky.w;
   diffuseColor.rgb = ambient + diffuse;
}


Blinn-Phong(布林-冯)

Blinn-Phong 光照模型是 Phong 光照模型的改进版,它引入了 "半向量" 的概念,使得镜面高光的计算更加高效。适用于模拟有光泽的物体表面。

305e47eebab47fe98002ba754fb4cd7b.webp

实现代码如下:

    
    
      void void blinnPhong(inout vec4 diffuseColor,in vec3 normal)
{
   vec3 N = normalize(normal);
   vec3 L = normalize(cc_mainLitDir.xyz * -1.0);
   float NL = max(dot(N, L), 0.0);
   vec3 diffuse = NL * diffuseColor.rgb * cc_mainLitColor.xyz;
   vec3 position;
   HIGHP_VALUE_FROM_STRUCT_DEFINED(position, v_position);
   vec3 cameraPosition = cc_cameraPos.xyz / cc_cameraPos.w;
   vec3 V = normalize(cameraPosition- position);
   vec3 H = normalize(L + V);
   float specularFactor = pow(max(0.0, dot(H,N)), bpParams.x*50.);
   vec3 specular = (specularFactor * cc_ambientSky.rgb * cc_mainLitColor.xyz);
   float shadowCtrl = 1.0;
   #if CC_RECEIVE_SHADOW && CC_SHADOW_TYPE == CC_SHADOW_MAP
     if (NL > 0.0) {
     #if CC_DIR_LIGHT_SHADOW_TYPE == CC_DIR_LIGHT_SHADOW_CASCADED
       shadowCtrl = CCCSMFactorBase(position, N, v_shadowBias);
     #endif
     #if CC_DIR_LIGHT_SHADOW_TYPE == CC_DIR_LIGHT_SHADOW_UNIFORM
       shadowCtrl = CCShadowFactorBase(CC_SHADOW_POSITION, N, v_shadowBias);
     #endif
     }
   #endif
   diffuse = (diffuse + specular) * (shadowCtrl);
}


Toon(卡通着色)

卡通模型或称 Cel-Shading,它通过将光照强度量离散化为几个等级,模拟出手绘动画的效果。适用于卡通或者艺术风格的渲染。

63583249269ba15e6a2beb2953ade693.webp

实现代码如下:

    
    
      void ToonShading (inout vec4 diffuseColor,in vec3 normal) {
   vec3 position;
   HIGHP_VALUE_FROM_STRUCT_DEFINED(position, v_position);
   vec3 V = normalize(cc_cameraPos.xyz - position);
   vec3 N = normalize(normal);
   vec3 L = normalize(-cc_mainLitDir.xyz);
   float NL = 0.5 * dot(N, L) + 0.5;
   float NH = 0.5 * dot(normalize(V + L), N) + 0.5;
   vec3 lightColor = cc_mainLitColor.rgb * (cc_mainLitColor.w * shadeParams.x);
   float shadeFeather = shadeParams.y;
   float shadeCtrl = mix(1., (1.-shadeParams.z), clamp(1.0 + (shadeParams.x - shadeFeather - NL) / shadeFeather, 0.0, 1.0));
   shadeCtrl *= mix(1., (1.-shadeParams.z*0.5), clamp(1.0 + (shadeParams.w - shadeFeather - NL) / shadeFeather, 0.0, 1.0));
   float specularWeight = 1.0 - pow(specularParams.x, 5.0);
   float specularMask = 1.0-smoothstep( NH, NH+ specularParams.y, specularWeight + EPSILON_LOWP);
   float shadowCtrl = 1.0;
   #if CC_RECEIVE_SHADOW && CC_SHADOW_TYPE == CC_SHADOW_MAP
     if (NL > 0.0) {
     #if CC_DIR_LIGHT_SHADOW_TYPE == CC_DIR_LIGHT_SHADOW_CASCADED
       shadowCtrl = CCCSMFactorBase(position, N, v_shadowBias+0.1);
     #endif
     #if CC_DIR_LIGHT_SHADOW_TYPE == CC_DIR_LIGHT_SHADOW_UNIFORM
       shadowCtrl = CCShadowFactorBase(CC_SHADOW_POSITION, N, v_shadowBias+0.1);
     #endif
     }
   #endif
   float diffuseCtrl = (shadowCtrl+specularMask*specularParams.z)*shadeCtrl;
   vec3 envColor = cc_ambientGround.rgb*cc_ambientSky.w;
   diffuseColor.rgb *= (envColor + (lightColor*diffuseCtrl));
 }

我们还可以通过边缘光(Rim Light)实现不同风格化渲染风格。

19dbed939b59021c306a8cd21b8c2fc6.webp

实现代码如下:

    
    
          #if USE_RIM_LIGHT
       float fRim = (1.0 - dot(v_view_normal,vec3(0,0,1.0))) * rimColor.w;
       color.rgb = mix(color.rgb,rimColor.rgb,fRim);
   #endif


PBR vs. Blinn-Phong

PBR - Physically Based Rendering, 是最新的光照模型,它试图更真实地模拟光线与物体表面的相互作用,包括漫反射和镜面反射。PBR 模型通常包括能量守恒和菲涅耳等效等物理原理,适用于模拟真实世界的渲染。

PBR 是几乎是所有现代标准图形引擎默认的光照模型,但 PBR 由于涉及过多的公式计算和贴图采样,它的 Shader 代码非常复杂,对用户设备算力要求较高。对这块有兴趣的同学可以直接查看 Cocos 引擎最新版本的内置 Shader 源码。

https://github.com/cocos/cocos-engine/tree/develop/editor/assets/chunks

但在很多情况下,我们不用 PBR 也能渲染出可接受的效果。

如下图所示:

a9975655c6ac47eb95e25ca110f66fd6.webp

左:Blinn-Phong,右:PBR

不同的光照模型适用于不同的渲染风格,可以根据具体的需求和场景来选择使用。例如,无光照模型适合广告牌或者地面指引,兰伯特光照模型适合无光泽的表面,卡通模型适合卡通或手绘风格的渲染,而 PBR 则适合模拟真实世界的高质量渲染。


常用术语

在 Shader 中,无论我们使用哪一种光照模型,都有一些通用的技术和术语,它们各自承担着不同的功能和目的。以下是一些概念的解释:

颜色(Color):这通常是一个 RGBA 值,表示一个像素的基本颜色。R、G、B分别代表红色、绿色和蓝色,A 代表透明度。这些值通常在 0 到 1 之间。

fff4eeb60667b0b87022e58ea302482f.webp

Albedo Map:不带任何光照信息的颜色贴图,主要表示物体表面的固有颜色,不受光照影响,通常用在 PBR 光照模型中。

92f4be1ca8631e9d367014bf451eb298.webp

Diffuse Map:漫反射贴图,可能会携带一些光照信息,比如 AO,Shading 等。一般用于传统光照模型。

Alpha Test:Alpha 测试是一种通过比较像素的 Alpha 值和预设阈值来决定是否丢弃像素的技术。这种技术常常用于实现透明和半透明效果。

实现代码如下:

    
        #if USE_ALPHA_TEST
     if (color.ALPHA_TEST_CHANNEL < colorScaleAndCutoff.w) discard;
   #endif

Normal Map:Normal Mapping 是一种用于模拟表面细节的技术。它使用一张贴图来存储向量,这个向量描述了表面在每一点上的法线方向,使得物体表面看起来有更多的细节。

8e7160c077bcc9bcafd8c5455f87cb20.webp

Emissive Map:Emissive Map 是一种纹理贴图,用于表示物体在没有外部光照的情况下自发的颜色和亮度。

Fog:雾是一种用于模拟大气效果的技术,它可以使远离观察者的物体看起来更模糊,颜色也会向雾的颜色过渡。

Image-Based Lighting (IBL):IBL 是一种使用环境反射贴图来模拟环境光照的技术。它可以产生更真实的反射和光照效果。

实现代码如下:

    
    
         #if CC_USE_IBL && USE_IBL
     vec3 cameraPosition = cc_cameraPos.xyz / cc_cameraPos.w;
     vec3 V = normalize(cameraPosition- position);
     vec3 env = vec3(1.);
     vec3 R = normalize(reflect(-V, N));
     vec3 rotationDir = RotationVecFromAxisY(R.xyz, cc_surfaceTransform.z, cc_surfaceTransform.w);
     vec4 envmap = fragTextureLod(cc_environment, rotationDir, bpParams.y * (cc_ambientGround.w - 1.0));
     #if CC_USE_IBL == IBL_RGBE
       env = unpackRGBE(envmap);
     #else
       env = SRGBToLinear(envmap.rgb);
     #endif
     diffuse = mix(env, diffuse, bpParams.x);
   #endif
   vec3 ambient = cc_ambientGround.rgb * diffuseColor.rgb * cc_ambientSky.w;
   diffuseColor.rgb = ambient + diffuse;
 }

Rim Light: 边缘光是一种模拟物体边缘被背光照亮的效果的技术,可以增加3D模型的立体感。

作用原理

上面提到的这些技术,步骤通常在 Fragment Shader 中以以下的顺序进行:

1. 颜色:首先,你需要知道物体的基本颜色,这通常通过读取Albedo贴图来实现。

2. Normal Map:然后,你可以应用Normal Map来改变物体表面的法线,从而模拟出更多的细节。

3. 光照计算:接着,你可以进行光照计算,这通常包括环境光、漫反射光、镜面反射光等的计算。在计算过程中,你可能会用到Rim Light来增加边缘的亮度。

4. IBL:然后,你可以根据全景图片来计算IBL,使得环境的反射和阴影效果更真实。 4a40723f2f5d8f0bec2265a868c2608e.webp5. Emissive Map:然后,你可以加上Emissive Map,使物体能在没有光源的情况下发光。

6. Alpha Test:最后,你可以进行Alpha测试,根据测试结果决定是否丢弃像素。

7. Fog:在所有的颜色和光照计算完毕后,你可以应用Fog效果,使远离观察者的物体颜色向雾的颜色过渡。

be8a10f769e26bc81f7626ff18e8767f.webp


性能分析

由于 PC 的 GPU 运算能力和带宽都比较强大,在处理这些光照模型时候,性能几乎相差不大。手机中的高端机型也不会受很大影响,只有少数低端机上 PBR 性能会弱于 Lambert。

同时我们观察到,使用阴影和描边(Outline)会使得顶点数翻倍,这是性能下降的主要原因,这主要有以下两个原因:

阴影生成:阴影通常是通过生成阴影映射(Shadow Map)来实现的。在这个过程中,场景需要从光源的视角进行一次额外的渲染。这意味着每个顶点需要被再次处理和光栅化,使得顶点数翻倍。

ed9a2524208ddf9a96f24cb9a08eee49.webp

描边生成额外的几何体:在原始模型的基础上生成一个稍大的版本,然后渲染这个大版本的反面,形成描边效果。这种方法会导致顶点数翻倍,因为你实际上是渲染了两个模型。

e1caec433965a3596ac2d46cb694a205.webp

通常我们可以通过开启  GPU Instancing  来提升游戏性能。

8721fa18b32853352fff2ea84f921dea.webp

如果模型有骨骼动画,建议启用烘焙动画来配合  GPU Instancing  使用。

dbe06369c00ebb914d559fb6b1d56699.webp

所以建议大家还是基于游戏风格去选择光照模型,实测下来,PBR 性能弱的主要原因是开启了 IBL。

源码免费获取

针对 Blinn-Phong,Lambert 模型内也写了简化版的 IBL,大家可以从 Cocos Store 下载全套源码包进行了解:

fb7998f04c652b4403a9f23154d8ccf9.webp

地址 https://store.cocos.com/app/detail/5256

点击【阅读原文】可快速跳转,免费的哟。


关注 Cocos 引擎官方公众号,你会变得更强!

浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报