【聲 明】
首先,這一系列文章均基于自己的理解和實(shí)踐谷丸,可能有不對(duì)的地方堡掏,歡迎大家指正。
其次刨疼,這是一個(gè)入門系列泉唁,涉及的知識(shí)也僅限于夠用鹅龄,深入的知識(shí)網(wǎng)上也有許許多多的博文供大家學(xué)習(xí)了。
最后亭畜,寫文章過(guò)程中扮休,會(huì)借鑒參考其他人分享的文章,會(huì)在文章最后列出拴鸵,感謝這些作者的分享玷坠。
碼字不易,轉(zhuǎn)載請(qǐng)注明出處宝踪!
教程代碼:【Github傳送門】 |
---|
目錄
一侨糟、Android音視頻硬解碼篇:
二厉膀、使用OpenGL渲染視頻畫面篇
- 1,初步了解OpenGL ES
- 2二拐,使用OpenGL渲染視頻畫面
- 3服鹅,OpenGL渲染多視頻,實(shí)現(xiàn)畫中畫
- 4百新,深入了解OpenGL之EGL
- 5企软,OpenGL FBO數(shù)據(jù)緩沖區(qū)
- 6,Android音視頻硬編碼:生成一個(gè)MP4
三饭望、Android FFmpeg音視頻解碼篇
- 1仗哨,F(xiàn)Fmpeg so庫(kù)編譯
- 2,Android 引入FFmpeg
- 3铅辞,Android FFmpeg視頻解碼播放
- 4厌漂,Android FFmpeg+OpenSL ES音頻解碼播放
- 5,Android FFmpeg+OpenGL ES播放視頻
- 6斟珊,Android FFmpeg簡(jiǎn)單合成MP4:視屏解封與重新封裝
- 7苇倡,Android FFmpeg視頻編碼
本文你可以了解到
本文主要介紹OpenGL相關(guān)的基礎(chǔ)知識(shí),包括坐標(biāo)系囤踩、著色器旨椒、基本渲染流程等。
一 簡(jiǎn)介
提到OpenGL堵漱,想必很多人都會(huì)說(shuō)综慎,我知道這個(gè)東西,可以用來(lái)渲染2D畫面和3D模型怔锌,同時(shí)又會(huì)說(shuō)寥粹,OpenGL很難变过、很高級(jí),不知道怎么用涝涤。
1媚狰、為什么OpenGL“感覺(jué)很難”?
- 函數(shù)多且雜阔拳,渲染流程復(fù)雜
- GLSL著色器語(yǔ)言不好理解
- 面向過(guò)程的編程思維崭孤,和Java等面向?qū)ο蟮木幊趟季S不同
2、OpenGL ES是什么糊肠?
為了解決以上問(wèn)題辨宠,讓OpenGL“學(xué)起來(lái)不是很難”,需要把其分解成一些簡(jiǎn)單的步驟货裹,然后簡(jiǎn)單的東西串聯(lián)起來(lái)嗤形,一切就水到渠成了。
首先弧圆,來(lái)看看什么是OpenGL赋兵。
- CPU和GPU
在手機(jī)上,有兩大元件搔预,一個(gè)是CPU霹期,一個(gè)是GPU。而手機(jī)上顯示圖形界面也有兩種方式拯田,一個(gè)是使用CPU來(lái)渲染历造,一個(gè)是使用GPU來(lái)渲染,可以說(shuō)船庇,GPU渲染其實(shí)是一種硬件加速吭产。
為什么GPU可以大大提高渲染速度,因?yàn)镚PU最擅長(zhǎng)的是并行浮點(diǎn)運(yùn)算溢十,可以用來(lái)對(duì)許許多多的像素做并行運(yùn)算垮刹。
OpenGL(Open Graphics Library)則是間接操作GPU的工具,是一組定義好的跨平臺(tái)和跨語(yǔ)言的圖形API张弛,是可用于2D和3D畫面渲染的底層圖形庫(kù)荒典,是由各個(gè)硬件廠家具體實(shí)現(xiàn)的編程接口。
- OpenGL 與 OpenGL ES
OpenGL ES 全稱:OpenGL for Embedded Systems吞鸭,是OpenGL 的子集寺董,是針對(duì)手機(jī) PAD等小型設(shè)備設(shè)計(jì)的,刪減了不必須的方法刻剥、數(shù)據(jù)類型遮咖、功能,減少了體積造虏,優(yōu)化了效率御吞。
3麦箍、 OpenGL ES版本
目前主要版本有1.0/1.1/2.0/3.0/3.1
- 1.0:Android 1.0和更高的版本支持這個(gè)API規(guī)范
- 2.0:不兼容 OpenGL ES 1.x。Android 2.2(API 8)和更高的版本支持這個(gè)API規(guī)范
- 3.0:向下兼容 OpenGL ES 2.x陶珠。Android 4.3(API 18)及更高的版本支持這個(gè)API規(guī)范
- 3.1:向下兼容 OpenGL ES3.0/2.0挟裂。Android 5.0(API 21)和更高的版本支持這個(gè)API規(guī)范
2.0 版本是 Android 目前支持最廣泛的版本,后續(xù)主要以該版本為主揍诽,進(jìn)行介紹和代碼編寫诀蓉。
二、OpenGL ES坐標(biāo)系
在音視頻開(kāi)發(fā)中暑脆,涉及到的坐標(biāo)系主要有兩個(gè):世界坐標(biāo)和紋理坐標(biāo)渠啤。
由于基本不涉及3D貼圖,所以只看x/y軸坐標(biāo)添吗,忽略z軸坐標(biāo)沥曹,涉及到3D相關(guān)知識(shí)可自行Google,不在討論范圍內(nèi)碟联。
首先來(lái)看兩個(gè)圖:
- OpenGL ES世界坐標(biāo)
通過(guò)名字就可以知道架专,這是OpenGL自己世界的坐標(biāo),是一個(gè)標(biāo)準(zhǔn)化坐標(biāo)系玄帕,范圍是 -1 ~ 1,原點(diǎn)在中間想邦。
- OpenGL ES紋理坐標(biāo)
紋理坐標(biāo)裤纹,其實(shí)就是屏幕坐標(biāo),標(biāo)準(zhǔn)的紋理坐標(biāo)原點(diǎn)是在屏幕的左下方丧没,而Android系統(tǒng)坐標(biāo)系的原點(diǎn)是在左上方的鹰椒。這是Android使用OpenGL需要注意的一個(gè)地方。
紋理坐標(biāo)的范圍是 0 ~ 1呕童。
注:坐標(biāo)系的xy軸方向很重要漆际,決定了如何做頂點(diǎn)坐標(biāo)和紋理坐標(biāo)映射。
那么夺饲,這兩個(gè)坐標(biāo)系究竟有什么關(guān)系呢奸汇?
世界坐標(biāo),是用于顯示的坐標(biāo)往声,即像素點(diǎn)應(yīng)該顯示在哪個(gè)位置由世界坐標(biāo)決定擂找。
紋理坐標(biāo),表示世界坐標(biāo)指定的位置點(diǎn)想要顯示的顏色浩销,應(yīng)該在紋理上的哪個(gè)位置獲取贯涎。即顏色所在的位置由紋理坐標(biāo)決定。
兩者之間需要做正確的映射慢洋,才能正常的顯示一張畫面塘雳。
三陆盘、OpenGL 著色器語(yǔ)言 GLSL
在OpenGL 2.0以后,加入了新的可編程渲染管線败明,可以更加靈活的控制渲染隘马。但也因此需要學(xué)習(xí)多一門針對(duì)GPU的編程語(yǔ)言,語(yǔ)法與C語(yǔ)言類似肩刃,名為GLSL祟霍。
- 頂點(diǎn)著色器 & 片元著色器
在介紹GLSL之前,先來(lái)看兩個(gè)比較陌生的名詞:頂點(diǎn)著色器和片元著色器盈包。
著色器沸呐,是一種可運(yùn)行在GPU上的小程序,用GLSL語(yǔ)言編寫呢燥。從命名上崭添,頂點(diǎn)著色器是用于操控頂點(diǎn)的程序,而片元著色器是用于操控像素顏色屬性的程序叛氨。
簡(jiǎn)單理解:其實(shí)就是對(duì)應(yīng)了以上兩個(gè)坐標(biāo)系:頂點(diǎn)著色器對(duì)應(yīng)世界坐標(biāo)呼渣,片元著色器對(duì)應(yīng)紋理坐標(biāo)。
畫面上的每個(gè)點(diǎn)寞埠,都會(huì)執(zhí)行一次頂點(diǎn)和片元著色器中的程序片段屁置,并且是并行執(zhí)行,最后渲染到屏幕上仁连。
- GLSL編程
下面蓝角,通過(guò)一個(gè)最簡(jiǎn)單的頂點(diǎn)著色器和片元著色器來(lái)簡(jiǎn)單介紹一下GLSL語(yǔ)言
#頂點(diǎn)著色器
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
#片元著色器
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0)
}
首先可以看到,GLSL語(yǔ)言是一種類C語(yǔ)言饭冬,著色器的框架基本和C語(yǔ)言一樣使鹅,在最上面聲明變量,接著是main函數(shù)昌抠。在著色器中患朱,有幾個(gè)內(nèi)建的變量,可以直接使用(這里只列出音視頻開(kāi)發(fā)常用的炊苫,還有其他的一些3D開(kāi)發(fā)會(huì)用到的):
-
頂點(diǎn)著色器的內(nèi)建輸入變量
gl_Position:頂點(diǎn)坐標(biāo)
gl_PointSize:點(diǎn)的大小裁厅,沒(méi)有賦值則為默認(rèn)值1
-
片元著色器內(nèi)建輸出變量
gl_FragColor:當(dāng)前片元顏色
看回上面的著色器代碼。
1)在頂點(diǎn)著色器中劝评,傳入了一個(gè)vec4的頂點(diǎn)坐標(biāo)xyzw姐直,然后直接傳遞給內(nèi)建變量gl_Position,即直接根據(jù)頂點(diǎn)坐標(biāo)渲染蒋畜,不再做位置變換声畏。
注:頂點(diǎn)坐標(biāo)是在Java代碼中傳入的,后面會(huì)講到,另外w是齊次坐標(biāo)插龄,2D渲染沒(méi)有作用
2)在片元著色器中愿棋,直接給gl_FragColor賦值,依然是一個(gè)vec4類型的數(shù)據(jù)均牢,這里表示rgba顏色值糠雨,為紅色。
可以看到vec4是一個(gè)4維向量徘跪,可用于表示坐標(biāo)xyzw甘邀,也可用表示rgba,當(dāng)然還有vec3垮庐,vec2等松邪,可以參考這篇文章:著色器語(yǔ)言GLSL,講的非常詳細(xì)哨查,建議看看逗抑。
這樣,兩個(gè)簡(jiǎn)單的著色器串聯(lián)起來(lái)后寒亥,每一個(gè)頂點(diǎn)(像素)都會(huì)顯示一個(gè)紅點(diǎn)邮府,最后屏幕會(huì)顯示一個(gè)紅色的畫面。
具體GLSL關(guān)于數(shù)據(jù)類型和語(yǔ)法不再展開(kāi)介紹溉奕,后面涉及到的GLSL代碼會(huì)做更深入的講解褂傀。更詳細(xì)的可以參考這位作者的文章【著色器語(yǔ)言GLSL】,非常詳盡加勤。
四紊服、Android OpenGL ES渲染流程
OpenGL的渲染流程說(shuō)實(shí)話是比較繁瑣的,也是讓很多人望而生畏的地方胸竞,但是,如果歸結(jié)起來(lái)参萄,其實(shí)整個(gè)渲染流程基本是固定的卫枝,只要把它按照固定的流程封裝好,其實(shí)并沒(méi)有那么復(fù)雜讹挎。
接下來(lái)校赤,就進(jìn)入實(shí)戰(zhàn),一層一層扒開(kāi)OpengGL的神秘面紗重荠。
1碌嘀、初始化
在Android中粟判,OpenGL通常配合GLSurfaceView使用,在GLSurfraceView中浑测,Google已經(jīng)封裝好了渲染的基礎(chǔ)流程。
這里需要單獨(dú)強(qiáng)調(diào)一下,OpenGL是基于線程的一個(gè)狀態(tài)機(jī)迁央,有關(guān)OpenGL的操作掷匠,比如創(chuàng)建紋理ID,初始化岖圈,渲染等讹语,都必須要在同一個(gè)線程中完成,否則會(huì)造成異常蜂科。
通常開(kāi)發(fā)者在剛剛接觸OpenGL的時(shí)候并不能深刻體會(huì)到這種機(jī)制顽决,原因是Google在GLSurfaceView中已經(jīng)幫開(kāi)發(fā)者做了這部分的內(nèi)容。這是OpenGL非常重要的一個(gè)方面导匣,在后續(xù)的有關(guān)EGL的文章中會(huì)繼續(xù)深入了解到才菠。
- 新建頁(yè)面
class SimpleRenderActivity : AppCompatActivity() {
//自定義的OpenGL渲染器,詳情請(qǐng)繼續(xù)往下看
lateinit var drawer: IDrawer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_simpler_render)
drawer = if (intent.getIntExtra("type", 0) == 0) {
TriangleDrawer()
} else {
BitmapDrawer(BitmapFactory.decodeResource(CONTEXT!!.resources, R.drawable.cover))
}
initRender(drawer)
}
private fun initRender(drawer: IDrawer) {
gl_surface.setEGLContextClientVersion(2)
gl_surface.setRenderer(SimpleRender(drawer))
}
override fun onDestroy() {
drawer.release()
super.onDestroy()
}
}
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.opengl.GLSurfaceView
android:id="@+id/gl_surface"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</android.support.constraint.ConstraintLayout>
頁(yè)面非常簡(jiǎn)單逐抑,放置了一個(gè)滿屏的GLSurfaceView鸠儿,初始化的時(shí)候,設(shè)置了OpenGL使用的版本為2.0厕氨,然后配置了渲染器SimpleRender进每,繼承自GLSurfaceView.Renderer
IDrawer將在繪制三角形的時(shí)候具體講解,定義該接口類只是為了方便拓展命斧,也可以直接將渲染代碼寫在SimpleRender中田晚。
- 實(shí)現(xiàn)渲染接口
class SimpleRender(private val mDrawer: IDrawer): GLSurfaceView.Renderer {
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
GLES20.glClearColor(0f, 0f, 0f, 0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
mDrawer.setTextureID(OpenGLTools.createTextureIds(1)[0])
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
}
override fun onDrawFrame(gl: GL10?) {
mDrawer.draw()
}
}
注意到,實(shí)現(xiàn)了三個(gè)回調(diào)接口国葬,這三個(gè)接口就是Google封裝好的流程中贤徒,暴露出來(lái)的接口,留給給開(kāi)發(fā)者實(shí)現(xiàn)初始化和渲染汇四,并且這三個(gè)接口的回調(diào)都在同一個(gè)線程中接奈。
- 在onSurfaceCreated中,調(diào)用了兩句OpenGL ES的代碼實(shí)現(xiàn)清屏通孽,清屏顏色為黑色序宦。
GLES20.glClearColor(0f, 0f, 0f, 0f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
同時(shí),創(chuàng)建了一個(gè)紋理ID背苦,并設(shè)置給Drawer互捌,如下:
fun createTextureIds(count: Int): IntArray {
val texture = IntArray(count)
GLES20.glGenTextures(count, texture, 0) //生成紋理
return texture
}
- 在onSurfaceChanged中,調(diào)用glViewport行剂,設(shè)置了OpenGL繪制的區(qū)域?qū)捀吆臀恢?/li>
這里所說(shuō)的繪制區(qū)域秕噪,是指OpenGL在GLSurfaceView中的繪制區(qū)域,一般都是全部鋪滿厚宰。
GLES20.glViewport(0, 0, width, height)
- 在onDrawFrame中腌巾,就是真正實(shí)現(xiàn)繪制的地方了。該接口會(huì)不停的回調(diào),刷新繪制區(qū)域壤躲。這里使用一個(gè)簡(jiǎn)單的三角形繪制來(lái)說(shuō)明整個(gè)繪制流程城菊。
2、渲染一個(gè)簡(jiǎn)單的三角形
先定義一個(gè)渲染接口類:
interface IDrawer {
fun draw()
fun setTextureID(id: Int)
fun release()
}
class TriangleDrawer(private val mTextureId: Int = -1): IDrawer {
//頂點(diǎn)坐標(biāo)
private val mVertexCoors = floatArrayOf(
-1f, -1f,
1f, -1f,
0f, 1f
)
//紋理坐標(biāo)
private val mTextureCoors = floatArrayOf(
0f, 1f,
1f, 1f,
0.5f, 0f
)
//紋理ID
private var mTextureId: Int = -1
//OpenGL程序ID
private var mProgram: Int = -1
// 頂點(diǎn)坐標(biāo)接收者
private var mVertexPosHandler: Int = -1
// 紋理坐標(biāo)接收者
private var mTexturePosHandler: Int = -1
private lateinit var mVertexBuffer: FloatBuffer
private lateinit var mTextureBuffer: FloatBuffer
init {
//【步驟1: 初始化頂點(diǎn)坐標(biāo)】
initPos()
}
private fun initPos() {
val bb = ByteBuffer.allocateDirect(mVertexCoors.size * 4)
bb.order(ByteOrder.nativeOrder())
//將坐標(biāo)數(shù)據(jù)轉(zhuǎn)換為FloatBuffer碉克,用以傳入給OpenGL ES程序
mVertexBuffer = bb.asFloatBuffer()
mVertexBuffer.put(mVertexCoors)
mVertexBuffer.position(0)
val cc = ByteBuffer.allocateDirect(mTextureCoors.size * 4)
cc.order(ByteOrder.nativeOrder())
mTextureBuffer = cc.asFloatBuffer()
mTextureBuffer.put(mTextureCoors)
mTextureBuffer.position(0)
}
override fun setTextureID(id: Int) {
mTextureId = id
}
override fun draw() {
if (mTextureId != -1) {
//【步驟2: 創(chuàng)建凌唬、編譯并啟動(dòng)OpenGL著色器】
createGLPrg()
//【步驟3: 開(kāi)始渲染繪制】
doDraw()
}
}
private fun createGLPrg() {
if (mProgram == -1) {
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexShader())
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShader())
//創(chuàng)建OpenGL ES程序,注意:需要在OpenGL渲染線程中創(chuàng)建漏麦,否則無(wú)法渲染
mProgram = GLES20.glCreateProgram()
//將頂點(diǎn)著色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader)
//將片元著色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader)
//連接到著色器程序
GLES20.glLinkProgram(mProgram)
mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition")
mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate")
}
//使用OpenGL程序
GLES20.glUseProgram(mProgram)
}
private fun doDraw() {
//啟用頂點(diǎn)的句柄
GLES20.glEnableVertexAttribArray(mVertexPosHandler)
GLES20.glEnableVertexAttribArray(mTexturePosHandler)
//設(shè)置著色器參數(shù)
GLES20.glVertexAttribPointer(mVertexPosHandler, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer)
GLES20.glVertexAttribPointer(mTexturePosHandler, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer)
//開(kāi)始繪制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
}
override fun release() {
GLES20.glDisableVertexAttribArray(mVertexPosHandler)
GLES20.glDisableVertexAttribArray(mTexturePosHandler)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
GLES20.glDeleteTextures(1, intArrayOf(mTextureId), 0)
GLES20.glDeleteProgram(mProgram)
}
private fun getVertexShader(): String {
return "attribute vec4 aPosition;" +
"void main() {" +
" gl_Position = aPosition;" +
"}"
}
private fun getFragmentShader(): String {
return "precision mediump float;" +
"void main() {" +
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);" +
"}"
}
private fun loadShader(type: Int, shaderCode: String): Int {
//根據(jù)type創(chuàng)建頂點(diǎn)著色器或者片元著色器
val shader = GLES20.glCreateShader(type)
//將資源加入到著色器中客税,并編譯
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}
}
雖然只是畫一個(gè)簡(jiǎn)單的三角形,代碼依然看起來(lái)很復(fù)雜撕贞。這里把它拆解為三個(gè)步驟更耻,就比較清晰明了了。
1) 初始化頂點(diǎn)坐標(biāo)
前面我們講到OpenGL的世界坐標(biāo)和紋理坐標(biāo)捏膨,在繪制前就需要先把這兩個(gè)坐標(biāo)確定好秧均。
【重要提示】
有一點(diǎn)還沒(méi)說(shuō)的是,OpenGL ES所有的畫面都是由三角形構(gòu)成的号涯,比如一個(gè)四邊形由兩個(gè)三角形構(gòu)成目胡,其他更復(fù)雜的圖形也都可以分割為大大小小的三角形。
因此链快,頂點(diǎn)坐標(biāo)也是根據(jù)三角形的連接來(lái)設(shè)置的誉己。其繪制方式有三種:
- GL_TRIANGLES:獨(dú)立頂點(diǎn)的構(gòu)成三角形
- GL_TRIANGLE_STRIP:復(fù)用頂點(diǎn)構(gòu)成三角形
- GL_TRIANGLE_FAN:復(fù)用第一個(gè)頂點(diǎn)構(gòu)成三角形
通常情況下,一般使用GL_TRIANGLE_STRIP繪制模式域蜗。那么一個(gè)四邊形的頂點(diǎn)順序看起來(lái)是這樣子的(v1-v2-v3)(v2-v3-v4)
對(duì)應(yīng)的紋理坐標(biāo)也要和頂點(diǎn)坐標(biāo)順序一致巨双,否則會(huì)出現(xiàn)顛倒,變形等異常
由于繪制的是三角形霉祸,所以兩個(gè)坐標(biāo)如下(這里只設(shè)置xy軸坐標(biāo)筑累,忽略z軸坐標(biāo),每?jī)蓚€(gè)數(shù)據(jù)構(gòu)成一個(gè)坐標(biāo)點(diǎn)):
//頂點(diǎn)坐標(biāo)
private val mVertexCoors = floatArrayOf(
-1f, -1f,
1f, -1f,
0f, 1f
)
//紋理坐標(biāo)
private val mTextureCoors = floatArrayOf(
0f, 1f,
1f, 1f,
0.5f, 0f
)
在initPos方法中丝蹭,由于底層不能直接接收數(shù)組疼阔,所以將數(shù)組轉(zhuǎn)換為ByteBuffer
2) 創(chuàng)建、編譯并啟動(dòng)OpenGL著色器
private fun createGLPrg() {
if (mProgram == -1) {
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, getVertexShader())
val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, getFragmentShader())
//創(chuàng)建OpenGL ES程序半夷,注意:需要在OpenGL渲染線程中創(chuàng)建,否則無(wú)法渲染
mProgram = GLES20.glCreateProgram()
//將頂點(diǎn)著色器加入到程序
GLES20.glAttachShader(mProgram, vertexShader)
//將片元著色器加入到程序中
GLES20.glAttachShader(mProgram, fragmentShader)
//連接到著色器程序
GLES20.glLinkProgram(mProgram)
mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition")
mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate")
}
//使用OpenGL程序
GLES20.glUseProgram(mProgram)
}
private fun getVertexShader(): String {
return "attribute vec4 aPosition;" +
"void main() {" +
" gl_Position = aPosition;" +
"}"
}
private fun getFragmentShader(): String {
return "precision mediump float;" +
"void main() {" +
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);" +
"}"
}
private fun loadShader(type: Int, shaderCode: String): Int {
//根據(jù)type創(chuàng)建頂點(diǎn)著色器或者片元著色器
val shader = GLES20.glCreateShader(type)
//將資源加入到著色器中迅细,并編譯
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}
上面已經(jīng)說(shuō)過(guò)巫橄,GLSL是針對(duì)GPU的編程語(yǔ)言,而著色器就是一段小程序茵典,為了能夠運(yùn)行這段小程序湘换,需要先對(duì)其進(jìn)行編譯和綁定,才能使用。
本例中的著色器就是上文提到的最簡(jiǎn)單的著色器彩倚。
可以看到筹我,著色器其實(shí)就是一段字符串
進(jìn)入loadShader中,通過(guò)GLES20.glCreateShader帆离,根據(jù)不同類型蔬蕊,獲取頂點(diǎn)著色器和片元著色器。
然后調(diào)用以下方法哥谷,編譯著色器
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
編譯好著色器以后岸夯,就是綁定,連接们妥,啟用程序即可猜扮。
還記得上面說(shuō)過(guò),著色器中的坐標(biāo)是由Java傳遞給GLSL嗎监婶?
細(xì)心的你可能發(fā)現(xiàn)了這兩句代碼
mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition")
mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate")
沒(méi)錯(cuò)旅赢,這就是Java和GLSL交互的通道,通過(guò)屬性可以給GLSL設(shè)置相關(guān)的值惑惶。
3) 開(kāi)始渲染繪制
private fun doDraw() {
//啟用頂點(diǎn)的句柄
GLES20.glEnableVertexAttribArray(mVertexPosHandler)
GLES20.glEnableVertexAttribArray(mTexturePosHandler)
//設(shè)置著色器參數(shù)煮盼, 第二個(gè)參數(shù)表示一個(gè)頂點(diǎn)包含的數(shù)據(jù)數(shù)量,這里為xy集惋,所以為2
GLES20.glVertexAttribPointer(mVertexPosHandler, 2, GLES20.GL_FLOAT, false, 0, mVertexBuffer)
GLES20.glVertexAttribPointer(mTexturePosHandler, 2, GLES20.GL_FLOAT, false, 0, mTextureBuffer)
//開(kāi)始繪制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3)
}
首先激活著色器的頂點(diǎn)坐標(biāo)和紋理坐標(biāo)屬性孕似,然后把初始化好的坐標(biāo)傳遞給著色器,最后啟動(dòng)繪制:
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 3)
繪制有兩種方式:glDrawArrays和glDrawElements刮刑,兩者區(qū)別在于glDrawArrays是直接使用定義好的頂點(diǎn)順序進(jìn)行繪制喉祭;而glDrawElements則是需要定義另外的索引數(shù)組,來(lái)確認(rèn)頂點(diǎn)的組合和繪制順序雷绢。
通過(guò)以上步驟泛烙,就可以在屏幕上看到一個(gè)紅色的三角形了。
可能有人就有疑問(wèn)了:繪制三角形的時(shí)候只是直接設(shè)置了像素點(diǎn)的顏色值翘紊,并沒(méi)有用到紋理蔽氨,紋理到底有什么用呢?
接下來(lái)帆疟,就用紋理來(lái)顯示一張圖片鹉究,看看紋理到底怎么使用。
建議先看清楚繪制三角形的流程踪宠,繪制圖片就是基于以上流程自赔,重復(fù)代碼就不再貼出。
3柳琢、紋理貼圖绍妨,顯示一張圖片
以下只貼出和繪制三角形不一樣的部分代碼润脸,詳細(xì)代碼請(qǐng)看源碼。
class BitmapDrawer(private val mTextureId: Int, private val mBitmap: Bitmap): IDrawer {
//-------【注1:坐標(biāo)變更了他去,由四個(gè)點(diǎn)組成一個(gè)四邊形】-------
// 頂點(diǎn)坐標(biāo)
private val mVertexCoors = floatArrayOf(
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
)
// 紋理坐標(biāo)
private val mTextureCoors = floatArrayOf(
0f, 1f,
1f, 1f,
0f, 0f,
1f, 0f
)
//-------【注2:新增紋理接收者】-------
// 紋理接收者
private var mTextureHandler: Int = -1
fun draw() {
if (mTextureId != -1) {
//【步驟2: 創(chuàng)建毙驯、編譯并啟動(dòng)OpenGL著色器】
createGLPrg()
//-------【注4:新增兩個(gè)步驟】-------
//【步驟3: 激活并綁定紋理單元】
activateTexture()
//【步驟4: 綁定圖片到紋理單元】
bindBitmapToTexture()
//----------------------------------
//【步驟5: 開(kāi)始渲染繪制】
doDraw()
}
}
private fun createGLPrg() {
if (mProgram == -1) {
//省略與繪制三角形一致的部分
//......
mVertexPosHandler = GLES20.glGetAttribLocation(mProgram, "aPosition")
mTexturePosHandler = GLES20.glGetAttribLocation(mProgram, "aCoordinate")
//【注3:新增獲取紋理接收者】
mTextureHandler = GLES20.glGetUniformLocation(mProgram, "uTexture")
}
//使用OpenGL程序
GLES20.glUseProgram(mProgram)
}
private fun activateTexture() {
//激活指定紋理單元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
//綁定紋理ID到紋理單元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId)
//將激活的紋理單元傳遞到著色器里面
GLES20.glUniform1i(mTextureHandler, 0)
//配置邊緣過(guò)渡參數(shù)
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
}
private fun bindBitmapToTexture() {
if (!mBitmap.isRecycled) {
//綁定圖片到被激活的紋理單元
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0)
}
}
private fun doDraw() {
//省略與繪制三角形一致的部分
//......
//【注5:繪制頂點(diǎn)加1,變?yōu)?】
//開(kāi)始繪制:最后一個(gè)參數(shù)灾测,將頂點(diǎn)數(shù)量改為4
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
}
private fun getVertexShader(): String {
return "attribute vec4 aPosition;" +
"attribute vec2 aCoordinate;" +
"varying vec2 vCoordinate;" +
"void main() {" +
" gl_Position = aPosition;" +
" vCoordinate = aCoordinate;" +
"}"
}
private fun getFragmentShader(): String {
return "precision mediump float;" +
"uniform sampler2D uTexture;" +
"varying vec2 vCoordinate;" +
"void main() {" +
" vec4 color = texture2D(uTexture, vCoordinate);" +
" gl_FragColor = color;" +
"}"
}
//省略和繪制三角形內(nèi)容一致的部分
//......
}
不一致的地方爆价,代碼中已經(jīng)做了標(biāo)識(shí)(見(jiàn)代碼中的【注:x】)。逐個(gè)來(lái)看看:
1)頂點(diǎn)坐標(biāo)
頂點(diǎn)坐標(biāo)和紋理坐標(biāo)由3個(gè)變成4個(gè)行施,組成一個(gè)長(zhǎng)方形允坚,組合方式也是GL_TRIANGLE_STRIP。
2)著色器
首先介紹一下GLSL中的限定符
- attritude:一般用于各個(gè)頂點(diǎn)各不相同的量蛾号。如頂點(diǎn)顏色稠项、坐標(biāo)等。
- uniform:一般用于對(duì)于3D物體中所有頂點(diǎn)都相同的量鲜结。比如光源位置展运,統(tǒng)一變換矩陣等。
- varying:表示易變量精刷,一般用于頂點(diǎn)著色器傳遞到片元著色器的量拗胜。
const:常量。
各行代碼解析如下:
private fun getVertexShader(): String {
return //頂點(diǎn)坐標(biāo)
"attribute vec2 aPosition;" +
//紋理坐標(biāo)
"attribute vec2 aCoordinate;" +
//用于傳遞紋理坐標(biāo)給片元著色器怒允,命名和片元著色器中的一致
"varying vec2 vCoordinate;" +
"void main() {" +
" gl_Position = aPosition;" +
" vCoordinate = aCoordinate;" +
"}"
}
private fun getFragmentShader(): String {
return //配置float精度埂软,使用了float數(shù)據(jù)一定要配置:lowp(低)/mediump(中)/highp(高)
"precision mediump float;" +
//從Java傳遞進(jìn)入來(lái)的紋理單元
"uniform sampler2D uTexture;" +
//從頂點(diǎn)著色器傳遞進(jìn)來(lái)的紋理坐標(biāo)
"varying vec2 vCoordinate;" +
"void main() {" +
//根據(jù)紋理坐標(biāo),從紋理單元中取色
" vec4 color = texture2D(uTexture, vCoordinate);" +
" gl_FragColor = color;" +
"}"
}
繪制過(guò)程新增了兩個(gè)步驟:
3)激活并綁定紋理單元
private fun activateTexture() {
//激活指定紋理單元
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
//綁定紋理ID到紋理單元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureId)
//將激活的紋理單元傳遞到著色器里面
GLES20.glUniform1i(mTextureHandler, 0)
//配置紋理過(guò)濾模式
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
//配置紋理環(huán)繞方式
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
}
由于顯示圖片需要用到紋理單元來(lái)傳遞整張圖片的內(nèi)容纫事,所以首先需要激活一個(gè)紋理單元勘畔。
為什么說(shuō)是一個(gè)紋理單元?
因?yàn)镺penGL ES中內(nèi)置了很多個(gè)紋理單元,并且是連續(xù)丽惶,比如GLES20.GL_TEXTURE0炫七,GLES20.GL_TEXTURE1,GLES20.GL_TEXTURE3...可以選擇其中一個(gè)钾唬,一般默認(rèn)選第一個(gè)GLES20.GL_TEXTURE0万哪,并且OpenGL默認(rèn)激活的就是第一個(gè)紋理單元。
另外抡秆,紋理單元GLES20.GL_TEXTURE1 = GLES20.GL_TEXTURE0 + 1奕巍,以此類推。
激活指定的紋理單元后儒士,需要把它和紋理ID做綁定的止,并且在傳遞到著色器中的時(shí)候:GLES20.glUniform1i(mTextureHandler, 0),第二個(gè)參數(shù)索引需要和紋理單元索引保持一致乍桂。
到這里冲杀,可以發(fā)現(xiàn),OpenGL方法的命名都是比較規(guī)律的睹酌,比如GLES20.glUniform1i對(duì)應(yīng)的是GLSL中的uniform限定符變量权谁;ES20.glGetAttribLocation對(duì)應(yīng)GLSL中的attribute限定符變量等等
最后四行代碼,用于配置紋理過(guò)濾模式和紋理環(huán)繞方式(對(duì)于這兩個(gè)模式的介紹引用自【LearnOpenGL-CN】)
- 紋理過(guò)濾模式
紋理坐標(biāo)不依賴于分辨率憋沿,它可以是任意浮點(diǎn)值旺芽,所以O(shè)penGL需要知道怎樣將紋理像素映射到紋理坐標(biāo)。
一般使用這兩個(gè)模式:GL_NEAREST(鄰近過(guò)濾)辐啄、GL_LINEAR(線性過(guò)濾)
當(dāng)設(shè)置為GL_NEAREST的時(shí)候采章,OpenGL會(huì)選擇中心點(diǎn)最接近紋理坐標(biāo)的那個(gè)像素。
當(dāng)設(shè)置為GL_LINEAR的時(shí)候壶辜,它會(huì)基于紋理坐標(biāo)附近的紋理像素悯舟,計(jì)算出一個(gè)插值,近似出這些紋理像素之間的顏色砸民。
- 紋理環(huán)繞方式
環(huán)繞方式 | 描述 |
---|---|
GL_REPEAT | 對(duì)紋理的默認(rèn)行為抵怎。重復(fù)紋理圖像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一樣岭参,但每次重復(fù)圖片是鏡像放置的反惕。 |
GL_CLAMP_TO_EDGE | 紋理坐標(biāo)會(huì)被約束在0到1之間,超出的部分會(huì)重復(fù)紋理坐標(biāo)的邊緣演侯,產(chǎn)生一種邊緣被拉伸的效果姿染。 |
GL_CLAMP_TO_BORDER | 超出的坐標(biāo)為用戶指定的邊緣顏色。 |
4)綁定圖片到紋理單元
激活了紋理單元以后秒际,調(diào)用texImage2D方法悬赏,就可以把bmp綁定到指定的紋理單元上面了。
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, mBitmap, 0)
5)繪制
繪制的時(shí)候程癌,最后一句的最后一個(gè)參數(shù)由三角形的3個(gè)頂點(diǎn)變成為長(zhǎng)方形的4個(gè)頂點(diǎn)舷嗡。如果還是填入3,你會(huì)發(fā)現(xiàn)會(huì)顯示圖片的一半嵌莉,即三角形(對(duì)角線分割開(kāi))进萄。
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
至此,一張圖片就通過(guò)紋理貼圖顯示出來(lái)了锐峭。
當(dāng)然中鼠,你會(huì)發(fā)現(xiàn),這張圖片是變形的沿癞,鋪滿整個(gè)GLSurfaceView窗口了援雇。這里就涉及到了頂點(diǎn)坐標(biāo)變換的問(wèn)題了,將在下一篇文章中具體講解椎扬。
五惫搏、總結(jié)
經(jīng)過(guò)上面簡(jiǎn)單的繪制三角形和紋理貼圖具温,可以總結(jié)出Android中OpenGL ES的2D繪制流程:
- 通過(guò)GLSurfaceView配置OpenGL ES版本,指定Render
- 實(shí)現(xiàn)GLSurfaceView.Renderer筐赔,復(fù)寫暴露的方法铣猩,并配置OpenGL顯示窗口,清屏
- 創(chuàng)建紋理ID
- 配置好頂點(diǎn)坐標(biāo)和紋理坐標(biāo)
- 初始化坐標(biāo)變換矩陣
- 初始化OpenGL程序茴丰,并編譯达皿、鏈接頂點(diǎn)著色和片段著色器,獲取GLSL中的變量屬性
- 激活紋理單元贿肩,綁定紋理ID峦椰,配置紋理過(guò)濾模式和環(huán)繞方式
- 綁定紋理(如將bitmap綁定給紋理)
- 啟動(dòng)繪制
以上基本是一個(gè)通用的流程,當(dāng)然渲染圖片和渲染視頻稍有不同汰规,以及第5點(diǎn)汤功,都將在下一篇說(shuō)到。