OpenGL
一、 基礎(chǔ)概念
1. 簡(jiǎn)介
Android 可通過開放圖形庫(kù) (OpenGL)(特別是 OpenGL ES API)來(lái)支持高性能 2D 和 3D 圖形梅惯。OpenGL 是一種跨平臺(tái)的圖形 API巾表,用于為 3D 圖形處理硬件指定標(biāo)準(zhǔn)的軟件接口笤休。OpenGL ES 是 OpenGL 規(guī)范的一種形式救崔,適用于嵌入式設(shè)備锭部。
2. 坐標(biāo)系
OpenGL ES中圖形的位置是通過坐標(biāo)系確定的囤耳,圖形的繪制方式由點(diǎn)篙顺、線、三角形構(gòu)成充择。在OpenGL中德玫,世界坐標(biāo)系是以屏幕中心為原點(diǎn)(0, 0, 0),且是始終不變的椎麦。當(dāng)我們面對(duì)屏幕宰僧,右邊是x正軸,上面是y正軸观挎,屏幕指向我們?yōu)閦正軸琴儿。長(zhǎng)度單位如下:窗口范圍按此單位是(-1,-1)到(1,1),即屏幕左下角坐標(biāo)為(-1嘁捷,-1)造成,右上角 坐標(biāo)為(1,1)。
3.著色器
著色器是用來(lái)描述頂點(diǎn)或像素特性的程序普气,它使用 GLSL(OpenGL Shading Language)語(yǔ)言開發(fā)谜疤,利用這種語(yǔ)言編寫程序可運(yùn)行在GPU上進(jìn)行圖像的處理或者渲染。著色器分為兩個(gè)部分,即Vertex Shader(頂點(diǎn)著色器)與Fragment Shader(片元著色器)兩部分夷磕,分別完成各自在OpenGL渲染管線中的功能履肃,頂點(diǎn)著色器的主要目的就是確定每個(gè)頂點(diǎn)的最終位置,而片段著色器的主要目的就是告訴GPU每個(gè)片段的最終顏色應(yīng)該是什么坐桩。
4.狀態(tài)機(jī)
狀態(tài)機(jī)是一種行為尺棋,即對(duì)象在其生命周期中響應(yīng)事件所經(jīng)歷的狀態(tài)序列以及對(duì)那些狀態(tài)事件的響應(yīng)。具有以下特點(diǎn):
- 有記憶功能绵跷,能夠記住當(dāng)前的狀態(tài)
- 可以接收輸入膘螟,根據(jù)輸入的內(nèi)容和自己的原先狀態(tài),修改自己當(dāng)前狀態(tài)碾局,并且可以有對(duì)應(yīng)輸出
- 當(dāng)進(jìn)入特殊狀態(tài)(停機(jī)狀態(tài))的時(shí)候荆残,便不再接收輸入,即停止工作
類推到OpenGL中净当,可以這么理解
- OpenGL可以記錄自己的狀態(tài)(如當(dāng)前所使用的顏色内斯、是否開啟了混合功能等)
- OpenGL可以接收輸入(調(diào)用OpenGL函數(shù)時(shí),可以理解成OpenGL在接收輸入)像啼,如調(diào)用glColor3f俘闯,即OpenGL接收到這個(gè)輸入后,會(huì)修改自己的“當(dāng)前顏色”這個(gè)狀態(tài)
- OpenGL可以進(jìn)入停止?fàn)顟B(tài)忽冻,不再接收輸入真朗。在程序退出前,OpenGL總會(huì)先停止工作
5.OpenGL渲染
人眼所看到的圖像是由屏幕上的像素點(diǎn)組成的僧诚,在內(nèi)存中遮婶,這些像素點(diǎn)可以組織成一個(gè)大的一維數(shù)組,每4個(gè)Byte即表示一個(gè)像素點(diǎn)的RGBA數(shù)據(jù)湖笨,而在顯卡中蹭睡,這些像素點(diǎn)可以組織成幀緩沖區(qū)(FrameBuffer)的形式,幀緩沖區(qū)保存了圖形硬件為了控制屏幕上所有像素的顏色和強(qiáng)度所需要的全部信息赶么。
渲染的整體流程:
1. 指定幾何對(duì)象:選擇以何種方式繪制幾何圖形,例如點(diǎn)脊串,面辫呻,三角形等。
2. 頂點(diǎn)處理:通過頂點(diǎn)著色器將本地坐標(biāo)轉(zhuǎn)換為坐標(biāo)系中的坐標(biāo)值琼锋。
3. 組裝圖元:在處理過頂點(diǎn)之后放闺,各個(gè)頂點(diǎn)被按照繪制命令中的規(guī)則組裝成圖元。
4. 光柵化操作:圖元數(shù)據(jù)在此被分成更小的單元并且對(duì)應(yīng)于幀緩沖區(qū)的像素缕坎,一個(gè)單元成為片元怖侦。即從頂點(diǎn)數(shù)據(jù)到顯示到設(shè)備上的像素的過程。
5. 片元處理:通過片元著色器計(jì)算每一一個(gè)片元的顏色值。
6. 幀緩沖操作:執(zhí)行幀緩沖的寫入操作匾寝,負(fù)責(zé)將最終的像素值寫到幀緩沖區(qū)中搬葬。幀緩沖區(qū)簡(jiǎn)單理解就是存儲(chǔ)屏幕上一幀畫面的區(qū)域。
二艳悔、案例-繪制圖片紋理
創(chuàng)建兩個(gè)GLSL代碼段-頂點(diǎn)著色器急凰,片元著色器
OpenGL ES實(shí)現(xiàn)3D繪圖和普通的2D繪圖即view利用canvas來(lái)繪制不一樣,OpenGL需要加載GLSL程式猜年,讓GPU進(jìn)行繪制抡锈。所以需要定義shader代碼,并在初始化的時(shí)候加載乔外。
vertex_shader.glsl
attribute vec4 av_Position;//頂點(diǎn)位置
attribute vec2 af_Position;//紋理位置
varying vec2 v_texPo;//紋理位置與fragment_shader交互
void main() {
v_texPo = af_Position;
gl_Position = av_Position;
}
fragment_shader.glsl
precision mediump float;//精度 為float
varying vec2 v_texPo;//紋理位置接收于vertex_shader
uniform sampler2D sTexture;//紋理
void main() {
gl_FragColor=texture2D(sTexture, v_texPo);
}
申明頂點(diǎn)/紋理坐標(biāo)及其緩沖數(shù)組
//頂點(diǎn)坐標(biāo)
var vertexData = floatArrayOf(
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
)
//紋理坐標(biāo) 對(duì)應(yīng)頂點(diǎn)坐標(biāo)與之映射
var textureData = floatArrayOf(
0f, 1f,
1f, 1f,
0f, 0f,
1f, 0f
)
init {
//這個(gè)buffer是本地代碼中用于存儲(chǔ)頂點(diǎn)矩陣數(shù)據(jù)
vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)//內(nèi)存大小為vertexData.size*4 頂點(diǎn)都存儲(chǔ)在一個(gè)浮點(diǎn)數(shù)組里床三,每個(gè)浮點(diǎn)有4個(gè)字節(jié)
.order(ByteOrder.nativeOrder())//字節(jié)緩沖區(qū)按照本地字節(jié)序組織它的內(nèi)容
.asFloatBuffer()//FloatBuffer類實(shí)例 避免直接操作字節(jié)
.put(vertexData)//把數(shù)據(jù)從Dalvik的內(nèi)存復(fù)制到本地內(nèi)存
vertexBuffer.position(0) //設(shè)置緩沖區(qū)起始位置
//分配紋理坐標(biāo)緩沖
textureBuffer = ByteBuffer.allocateDirect(textureData.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(textureData)
textureBuffer.position(0)
}
加載GLSL代碼并將其鏈接到程式中
調(diào)用glCreateShader()方法-傳入渲染器類型參數(shù)type,創(chuàng)建對(duì)應(yīng)的著色器對(duì)象杨幼;
調(diào)用glShaderSource()方法-傳入著色器對(duì)象和字符串shaderCode定義的源代碼撇簿,將二者關(guān)聯(lián)起來(lái);
調(diào)用glCompileShader()方法-傳入著色器對(duì)象推汽,對(duì)其進(jìn)行編譯补疑。
override fun onSurfaceCreated() {
//創(chuàng)建程式 加載頂點(diǎn)著色器
program = ShaderUtils.createProgram(
context.resources,
"vertex_shader.glsl",
"fragment_shader.glsl"
).also {
//獲取頂點(diǎn)坐標(biāo)字段
avPosition = GLES32.glGetAttribLocation(it, "av_Position")
//獲取紋理坐標(biāo)字段
afPosition = GLES32.glGetAttribLocation(it, "af_Position")
//
bitmapTexture = GLES32.glGetUniformLocation(it, "sTexture")
//創(chuàng)建要顯示的圖片紋理
createTextureId(bitmap)
}
}
/**
* 創(chuàng)建著色器程序?qū)ο? *
* @param vertexShaderCode :頂點(diǎn)著色器代碼
* @param fragmentShaderCode :片元著色器代碼
* @return program
*/
private fun createProgram(
vertexShaderCode: String?,
fragmentShaderCode: String?
): Int {
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
if (vertexShader == 0) {
return 0
}
val fragmentShader =
loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
if (fragmentShader == 0) {
return 0
}
//創(chuàng)建空的程式
var program = GLES20.glCreateProgram()
if (0 != program) {
//關(guān)聯(lián)兩個(gè)渲染器
GLES20.glAttachShader(program, vertexShader)
GLES20.glAttachShader(program, fragmentShader)
GLES20.glLinkProgram(program)
val linkStatus = IntArray(1)
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] != GLES20.GL_TRUE) {
GLES20.glDeleteProgram(program)
program = 0
}
}
return program
}
/**
* 加載編譯頂點(diǎn)渲染器
*
* @param type :shader類型
* @param shadeCode :著色器代碼
* @return shader
*/
private fun loadShader(type: Int, shadeCode: String?): Int {
var shader = GLES20.glCreateShader(type)
if (0 != shader) {
GLES20.glShaderSource(shader, shadeCode)
GLES20.glCompileShader(shader)
val compiled = IntArray(1)
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0)
if (compiled[0] == 0) {
GLES20.glDeleteShader(shader)
shader = 0
}
}
return shader
}
創(chuàng)建圖片紋理ID
fun createTextureId(bitmap: Bitmap?): Int {
var result = 0
bitmap?.let {
//生產(chǎn)一個(gè)紋理
val textureIds = IntArray(1)
GLES32.glGenTextures(1, textureIds, 0)
//綁定為 2D紋理
GLES32.glBindTexture(GLES32.GL_TEXTURE_2D, textureIds[0])
//設(shè)置環(huán)繞模式
GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_WRAP_S, GLES32.GL_REPEAT)
GLES32.glTexParameteri(GLES32.GL_TEXTURE_2D, GLES32.GL_TEXTURE_WRAP_T, GLES32.GL_REPEAT)
//設(shè)置過濾模式
GLES32.glTexParameteri(
GLES32.GL_TEXTURE_2D,
GLES32.GL_TEXTURE_MIN_FILTER,
GLES32.GL_LINEAR
)
GLES32.glTexParameteri(
GLES32.GL_TEXTURE_2D,
GLES32.GL_TEXTURE_MAG_FILTER,
GLES32.GL_LINEAR
)
//綁定 bitmap到 textureIds[0] 這個(gè)2D紋理上
GLUtils.texImage2D(GLES32.GL_TEXTURE_2D, 0, bitmap, 0)
val bitmapBuffer = ByteBuffer.allocate(bitmap.height * bitmap.width * 4)
bitmap.copyPixelsToBuffer(bitmapBuffer)
//將bitmapBuffer位置移動(dòng)到初始位置
bitmapBuffer.flip()
//設(shè)置內(nèi)存大小綁定內(nèi)存地址
GLES32.glTexImage2D(
GLES32.GL_TEXTURE_2D, 0, GLES32.GL_RGBA, bitmap.width, bitmap.height,
0, GLES32.GL_RGBA, GLES32.GL_UNSIGNED_BYTE, bitmapBuffer
)
//退出 紋理的設(shè)置,進(jìn)入下一環(huán)節(jié)
GLES32.glBindTexture(GLES32.GL_TEXTURE_2D, 0)
result = textureIds[0]
}
return result
}
繪制紋理到屏幕上
override fun onDrawFrame() {
//清空顏色
GLES32.glClear(GLES32.GL_COLOR_BUFFER_BIT)
//設(shè)置背景顏色
GLES32.glClearColor(1f, 1f, 1f, 1f)
//使用GLSL程式
GLES32.glUseProgram(program)
//繪制視頻源 流程為->配置歹撒、綁定id莲组、釋放
GLES32.glEnableVertexAttribArray(avPosition)
GLES32.glEnableVertexAttribArray(afPosition)
//頂點(diǎn)著色器的紋理/頂點(diǎn)坐標(biāo)
GLES32.glVertexAttribPointer(avPosition, 2, GLES32.GL_FLOAT, false, 0, vertexBuffer)
GLES32.glVertexAttribPointer(afPosition, 2, GLES32.GL_FLOAT, false, 0, textureBuffer)
//傳入圖片紋理
GLES32.glBindTexture(GLES32.GL_TEXTURE_2D, bitmapTexture)
GLES32.glDrawArrays(GLES32.GL_TRIANGLE_STRIP, 0, 4)
//釋放
GLES20.glDisableVertexAttribArray(avPosition)
GLES20.glDisableVertexAttribArray(afPosition)
GLES32.glBindTexture(GLES32.GL_TEXTURE_2D, 0)
GLES32.glBindBuffer(GLES32.GL_ARRAY_BUFFER, 0)
}
三、更多
GLSL基礎(chǔ)語(yǔ)法
GLSL是一種面向過程的語(yǔ)言暖夭,和Java的面向?qū)ο笫遣煌摹?/p>
GLSL的基本語(yǔ)法與C/C++基本相同锹杈。
它完美的支持向量和矩陣操作。
它是通過限定符操作來(lái)管理輸入輸出類型的迈着。(in竭望、out)
GLSL提供了大量的內(nèi)置函數(shù)來(lái)提供豐富的擴(kuò)展功能。
GLSL中的數(shù)據(jù)類型主要分為標(biāo)量裕菠、向量咬清、矩陣、采樣器奴潘、結(jié)構(gòu)體兰粉、數(shù)組喉誊、空類型七種類型:
//標(biāo)量(只有大小沒有方向)
float a=1.0;
int b=1;
bool c=true;
vec2 d=vec2(1.0,2.0);
vec3 e=vec3(1.0,2.0,3.0)
vec4 f=vec4(vec3,1.2);
vec4 g=vec4(0.2); //相當(dāng)于vec(0.2,0.2,0.2,0.2)
vec4 h=vec4(a,a,1.3,a);
//矩陣
mat2 i=mat2(0.1,0.5,1.2,2.4);
mat2 j=mat2(0.8); //相當(dāng)于mat2(0.8,0.8,0.8,0.8)
mat3 k=mat3(e,e,1.2,1.6,1.8);
類型轉(zhuǎn)換
GLSL的類型轉(zhuǎn)換與C不同御蒲。在GLSL中類型不可以自動(dòng)提升腻格,比如float a=1;就是一種錯(cuò)誤的寫法,必須嚴(yán)格的寫成float a=1.0奈虾,也不可以強(qiáng)制轉(zhuǎn)換夺谁,即float a=(float)1;也是錯(cuò)誤的寫法廉赔,但是可以用內(nèi)置函數(shù)來(lái)進(jìn)行轉(zhuǎn)換,如float a=float(1);還有float a=float(true);(true為1.0匾鸥,false為0.0)等蜡塌,值得注意的是,低精度的int不能轉(zhuǎn)換為低精度的float扫腺。限定符
attritude:一般用于各個(gè)頂點(diǎn)各不相同的量岗照。如頂點(diǎn)顏色、坐標(biāo)等笆环。
uniform:一般用于對(duì)于3D物體中所有頂點(diǎn)都相同的量攒至。比如光源位置,統(tǒng)一變換矩陣等躁劣。
varying:表示易變量迫吐,一般用于頂點(diǎn)著色器傳遞到片元著色器的量。
const:常量账忘。
浮點(diǎn)精度
- lowp:低精度志膀。8位。
- mediump:中精度鳖擒。10位溉浙。
- highp:高精度。16位蒋荚。
內(nèi)建變量
gl_Position: 用于vertex shader, 寫頂點(diǎn)位置戳稽;被圖元收集、裁剪等固定操作功能所使用期升;
其內(nèi)部聲明是:highp vec4 gl_Position;
gl_PointSize: 用于vertex shader, 寫光柵化后的點(diǎn)大小惊奇,像素個(gè)數(shù);
其內(nèi)部聲明是:mediump float gl_Position;
gl_FragColor: 用于Fragment shader播赁,寫fragment color颂郎;被后續(xù)的固定管線使用;
mediump vec4 gl_FragColor;
gl_FragData: 用于Fragment shader容为,是個(gè)數(shù)組乓序,寫gl_FragData[n] 為data n;被后續(xù)的固定管線使用坎背;
mediump vec4 gl_FragData[gl_MaxDrawBuffers];
gl_FragColor和gl_FragData是互斥的竭缝,不會(huì)同時(shí)寫入;
gl_FragCoord: 用于Fragment shader,只讀沼瘫, Fragment相對(duì)于窗口的坐標(biāo)位置 x,y,z,w; 這個(gè)是固定管線圖元差值后產(chǎn)生的;z 是深度值; mediump vec4 gl_FragCoord;
gl_FrontFacing: 用于判斷 fragment是否屬于 front-facing primitive咙俩;只讀耿戚;
bool gl_FrontFacing;
gl_PointCoord: 僅用于 point primitive; mediump vec2 gl_PointCoord;