卡通風格着色器及描邊效果實現(基於WebGL 2.0實現)
在開發過程中,我們並不總是以真實效果作爲追求,最近在學習的過程中,看到了一些非真實質感的開發實現。本次帶來一個球體的卡通效果及描邊的demo。
先看一下效果圖:
接下來,簡單的闡述一下其中的原理吧。
1.卡通風格的開發,首先需要對物體進行普通的光照計算,得到物體各個區域的的結果光照強度,然後根據光照的強度對物體顏色進行賦值。
提示:我分了5個等級,光照強度我取的都是大於1的部分,demo中的光我採用的是散射光。
2.物體描邊效果的實現,其開發要點是確定當前着色的片元是否處在邊緣的位置,這裏介紹一種比較簡單的方法,就是計算視線向量與物體表面的夾角,若夾角大於一定的值則認爲是邊緣。
提示:計算的時候我們只需要求出視線向量(攝像機位置減去頂點位置)和法線向量,並對其進行單位化,然後兩個向量做點積即可。
然後看下demo的核心代碼,demo是基於WebGL 2.0開發,最重要的是頂點着色器和片元着色器的開發,着色器爲version 300es,然後代碼涉及的曲面構建還有將矩陣和座標數組等送入渲染管線的部分就暫時不講了,比較簡單,涉及到底層的API,大家可以結合《WebGL編程指南》和khronos的 WebGL 2.0官方文檔進行學習。
頂點着色器:
#version 300 es
uniform mat4 uMVPMatrix; //總變換矩陣
uniform mat4 uMMatrix; //變換矩陣
uniform vec3 uCamera; //攝像機位置
uniform vec3 uLightLocationSun; //光源位置
in vec3 aPosition; //頂點位置
in vec3 aNormal; //法向量
out float vDiffuse; //用於傳遞給片元着色器的變量
out float vStroke; //描邊係數
//光照及描邊係數計算的方法
void pointLight(
in vec3 normal, //法向量
out float diffuse, //方向光最終強度
out float stroke, //描邊係數
in float lightDiffuse //光照強度
){
//計算變換後的法向量
vec3 normalTarget=aPosition+normal;
vec3 newNormal=(uMMatrix*vec4(normalTarget,1)).xyz-(uMMatrix*vec4(aPosition,1)).xyz;
//對法向量規格化
newNormal=normalize(newNormal);
//計算從表面點到攝像機的向量
vec3 eye= normalize(uCamera-(uMMatrix*vec4(aPosition,1)).xyz);
//計算描邊係數
stroke=max(0.0,dot(newNormal,eye));
//計算從表面點到光源位置的向量vp
vec3 vp= normalize(uLightLocationSun-(uMMatrix*vec4(aPosition,1)).xyz);
//格式化vp
vp=normalize(vp);
//求法向量與vp的點積與0的最大值
float nDotViewPosition=max(0.0,dot(newNormal,vp));
//計算散射光的最終強度
diffuse=lightDiffuse*nDotViewPosition;
}
void main(){
//根據總變換矩陣計算此次繪製此頂點位置
gl_Position = uMVPMatrix * vec4(aPosition,1);
//計算光照及描邊係數並傳給片元着色器
pointLight(normalize(aNormal),vDiffuse,vStroke,1.0);
}
片元着色器:
#version 300 es
precision mediump float; //給出浮點默認精度
in float vDiffuse; //接收從頂點着色器過來散射光最終強度
out vec4 fragColor; //傳遞到渲染管線的片元顏色
in float vStroke; //描邊係數
void main()
{
//默認顏色
vec3 color=vec3(0.0,0.0,1.0);
float diffuse=vDiffuse;
//根據最初光照強度來確認每部分的最終光照強度
if(diffuse>0.8){ diffuse=1.0; }
else if(diffuse>0.6){ diffuse=0.8; }
else if(diffuse>0.4){ diffuse=0.6; }
else if(diffuse>0.2){ diffuse=0.4; }
else { diffuse=0.25; }
//描邊顏色
const vec4 strokeColor=vec4(0.0,0.0,0.0,1.0);
//判斷是否進行描邊
float finalStroke=step(0.25,vStroke);
//輸出片元顏色
fragColor=finalStroke*vec4(color*diffuse,1.0)+(1.0-finalStroke)*strokeColor;
}
代碼非常簡單了,當然卡通及描邊效果的開發還有很多種開發方法。本文中的非真實質感效果並未使用階梯性變化的紋理,不過實現的大致思路差不多;描邊效果的實現還有跟多種,例如沿法線擠出(就是在物體的周圍進行繪製,描邊的座標就是物體座標沿法線加個值),還有在攝像機空間擠出(就是攝像機空間下進行沿法線擠出的操作)等。大家可以進行嘗試開發。