自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

移動應用中使用OpenGL生成轉(zhuǎn)場特效

移動開發(fā)
本議題主要包含了對OpenGL的簡單介紹及相關(guān)API使用,GLSL著色器語言的基本使用,以及如何通過編寫自定義的著色器程序來實現(xiàn)圖片的轉(zhuǎn)場效果。

作者 | jzg,攜程資深前端開發(fā)工程師,專注Android開發(fā);zcc,攜程高級前端開發(fā)工程師,專注iOS開發(fā)。

一、前言

隨著移動端短視頻的火熱,音視頻編輯工具在做內(nèi)容類APP上的地位舉足輕重。豐富的轉(zhuǎn)場方式可以給短視頻帶來更多炫酷的效果,從而更好地贏得用戶青睞。本議題主要包含了對OpenGL的簡單介紹及相關(guān)API使用,GLSL著色器語言的基本使用,以及如何通過編寫自定義的著色器程序來實現(xiàn)圖片的轉(zhuǎn)場效果。

二、為什么使用OpenGL以及使用的難點

視頻的轉(zhuǎn)場效果離不開圖形的處理,移動設備在處理3D圖形相關(guān)的計算時一般都會選擇使用GPU。相較于CPU,GPU在圖像動畫處理時具有更高效的性能。移動設備以android為例,GPU處理提供了兩套不同的API,分別是Vulkan和OpenGL ES。其中VulKan只支持 Android 7.0 以上的設備,OpenGL ES 則支持所有的 Android 版本,而iOS并沒有對vulkan的官方支持。同時 OpenGL ES 作為 OpenGL 的子集,針對手機、PDA 和游戲主機等嵌入式設備去除了 glBegin/glEnd,四邊形、多邊形等復雜圖元等許多非絕對必要的特性,消除它的冗余功能,從而提供了更容易學習和易于在移動圖形硬件中實現(xiàn)的庫。

目前,在短視頻圖像處理中, OpenGL ES 憑借良好的系統(tǒng)支持性和功能的高度精簡性,成為了最廣泛的 GPU 處理 API 之一。為了方便,本文中提到的 OpenGL 即表示 OpenGL ES。

使用OpenGL處理視頻轉(zhuǎn)場的難點是如何編寫轉(zhuǎn)場效果的著色器,關(guān)于這一點,我們可以參考開源的GLTransitions網(wǎng)站。該網(wǎng)站有很多開源的轉(zhuǎn)場效果,我們可以借鑒并學習,下文會有較為詳細的介紹。

三、OpenGL的基本介紹和轉(zhuǎn)場應用

OpenGL 是一種開放式的圖形庫,用于渲染2D、3D矢量圖形的跨語言,跨平臺的應用程序編程接口。OpenGL 可以?來做什么?

  • 視頻,圖形,圖?處理
  • 2D/3D 游戲引擎開發(fā)
  • 科學可視化
  • 醫(yī)學軟件開發(fā)
  • CAD(計算機輔助技術(shù))
  • 虛擬實境(AR,VR)
  • AI ??智能

我們使用OpenGL來處理視頻轉(zhuǎn)場,就是上面提到的用OpenGL來對視頻、圖形、圖片進行處理。

在使用OpenGL進行繪制時,我們主要關(guān)注的是頂點著色器和片元著色器。頂點著色器用來確定繪制圖形的頂點位置,片元著色器負責給圖形添加顏色。主要繪制流程如下圖:

圖片

渲染的流程有以下幾步:

1)頂點數(shù)據(jù)的輸入:

頂點數(shù)據(jù)用來為后面的頂點著色器等階段提供處理的數(shù)據(jù)。

2)頂點著色器:

頂點著色器主要功能是進行坐標變換。

3)幾何著色器:

與頂點著色器不同,幾何著色器的輸入是完整的圖元(比如,點),輸出可以是一個或多個其他的圖元(比如,三角面),或者不輸出任何的圖元,幾何著色器是可選的。

