OpenGL (一)OpenGL ES 繪制基礎

OpenGL (一)OpenGL ES 繪制基礎
OpenGL (二)GLSurface 視頻錄制
OpenGL (三)濾鏡filter 應用
OpenGL (四)貼紙和磨皮理論

Open Graphics Library
圖形領域的工業(yè)標準雳殊,是一套與硬件無關的跨編程語言、跨平臺的俺孙、專業(yè)的圖形編程(軟件)接口。它用于二維聚谁、三維圖像搪搏,是一個功能強大,調(diào)用方便的底層圖形庫花盐。

OpenGL ES(OpenGL for Embedded Systems)
針對手機执虹、PDA和游戲主機等嵌入式設備而設計的OpenGL API 子集拓挥。

在Android SDK與NDK中均有提供OpenGL ES的類庫。所以我們可以借助Java袋励、C/C++來使用OpenGL

和面向?qū)ο蟛煌钠。琌penGL 是個狀態(tài)機,面向過程編程茬故。代碼量比較多盖灸,難度較大。但是現(xiàn)在流 行的各種美顏相機均牢、短視頻APP實現(xiàn)各種圖像渲染效果都需要OpenGL來完成糠雨。

圖像處理流程一般的方式如下才睹;

圖像處理流程.png

很多圖形處理的書都有這樣一個經(jīng)典的圖

OpenGL管線.png

抓了一張圖徘跪,還是說明一下,OpenGL管線琅攘,通俗 的說所有的圖形都是由三角行構(gòu)成垮庐,只要給定定點就好。
把三角形細分成像素點坞琴,每個像素點放大哨查,就是我們第三個Fragments。根據(jù)像素點的位置剧辐,用著色器上色寒亥。
最后精工Fragment處理邮府,行程圖像。

EGL

OpenGL ES只是圖形API溉奕,不負責管理(顯示)窗口褂傀,窗口的管理交由各個設備自己來完成。OpenGL ES調(diào)用用于渲染紋理多邊形加勤,而 EGL 調(diào)用用于將渲染放到屏幕上仙辟。

GLSurfaceView

調(diào)用任何 OpenGL 函數(shù)前,必須已經(jīng)創(chuàng)建了 OpenGL 上下文鳄梅。Android中GLSurfaceView中會為我們初始化OpenGL上下文叠国。

內(nèi)部啟動一個子線程(GL線 程)來初始化ELG環(huán)境,并完成OpenGL的繪制戴尸。使用GLSurfaceView粟焊,我們只能在這個EGL線程調(diào)用 OpenGL函數(shù)。

類關系.png

繼承至SurfaceView校赤,它內(nèi)嵌的Surface專門負責OpenGL渲染吆玖。

  • 管理Surface與EGL;
  • 允許自定義渲染器(render)马篮;
  • 支持按需渲染和連續(xù)渲染沾乘;

GLThread定義在GLSurfaceView內(nèi)部,我們怎么在這個線程中去執(zhí)行我們編寫的代碼?
GLSurfaceView并不是一創(chuàng)建就會啟動GL線程初始化環(huán)境浑测。你可以把GLSurfaceView當成普通的 SurfaceView來使用翅阵。但是如果我們需要使用OpenGL ES在GLSurfaceView中繪制,那么我們需要調(diào) 用:


//使用OpenGL ES 2.0 context. 
setEGLContextClientVersion(2); 
//設置渲染回調(diào)接口
renderer = new CameraRender(this);
setRenderer(renderer);
/**
 * 刷新方式:注意必須在setRenderer 后面設置
 * RENDERMODE_WHEN_DIRTY 手動刷新迁央,調(diào)用requestRender()回調(diào)一次渲染器的onDraw方
 * 法;
 * RENDERMODE_CONTINUOUSLY 自動刷新掷匠,大概16ms自動回調(diào)一次渲染器的onDraw方法
 */
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

其中 CameraRender 是一個實現(xiàn)了 GLSurfaceView.Renderer 接口的類。這個接口定義為:

