基于 Blinn-Phong 的高性能 Shader,支持陰影和環(huán)境反射
引言:社區(qū)高產(chǎn)大戶孫二喵同學(xué)今天給大家?guī)砹巳椎膫鹘y(tǒng)光照模型 Shader,并集成了 Cocos Creator 的光照、陰影和環(huán)境反射能力,讓你在渲染效果和性能之間自由權(quán)衡。
正文開始
Cocos Creator 引擎的 3D 渲染功能,從一開始就支持了標(biāo)準(zhǔn)的現(xiàn)代化渲染流程,如 PBR材質(zhì)、HDR 渲染等等。但對于一些算力緊張或者對發(fā)熱耗電控制嚴(yán)格的平臺,我們需要一些更為低功耗的渲染方式來降低渲染開銷。基于這個需求,我基于傳統(tǒng)光照模型重寫了一套高性能的 Cocos Shader,并且能夠利用 Cocos Creator 內(nèi)置的光照、陰影和環(huán)境反射能力。希望能給有需要的開發(fā)者帶來幫助。
- 體驗(yàn)地址:
-
源碼地址:
https://store.cocos.com/app/detail/5256
在計(jì)算機(jī)圖形學(xué)中,光照模型(Lighting Model)用于模擬物體表面在光線照射下的反射效果。本文使用 Cocos Creator 來演示常用的光照模型的效果和示例代碼(使用GLSL語言)。
Unlit(無光照)
無光照模型并不考慮光照影響,只將物體的顏色或紋理直接渲染到屏幕。這種模型適用于不需要光照影響的場景,例如廣告牌或者地面指引等特效。
實(shí)現(xiàn)代碼如下:
void main()
{
vec4 o = mainColor; //材質(zhì)顏色
return CCFragOutput(o);
}
Lambert(蘭伯特)
蘭伯特光照模型是一種描述漫反射的光照模型,其假設(shè)物體表面對光的反射不依賴于觀察者的位置。這種模型常用于模擬非金屬、非鏡面的物體表面。
實(shí)現(xiàn)代碼如下:
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 度角時,反射強(qiáng)度為 0.5 而非 0 ,從而使陰影部分不那么暗,這里做了下優(yōu)化增加了 diffuseWrap 的參數(shù),用 pow(NL * diffuseWrap + (1.-diffuseWrap),2.0) 代替 pow(NL *0.5 +00.5,2.) 。此光照模型常用于卡通或非真實(shí)感渲染。
相比蘭伯特模型,半蘭伯特模型的陰影部分不那么暗(下圖左側(cè)),我們也可以通過 diffuseWrap 去控制暗部的陰影強(qiáng)度。
實(shí)現(xiàn)代碼如下:
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 光照模型的改進(jìn)版,它引入了 "半向量" 的概念,使得鏡面高光的計(jì)算更加高效。適用于模擬有光澤的物體表面。
實(shí)現(xiàn)代碼如下:
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,它通過將光照強(qiáng)度量離散化為幾個等級,模擬出手繪動畫的效果。適用于卡通或者藝術(shù)風(fēng)格的渲染。
實(shí)現(xiàn)代碼如下:
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)實(shí)現(xiàn)不同風(fēng)格化渲染風(fēng)格。