4)圖元組裝、光柵化:

圖元組裝將輸入的頂點組裝成指定的圖元,經(jīng)過圖元組裝以及屏幕映射階段后,我們將物體坐標變換到了窗口坐標,光柵化是個離散化的過程,將3D連續(xù)的物體轉(zhuǎn)化為離散屏幕像素點的過程。

5)片元著色器(片段著色器):

片元著色器用來決定屏幕上像素的最終顏色。

6)混合測試:

渲染的最后一個階段是測試混合階段。測試包括裁切測試、Alpha測試、模板測試和深度測試。沒有經(jīng)過測試的片段會被丟棄,不需要進行混合階段,經(jīng)過測試的片段會進入混合階段。

經(jīng)過以上幾個步驟,OpenGL就能將最終的圖形顯示到屏幕上。

在OpenGL繪制流程中,我們能夠編碼的就是Vertex Shader(頂點著色器) 和 Fragment Shader(片元著色器)。這也是渲染過程中必備的2個著色器。

Vertex Shader處理從客戶端輸入的數(shù)據(jù)、應用變換、進行其他的類型的數(shù)學運算來計算光照效果、位移、顏色值等。比如為了渲染共有3個頂點的三角形,Vertex Shader將執(zhí)行3次,也就是為了每個頂點執(zhí)行一次。

圖中的3個頂點已經(jīng)組合在一起,而三角形也已經(jīng)逐個片段的進行了光柵化。每個片段通過執(zhí)行Fragment Shader進行填充。Fragment Shader會輸出我們屏幕上看到的最終顏色值。

在繪制圖形的時候,我們會使用到OpenGL的多種狀態(tài)變量,例如當前的顏色,控制當前視圖和投影變換、直線和多邊形點畫模式、多邊形繪圖模式、像素包裝約定、光照的位置和特征以及被繪制物體的材料屬性等??梢栽O置它的各種狀態(tài)(或模式),然后讓這些狀態(tài)一直生效,直到再次修改它們。

以把當前顏色設置為白色、紅色或其他任何顏色,在此之后繪制的所有物體都將使用這種顏色,直到再次把當前顏色設置為其他顏色。許多表示模式的狀態(tài)變量可以用glEnable()和glDisable()。所以我們說OpenGL是一個狀態(tài)機。

因為OpenGL在渲染處理過程中會順序執(zhí)行一系列操作,就如流水線作業(yè)一樣,所以我們將OpenGL繪制的流程稱為渲染管線,包括固定管線和可編程管線。我們使用的是可編程管線,在可編程管線里,頂點的位置、顏色、貼圖座標、貼圖傳進來之后,如何對數(shù)據(jù)進行改動,產(chǎn)生的片元如何生成結(jié)果,可以很自由地控制。

下面就簡單介紹一下管線和在可變編程管線中必不可少的GLSL(著色器語言)。

管線:渲染管線可以理解為渲染流水線。指的是輸入需要渲染的3D物體的相關(guān)描述信息數(shù)據(jù)(例:頂點坐標、頂點顏色、頂點紋理等),經(jīng)過渲染管線一系列的變化和渲染過程,輸出一幀最終的圖像。簡單理解就是一堆原始圖形數(shù)據(jù)經(jīng)過一個輸送管道,期間經(jīng)過各種變化處理最終出現(xiàn)展示到屏幕的過程。管線又分為固定管線和可編程管線兩種。

固定管線:在渲染圖像的過程,我們只能通過調(diào)用GLShaderManager類的固定管線效果實現(xiàn)一系列的著色器處理。

可編程管線:在渲染圖像的過程,我們能夠使用自定義頂點著色器和片元著色器的去處理數(shù)據(jù)的過程。由于OpenGL的使用場景非常豐富,固定管線或者存儲著色器無法完成的任務,這時我們可以使用可編程管線去處理。