public interface Renderer {
//窗口畫布準備完成
void onSurfaceCreated(GL10 gl, EGLConfig config); 
//畫布發(fā)生改變(如:橫豎切換)
void onSurfaceChanged(GL10 gl, int width, int height); 
//繪制
void onDrawFrame(GL10 gl);
}

所謂的刷新就是回調(diào)這個接口的 onDrawFrame 方法讓我們進行OpenGL ES的繪制調(diào)用岖圈。其實 GLSurfaceView的setRenderer方法源碼實現(xiàn)為:

    public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (mEGLConfigChooser == null) {
            mEGLConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (mEGLContextFactory == null) {
            mEGLContextFactory = new DefaultContextFactory();
        }
        if (mEGLWindowSurfaceFactory == null) {
            mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        mRenderer = renderer;
        mGLThread = new GLThread(mThisWeakRef);
        mGLThread.start();
    }

可以看到讹语,如果需要在GLSurfaceView中配置的ELG環(huán)境并使用OpenGL ES就必須設置渲染接口。這樣 才會啟動線程初始化OpenGL ES的環(huán)境蜂科。而渲染接口需要實現(xiàn)的方法就是在GLThread回調(diào)的顽决。

GLThread定義在GLSurfaceView內(nèi)部,我們怎么在這個線程中去執(zhí)行我們編寫的代碼?
渲染接口Renderer的三個回調(diào)方法就是在該線程中回調(diào)的导匣。

下面我們需要使用OpenGL ES在GLSurfaceView中顯示攝像頭的圖像才菠。 這里在之前的文章有說過
請轉(zhuǎn)到 RTMP(四)交叉編譯與CameraX 結(jié)尾有提到

Renderer渲染
前面我們說了,我們需要在GLThread中去調(diào)用OpenGL ES的方法來完成渲染贡定。因此我們的主要工作就 是在實現(xiàn)了Renderer接口的類中我們需要實現(xiàn)的: onSurfaceCreated 赋访、 onSurfaceChanged 與 onDrawFrame 三個方法中完成繪制。

onSurfaceCreated
在OpenGL ES環(huán)境準備完成之后,會在GLThread中回調(diào) onSurfaceCreated 方法蚓耽。在這個方法中我們 需要準備我們需要繪制的圖像渠牲。

OpenGL紋理可以看成是能在OpenGL中使用的圖像,在代碼中我們通過id來操作紋理步悠。

 @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    //創(chuàng)建OpenGL 紋理 ,把攝像頭的數(shù)據(jù)與這個紋理關聯(lián)
        textures = new int[1];  //當做能在opengl用的一個圖片的ID
        mCameraTexure.attachToGLContext(textures[0]);
        // 當攝像頭數(shù)據(jù)有更新回調(diào) onFrameAvailable
        mCameraTexure.setOnFrameAvailableListener(this);

        screenFilter = new ScreenFilter(cameraView.getContext());
    }

 @Override
    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
        //  請求執(zhí)行一次 onDrawFrame
        cameraView.requestRender();
    }

讓攝像頭數(shù)據(jù)綁定到我們創(chuàng)建的紋理 textures[0] 之后嘱兼,后續(xù)我們會將 textures[0] 交給OpenGL ES 的Sharder著色器去進行渲染,并且在渲染時也能去實現(xiàn)各種圖像效果贤徒。

OpenGL Sharder(著色器)需要使用GLSL(著色器語言)編寫

上述代碼中的 mCameraTexure 就是攝像頭預覽數(shù)據(jù)繪制的窗口芹壕。 attachToGLContext 方法能夠讓攝像
頭預覽的窗口與我們的紋理綁定起來。

攝像頭捕獲

由于我們本次會使用CameraX來完成攝像機的使用接奈,使用CameraX踢涌,將捕獲的圖像與OpenGL紋理關聯(lián) 起來也非常簡單。再來回顧下我們之前直接通過CameraX與TextureView顯示預覽的代碼序宦。

 // 預覽配置
    private Preview getPreView() {
        // 分辨率并不是最終的分辨率睁壁,CameraX會自動根據(jù)設備的支持情況,結(jié)合你的參數(shù)互捌,設置一個最為接近的分辨率
        PreviewConfig previewConfig = new PreviewConfig.Builder()
                .setTargetResolution(new Size(640, 480))
                .setLensFacing(currentFacing) //前置或者后置攝像頭
                .build();
        Preview preview = new Preview(previewConfig);
        preview.setOnPreviewOutputUpdateListener(listener);
        return preview;
    }