實(shí)現(xiàn)代碼如下:
#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, 是最新的光照模型,它試圖更真實(shí)地模擬光線與物體表面的相互作用,包括漫反射和鏡面反射。PBR 模型通常包括能量守恒和菲涅耳等效等物理原理,適用于模擬真實(shí)世界的渲染。
PBR 是幾乎是所有現(xiàn)代標(biāo)準(zhǔn)圖形引擎默認(rèn)的光照模型,但 PBR 由于涉及過多的公式計(jì)算和貼圖采樣,它的 Shader 代碼非常復(fù)雜,對用戶設(shè)備算力要求較高。對這塊有興趣的同學(xué)可以直接查看 Cocos 引擎最新版本的內(nèi)置 Shader 源碼。
https://github.com/cocos/cocos-engine/tree/develop/editor/assets/chunks
但在很多情況下,我們不用 PBR 也能渲染出可接受的效果。
如下圖所示:
左:Blinn-Phong,右:PBR
不同的光照模型適用于不同的渲染風(fēng)格,可以根據(jù)具體的需求和場景來選擇使用。例如,無光照模型適合廣告牌或者地面指引,蘭伯特光照模型適合無光澤的表面,卡通模型適合卡通或手繪風(fēng)格的渲染,而 PBR 則適合模擬真實(shí)世界的高質(zhì)量渲染。
常用術(shù)語
在 Shader 中,無論我們使用哪一種光照模型,都有一些通用的技術(shù)和術(shù)語,它們各自承擔(dān)著不同的功能和目的。以下是一些概念的解釋:
顏色(Color):這通常是一個 RGBA 值,表示一個像素的基本顏色。R、G、B分別代表紅色、綠色和藍(lán)色,A 代表透明度。這些值通常在 0 到 1 之間。
Albedo Map:不帶任何光照信息的顏色貼圖,主要表示物體表面的固有顏色,不受光照影響,通常用在 PBR 光照模型中。
Diffuse Map:漫反射貼圖,可能會攜帶一些光照信息,比如 AO,Shading 等。一般用于傳統(tǒng)光照模型。
Alpha Test:Alpha 測試是一種通過比較像素的 Alpha 值和預(yù)設(shè)閾值來決定是否丟棄像素的技術(shù)。這種技術(shù)常常用于實(shí)現(xiàn)透明和半透明效果。
實(shí)現(xiàn)代碼如下:
#if USE_ALPHA_TEST
if (color.ALPHA_TEST_CHANNEL < colorScaleAndCutoff.w) discard;
#endif
Normal Map:Normal Mapping 是一種用于模擬表面細(xì)節(jié)的技術(shù)。它使用一張貼圖來存儲向量,這個向量描述了表面在每一點(diǎn)上的法線方向,使得物體表面看起來有更多的細(xì)節(jié)。
Emissive Map:Emissive Map 是一種紋理貼圖,用于表示物體在沒有外部光照的情況下自發(fā)的顏色和亮度。
Fog:霧是一種用于模擬大氣效果的技術(shù),它可以使遠(yuǎn)離觀察者的物體看起來更模糊,顏色也會向霧的顏色過渡。
Image-Based Lighting (IBL):IBL 是一種使用環(huán)境反射貼圖來模擬環(huán)境光照的技術(shù)。它可以產(chǎn)生更真實(shí)的反射和光照效果。
實(shí)現(xiàn)代碼如下:
#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: 邊緣光是一種模擬物體邊緣被背光照亮的效果的技術(shù),可以增加3D模型的立體感。
作用原理
上面提到的這些技術(shù),步驟通常在 Fragment Shader 中以以下的順序進(jìn)行:
1. 顏色:首先,你需要知道物體的基本顏色,這通常通過讀取Albedo貼圖來實(shí)現(xiàn)。
2. Normal Map:然后,你可以應(yīng)用Normal Map來改變物體表面的法線,從而模擬出更多的細(xì)節(jié)。
3. 光照計(jì)算:接著,你可以進(jìn)行光照計(jì)算,這通常包括環(huán)境光、漫反射光、鏡面反射光等的計(jì)算。在計(jì)算過程中,你可能會用到Rim Light來增加邊緣的亮度。
4. IBL:然后,你可以根據(jù)全景圖片來計(jì)算IBL,使得環(huán)境的反射和陰影效果更真實(shí)。
5. Emissive Map:然后,你可以加上Emissive Map,使物體能在沒有光源的情況下發(fā)光。
6. Alpha Test:最后,你可以進(jìn)行Alpha測試,根據(jù)測試結(jié)果決定是否丟棄像素。
7. Fog:在所有的顏色和光照計(jì)算完畢后,你可以應(yīng)用Fog效果,使遠(yuǎn)離觀察者的物體顏色向霧的顏色過渡。
性能分析
由于 PC 的 GPU 運(yùn)算能力和帶寬都比較強(qiáng)大,在處理這些光照模型時候,性能幾乎相差不大。手機(jī)中的高端機(jī)型也不會受很大影響,只有少數(shù)低端機(jī)上 PBR 性能會弱于 Lambert。
同時我們觀察到,使用陰影和描邊(Outline)會使得頂點(diǎn)數(shù)翻倍,這是性能下降的主要原因,這主要有以下兩個原因:
陰影生成:陰影通常是通過生成陰影映射(Shadow Map)來實(shí)現(xiàn)的。在這個過程中,場景需要從光源的視角進(jìn)行一次額外的渲染。這意味著每個頂點(diǎn)需要被再次處理和光柵化,使得頂點(diǎn)數(shù)翻倍。
描邊生成額外的幾何體:在原始模型的基礎(chǔ)上生成一個稍大的版本,然后渲染這個大版本的反面,形成描邊效果。這種方法會導(dǎo)致頂點(diǎn)數(shù)翻倍,因?yàn)槟銓?shí)際上是渲染了兩個模型。
通常我們可以通過開啟 GPU Instancing 來提升游戲性能。
如果模型有骨骼動畫,建議啟用烘焙動畫來配合 GPU Instancing 使用。
所以建議大家還是基于游戲風(fēng)格去選擇光照模型,實(shí)測下來,PBR 性能弱的主要原因是開啟了 IBL。
源碼免費(fèi)獲取
針對 Blinn-Phong,Lambert 模型內(nèi)也寫了簡化版的 IBL,大家可以從 Cocos Store 下載全套源碼包進(jìn)行了解:
地址 : https://store.cocos.com/app/detail/5256
點(diǎn)擊【閱讀原文】可快速跳轉(zhuǎn),免費(fèi)的喲。
關(guān)注 Cocos 引擎官方公眾號,你會變得更強(qiáng)!