OpenGL著色語言(OpenGL Shading Language)是用來在OpenGL中著色編碼的語言,也即開發(fā)人員寫的短小的自定義程序,他們是在GPU(Graphic Processor Unit圖形處理單元)上執(zhí)行的,代替了固定的渲染管線的一部分,使渲染管線中不同層次具有可編程性。它可以得到當前OpenGL 中的狀態(tài),GLSL內(nèi)置變量進行傳遞。GLSL其使用C語言作為基礎高階著色語言,避免了使用匯編語言或硬件規(guī)格語言的復雜性。

GLSL的著色器代碼分成2個部分:VertexShader(頂點著色器) 和 Fragment Shader(片元著色器)。

著色器(Shader)是用來實現(xiàn)圖像渲染的,用來替代固定渲染管線的可編輯程序。其中Vertex Shader(頂點著色器)主要負責頂點的幾何關(guān)系等的運算,Pixel Shader(像素著色器)主要負責片源顏色等的計算。

頂點著色器是一個可編程的處理單元,一般用來處理圖形每個頂點變換(旋轉(zhuǎn)/平移/投影等)、光照、材質(zhì)的應用與計算等頂點的相關(guān)操作。頂點著色器是逐頂點運算的程序,每個頂點數(shù)據(jù)都會執(zhí)行一次。替代了原有固定管線的頂點變換、光照計算,采用GLSL進行開發(fā) 。我們可以根據(jù)自己的需求采用著色語言自行開發(fā)頂點變換、光照等功能,大大增加了程序的靈活性。

頂點著色器工作過程為將原始的頂點幾何信息(頂點坐標、顏色、紋理)及其他屬性傳送到頂點著色器中,經(jīng)過自定義的頂點著色程序處理產(chǎn)生變化后的頂點位置信息,將變化后的頂點位置信息傳遞給后續(xù)圖元裝配階段,對應的頂點紋理、顏色等信息則經(jīng)光柵化后傳遞到片元著色器。

頂點著色器的輸入主要為待處理頂點相應的attribute、uniform、采樣器以及臨時變量,輸出主要為經(jīng)過頂點著色器后生成的varying及一些內(nèi)建輸出變量。

頂點著色器示例代碼:

//頂點位置
attribute vec4 Position;
//紋理坐標
attribute vec2 TextureCoord;
//紋理坐標 用于接收和傳遞給片元著色器的紋理坐標
varying vec2 varyTextureCoord;
void main() {
gl_Position = Position;
varyTextureCoord = TextureCoord;
}
//高精度
precision highp float;
//用于接收頂點著色器的紋理坐標
varying vec2 varyTextureCoord;
//圖片紋理
uniform sampler2D Texture;
//圖片紋理
uniform sampler2D Texture2;
const vec2 direction = vec2(0.0, 1.0);
void main(){
vec2 p = varyTextureCoord.xy/vec2(1.0).xy;
vec4 color = mix(texture2D(Texture, varyTextureCoord), texture2D(Texture2, varyTextureCoord), step(1.0-p.y,progress));
gl_FragColor = vec4(color);
}

3.1.4 三種向OpenGL著?器傳遞數(shù)據(jù)的?法

上面的頂點著色器和片元著色器里出現(xiàn)了attribute,varying,uniform等類型定義,下面就簡單介紹一下這三種類型。

attribute

attribute:attribute變量是只能在頂點著色器中使用的變量,一般用attribute變量來表示一些頂點的數(shù)據(jù),如:頂點坐標,法線,紋理坐標,頂點顏色等。

uniform

uniform:uniform變量是外部application程序傳遞給著色器的變量,uniform變量就像是C語言里面的常量,也就是說著色器只能用而不能修改uniform變量。

varying

varying:從頂點著色器傳遞到片元著色器的量,如用于傳遞到片元著色器中的頂點顏色,可以使用varying(易變變量)。

注意點: Attributes不能夠直接傳遞給Fragment Shader,如果需要傳遞給Fragment Shader,則需要通過Vertex Shader間接的傳遞過去。而 Unifrom和Texture Data可以直接傳遞給Vertex Shader和Fragment Shader,具體怎么傳遞,依需求而定。