// 綁定聲明周期
        CameraX.bindToLifecycle(lifecycleOwner, getPreView());

@Override
    public void onUpdated(Preview.PreviewOutput output) {
        mCameraTexure = output.getSurfaceTexture();
//        if (mCameraV.getSurfaceTexture() != surfaceTexture) {
//            if (textureView.isAvailable()) {
//                ViewGroup parent = (ViewGroup) displayer.getParent();
//                parent.removeView(displayer);
//                parent.addView(displayer, 0);
//                parent.requestLayout(); }
//                //設置布局中TextureView中的紋理畫布完成預覽
//            textureView.setSurfaceTexture(mCameraTexure); }
//        }
    }

著色器

著色器(Shader)是運行在GPU上的小程序潘明。

頂點著色器(vertex shader)
如何處理頂點、法線等數(shù)據(jù)的小程序秕噪。

#version 120
attribute vec4 vPosition;//變量 float[4] 一個頂點 java 傳過來
attribute vec2 vCoord;//傳給片元進行采樣的紋理坐標
varying vec2 aCoord;//易變變量 和片元寫的一模一樣 會傳給片元
void main() {
//    gl_Position = vec4(vec3(0.0), 1.0);
    // 內(nèi)置變量:把坐標點賦值給gl_position就ok
    gl_Position = vPosition;
    aCoord = vCoord;
}

這段程序就是一個頂點著色器的代碼钳降。與Java或者C一樣,程序入口就是 main 方法腌巾。在 main 方法中只 要我們把要畫的物體形狀的點坐標給到 gl_position 即可遂填。 vPosition 就是我們在CPU中確定的物體 形狀坐標點。現(xiàn)在我們需要繪制的是攝像頭采集圖像澈蝙,也就是矩形吓坚,那就需要傳遞4個頂點坐標數(shù)據(jù)到 著色器中。

與我們熟悉的Java語法類似灯荧, vPosition 是變量名礁击, vec4 是類型,而應用程序傳遞到頂點著色器中的 變量值需要使用 attribute 修飾逗载《吡可以看成是 in 頂點著色器輸入變量的修飾。

vec4 其實就是vector向量撕贞,4表示這個向量包含4個數(shù)據(jù)更耻。需要注意的是vec4只能存儲float數(shù)據(jù)测垛, 如果需要存儲整型則需要使用ivec4捏膨。

aCoord 定義為存儲2個float的vec2類型,被varying修飾。如果說 attribute 是 in 輸入變量的修飾号涯, 那么 varying 就是 out 輸出變量目胡。我們會在片元著色器使用這個變量。

這個頂點著色器中沒有任何邏輯链快,只進行了賦值的操作誉己,那么作為 attribute 輸入的值 vPosition 與 vCoord 就需要我們在Android中傳遞進來。這兩個值分別為頂點坐標與紋理坐標域蜗。

我們需要根據(jù)OpenGL世界坐標系傳遞頂點坐標點來確定繪制的幾何形狀巨双,需要在繪制的幾何表面進行 貼圖,就需要按照紋理坐標貼合幾何頂點霉祸。頂點著色器執(zhí)行4次筑累, vPostion 接收的點坐標與 vCoord 分 別為:

坐標.png
-1.0,-1.0 與 0,0
 1.0,-1.0 與 1.0,0
 -1.0,1.0 與 0,1.0 
1.0,1.0 與 1.0,1.0

和U3D 坐標系類似

片元著色器(fragment shader)
如何處理光、陰影丝蹭、遮擋慢宗、環(huán)境等等對物體表面的影響,最終生成一副圖像的小程序奔穿。

#version 120
precision mediump float;//數(shù)據(jù)精度
varying vec2 aCoord;
// uniform 片元著色器必須用這個
uniform samplerExternalOES vTexture;//samplerExternalOES 圖片镜沽,采樣器
void main() {
//    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    gl_FragColor = texure2D(vTexture,aCoord);//rgba
}

