作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/103335598相機基礎濾鏡
上文中我們通過 ImageReader 擷取到 Camera2 預覽的 YUV 資料,然後利用 OpenGLES 渲染實作相機預覽,這一節将利用 GLSL (OpenGL 着色器語言)基于不同的着色器實作多種基礎濾鏡。
GLSL 一些使用頻率比較高的内建函數
内建函數 | 函數說明 |
float distance (genType p0, genType p1) | 計算向量 p0 ,p1 之間的距離 |
float length (genType x) | 傳回向量 x 的長度 |
genType floor (genType x) | 傳回小于等于 x 的最大整數值 |
genType ceil (genType x) | 傳回大于等于 x 的最小整數值 |
genType mod (genType x, float y) | 傳回 x – y * floor (x / y) ,即求模計算 % |
float dot (genType x, genType y) | 向量 x ,y 之間的點積 |
vec3 cross (vec3 x, vec3 y) | 向量 x ,y 之間的叉積 |
genType normalize (genType x) | 标準化向量,傳回一個方向和 x 相同但長度為 1 的向量 |
動态網格

動态網格濾鏡主要是将紋理劃分為多個網格,然後根據一個偏移量動态改變網格線的寬度。mod 和 floor 為 GLSL 的内建函數,分别表示取模和取整。需要注意的是,計算之前需要将紋理坐标系轉換為圖檔坐标系,保證網格沒有被拉伸。
//dynimic mesh 動态網格着色器
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
uniform float u_offset;//偏移量
uniform vec2 texSize;//紋理尺寸
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
void main()
{
vec2 imgTexCoord = v_texcoord * texSize;//将紋理坐标系轉換為圖檔坐标系
float sideLength = texSize.y / 6.0;//網格的邊長
float maxOffset = 0.15 * sideLength;//設定網格線寬度的最大值
float x = mod(imgTexCoord.x, floor(sideLength));
float y = mod(imgTexCoord.y, floor(sideLength));
float offset = u_offset * maxOffset;
if(offset <= x
&& x <= sideLength - offset
&& offset <= y
&& y <= sideLength - offset)
{
gl_FragColor = YuvToRgb(v_texcoord);
}
else
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
分屏
分屏濾鏡的原理是在多個指定區域内對整個紋理進行下采樣(縮小),進而實作整個圖像在多個區域内多次顯示。
//分屏(四分屏)
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
void main()
{
vec2 newTexCoord = v_texcoord;
if(newTexCoord.x < 0.5)
{
newTexCoord.x = newTexCoord.x * 2.0;
}
else
{
newTexCoord.x = (newTexCoord.x - 0.5) * 2.0;
}
if(newTexCoord.y < 0.5)
{
newTexCoord.y = newTexCoord.y * 2.0;
}
else
{
newTexCoord.y = (newTexCoord.y - 0.5) * 2.0;
}
gl_FragColor = YuvToRgb(newTexCoord);
}
縮放的圓
縮放的圓效果實作主要依賴偏移量來動态改變圓半徑的大小,在半徑區域内對紋理采樣顯示圖像,在半徑區域外傳回一個固定顔色(如白色)。distance 也是 GLSL 的内建函數,用于計算兩點之間的距離。另外需要注意是,在計算之前首先要将紋理坐标系轉換為圖檔坐标系,否則繪制的将會是一個橢圓形圖像(圖像寬高不同的情況下),想一想為什麼會這樣?
//scale circle 縮放的圓
#version 100
precision highp float;
varying vec2 v_texcoord;
uniform lowp sampler2D s_textureY;
uniform lowp sampler2D s_textureU;
uniform lowp sampler2D s_textureV;
uniform float u_offset;
uniform vec2 texSize;
vec4 YuvToRgb(vec2 uv) {
float y, u, v, r, g, b;
y = texture2D(s_textureY, uv).r;
u = texture2D(s_textureU, uv).r;
v = texture2D(s_textureV, uv).r;
u = u - 0.5;
v = v - 0.5;
r = y + 1.403 * v;
g = y - 0.344 * u - 0.714 * v;
b = y + 1.770 * u;
return vec4(r, g, b, 1.0);
}
void main()
{
vec2 imgTex = v_texcoord * texSize;//将紋理坐标系轉換為圖檔坐标系
float r = (u_offset + 0.208 ) * texSize.x;
if(distance(imgTex, vec2(texSize.x / 2.0, texSize.y / 2.0)) < r)
{
gl_FragColor = YuvToRgb(v_texcoord);
}
else
{
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
}
在計算之前首先要将紋理坐标系轉換為圖檔坐标系,其原因在于紋理縱橫坐标的取值範圍均為 [0, 1] ,從數值上看紋理的縱橫方向長度相同,但是在 OpenGL 采樣時,圖像的寬高比往往不是 1 ,這就導緻了數值相同的縱橫坐标,對應不同的采樣權重,出現了預期繪制圓形而實際上卻繪制出橢圓的情況。
聯系與交流
技術交流/擷取源碼可以添加我的微信:Byte-Flow
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。