3.1.5 如何使用OpenGL來繪制一張圖片

上面介紹了頂點著色器和片元著色器,以及如何向OpenGL程序傳遞數(shù)據(jù)的方法。

現(xiàn)在我們就利用剛剛介紹的一些知識點,通過OpenGL程序?qū)D片繪制到屏幕上,這也是制作圖片輪播轉(zhuǎn)場特效的前提。圖片的繪制對于OpenGL來說就是紋理的繪制,這里只為了展示效果,不使用變換矩陣來處理圖片的寬高比例,直接鋪滿整個窗口。

首先定義一個頂點著色器:

attribute vec4 a_position;//傳入的頂點坐標
attribute vec2 a_texCoord;//傳入的紋理坐標
varying vec2 v_texCoord;//傳遞給片元著色器的紋理坐標
void main()
{
gl_Position = a_position;//將頂點坐標賦值給OpenGL的內(nèi)置變量
v_texCoord = a_texCoord;//將傳入的紋理坐標傳遞給片元著色器
}
再定義一個片元著色器:
precision mediump float;//定義float精度,紋理坐標使用的是一個float類型的二維向量vec2
uniform sampler2D u_texture;//紋理
varying vec2 v_texCoord;//紋理坐標
void main(){
gl_FragColor = texture2D(u_texture, v_texCoord);//2D紋理采樣,將顏色賦值給OpenGL的內(nèi)置變量gl_FragColor
}

再給出Android端使用這兩個著色器繪制一個圖片紋理的代碼:

class SimpleImageRender(private val context: Context) : GLSurfaceView.Renderer {
//頂點坐標
private val vCoordinates = floatArrayOf(
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
)
//紋理坐標
private val textureCoordinates = floatArrayOf(
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
)
//OpenGL程序id
var programId = 0
//頂點坐標句柄
var vCoordinateHandle = 0
//紋理坐標句柄
var textureCoordinateHandle = 0
//紋理id
var textureId = 0
private val vertexBuffer =
ByteBuffer.allocateDirect(vCoordinates.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vCoordinates)


private val textureBuffer =
ByteBuffer.allocateDirect(textureCoordinates.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureCoordinates)


override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
vertexBuffer.position(0)
textureBuffer.position(0)
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
//根據(jù)頂點著色器和片元著色器編輯鏈接OpenGL程序
programId =
loadShaderWithResource(context, R.raw.simple_image_vs, R.raw.simple_image_fs)
//獲取頂點坐標的句柄
vCoordinateHandle = GLES20.glGetAttribLocation(programId, "a_position")
//獲取紋理坐標的句柄
textureCoordinateHandle = GLES20.glGetAttribLocation(programId, "a_texCoord")
//生成紋理
val textureIds = IntArray(1)
GLES20.glGenTextures(1, textureIds, 0)
if (textureIds[0] == 0) {
return
}
textureId = textureIds[0]
//綁定紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
//環(huán)繞方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT)
//過濾方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)


val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.scene1)
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
bitmap.recycle()
}


override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
}


override fun onDrawFrame(gl: GL10?) {
//清屏,清理掉顏色的緩沖區(qū)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
//設置清屏的顏色,這里是float顏色的取值范圍的[0,1]
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)


//使用program
GLES20.glUseProgram(programId)


//設置為可用的狀態(tài)
GLES20.glEnableVertexAttribArray(vCoordinateHandle)
//size 指定每個頂點屬性的組件數(shù)量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
//stride 指定連續(xù)頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
//size 2 代表(x,y),stride 8 代表跨度 (2個點為一組,2個float有8個字節(jié))
GLES20.glVertexAttribPointer(vCoordinateHandle, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer)


GLES20.glEnableVertexAttribArray(textureCoordinateHandle)
GLES20.glVertexAttribPointer(
textureCoordinateHandle,
2,
GLES20.GL_FLOAT,
false,
8,
textureBuffer
)


GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)


}
}

這樣就完成了一個圖片的繪制:

3.2 OpenGL的轉(zhuǎn)場特效應用

3.2.1 移植開源的轉(zhuǎn)場效果

什么是轉(zhuǎn)場效果?一般來說,就是兩個視頻畫面之間的過渡銜接效果。在opengl中,圖片的轉(zhuǎn)場,其實就是兩個紋理的過渡切換。在這里推薦一個開源項目,該項目主要用來收集各種GL轉(zhuǎn)場特效及其 GLSL 實現(xiàn)代碼,開發(fā)者可以很方便地移植到自己的項目中。

GLTransitions 項目有接近大概70種轉(zhuǎn)場特效,能夠非常方便的使用在圖片或者視頻的轉(zhuǎn)場中,很多轉(zhuǎn)場特效包含了混合、邊緣檢測、腐蝕膨脹等常見的圖像處理方法,由易到難。

對于想學習 GLSL 的同學,既能快速上手,又能學習到一些高階圖像處理方法 GLSL 實現(xiàn),強烈推薦。

由于glsl代碼在各個平臺都是通用的,所以將GLTransitions的效果移植到移動端也是比較簡單的?,F(xiàn)在我們以該網(wǎng)站的第一個轉(zhuǎn)場效果為例,介紹一下移植的大致流程。

首先我們來看一下轉(zhuǎn)場所需的片元著色器的代碼,這是實現(xiàn)轉(zhuǎn)場的關(guān)鍵。其中sign函數(shù),mix函數(shù),fract函數(shù),step函數(shù)是glsl的內(nèi)置函數(shù)。這里只為了展示效果,不使用變換矩陣來處理圖片的寬高比例,直接鋪滿整個窗口。

uniform vec2 direction; // = vec2(0.0, 1.0)


vec4 transition (vec2 uv) {
vec2 p = uv + progress * sign(direction);
vec2 f = fract(p);
return mix(
getToColor(f),
getFromColor(f),
step(0.0, p.y) * step(p.y, 1.0) * step(0.0, p.x) * step(p.x, 1.0)
);
}

我們可以看到,從GLTransitions的片元著色器代碼已經(jīng)提供了轉(zhuǎn)場效果,但是還需要使用者進行一些修改。以上面的代碼為例,需要我們自己定義一個轉(zhuǎn)場進度的變量progress(取值為0到1的浮點數(shù))。還有轉(zhuǎn)場最基本的兩個要素,即圖片紋理,一個轉(zhuǎn)場需要兩個圖片紋理,從紋理1過渡到紋理2,getToColor和getFromColor就是對紋理1和紋理2取色的函數(shù)。當然還有必不可少的main函數(shù),將我們程序計算的顏色賦值給gl_FragColor,所以我們要將上面的片元著色器代碼修改一下。如下:

precision mediump float;
uniform vec2 direction;// = vec2(0.0, 1.0)
uniform float progress;//轉(zhuǎn)場的進度
uniform sampler2D u_texture0;//紋理1
uniform sampler2D u_texture1;//紋理2
varying vec2 v_texCoord;//紋理坐標
vec4 transition (vec2 uv) {
vec2 p = uv + progress * sign(direction);
vec2 f = fract(p);
return mix(
texture2D(u_texture1, f),
texture2D(u_texture0, f),
step(0.0, p.y) * step(p.y, 1.0) * step(0.0, p.x) * step(p.x, 1.0)
);
}


void main(){
gl_FragColor = transition(v_texCoord);
}

這里也順便給出頂點著色器的代碼,主要就是設置頂點坐標和紋理坐標,關(guān)于這兩個坐標上文已經(jīng)介紹過了,這里就不贅述了。代碼如下:

attribute vec4 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main()
{
gl_Position = a_position;
v_texCoord = a_texCoord;
}