片元著色器中,使用內(nèi)置函數(shù) texture2D 采集對應坐標點的像素贱田,賦值給 gl_FragColor 即可缅茉。

#extension GL_OES_EGL_image_external : require :Android攝像頭只能用samplerExternalOES 類型的紋理去接收攝像頭的畫面,而使用samplerExternalOES需要開啟 GL_OES_EGL_image_external功能男摧。

uniform 變量是Android中需要傳遞給著色器的變量宾舅,不能被著色器修改〔室校可以用于修飾共享變 量(在頂點和片元中聲明方式完全一樣)筹我。

aCoord 的定義需要和頂點著色器一模一樣,但是在片元著色器能讀取之前會通過光柵化傳遞帆离, 光柵化程序在三角形的三個頂點之間進行插值處理蔬蕊,會訪問三個頂點之間每一個像素,然后對每 個像素點執(zhí)行片元著色器哥谷。所以在片元著色器中 aCoord 的值可以看成幾何中每一個像素點的坐 標岸夯。

texture2D(vTexture,aCoord) ,則是利用攝像頭紋理的采樣器 vTexture 獲取 aCoord 坐標的 像素RGBA值并賦值給 gl_FragColor 们妥,OpenGL就知道當前處理的片元是什么顏色從而繪制猜扮。

實際上,在Android中我們就是通過OpenGL接口給著色器傳參就好啦监婶。我們可以在渲染接口的 onDraw 中調(diào)用OpenGL動態(tài)的給著色器傳遞參數(shù)旅赢。而各種繪制效果的處理在著色器中來完成齿桃。

GLSL

OpenGL著色語言(OpenGL Shading Language)

attribute
屬性變量,頂點著色器輸入數(shù)據(jù)煮盼。如:頂點坐標短纵、紋理坐標、顏色等僵控。

uniforms
一致變量香到。在著色器執(zhí)行期間一致變量的值是不變的。類似常量报破,但不同的是悠就,這個值在編譯時期是未知的,由著色器外部初始化充易。

varying
易變變量理卑,頂點著色器輸出數(shù)據(jù)。是從頂點著色器傳遞到片元著色器的數(shù)據(jù)變量

著色器使用

著色器使用.png

OpenGL 的結(jié)構(gòu)流程

結(jié)構(gòu).png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載蔽氨,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者藐唠。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市鹉究,隨后出現(xiàn)的幾起案子宇立,更是在濱河造成了極大的恐慌,老刑警劉巖自赔,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妈嘹,死亡現(xiàn)場離奇詭異,居然都是意外死亡绍妨,警方通過查閱死者的電腦和手機润脸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來他去,“玉大人毙驯,你說我怎么就攤上這事≡植猓” “怎么了爆价?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長媳搪。 經(jīng)常有香客問我铭段,道長,這世上最難降的妖魔是什么秦爆? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任序愚,我火速辦了婚禮,結(jié)果婚禮上等限,老公的妹妹穿的比我還像新娘爸吮。我一直安慰自己芬膝,他們只是感情好,可當我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布拗胜。 她就那樣靜靜地躺著,像睡著了一般怒允。 火紅的嫁衣襯著肌膚如雪埂软。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天纫事,我揣著相機與錄音勘畔,去河邊找鬼。 笑死丽惶,一個胖子當著我的面吹牛炫七,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钾唬,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼万哪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抡秆?” 一聲冷哼從身側(cè)響起奕巍,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儒士,沒想到半個月后的止,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡着撩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年诅福,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拖叙。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡氓润,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出薯鳍,到底是詐尸還是另有隱情旺芽,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布辐啄,位于F島的核電站采章,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏壶辜。R本人自食惡果不足惜悯舟,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砸民。 院中可真熱鬧抵怎,春花似錦奋救、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姿染,卻和暖如春背亥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悬赏。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工狡汉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闽颇。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓盾戴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兵多。 傳聞我的和親對象是個殘疾皇子尖啡,可洞房花燭夜當晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容