該原創文章首發於微信公衆號:字節流動
舊文中我們用 OpenGL 給小姐姐實現了瘦身和大長腿效果,結果小姐姐眯着眼睛、嘟着嘴說,我需要瘦身和大長腿效果嗎?
筆者若有所思地看着她的眼睛和臉龐,終於弄明白了她需要的是什麼效果。
言歸正傳,無論是實現瘦身大長腿效果,還是實現瘦臉大眼效果,其本質上都是對圖像中某些區域的像素按照我們設定的規則進行移動,而 OpenGL 的片段着色器天然適合處理像素(紋素)層面的操作。
OpenGL 實現大眼效果
爲了更好的展示大眼效果,動圖中形變參數設置較大,所以看起來比較誇張。
OpenGL 實現大眼效果,可以參照放大鏡的實現原理,即將紋理上一塊區域採樣後映射到一塊相對較大的區域。
本文所實現的大眼效果進行了簡化,是在以人眼爲中心的圓形區域內進行放大,距離圓心越遠,放大的強度越大。
如上圖所示,圓內部爲發生形變(放大)的區域,紅點爲不發生形變時的採樣點(原始紋理座標),綠點爲發生形變時對應的採樣點(紋理座標發生偏移)。
實現眼睛放大效果的着色器腳本(代碼中指定了圖片人眼中心座標和人眼半徑):
#version 300 es
precision highp float;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;
in vec2 v_texCoord;
uniform highp vec2 u_LeftEyeCenterPos;// 左眼中心點
uniform highp vec2 u_RightEyeCenterPos;// 右眼中心點
uniform highp float u_ScaleRatio;//放大係數
uniform highp float u_Radius;// 影響半徑
uniform vec2 u_ImgSize;//圖片分辨率
vec2 warpEyes(vec2 centerPos, vec2 curPos, float radius, float scaleRatio)
{
vec2 result = curPos;
vec2 imgCurPos = curPos * u_ImgSize;
float d = distance(imgCurPos, centerPos);
if (d < radius)
{
float gamma = 1.0 - scaleRatio * pow(smoothstep(0.0, 1.0, d / radius) - 1.0, 2.0);
result = centerPos + gamma * (imgCurPos - centerPos);
result = result / u_ImgSize;
}
return result;
}
void main()
{
vec2 newTexCoord = warpEyes(u_LeftEyeCenterPos, v_texCoord, u_Radius, u_ScaleRatio);
newTexCoord = warpEyes(u_RightEyeCenterPos, newTexCoord, u_Radius, u_ScaleRatio);
outColor = texture(s_TextureMap, newTexCoord);
}
OpenGL 實現瘦臉效果
瘦臉效果的實現,是將指定區域內的像素按照一定的規則進行整體偏移,從而形成一種對臉部擠壓的效果。
如上圖所示,BC表示偏移方向和偏移程度的向量,將圓內的所有像素按照向量BC的方向進行一定程度的偏移,像素偏移的強度,和像素與圓心的距離相關,越靠近圓心強度越大。
爲了簡化計算流程,只做原理性展示,我們選取 了3 個人臉部位的關鍵點(如上圖,左右太陽穴和下巴),再由關鍵點計算出來控制點(太陽穴和下巴的連線的中心點),控制點組成上述的 BC 向量。當然你若想快速驗證瘦臉效果,可以直接手動指定。
瘦臉效果的着色器腳本:
#version 300 es
precision highp float;
layout(location = 0) out vec4 outColor;
in vec2 v_texCoord;
uniform sampler2D s_TextureMap;
uniform vec2 u_texSize;//圖像分辨率
uniform vec4 u_preCtrlPoints;//pre控制點
uniform vec4 u_curCtrlPoints;//cur控制點
uniform float u_reshapeRadius;//影響半徑
uniform float u_reshapeRatio;//強度
vec2 face_slender_1(vec2 prePoint, vec2 curPoint, vec2 texCoord, float radius, vec2 texSize)
{
vec2 pos = texCoord;
vec2 newSrc = prePoint * texSize;
vec2 newDst = curPoint * texSize;
vec2 newTex = texCoord * texSize;
float newRadius = radius;
float r = distance(newSrc, newTex);
if (r < newRadius)
{
float alpha = 1.0 - r / newRadius;
vec2 displacementVec = (newDst - newSrc) * pow(alpha, 2.0) * 0.002 * u_reshapeRatio;
pos = (newTex - displacementVec) / texSize;
}
return pos;
}
void main() {
vec2 leftPreCtrl = u_preCtrlPoints.xy;
vec2 rightPreCtrl = u_preCtrlPoints.zw;
vec2 leftCurCtrl = u_curCtrlPoints.xy;
vec2 rightCurCtrl = u_curCtrlPoints.zw;
vec2 newTexCoord = face_slender_1(leftPreCtrl, leftCurCtrl, v_texCoord, u_reshapeRadius, u_texSize);
newTexCoord = face_slender_1(rightPreCtrl, rightCurCtrl, newTexCoord, u_reshapeRadius, u_texSize);
outColor = texture(s_TextureMap, newTexCoord);
}
實現代碼路徑:
Android_OpenGLES_3_0