現(xiàn)在頂點著色器和片元著色器這兩個關(guān)鍵的著色器程序都有了,一個基本的轉(zhuǎn)場就實現(xiàn)了。只要在我們的程序中使用這兩個著色器,在繪制的時候根據(jù)當前的幀數(shù)不停地更新兩個紋理和轉(zhuǎn)場的進度就可以了。

下面給出繪制時的代碼邏輯,以安卓為例:       

frameIndex++ 
GLES20.glUseProgram(programId)


GLES20.glEnableVertexAttribArray(vCoordinateHandle)
GLES20.glVertexAttribPointer(vCoordinateHandle, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer)


GLES20.glEnableVertexAttribArray(textureCoordinateHandle)
GLES20.glVertexAttribPointer(
textureCoordinateHandle,
2,
GLES20.GL_FLOAT,
false,
8,
textureBuffer
)


val uTexture0Handle = GLES20.glGetUniformLocation(programId, "u_texture0")
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(
GLES20.GL_TEXTURE_2D,
imageTextureIds[(frameIndex / transitionFrameCount) % imageNum]
)
GLES20.glUniform1i(uTexture0Handle, 0)


val uTexture1Handle = GLES20.glGetUniformLocation(programId, "u_texture1")
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
GLES20.glBindTexture(
GLES20.GL_TEXTURE_2D,
imageTextureIds[(frameIndex / transitionFrameCount + 1) % imageNum]
)
GLES20.glUniform1i(uTexture1Handle, 1)


val directionHandle = GLES20.glGetUniformLocation(programId, "direction")
GLES20.glUniform2f(directionHandle, 0f, 1f)


val uOffsetHandle = GLES20.glGetUniformLocation(programId, "u_offset")
val offset = (frameIndex % transitionFrameCount) * 1f / transitionFrameCount
GLES20.glUniform1f(uOffsetHandle, offset)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

以上就是將一個GLTransitions網(wǎng)站中的轉(zhuǎn)場特效移植到Android端的基本流程。iOS的也是類似的,非常方便。

3.2.2 實現(xiàn)復雜轉(zhuǎn)場效果

通過上面的介紹,我們已經(jīng)對如何使用opengl來處理圖片轉(zhuǎn)場有了一個簡單的了解。但是剛剛的操作只能讓多張圖片都使用同一種轉(zhuǎn)場,這樣比較單調(diào)乏味。下面介紹一個思路,在用多張圖片合成轉(zhuǎn)場效果時,將不同的轉(zhuǎn)場效果組合起來使用。

回想一下,剛剛做轉(zhuǎn)場移植的時候,只是使用了一個opengl程序?,F(xiàn)在咱們來加載多個opengl程序,然后在不同的時間段使用對應的opengl程序,這樣就能比較方便地實現(xiàn)多個轉(zhuǎn)場效果的組合使用了。

首先定義一個IDrawer接口,表示一個使用opengl程序的對象:

interface IDrawer {
//準備階段,準備程序,資源
fun onPrepare()
//繪制
fun onDraw(frameIndex:Int){}


fun onSurfaceChanged(p0: GL10?, width: Int, height: Int){


}
}

然后定義一個render,來控制如何使用這些IDrawer:

class ComposeRender : GLSurfaceView.Renderer {
private var frameIndex = 0//當前繪制了多少幀
private var drawersFrames = 0 //所有的drawer繪制一遍需要的幀數(shù),目前每一個drawer占用200幀
private val framesPerDrawer = 200//每一個IDrawer繪制所需要的幀數(shù),這里暫時固定為200


//使用的IDrawer集合
private val drawers = mutableListOf(
HelloWorldTransitionDrawer(),
SimpleTransitionDrawer(),
PerlinTransitionDrawer(),
)


init {
drawersFrames = drawers.size.times(framesPerDrawer)
}


override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
//設置清屏的顏色,這里是float顏色的取值范圍的[0,1]
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
//清屏,清理掉顏色的緩沖區(qū)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
drawers.forEach {
it.onPrepare()
}
}


