本文講解2D游戲中汤锨,如何利用法線貼圖來實(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í)間影響因子 ×〗筷肌(光照原始透明度)
以上混合透明度可以根據(jù)自己想要的效果自定義即可。
shader代碼如下:
u_timeValue // 傳入的時(shí)間值笤妙,數(shù)值自定義冒掌,可每幀隨機(jī)變化噪裕,來模擬閃爍效果 。更真實(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)換”尽0~1
strength3 = strength3 * 0.5 + 0.5; // 時(shí)間因子轉(zhuǎn)換 0.5~1 防止閃爍過于強(qiáng)烈很突兀
vec3 mixColor = color.rgb + lightColor.rgb * strength * strength2 * strength3 * lightColor.a; // 最終顏色混合公式.混合方式和參數(shù)可以自定義 這里采用顏色直接相加
color = vec4(mixColor, color.a); // 角色像素最終顏色