国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

移動應用中使用OpenGL生成轉場特效

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

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

一、前言

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

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

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

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

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

三、OpenGL的基本介紹和轉場應用

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

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

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

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

圖片

渲染的流程有以下幾步:

1)頂點數據的輸入:

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

2)頂點著色器:

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

3)幾何著色器:

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

4)圖元組裝、光柵化:

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

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

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

6)混合測試:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

頂點著色器示例代碼:

//頂點位置
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著?器傳遞數據的?法

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

attribute

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

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程序傳遞數據的方法。

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

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

attribute vec4 a_position;//傳入的頂點坐標
attribute vec2 a_texCoord;//傳入的紋理坐標
varying vec2 v_texCoord;//傳遞給片元著色器的紋理坐標
void main()
{
gl_Position = a_position;//將頂點坐標賦值給OpenGL的內置變量
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的內置變量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)
//根據頂點著色器和片元著色器編輯鏈接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)
//環繞方式
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?) {
//清屏,清理掉顏色的緩沖區
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
//設置清屏的顏色,這里是float顏色的取值范圍的[0,1]
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)


//使用program
GLES20.glUseProgram(programId)


//設置為可用的狀態
GLES20.glEnableVertexAttribArray(vCoordinateHandle)
//size 指定每個頂點屬性的組件數量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
//stride 指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
//size 2 代表(x,y),stride 8 代表跨度 (2個點為一組,2個float有8個字節)
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的轉場特效應用

3.2.1 移植開源的轉場效果

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

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

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

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

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

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

precision mediump float;
uniform vec2 direction;// = vec2(0.0, 1.0)
uniform float progress;//轉場的進度
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);
}

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

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

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

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

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網站中的轉場特效移植到Android端的基本流程。iOS的也是類似的,非常方便。

3.2.2 實現復雜轉場效果

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

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

首先定義一個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繪制一遍需要的幀數,目前每一個drawer占用200幀
private val framesPerDrawer = 200//每一個IDrawer繪制所需要的幀數,這里暫時固定為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)
//清屏,清理掉顏色的緩沖區
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++
//清屏,清理掉顏色的緩沖區
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
val offset = frameIndex % drawersFrames
val logicFrame = if (offset == 0) 1 else offset
//計算當前的幀數輪到哪個IDrawer的繪制,讓對應的IDrawer進行繪制
drawers.forEachIndexed { index, iDrawer ->
if (logicFrame <= (index + 1).times(framesPerDrawer) && logicFrame >= index.times(
framesPerDrawer
)
) {
iDrawer.onDraw(logicFrame - index.times(framesPerDrawer))
}
}
}
}

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

下面給出其中一個IDrawer的實現類:

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


//轉場需要耗費的幀數,這里固定寫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)


//設置為可用的狀態
GLES20.glEnableVertexAttribArray(vCoordinateHandle)
//size 指定每個頂點屬性的組件數量。必須為1、2、3或者4。初始值為4。(如position是由3個(x,y,z)組成,而顏色是4個(r,g,b,a))
//stride 指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
//size 2 代表(x,y),stride 8 代表跨度 (2個點為一組,2個float有8個字節)
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])
//環繞方式
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()
}
}
}

這樣就可以達到將多個轉場組合使用的目的。

四、總結

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

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

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

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

責任編輯:未麗燕 來源: 攜程技術
相關推薦

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開發者

2010-08-05 09:54:56

Flex特效

2009-09-22 12:17:59

ibmdwLotus

2021-12-20 20:30:48

鴻蒙HarmonyOS應用

2023-12-22 09:11:45

AndroidNFC移動開發

2011-02-22 10:23:43

2013-06-18 23:26:36

移動應用用戶使用成本控制管理移動互聯網

2011-09-02 16:42:51

Sencha ToucWeb應用

2013-10-09 11:15:49

Ubuntu應用程序

2011-08-11 13:26:30

iPhoneNSLocalized

2009-06-19 13:45:53

Java應用程序Jfreechart

2011-07-21 14:50:06

Core Data SQL

2010-05-03 11:05:26

Widget開發

2021-09-07 10:24:36

Vue應用程序Web Workers

2011-05-27 08:48:13

Android HTML

2010-08-10 15:17:08

應用安全網絡測試

2009-04-12 08:46:43

Symbian諾基亞移動OS
點贊
收藏

51CTO技術棧公眾號