override fun onSurfaceChanged(p0: GL10?, p1: Int, p2: Int) {
GLES20.glViewport(0, 0, p1, p2)
drawers.forEach {
it.onSurfaceChanged(p0, p1, p2)
}
}


override fun onDrawFrame(p0: GL10?) {
frameIndex++
//清屏,清理掉顏色的緩沖區(qū)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
val offset = frameIndex % drawersFrames
val logicFrame = if (offset == 0) 1 else offset
//計算當前的幀數(shù)輪到哪個IDrawer的繪制,讓對應的IDrawer進行繪制
drawers.forEachIndexed { index, iDrawer ->
if (logicFrame <= (index + 1).times(framesPerDrawer) && logicFrame >= index.times(
framesPerDrawer
)
) {
iDrawer.onDraw(logicFrame - index.times(framesPerDrawer))
}
}
}
}

這里為了方便展示流程,先將紋理和每個轉(zhuǎn)場的耗時(即使用的幀數(shù))的使用固定值寫在代碼里。比如現(xiàn)在有四張圖片編號為1,2,3,4,我們就定義三個IDrawer A,B,C。A使用圖片1和圖片2,B使用圖片2和圖片3,C使用圖片3和圖片4,然后每個轉(zhuǎn)場都耗時200幀,這樣就能實現(xiàn)三個opengl程序的組合轉(zhuǎn)場了。

下面給出其中一個IDrawer的實現(xiàn)類:

class HelloWorldTransitionDrawer() : IDrawer {
private val imageNum = 2//需要使用兩個圖片紋理


//轉(zhuǎn)場需要耗費的幀數(shù),這里固定寫200幀
private val transitionFrameCount = 200
private val vCoordinates = floatArrayOf(
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
)
private val textureCoordinates = floatArrayOf(
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f
)
var programId = 0
var vCoordinateHandle = 0
var textureCoordinateHandle = 0
var imageTextureIds = IntArray(imageNum)
private val vertexBuffer =
ByteBuffer.allocateDirect(vCoordinates.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vCoordinates).position(0)


private val textureBuffer =
ByteBuffer.allocateDirect(textureCoordinates.size * 4).order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureCoordinates).position(0)


override fun onPrepare() {
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
programId =
loadShaderWithResource(
MyApplication.getApp(),
R.raw.helloworld_transition_vs,
R.raw.helloworld_transition_fs
)
vCoordinateHandle = GLES20.glGetAttribLocation(programId, "a_position")
textureCoordinateHandle = GLES20.glGetAttribLocation(programId, "a_texCoord")
//生成紋理
val textureIds = IntArray(1)
GLES20.glGenTextures(1, textureIds, 0)
if (textureIds[0] == 0) {
return
}
loadTextures(intArrayOf(R.drawable.scene1, R.drawable.scene2))
}


override fun onDraw(frameIndex:Int) {
//使用program
GLES20.glUseProgram(programId)


//設置為可用的狀態(tài)
GLES20.glEnableVertexAttribArray(vCoordinateHandle)
//size 指定每個頂點屬性的組件數(shù)量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
//stride 指定連續(xù)頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
//size 2 代表(x,y),stride 8 代表跨度 (2個點為一組,2個float有8個字節(jié))
GLES20.glVertexAttribPointer(vCoordinateHandle, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer)


GLES20.glEnableVertexAttribArray(textureCoordinateHandle)
GLES20.glVertexAttribPointer(
textureCoordinateHandle,
2,
GLES20.GL_FLOAT,
false,
8,
textureBuffer
)


val uTexture0Handle = GLES20.glGetUniformLocation(programId, "u_texture0")
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(
GLES20.GL_TEXTURE_2D,
imageTextureIds[0]
)
GLES20.glUniform1i(uTexture0Handle, 0)


val uTexture1Handle = GLES20.glGetUniformLocation(programId, "u_texture1")
GLES20.glActiveTexture(GLES20.GL_TEXTURE1)
GLES20.glBindTexture(
GLES20.GL_TEXTURE_2D,
imageTextureIds[1]
)
GLES20.glUniform1i(uTexture1Handle, 1)


val directionHandle = GLES20.glGetUniformLocation(programId, "direction")
GLES20.glUniform2f(directionHandle, 0f, 1f)


val uOffsetHandle = GLES20.glGetUniformLocation(programId, "u_offset")
val offset = (frameIndex % transitionFrameCount) * 1f / transitionFrameCount
GLES20.glUniform1f(uOffsetHandle, offset)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
}


