《Shader从入门到入土6.0》 使用光照模型实现个性化渲染

之前水了很多2D的,最近搬砖实在忙,二喵拿出点之前的存货应付下,主要介绍下不同的光照模型的效果和性能测试对比。

地址: http://learncocos.com/light

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

历史划水:




Unlit(无光照)

无光照模型并不考虑光照影响,只将物体的颜色或纹理直接渲染到屏幕。这种模型适用于不需要光照影响的场景,例如广告牌或者地面指引,对于lowpoly或者色卡贴图的模型,如本demo的机甲蜜蜂,则不太适合。

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

Lambert(朗伯模型)

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


lambert 的光照模型如下

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.)代替pow(NL *0.5 + 0.5,2.)。此模型常用于卡通或非真实感渲染。


#和lambert对比


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(布林-冯模型)

布林-冯模型是Phong模型的改进版,它引入了"半向量"的概念,使得镜面高光的计算更加贴近微表面理论。适用于模拟有光泽的物体表面。

Blinn-Phong的光照模型如下

这里使用了Shiness去混合IBL的贴图

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

相比如官方的内置的Toon Shader,我额外增加Rim Light 和Specular 的羽化, 同时也精简了 2层Shade 的设置。


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));
  }

PBR(Physically Based Rendering,基于物理的渲染)

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

PBR的GLSL代码较长,通常包括对多个物理特性的计算,如粗糙度、金属度等。因此,这里不提供完整的PBR GLSL代码。

同时由于这个模型没有PBR贴图,所以在效果上也和BlinnPhong相差不大。

以上只是简化的Shader代码,真实的实现会更复杂,因为需要处理纹理、阴影、间接光照等因素。同时,代码中使用的变量(如lightPos,FragPos,norm,lightColor,ambient,objectColor等)会在顶点着色器(vertex shader)或其他地方进行计算和传递。


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

常用功能

在Shader中,通常有一些常用的技术和术语,它们各自承担着不同的功能和目的。以下是你提到的一些概念的解释:

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

Albedo Map:又称Diffuse Map,是一种纹理贴图,主要表示物体表面的固有颜色,不受光照影响。

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

Normal Map:Normal Mapping是一种用于模拟表面细节的技术。它使用RGB值来代表一个向量,这个向量描述了表面在每一点上的法线方向,使得物体表面看起来有更多的细节,使用Normal Map会增加显存,也会增加GPU贴图解析的压力。

Emissive Map:Emissive Map是一种纹理贴图,用于表示物体在没有外部光照的情况下自发的颜色和亮度,使用Emissive Map会增加显存,也会增加GPU贴图解析的压力。

Fog:雾是一种用于模拟大气效果的技术,它可以使远离观察者的物体看起来更模糊,颜色也会向雾的颜色过渡,使用雾效的开销不大,是营造氛围感的高性价比方法。

Image-Based Lighting (IBL):IBL是一种使用全景图像来模拟环境光照的技术。它可以产生更真实的反射和阴影效果,IBL的开销较大,建议低端机型通过开关控制使用。

Rim Light:边缘光是一种模拟物体边缘被背光照亮的效果的技术,可以增加3D模型的立体感,各个材质中都可以开启,使用边缘光开销不大,可以有效提升卡通风格的立体感,也可以用作人物或者怪物受伤。

GPU Instancing:GPU Instancing是一种非常有效的技术,可以在3D场景中渲染大量相同的对象,而不会对性能产生太大的影响。这是通过让GPU一次性渲染多个实例,而不是单独渲染每个实例来实现的。


*通过GPU Instancing 减少了大量Drawcall

这些步骤通常在Fragment Shader中以以下的顺序进行:

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

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

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

4.IBL:然后,你可以根据全景图片来计算IBL,使得环境的反射和阴影效果更真实。


5.Emissive Map:然后,你可以加上Emissive Map,使物体能在没有光源的情况下发光。


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

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

性能分析,光照模型对性能影响不大

这里测试故意没开Instancing和烘焙动画,只是单纯测试不同光照模型的渲染开销。
#instancing 数据如下:

我们发现PC上的现代GPU在处理这些光照模型时候,性能几乎相差不大,而且手机的高端机型也不会受很大影响,只有少数低端机上PBR性能会弱于Lambert。

image

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

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

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

所以建议大家还是基于游戏风格去选择光照模型,实测下来,PBR性能弱的主要原因是开启了IBL,同时还有各种PBR和Normal Map的贴图解析。

我在Blinn-Phong,Lambert模型内也写了简化版的IBL,大家可以下载Demo 体验一下。

好了老规矩Demo 15楼,码字不易 xD

10赞

大佬牛皮,带带我

大佬牛皮,带带我~

喵哥,牛皮,看不懂也要支持下!

大佬牛皮,带带我~

这个我必须得Mark。

先撒泡尿再看

喵哥威武,战略mark

标记了一处地点

https://github.com/iwae/LightingModel
先丢Github了,大佬们求个star,晚点丢store

厉害,顶一下

牛蛙牛蛙牛蛙