你們好,我係蒼王。java
如下是我這個系列的相關文章,有興趣能夠參考一下,能夠給個喜歡或者關注個人文章。git
OpenGL和音視頻相關的文章,將會在 [OpenGL]將來視覺-MagicCamera3實用開源庫 當中給你們呈現github
裏面會記錄我編寫這個庫的一些經歷和經驗。數組
提到抖音特效,相信不少人都會看過這篇文章緩存
裏面提供了六種抖音特效的編寫和實現,是使用java代碼來實現的,其中提供的demo,並沒提供能夠選擇哪一種效果,默認錄製完小視頻後使用了幻覺的效果。框架
這邊花了一些時間將這六種效果編寫爲C++的opengles的代碼,並可以在預覽的界面中能夠選擇,已經支持15秒短視頻錄製(硬解錄製)。函數
這裏整個框架都是使用C++來編寫的,因此若是你尋找C++框架和多種濾鏡特效,將會很是適合大家,MagicCamera3,歡迎fork和star。post
效果說明,上一幀的透明度不斷減小,疊加在如今這幀的上面,並有放大擴散效果。 ui
void MagicSoulOutFilter::onDrawArraysPre() {
//存在兩個圖層,開啓顏色混合
glEnable(GL_BLEND);
//透明度混合
// 表示源顏色乘以自身的alpha 值,目標顏色乘目標顏色值混合,若是不開啓,直接被目標的畫面覆蓋
glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA);
//最大幀數mMaxFrames爲15,靈魂出竅只顯示15幀,設定有8幀不顯示
mProgress = (float)mFrames/mMaxFrames;
//當progress大於1後置0
if (mProgress > 1.0f){
mProgress = 0;
}
mFrames++;
//skipFrames爲8,23幀後置爲0
if (mFrames > mMaxFrames + mSkipFrames){
mFrames = 0;
}
//setIdentityM函數移植於java中Matrix.setIdentity,初始化矩陣全置爲0
setIdentityM(mMvpMatrix,0);
//第一幀沒有放大,直接設置單位矩陣
glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix);
//透明度爲1
float backAlpha = 1;
if (mProgress > 0){ //若是是靈魂出竅效果顯示中
//計算出竅時透明度值
alpha = 0.2f - mProgress * 0.2f;
backAlpha = 1 - alpha;
}
//設置不顯示靈魂出竅效果時,背景不透明度,否則會黑色
glUniform1f(mAlphaLocation,backAlpha);
/**顯示相機畫面**/
}
void MagicSoulOutFilter::onDrawArraysAfter() {
if (mProgress>0){ //若是是靈魂出竅效果顯示中
glUniform1f(mAlphaLocation,alpha);
//設置放大值
float scale = 1 + mProgress;
//設置正交矩陣放大
scaleM(mMvpMatrix,0,scale,scale,scale);
//設置到shader裏面
glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix);
//畫出靈魂出校效果
glDrawArrays(GL_TRIANGLE_STRIP,0,4);
}
//關閉顏色混合
glDisable(GL_BLEND);
}
複製代碼
這個效果要注意的是
1.要開啓顏色混合,否則靈魂出竅的畫面會直接覆蓋到上面,直接變成了循環放大的效果
2.攝像頭採集的幀數並非恆定的,會有變化的,要看fps,因此會偶爾感受效果幀圖時快時慢
3.使用了java的Matrix中的函數實現正交矩陣的變換,有其餘人推薦glm的函數,不知道是否更加高效就是了。
抖動效果包含着兩種基礎效果
1.放大
2.色值偏移
上一個靈魂出竅的效果已經分析過放大效果了,是同樣的。
那這裏最主要是色值偏移的效果,下面是色值偏移的計算。
#version 300 es
precision mediump float;
//每一個點的xy座標
in vec2 textureCoordinate;
//對應紋理
uniform sampler2D inputImageTexture;
uniform float uTextureCoordOffset;
out vec4 glFragColor;
void main()
{
//直接採樣藍色色值
vec4 blue = texture(inputImageTexture,textureCoordinate);
//從效果看,綠色和紅色色值特別明顯,因此須要對其色值偏移。綠色和紅色須要分開方向,否則重疊一塊兒會混色。
//座標向左上偏移,而後再採樣色值
vec4 green = texture(inputImageTexture, vec2(textureCoordinate.x + uTextureCoordOffset, textureCoordinate.y + uTextureCoordOffset));
//座標向右下偏移,而後再採樣色值
vec4 red = texture(inputImageTexture,vec2(textureCoordinate.x - uTextureCoordOffset,textureCoordinate.y - uTextureCoordOffset));
//RG兩個通過偏移後分別採樣,B沿用原來的色值,透明度爲1,組合最終輸出
glFragColor = vec4(red.r,green.g,blue.b,blue.a);
}
複製代碼
在畫圖前調用計算偏移和放大值
void MagicShakeEffectFilter::onDrawArraysPre() {
mProgress = (float)mFrames/mMaxFrames;
if (mProgress>1){
mProgress = 0;
}
mFrames++;
if (mFrames>mMaxFrames + mSkipFrames){
mFrames = 0;
}
float scale= 1.0f+0.2f*mProgress;
//清空正交矩陣
setIdentityM(mMvpMatrix,0);
//設置正交矩陣放大,在原位置的地方放大長寬
scaleM(mMvpMatrix,0,scale,scale,1.0f);
glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix);
//設置色值偏移量
float textureCoordOffset = 0.01f *mProgress;
glUniform1f(mTextureCoordOffsetLocation,textureCoordOffset);
}
複製代碼
其效果分爲兩部分,
1.某一行像素值偏移一段距離,產生割裂的感受,並且是隨着y軸變化的
(1)經過輸入紋理中y值到生成(-1到1)的值 jitter
(2)jitter使用step比較輸入的y偏移值來判斷是否產生偏移
(3)取輸入的x偏移值賦給jitter
(4)經過計算偏移值再計算RGB的份量
(5)最後組合輸出
2.色值偏移
如下是片斷着色器代碼,估計初學入門者跟我同樣,看得估計也不是很懂,這邊須要瞭解glsl的內建函數,還有色值偏移,還有的顏色的敏感性的。
#version 300 es
precision highp float;
in vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
//這是個二階向量,x是橫向偏移的值,y是閾值
uniform vec2 uScanLineJitter;
//顏色偏移的值
uniform float uColorDrift;
out vec4 glFragColor;
float nrand(in float x,in float y){
//fract(x) = x - floor(x);
//dot是向量點乘,,sin就是正弦函數
return fract(sin(dot(vec2(x,y) ,vec2(12.9898,78.233))) * 43758.5453);
}
void main()
{
float u = textureCoordinate.x;
float v = textureCoordinate.y;
//用y計算0~1的隨機值,再取值-1~1的數
float jitter = nrand(v ,0.0) * 2.0 - 1.0;
float drift = uColorDrift;
//計算向左或向右偏移
//意思是,若是第一個參數大於第二個參數,那麼返回0,不然返回1
float offsetParam = step(uScanLineJitter.y,abs(jitter));
//若是offset爲0就不偏移,若是爲1,就偏移jtter*uScanLineJitter.x的位置
jitter = jitter * offsetParam * uScanLineJitter.x;
//這裏計算最終的像素值,紋理座標是0到1之間的數,若是小於0,那麼圖像就捅到屏幕右邊去,若是超過1,那麼就捅到屏幕左邊去,造成顏色偏移
vec4 color1 = texture(inputImageTexture,fract(vec2(u + jitter ,v)));
vec4 color2 = texture(inputImageTexture,fract(vec2(u + jitter + v * drift ,v)));
glFragColor = vec4(color1.r ,color2.g ,color1.b ,1.0);
}
複製代碼
經過幀數的不一樣來計算偏移
void MagicGlitchFilter::onDrawArraysPre() {
glUniform2f(mScanLineJitterLocation,mJitterSequence[mFrames],mThreshHoldSequence[mFrames]);
glUniform1f(mColorDriftLocation,mDriftSequence[mFrames]);
mFrames ++;
if (mFrames>mMaxFrames){
mFrames = 0;
}
}
void MagicGlitchFilter::onDrawArraysAfter() {
}
void MagicGlitchFilter::onInit() {
GPUImageFilter::onInit();
mScanLineJitterLocation = glGetUniformLocation(mGLProgId,"uScanLineJitter");
mColorDriftLocation = glGetUniformLocation(mGLProgId,"uColorDrift");
}
void MagicGlitchFilter::onInitialized() {
GPUImageFilter::onInitialized();
//顏色偏移量
mDriftSequence = new float[9]{0.0f, 0.03f, 0.032f, 0.035f, 0.03f, 0.032f, 0.031f, 0.029f, 0.025f};
//偏移的x值
mJitterSequence = new float[9]{0.0f, 0.03f, 0.01f, 0.02f, 0.05f, 0.055f, 0.03f, 0.02f, 0.025f};
//偏移的y值
mThreshHoldSequence = new float[9]{1.0f, 0.965f, 0.9f, 0.9f, 0.9f, 0.6f, 0.8f, 0.5f, 0.5f};
}
複製代碼
縮放效果是最簡單的,經過中間幀的計算出放大和縮小正交矩陣
void MagicScaleFilter::onDrawArraysPre() {
if (mFrames <= mMiddleFrames){ //根據中間幀爲間隔,放大過程
mProgress = mFrames * 1.0f /mMiddleFrames;
} else{ //縮小過程
mProgress = 2.0f - mFrames * 1.0f /mMiddleFrames;
}
setIdentityM(mMvpMatrix, 0);
float scale = 1.0f+0.3f*mProgress;
//正交矩陣放大
scaleM(mMvpMatrix,0,scale,scale,scale);
glUniformMatrix4fv(mMvpMatrixLocation,1,GL_FALSE,mMvpMatrix);
mFrames++;
if (mFrames>mMaxFrames){
mFrames = 0;
}
}
複製代碼
也就是經過取幀的時間來計算白色比例(rgb 0爲黑色,1爲白色)
#version 300 es
precision mediump float;
in vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
//控制曝光程度
uniform float uAdditionalColor;
out vec4 glFragColor;
void main()
{
vec4 color = texture(inputImageTexture,textureCoordinate);
//最大值爲1,色值所有變白,最小值回回到本來的色值
glFragColor = vec4(color.r + uAdditionalColor,color.g+uAdditionalColor,color.b+uAdditionalColor,color.a);
}
複製代碼
輸入progress比例值
void MagicShineWhiteFilter::onDrawArraysPre() {
if (mFrames<=mMiddleFrames){ //根據中間值來增長色值
mProgress = mFrames*1.0f /mMiddleFrames;
} else{ //減小色值
mProgress = 2.0f-mFrames*1.0f /mMiddleFrames;
}
mFrames++;
if (mFrames > mMaxFrames){
mFrames = 0;
}
glUniform1f(mAdditionColorLocation,mProgress);
}
複製代碼
幻覺這個效果須要使用Lut紋理,以及fbo緩存混色疊加
1.Lut圖說白了,就是顏色查找替換,Lut圖通常可使用ps輸出,經過設計師給出,能夠大大減小編寫濾鏡的的編寫。
2.FBO提供了一系列的緩衝區,包括顏色緩衝區、深度緩衝區和模板緩衝區(須要注意的是FBO中並無提供累積緩衝區)這些邏輯的緩衝區在FBO中被稱爲 framebuffer-attachable images說明它們是能夠綁定到FBO的二維像素數組。
FBO中有兩類綁定的對象:紋理圖像(texture images)和渲染圖像(renderbuffer images)。若是紋理對象綁定到FBO,那麼OpenGL就會執行渲染到紋理(render to texture)的操做,若是渲染對象綁定到FBO,那麼OpenGL會執行離屏渲染(offscreen rendering),這裏這兩種都會使用到。
初始化頂點着色器和片斷着色器,Lut紋理,以及初始化fbo id
void MagicVerigoFilter::onInitialized() {
GPUImageFilter::onInitialized();
//用於上一幀數據,用於下一幀
mLastFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"common_f.glsl")->c_str());
//此幀使用的繪製program
mCurrentFrameProgram = loadProgram(readShaderFromAsset(mAssetManager,"nofilter_v.glsl")->c_str(),readShaderFromAsset(mAssetManager,"verigo_f2.glsl")->c_str());
//Lut紋理
mLutTexture = loadTextureFromAssetsRepeat(mAssetManager,"lookup_vertigo.png");
}
void MagicVerigoFilter::onInputSizeChanged(const int width, const int height) {
mScreenWidth = width;
mScreenHeight = height;
//創建三個fbo紋理
mRenderBuffer = new RenderBuffer(GL_TEXTURE8,width,height);
mRenderBuffer2 = new RenderBuffer(GL_TEXTURE9,width,height);
mRenderBuffer3 = new RenderBuffer(GL_TEXTURE10,width,height);
}
複製代碼
RenderBuffer使用到的fbo初始化,紋理綁定,和紋理記錄
//fbo初始化
RenderBuffer::RenderBuffer(GLenum activeTextureUnit, int width, int height) {
mWidth = width;
mHeight = height;
//激活紋理插槽
glActiveTexture(activeTextureUnit);
// mTextureId = get2DTextureRepeatID();
//紋理id
mTextureId = get2DTextureID();
// unsigned char* texBuffer = (unsigned char*)malloc(sizeof(unsigned char*) * width * height * 4);
// glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,texBuffer);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
//生成fb的id
glGenFramebuffers(1,&mFrameBufferId);
glBindFramebuffer(GL_FRAMEBUFFER,mFrameBufferId);
//生成渲染緩衝區id
glGenRenderbuffers(1,&mRenderBufferId);
glBindRenderbuffer(GL_RENDERBUFFER,mRenderBufferId);
//指定存儲在 renderbuffer 中圖像的寬高以及顏色格式,並按照此規格爲之分配存儲空間
glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT16,width,height);
//復位
glBindFramebuffer(GL_FRAMEBUFFER,0);
glBindRenderbuffer(GL_RENDERBUFFER,0);
}
//fbo繪製前配置
void RenderBuffer::bind() {
//清空視口
glViewport(0,0,mWidth,mHeight);
//綁定fb的紋理id
glBindFramebuffer(GL_FRAMEBUFFER,mFrameBufferId);
//綁定2D紋理關聯到fbo
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,mTextureId,0);
//綁定fbo紋理到渲染緩衝區對象
glBindRenderbuffer(GL_RENDERBUFFER,mRenderBufferId);
//將渲染緩衝區做爲深度緩衝區附加到fbo
glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,mRenderBufferId);
//檢查fbo的狀態
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE){
ALOGE("framebuffer error");
}
}
//fbo記錄
void RenderBuffer::unbind() {
//移除綁定
glBindFramebuffer(GL_FRAMEBUFFER,0);
glBindRenderbuffer(GL_RENDERBUFFER,0);
// glActiveTexture(GL_TEXTURE0);
}
複製代碼
1.這裏先保存攝像頭數據到第一個fbo中。
2.攝像頭數據和Lut紋理混合,以及再使用上一幀(第二個fbo的紋理)的色值組合後,顯示到屏幕上。
3.將第2步顯示的畫面數據保存到第三個fbo中
4.將第三個fbo中的紋理再次保存到第二個fbo中,用於下一幀的繪製。(沒法減小這一步,否則會灰屏)
void MagicVerigoFilter::onDrawPrepare() {
//綁定紋理
mRenderBuffer->bind();
glClear(GL_COLOR_BUFFER_BIT);
}
void MagicVerigoFilter::onDrawArraysAfter() {
//將攝像頭的數據保存到mRenderBuffer的fbo中
mRenderBuffer->unbind();
//在頂層畫幀,真正畫繪製的畫面
drawCurrentFrame();
mRenderBuffer3->bind();
//繪製當前幀到mRenderBuffer3的fbo中
drawCurrentFrame();
//將當前幀保存到生成mRenderBuffer3的fbo
mRenderBuffer3->unbind();
mRenderBuffer2->bind();
//使用mRenderBuffer的fbo,再繪製mRenderBuffer2的紋理fbo中
drawToBuffer();
//生成mRenderBuffer2的fbo,用於下一幀的繪製
mRenderBuffer2->unbind();
mFirst = false;
}
複製代碼
色彩混合仍是要看shader。
#version 300 es
precision mediump float;
in mediump vec2 textureCoordinate;
uniform sampler2D inputImageTexture; //當前輸入紋理
uniform sampler2D inputTextureLast; //上一次紋理
uniform sampler2D lookupTable; // 顏色查找表紋理
out vec4 glFragColor;
//固定的Lut紋理對換計算
vec4 getLutColor(vec4 textureColor,sampler2D lookupTexture){
float blueColor = textureColor.b * 63.0;
mediump vec2 quad1;
quad1.y = floor(floor(blueColor)/8.0);
quad1.x = floor(blueColor) - quad1.y*8.0;
mediump vec2 quad2;
quad2.y = floor(ceil(blueColor) /8.0);
quad2.x = ceil(blueColor) - quad2.y*8.0;
highp vec2 texPos1;
texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos1.y = (quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
texPos1.y = 1.0-texPos1.y;
highp vec2 texPos2;
texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);
texPos2.y = (quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g);
texPos2.y = 1.0-texPos2.y;
lowp vec4 newColor1 = texture(lookupTexture,texPos1);
lowp vec4 newColor2 = texture(lookupTexture,texPos2);
lowp vec4 newColor = mix(newColor1,newColor2,fract(blueColor));
return newColor;
}
void main(){
//上一幀紋理
vec4 lastFrame = texture(inputTextureLast,textureCoordinate);
//此幀對應的Lut轉換紋理
vec4 currentFrame = getLutColor(texture(inputImageTexture,textureCoordinate),lookupTable);
//上一幀和此幀混色處理
glFragColor = vec4(0.95 * lastFrame.r + 0.05* currentFrame.r,currentFrame.g * 0.2 + lastFrame.g * 0.8, currentFrame.b,1.0);
}
複製代碼
這裏最難理解應該是疊色,移動的時候,很明顯看到移出的是藍色,取出此幀的藍色部分移出,藍色的部分就應該全取此幀的藍色值。
因此多試幾回的經驗值就是
glFragColor = vec4(0.95 * lastFrame.r + 0.05* currentFrame.r,currentFrame.g * 0.2 + lastFrame.g * 0.8, currentFrame.b,1.0);
暫時介紹六種濾鏡效果,之後會不定時更新效果,有興趣的同窗,能夠關注點贊一下。
新建一個專欄羣,但願有興趣的同窗多多討論。