private fun loadTextures(resIds: IntArray) {
if (resIds.isEmpty()) return
//直接生成兩個紋理
GLES20.glGenTextures(2, imageTextureIds, 0)
resIds.forEachIndexed { index, resId ->
if (imageTextureIds.indexOfFirst {
it == 0


} == 0) return
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + index)
//綁定紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imageTextureIds[index])
//環(huán)繞方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT)
//過濾方式
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR
)
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR
)


val bitmap = BitmapFactory.decodeResource(MyApplication.getApp().resources, resId)
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0)
bitmap.recycle()
}
}
}

這樣就可以達到將多個轉(zhuǎn)場組合使用的目的。

四、總結(jié)

在移動端進行圖形處理時,OpenGL憑借其效率高,兼容性好的優(yōu)勢,得到了大家的青睞。

本文對OpenGL的基本概念和繪制流程進行了簡單介紹,讓大家對OpenGL的繪制流程有了一個初步的認識。在繪制流程中,對我們開發(fā)者比較重要的是使用GLSL來編寫頂點著色器和片元著色器。在使用OpenGL處理圖片輪播轉(zhuǎn)場時,關(guān)鍵點是編寫轉(zhuǎn)場所需的著色器,我們可以參考GLTransitions網(wǎng)站的開源轉(zhuǎn)場效果。該網(wǎng)站提供豐富的轉(zhuǎn)場效果和著色器代碼,可以很方便的移植到客戶端中。

對于實現(xiàn)復雜轉(zhuǎn)場,即將多個轉(zhuǎn)場效果組合使用,本文也提供了一個思路,就是組合使用多個OpenGL程序,在對應的時間點加載并使用對應的OpenGL程序。

鑒于篇幅原因,本文分享了部分我們基于OpenGL開發(fā)視頻轉(zhuǎn)場特效的思考與實踐,希望對大家有所幫助。

責任編輯:未麗燕 來源: 攜程技術(shù)
相關(guān)推薦

2012-05-02 16:25:47

JavaSwing

2011-07-29 11:10:56

IOS SimpleLogg 日志

2011-08-17 14:57:31

iPhone應用視頻播放

2021-02-25 11:19:37

谷歌Android開發(fā)者

2010-08-05 09:54:56

Flex特效

2009-09-22 12:17:59

ibmdwLotus

2023-12-22 09:11:45

AndroidNFC移動開發(fā)

2011-02-22 10:23:43

2021-12-20 20:30:48

鴻蒙HarmonyOS應用

2011-08-11 13:26:30

iPhoneNSLocalized

2009-06-19 13:45:53

Java應用程序Jfreechart

2013-10-09 11:15:49

Ubuntu應用程序

2011-07-21 14:50:06

Core Data SQL

2010-05-03 11:05:26

Widget開發(fā)

2011-05-27 08:48:13

Android HTML

2021-09-07 10:24:36

Vue應用程序Web Workers

2011-09-02 16:42:51

Sencha ToucWeb應用

2013-06-18 23:26:36

移動應用用戶使用成本控制管理移動互聯(lián)網(wǎng)

2009-04-12 08:46:43

Symbian諾基亞移動OS

2010-08-10 15:17:08

應用安全網(wǎng)絡測試
點贊
收藏

51CTO技術(shù)棧公眾號