本文講解2D游戲中,如何利用法線貼圖來(lái)實(shí)現(xiàn)有材質(zhì)特性、全角度、且受時(shí)間影響的接近真實(shí)的光照效果。
本文鏈接 游戲shader(6):利用法線貼圖實(shí)現(xiàn)動(dòng)態(tài)2D光照效果
一、制作背景
? ? 2D游戲中,場(chǎng)景中有多個(gè)火把、蠟燭、燈光等貼圖,讓場(chǎng)景非常真實(shí)絢麗。但遺憾的是,周圍的物體和經(jīng)過的游戲角色,并不受這些光的影響。
? ? 我們要做的就是讓周圍的物體和經(jīng)過的游戲角色,實(shí)時(shí)地受到這些燈光的影響,實(shí)現(xiàn)仿3D效果,增強(qiáng)2D游戲的體驗(yàn)和真實(shí)性。
二、效果動(dòng)態(tài)圖對(duì)比
? ? 以下展示了不受光照影響和受光照影響的效果對(duì)比圖。很明顯,對(duì)旁邊的火把光作出反饋時(shí),角色更加真實(shí)。


三、實(shí)現(xiàn)原理
? ? 實(shí)現(xiàn)基本原理是,我們將環(huán)境光源的坐標(biāo)、顏色、強(qiáng)度實(shí)時(shí)傳入角色的片元shader,據(jù)此重新計(jì)算角色的顏色。
? ? 那么如何確定角色身上哪些像素接受光照、哪些不接受、又接受多少呢?這個(gè)就要用到法線貼圖了。在法線貼圖上,每個(gè)紋理像素的RGB不再代表顏色分量,而是代表角色身上每個(gè)像素的法向量信息。關(guān)于法線貼圖定義請(qǐng)自行查閱相關(guān)資料。
? ? 步驟如下:
? ? 1、定義光照影響因子。這里用光源坐標(biāo)、顏色、強(qiáng)度、時(shí)間變量幾個(gè)uniform變量。
? ? 2、準(zhǔn)備法線貼圖。一種方法是自定義每個(gè)像素的法向量信息,最后導(dǎo)出貼圖。一種是利用法線貼圖生成工具,這里推薦一個(gè)在線法線貼圖生成工具? https://cpetry.github.io/NormalMap-Online/? ,上傳貼圖,調(diào)整參數(shù),并生成和下載的法線貼圖。這里用的是在線生成。
? ? 3、將法線貼圖紋理,作為shader的uniform參數(shù)傳入供采樣。

四、關(guān)鍵實(shí)現(xiàn)和代碼
? ? 一些算法做簡(jiǎn)化處理。思想粗略描述如下:
1、強(qiáng)度距離衰減因子 = 光照強(qiáng)度 × ((感光距離最大值常量 - 角色紋理像素到光源的距離 )/ 感光距離最大值常量);
2、法向量2D平面分量 = vec2(法線貼圖顏色.R,法線貼圖顏色.B)* 2.0; // 2d游戲中忽略Z分量
3、光照反饋強(qiáng)度因子 = 點(diǎn)積(光源到角色像素的向量, 法向量2D平面分量)
4、時(shí)間因子。動(dòng)態(tài)變化以實(shí)現(xiàn)強(qiáng)弱閃爍效果。更真實(shí)地,應(yīng)該以光源實(shí)際強(qiáng)度的值為準(zhǔn),在CPU去計(jì)算。這里簡(jiǎn)便起見,用時(shí)間因子模擬
4、角色像素最終顏色RGB = 角色紋理原始顏色 × (1-光照原始透明度) + 光照原始顏色RGB × 強(qiáng)度距離衰減因子 × 光照反饋強(qiáng)度因子 × 時(shí)間影響因子 ×?。ü庹赵纪该鞫龋?/p>
以上混合透明度可以根據(jù)自己想要的效果自定義即可。
shader代碼如下:
u_timeValue // 傳入的時(shí)間值,數(shù)值自定義,可每幀隨機(jī)變化,來(lái)模擬閃爍效果 。更真實(shí)地,應(yīng)該以光源實(shí)際強(qiáng)度的值為準(zhǔn),在CPU去計(jì)算。這里簡(jiǎn)便起見,用時(shí)間因子模擬。
vec4 normalColor = texture2D(u_normalTexture, uvn); // 該片元坐標(biāo)在法線貼圖上的采樣顏色
vec2 lightPos = u_lightPos; // 光源的位置
vec4 lightColor = u_lightColor; // 光的原始顏色
float lightRadius = u_lightRadius; // 光能照到的最大半徑
float px = u_nodePos.x + (uvx -u_nodeAnchor.x) * u_nodeWidth; // 計(jì)算角色身上某像素的x位置
float py = u_nodePos.y + (u_nodeAnchor.y - uvy) * u_nodeHeight; // 計(jì)算角色身上某像素的y位置
vec2 vecLight = vec2(px - lightPos.x, py - lightPos.y); // 光源到角色身上某像素的向量
vec2 vecLightN = normalize(vecLight); // 光源到角色身上某像素向量的法向量
float dis = length(vecLight); // 光源到角色身上某像素的距離
vec2 normalVec = vec2(normalColor.r - 0.5, normalColor.g - 0.5) * 2.0; // 法線貼圖坐標(biāo)空間轉(zhuǎn)到顏色空間(-1~1轉(zhuǎn)0~1)
if (u_nodeScaleX == -1.0) { // 可能的x翻轉(zhuǎn)
? ? normalVec.r = -normalVec.r;
}
if (u_texture_flipY > 0.0) { // 可能的y翻轉(zhuǎn)
? ? normalVec.g = -normalVec.g;
}
float strength2 = max(dot(vecLightN, normalVec), 0.0); // 關(guān)鍵點(diǎn):法向量和vecLightN 向量做點(diǎn)積運(yùn)算,得到反饋強(qiáng)度值
float strength = smoothstep(0.0, 1.0, 1.0 - dis/lightRadius); // 計(jì)算距離衰減因子,這里采用平滑插值函數(shù)
float time = u_timeValue / timeRatio; // 時(shí)間影響因子
float timeYu = time - float(int(time));// 計(jì)算要用到的時(shí)間余數(shù)
timeYu = timeYu * 0.5 + 0.5 * timeRatio;// 時(shí)間轉(zhuǎn)換到0~timeRatio范圍
float strength3 = timeYu / timeRatio; // 時(shí)間因子轉(zhuǎn)換?。啊?/p>
strength3 = strength3 * 0.5 + 0.5; // 時(shí)間因子轉(zhuǎn)換?。?5~1 防止閃爍過于強(qiáng)烈很突兀
vec3 mixColor = color.rgb + lightColor.rgb * strength * strength2 * strength3 * lightColor.a; // 最終顏色混合公式.混合方式和參數(shù)可以自定義 這里采用顏色直接相加
color = vec4(mixColor, color.a); // 角色像素最終顏色