色婷婷av在线| www.成人精品免费网站青椒| 一区二区中文字幕在线观看| 日韩av在线网页| 9191在线| 色综合久久中文综合久久牛| 精品视频在线观看一区| 免费不卡在线视频| 国产精品入口免费| 婷婷亚洲综合| 国产精品亚洲综合天堂夜夜| 人人精品视频| 奇米影视亚洲狠狠色| aiai久久| 久久久人成影片一区二区三区观看| **欧美日韩在线观看| 日韩成人中文电影| 极品在线视频| 日韩精品亚洲视频| 丁香花在线影院| 欧美大片一区二区| 国产美女一区视频| 亚洲女人天堂成人av在线| 香蕉伊大人中文在线观看| 日韩精品在线播放| 主播大秀视频在线观看一区二区| 亚洲色图激情小说| 国产色99精品9i| 久久久精品久久久| 群体交乱之放荡娇妻一区二区| 欧美亚洲国产视频小说| 成人在线一区| 国产伦精品一区二区三区视频黑人 | 欧美一区二区三区四区视频| yellow91字幕网在线| 精品国产第一区二区三区观看体验| а_天堂中文在线| 中文国产亚洲喷潮| 亚欧日韩另类中文欧美| 欧美激情免费看| 欧美人与牛zoz0性行为| 成人午夜小视频| 久久精品天堂| 亚洲人精品午夜射精日韩 | 视色,视色影院,视色影库,视色网 日韩精品福利片午夜免费观看 | 亚洲国产高清福利视频| 日韩免费小视频| 欧美激情在线播放| 欧美在线二区| 一级二级三级欧美| 久久久久久久综合日本| 中文字幕在线播放第一页| 欧美一区二区视频网站| 国产精品第一国产精品| 欧美亚洲国产精品| 亚洲国产高清一区二区三区| 黄色录像特级片| 亚洲尤物视频在线| 大菠萝精品导航| 欧洲一区二区视频| 蜜乳av一区二区| 可播放的18gay1069| 精品国产乱码久久久久久图片| 一区二区在线视频观看| 国产精品免费视频一区二区| 成人小视频在线| 青青草手机在线| 最近2019年日本中文免费字幕| 超碰成人久久| 久久久久久久久影视| 亚洲高清免费在线| 丝袜美腿一区| 97av影视网在线观看| www.色精品| 亚洲免费视频一区二区三区| www.日韩av.com| 亚洲色诱最新| 老司机aⅴ毛片免费观看| 亚洲激情在线观看视频免费| 欧美日韩一区二区综合| 人体内射精一区二区三区| 欧美最猛性xxxxx直播| 日本免费一区二区视频| 青娱乐一区二区| 樱花草国产18久久久久| 天堂av中文在线观看| 国产在线精品一区免费香蕉| 国产福利一区在线观看| a天堂在线资源| 国产91精品不卡视频| 麻豆一区二区三区| 国产露出视频在线观看| 欧美一级淫片丝袜脚交| 粉嫩一区二区三区在线看| 免费观看在线黄色网| 国产精品小说在线| 国产女同性恋一区二区| sis001欧美| 欧美一区二区三区电影在线观看| 偷窥少妇高潮呻吟av久久免费 | 日韩av在线一区| 午夜久久美女| 浪潮av在线| 久久久久久久国产精品| 国产精品亚洲成人| 超碰公开在线| 91九色极品视频| 亚洲香肠在线观看| 美女视频免费精品| 日韩有码免费视频| 中文字幕久热精品视频在线| 免费一级片91| 激情在线小视频| 极品尤物一区二区三区| 色综合中文字幕| 国产精品精品| 一个人看的www一区| 国产99久久精品一区二区永久免费 | 欧美人妖巨大在线| 99精品在线免费在线观看| 国产激情三区| 高清一区二区三区日本久| 91在线小视频| 成人在线视频观看| 男人添女荫道口图片| 国产一区二区日韩精品欧美精品| 久久国内精品视频| 毛片在线网站| 男同互操gay射视频在线看| 亚洲激情久久久| 狠狠色狠狠色合久久伊人| 黄页在线观看免费| 亚洲在线色站| 国产一区二区三区高清在线观看| 国产91丝袜在线播放| 韩漫成人漫画| 免费看国产一级片| 欧美激情亚洲自拍| 亚洲男同1069视频| 精品视频免费| 国产黄色在线播放| 日韩欧美第二区在线观看| 日韩高清人体午夜| 97久久精品人人爽人人爽蜜臀 | 夜久久久久久| 青春草视频在线观看| 亚洲v国产v| 中文字幕欧美日韩精品| 久久久久久久久久久黄色| 第四色在线一区二区| 男女羞羞视频网站| 91久久精品美女高潮| 欧美在线999| 免费av网站大全久久| 欧美三级精品| 超碰成人福利网| 亚洲xxxx做受欧美| 91精品国产综合久久香蕉麻豆 | 精品国产乱码久久久久久樱花| 亚洲天堂2018av| 国产精品久久一| 欧美精品vⅰdeose4hd| 另类小说欧美激情| 99精品国产高清一区二区麻豆| 污污免费网站| 欧美日韩国产综合视频在线| 国产午夜精品免费一区二区三区| 国产精品久久一级| 精品9999| 国产亚洲精彩久久| 国产传媒视频在线观看| 久久精品第九区免费观看| 国产一区二区三区18| 亚洲免费观看高清完整版在线观看 | 亚洲精品一区二区| 免费观看成人在线| 国产亚洲视频在线| 亚洲国产综合色| 免费在线观看日韩欧美| 亚洲乱码一区| 黄网站免费在线播放| 一区二区传媒有限公司| 国产综合久久久久久| 日韩高清av一区二区三区| 亚洲色欲色欲www| 麻豆精品网站| 九一成人免费视频| 成人美女大片| 国产69精品久久久久孕妇| 亚洲第一精品区| 成人av在线亚洲| 久久人人爽人人爽人人片亚洲| 欧美中文字幕一区| 国产精品丝袜久久久久久app| 国产一区二区三区久久久久久久久| 欧美经典影片视频网站| caoporm免费视频在线| 黄色网免费看| 精品久久久无码人妻字幂| 亚洲自拍另类欧美丝袜|