作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/104546234
實作原理
OpenGL ES 實作瘦身和大長腿效果比較友善,使用紋理映射技術借助于 OpenGL 的圖像雙線性插值算法可以輕易實作圖像的伸縮效果。
回顧下前面講的,什麼是紋理?在 OpenGL 中,紋理實際上是一個可以被采樣的複雜資料集合,是 GPU 使用的圖像資料結構,紋理分為 2D 紋理、 立方圖紋理和 3D 紋理。2D 紋理是 OpenGLES 中最常用和最常見的紋理形式,是一個圖像資料的二維數組。紋理中的一個單獨資料元素稱為紋素或紋理像素。
什麼是紋理映射?紋理映射就是通過為圖元的頂點坐标指定恰當的紋理坐标,通過紋理坐标在紋理圖中標明特定的紋理區域,最後通過紋理坐标與頂點的映射關系,将標明的紋理區域映射到指定圖元上。
紋理映射也稱為紋理貼圖,簡單地說就是将紋理坐标(紋理坐标系)所指定的紋理區域,映射到頂點坐标(渲染坐标系或OpenGLES 坐标系)對應的區域。
OpenGL 拉伸的原理我們搞清楚了,還有一個問題需要解決:由于不同手機螢幕的分辨率一般不同,這就導緻圖檔被渲染到螢幕上之後,得到結果圖的分辨率不符合我們的預期。
這裡我們需要用到
OpenGL 離屏渲染技術,離屏渲染顧名思義,可以讓渲染操作不用再渲染到螢幕上,而是渲染到一塊離屏緩存中,然後可以使用 glReadPixels 或者 HardwareBuffer 将渲染後的圖像資料讀出來,進而實作在背景利用 GPU 完成對圖像的處理,避免了直接将結果圖渲染到螢幕上導緻的分辨率問題。
效果實作
實作瘦身大長腿效果使用到的着色器腳本,主要就是實作一個正常的紋理采樣。
const char vShaderStr[] =
"#version 300 es \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec2 a_texCoord; \n"
"out vec2 v_texCoord; \n"
"uniform mat4 u_MVPMatrix; \n"
"void main() \n"
"{ \n"
" gl_Position = u_MVPMatrix * a_position; \n"
" v_texCoord = a_texCoord; \n"
"} \n";
const char fShaderStr[] =
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
"uniform sampler2D s_TextureMap; \n"
"void main() \n"
"{ \n"
" outColor = texture(s_TextureMap, v_texCoord);\n"
"}";
瘦身效果實作
瘦身效果實作是将指定的身體區域映射到一個寬度相對減小的區域,而指定身體區域之外的部分區域保持原來的比例,這樣渲染出來圖像的身體區域進行了壓縮(瘦身)。類似,想實作變胖(拉仇恨)的效果,便是将指定的身體區域映射到一個寬度相對增大的區域。
如圖所示,為實作瘦身我們使用了 8 個頂點 V0~V7 ,8 個頂點将圖像分割成了 6 個三角面片,其中 V2、V3、V4、V5 四個頂點所圍成的區域表示要發生形變的區域,箭頭方向表示形變的方式是壓縮,各個頂點坐标的 x 分量需要偏移 m_dt (根據壓縮方向确定正負偏移)。
實作瘦身效果的着色器程式使用的頂點坐标和紋理坐标:
/** 8 points horizontal mode*/
GLfloat vFboVertices[] = {
-1.0f, 1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
(x1 - m_dt) / (1 + m_dt), -1.0f, 0.0f,
(x1 - m_dt) / (1 + m_dt), 1.0f, 0.0f,
(x2 + m_dt) / (1 + m_dt), 1.0f, 0.0f,
(x2 + m_dt) / (1 + m_dt), -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
};
//fbo 紋理坐标
GLfloat vFboTexCoors[] = {
0.0f, 0.0f,
0.0f, 1.0f,
m_DeformationRect.left, 1.0f,
m_DeformationRect.left, 0.0f,
m_DeformationRect.right, 0.0f,
m_DeformationRect.right, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
};
其中 m_dt 表示控制瘦身程度的形變因子,m_DeformationRect 表示一個歸一化的區域選擇框。我們可以在 UI 上調節進度條來控制改變形變程度,滑動選擇框來制定形變的區域。
大長腿效果實作
大長腿效果的實作可以類比瘦身,将指定的腿部區域映射到一個高度相對增大的區域,而指定腿部區域之外的部分區域保持原來的比例,這樣渲染出來圖像的腿部區域進行了拉伸(大長腿)。類似,想實作小短腿(如果有人用到的話)的效果,便是将指定的身體區域映射到一個高度相對減小的區域(實作壓縮)。
如圖所示,為實作大長腿效果我們同樣使用了 8 個頂點 V0~V7 其中 V1、V4、V7、V2 四個頂點所圍成的區域表示要發生形變的區域,箭頭方向表示形變的方式是拉伸,各個頂點坐标的 y 分量需要偏移 m_dt (根據拉伸方向确定正負偏移)。
/** 8 points vertical mode*/
GLfloat vFboVertices[] = {
-1.0f, 1.0f, 0.0f,
-1.0f, (y1 + m_dt) / (1 + m_dt), 0.0f,
1.0f, (y1 + m_dt) / (1 + m_dt), 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, (y2 - m_dt) / (1 + m_dt), 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
1.0f, (y2 - m_dt) / (1 + m_dt), 0.0f,
};
//fbo 紋理坐标
GLfloat vFboTexCoors[] = {
0.0f, 0.0f,
0.0f, m_DeformationRect.top,
1.0f, m_DeformationRect.top,
1.0f, 0.0f,
0.0f, m_DeformationRect.bottom,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, m_DeformationRect.bottom,
};
不同的是,大長腿效果的實作是豎直方向上的拉伸,而瘦身的實作是水準方向上的拉伸。另外還需注意的是,我們對圖檔進行拉伸或者縮放之後,結果圖的實際尺寸會發生改變,是以每次調整形變後,都需要為離屏渲染的幀緩沖區對象 FBO 綁定對應新尺寸的紋理作為顔色附着。
//由于圖像尺寸改變,删除舊紋理
if (m_FboTextureId) {
glDeleteTextures(1, &m_FboTextureId);
}
//生成新紋理
glGenTextures(1, &m_FboTextureId);
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, m_FboId);
glBindTexture(GL_TEXTURE_2D, m_FboTextureId);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_FboTextureId,
0);
//判斷是水準拉伸還是豎直拉伸,然後按照新圖像的尺寸初始化紋理
if (m_bIsVerticalMode) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_RenderImg.width,
static_cast<GLsizei>(m_RenderImg.height * (1 + m_dt)), 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
static_cast<GLsizei>(m_RenderImg.width * (1 + m_dt)),
m_RenderImg.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
LOGCATE("MyGLRender::InitBuffers glCheckFramebufferStatus status != GL_FRAMEBUFFER_COMPLETE");
}
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, GL_NONE);
離屏渲染時,由于圖像尺寸發生改變,這個也需要對視口進行調整:
//判斷是水準拉伸還是豎直拉伸,設定視口大小
if (m_bIsVerticalMode) {
glViewport(0, 0, static_cast<GLsizei>(m_RenderImg.width),
static_cast<GLsizei>(m_RenderImg.height * (1 + m_dt)));
} else {
glViewport(0, 0, static_cast<GLsizei>(m_RenderImg.width * (1 + m_dt)),
static_cast<GLsizei>(m_RenderImg.height));
}
與 AI 算法結合
我們現在是手動指定形變區域實作瘦身和大長腿效果,但是如果與身體關鍵點檢測算法一起使用,我們便可以省去手動操作這一步,拿到身體的關鍵點(算法檢測結果)便可以計算出人體及各個部位的區域,按照類似的原理我們還可以實作瘦腰、瘦腿、豐胸等效果,我們後續将與 AI 算法結合來開發更加豐富的功能。
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。