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

引言:社区高产大户孙二喵同学今天给大家带来了全套的传统光照模型 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语言)。

3e410f31d853d173b40e41a86bf95cde.png


Unlit(无光照)

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

fce2c9e8f99c859923b28157ff207bdd.png

实现代码如下:

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

Lambert(兰伯特)

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

61874653fdf22a6a056b1b1025770723.png

实现代码如下:

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.)。此光照模型常用于卡通或非真实感渲染。

b119c332950ad8f23a8fd87c595beb0f.png

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

976b14b11017195147d2800974727ad6.png

实现代码如下:

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

96900efd97ce84dd0f6960b723205ad9.png

实现代码如下:

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,它通过将光照强度量离散化为几个等级,模拟出手绘动画的效果。适用于卡通或者艺术风格的渲染。

952ab6395ffa71f424e33bb7b8c74845.png

实现代码如下:

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)实现不同风格化渲染风格。

01b687445b83cf2c315e8259f57fdde2.png

实现代码如下:

#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 也能渲染出可接受的效果。

如下图所示:

28c69d3b5a029b23b6e59ffbf1ea465f.png

左:Blinn-Phong,右:PBR

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

常用术语

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

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

910da45919031db72a00213877fc8254.png

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

2e6cbd21a41a16d99e2e35dd0f788b26.png

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 是一种用于模拟表面细节的技术。它使用一张贴图来存储向量,这个向量描述了表面在每一点上的法线方向,使得物体表面看起来有更多的细节。

60aa9861ba382ad7a50789756e2fd6b7.png

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,使得环境的反射和阴影效果更真实。64010e779bd11086f61ac78823146550.png5. Emissive Map:然后,你可以加上Emissive Map,使物体能在没有光源的情况下发光。

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

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

0caf53785cd624a9f7c05ee8f2c8d74b.png


性能分析

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

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

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

89211a18dcf67f2edab45738ca53295f.png

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

b8a4cb9902875d5a6d50c6fa051303c1.png

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

c1415f06c0e9a2d50c440f2255e4c1a9.png

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

c57a3b6cd2ecd3e7359a4762d6f151f6.png

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

源码免费获取

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

80778f40f546f3d86a832edd6f81223e.png

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

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

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

猜你喜欢

转载自blog.csdn.net/weixin_44053279/article/details/132506908