OpenGL ES for Android 环境搭建

在Android上运行OpenGL ES程序需要用到GLSurfaceView控件,GLSurfaceView继承自SurfaceView并实现了GLThread,通过OpenGL ES进行绘制。

  • OpenGL ES1.0是基于OpenGL 1.3的,OpenGL ES1.1是基于OpenGL 1.5的。Android1.0和更高的版本支持这个API规范。OpenGL ES 1.x是针对固定硬件管线的。
  • OpenGL ES2.0是基于OpenGL 2.0的,不兼容OpenGL ES 1.x。Android 2.2(API 8)和更高的版本支持这个API规范。OpenGL ES 2.x是针对可编程硬件管线的。
  • OpenGL ES3.0的技术特性几乎完全来自OpenGL 3.x的,向下兼容OpenGL ES 2.x。Android 4.3(API 18)及更高的版本支持这个API规范。
  • OpenGL ES3.1基本上可以属于OpenGL 4.x的子集,向下兼容OpenGL ES3.0/2.0。Android 5.0(API 21)和更高的版本支持这个API规范。

Android工程中OpenGL ES的版本在AndroidManifest.xml中指定:

<uses-feature android:glEsVersion="0x00020000" android:required=true" />

0x00020000表示支持OpenGL ES 2.0。

<?xml version=1.0" encoding=utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=http://schemas.android.com/apk/res/android"
xmlns:app=http://schemas.android.com/apk/res-auto
xmlns:tools=http://schemas.android.com/tools
android:layout_width=match_parent
android:layout_height=
tools:context=.MainActivity">

<android.opengl.GLSurfaceView
android:id=@+id/glSurfaceView"/>

</androidx.constraintlayout.widget.ConstraintLayout>

在Activity中初始化GLSurfaceView

class TriangleActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//设置opengl es版
glSurfaceView.setEGLContextClientVersion(2)
设置renderer
glSurfaceView.setRenderer(MyRenderer(context = baseContext))
设置渲染模式
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}

override fun onResume() {
super.onResume()
glSurfaceView.onResume()
}

 fun onPause() {
super.onPause()
glSurfaceView.onPause()
}
}

 

OpenGL ES版本号和AndroidManifest.xml中版本号保持一致,当然我们也可以在设置版本之前判断当前设备是否支持设置的版本,下面的代码判断是支持ES 2.0版本。

fun supportsEs2(context: Context): Boolean {
val configurationInfo =
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).deviceConfigurationInfo
return configurationInfo.reqGlEsVersion >= 0x20000
}

 

setRenderMode方法是设置GLSurfaceView渲染模式,渲染模式有RENDERMODE_WHEN_DIRTY和RENDERMODE_CONTINUOUSLY两种,RENDERMODE_WHEN_DIRTY表示当需要的时候才渲染,只有在调用requestRender或者onResume等方法时才渲染,RENDERMODE_CONTINUOUSLY表示一直渲染。
setRenderMode一定要在setRenderer方法之后调用,另外一般需要在Activity或者Fragment的onPause和onResume生命周期中调用GLSurfaceView的onPause和onResume方法,节省系统资源。


Renderer必须实现GLSurfaceView.Renderer接口,并实现onSurfaceCreated,onDrawFrame,onSurfaceChanged方法,OpenGL ES的渲染工作由此Renderer实现。
MyRenderer的实现:

 MyRenderer(val context: Context) : GLSurfaceView.Renderer {
override fun onDrawFrame(p0: GL10?) {
}
override fun onSurfaceChanged(p0: GL10?,width: Int,height: Int){
}
override fun onSurfaceCreated(p0: GL10?,p1: EGLConfig?) {
}
}

 

onSurfaceCreated,onSurfaceChanged方法说明如下:

  • onSurfaceCreated:GLSurfaceView创建完成,也代表OpenGL ES环境创建完成,通常情况下在此方法中创建Program及初始化参数。
  • onSurfaceChanged:当Surface发生变化的时候回调,比如竖屏转横屏导致GLSurfaceView大小发生变化,通常情况下在此方法中设置绘制窗口及和GLSurfaceView大小有关系的参数。
  • onDrawFrame:执行OpenGL ES渲染工作,由系统以一定的频率来调用重绘View,当设置GLSurfaceView的渲染模式为GLSurfaceView.RENDERMODE_CONTINUOUSLY或不设置时,系统就会主动回调onDrawFrame()方法,如果设置为 RENDERMODE_WHEN_DIRTY ,手动调用requestRender(),才会渲染。

在OpenGL ES中Shader和Program是两个非常重要的概念,Program需要Vertex Shader(顶点Shader和Fragment Shader(片段Shader),Renderer的渲染就是在执行Program。

  • Vertex Shader(顶点Shader)处理顶点数据,对于发送给GPU的每一个顶点都要执行一次Vertex Shader,它的作用就是把顶点在虚拟空间中的三维坐标变换为屏幕上的二维坐标,并带有深度信息。
  • Fragment Shader计算每个像素的颜色和其他属性。它通过应用光照值、凹凸贴图,阴影,镜面高光,半透明等处理来计算像素的颜色并输出。


Shader可以以字符串形式存在也可以单独存放在文件中,建议写在assets目录下并以.glsl结尾,因为Android Studio安装GLSL插件可以高亮其代码,便于查找错误。
在assets下创建glsl文件夹,用于存放glsl文件,创建triangle_vertex.glsl文件,保存Vertex Shader代码:

attribute vec4 vPosition;
void main() { 
gl_Position = vPosition;
}

 

创建triangle_fragment.glsl文件,保存Fragment Shader代码:

precision mediump float;
 main()
{
gl_FragColor = vec4(1,0,1)">1);
}

 

上面代码表示顶点区域内绘制为红色,vec4内的值表示r,g,b,a。
将上面2个shader文件编译为Shader,

private fun compileShader(shaderType: Int,shaderSource: String): Int {
创建一个空shader
var shaderHandle: Int = GLES20.glCreateShader(shaderType)
if (shaderHandle != 0) {
加载shader源码
GLES20.glShaderSource(shaderHandle,shaderSource)
编译shader
GLES20.glCompileShader(shaderHandle)
val compileStatus = IntArray(检查shader状态
GLES20.glGetShaderiv(shaderHandle,GLES20.GL_COMPILE_STATUS,compileStatus,1)">if (compileStatus[0] == 输入shader异常日志
Log.e(TAG,1)">Error compile shader:${GLES20.glGetShaderInfoLog(shaderHandle)}删除shader
GLES20.glDeleteShader(shaderHandle)
shaderHandle = 
}
}
if (shaderHandle == ) {
Log.e(TAG,Error create shader)
}
return shaderHandle
}

 

ShaderType分为GLES20.GL_VERTEX_SHADER和GLES20.GL_FRAGMENT_SHADER,GLES20.GL_VERTEX_SHADER编译Vertex Shader的,GLES20.GL_ FRAGMENT _SHADER编译Fragment Shader。
将Shader链接到program,

fun createAndLinkProgram(vertexCode: String,fragmentCode: String): Int {
创建一个空的program
var programHandle = GLES20.glCreateProgram()
if (programHandle != 编译shader
val vertexShaderHandle = compileShader(GLES20.GL_VERTEX_SHADER,vertexCode)
val fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER,fragmentCode)
绑定shader和program
GLES20.glAttachShader(programHandle,vertexShaderHandle)
GLES20.glAttachShader(programHandle,fragmentShaderHandle)
链接program
GLES20.glLinkProgram(programHandle)

val linkStatus = IntArray(检测program状态
GLES20.glGetProgramiv(programHandle,GLES20.GL_LINK_STATUS,linkStatus,1)">if (linkStatus[Error link program:${GLES20.glGetProgramInfoLog(programHandle)}删除program
GLES20.glDeleteProgram(programHandle)
programHandle = if (programHandle == Error create program programHandle
}

最终返回program的句柄,这2步是固定的,因为我们将其封装为工具类GLTools,供以后使用。
使用工具类GLTools创建program:

 fun createProgram() {
var vertexCode =
AssetsUtils.readAssetsTxt(
context = context,filePath = glsl/triangle_vertex.glsl
)
var fragmentCode =glsl/triangle_fragment.glsl
)
mProgramHandle = GLTools.createAndLinkProgram(vertexCode,fragmentCode)
}

 


program的创建放在Renderer的onSurfaceCreated方法中,创建成功后,获取Shader中参数句柄及设置顶点数据。获取Vertex Shader中vPosition句柄:

val loc = GLES20.glGetAttribLocation(mProgramHandle,1)">vPosition")

 

顶点数据需要了解顶点坐标系统,如下图:


顶点坐标轴以屏幕中心为原点(0,0),z轴的正方向为穿透屏幕指向外面。三角形的顶点坐标设置如下:

var vertexBuffer = GLTools.array2Buffer(
floatArrayOf(
0.0f,1)">0.5f, top
- bottom left
0.0f  bottom right
)
)

 


工具类GLTools中array2Buffer是将顶点数据转换为FloatBuffer,array2Buffer方法定义如下:

fun array2Buffer(array: FloatArray): FloatBuffer {
val bb = ByteBuffer.allocateDirect(array.size * 4)
bb.order(ByteOrder.nativeOrder())
var buffer = bb.asFloatBuffer()
buffer.put(array)
buffer.position( buffer
}

 


创建OpenGL ES绘制窗口通常是在onSurfaceChanged中设置,

GLES20.glViewport(


绘制在onDrawFrame中执行,

) {
GLES20.glUseProgram(mProgramHandle)
GLTools.setAttributePointer(vPositionLoc,vertexBuffer,1)">3)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES,1)">)
}

 


GLES20.glUseProgram(mProgramHandle)表示启动当前program,mProgramHandle是上面创建Program返回的句柄。

GLTools.setAttributePointer(vPositionLoc,3)表示将顶点数据设置给program,参数说明情况如下:

  • Location是刚才获取vPosition的句柄
  • buffers 是生成的顶点数据,
  • pointSize 表示每个顶点个数,上面顶点的个数是3个,

setAttributePointer为封装的工具类方法:

fun setAttributePointer(location: Int,buffers: FloatBuffer,pointSize: Int) {
buffers.position()
GLES20.glEnableVertexAttribArray(location)
GLES20.glVertexAttribPointer(location,pointSize,GLES20.GL_FLOAT,false,buffers)
}

 

glDrawArrays方法是绘制,参数说明情况如下:

  • 第一个参数mode,表示绘制的方式,可选择的值有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
  • 第二个参数表示从数组缓存中的哪一位开始绘制,一般为0。
  • 第三个参数表示绘制顶点的数量。

往期回顾

 

OpenGL ES for Android 总览

 

 

如果此文章对您有所帮助,欢迎扫码关注订阅号。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要讲解了“FlutterComponent动画的显和隐怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究...
这篇文章主要讲解了“flutter微信聊天输入框功能如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“f...
本篇内容介绍了“Flutter之Navigator的高级用法有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
这篇文章主要介绍“Flutter怎么使用Android原生播放器”,在日常操作中,相信很多人在Flutter怎么使用Android原生播放器问题上存在疑惑,小编查阅了各式资料,整...
Flutter开发的android端如何修改APP名称,logo,版本号,具体的操作步骤:修改APP名称找到文件:android\\app\\src\\main\\AndroidManifest.xml
Flutter路由管理初识路由概念一.路由管理1.1.Route1.2.MaterialPageRoute1.3.Navigator1.4.路由传值1.5 命名路由1.6.命名路由参数传递1.7.适配二、路由钩子三、onUnknownRoute四、结尾初识路由概念路由的概念由来已久,包括网络路由、后端路由,到现在广为流行的前端路由。无论路由的概念如何应用,它的核心是一个路由映射表。比如:名字 detail 映射到 DetailPage 页面等。有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发
前提:针对Android开发者(windows系统下),已安装Git,AndroidStudio(建议4.0+版本)一.下载Flutter SDK地址:https://flutter.dev/docs/development/tools/sdk/releases,在 Stable channel (Windows)里面下最新版本即可。Flutter的渠道版本会不停变动,请以Flutter官网为准。在中国,要想正常获取安装包列表或下载安装包,可能需要翻墙,也可以去Flutter github项目下去下载安
一、变量变量是一个引用,根据Dart中“万物皆对象”原则,即变量存储的都是对象的引用,或者说它们都是指向对象。1.1.声明变量://1.不指定类型var name = 'aaa';//2.明确指定类型String name = 'aaa';因为有类型推导,所以两种实现效果一样,官方推荐在函数内的本地变量尽量使用var声明。在变量类型并不明确的情况下,可以使用dynamic关键字//3.使用dynamic关键字dynamic name = 'aaa';1.2.默认值未初始化的变量
前言Flutter2.0发布不久,对web的支持刚刚进入stable阶段。初学几天,构建web应用时候碰到一些问题,比如中文显示成乱码,然后加载图片出现图片跨域问题:Failed to load network image...Trying to load an image from another domain?1.开启web端构建:使用下面这个命令才可以开启Web端构建的支持flutter config --enable-web提示我们:重新启动编辑器,以便它们读取新设置。2.重
一.Flutter打Android release包的步骤:1.为项目创建一个.jks签名文件(很简单,跳过)2.创建一个文件key.properties,直接复制下面key.properties位置如图:在里面输入一下内容:storePassword=iflytekkeyPassword=iflytekkeyAlias=teachingmachinestoreFile=E:/teacher/app/keys/TeachingMachine.jks输入你自己的passwork以及
1 问题Android原生向js发消息,并且可以携带数据2 实现原理Android原生可以使用RCTEventEmitter来注册事件,然后这里需要指定事件的名字,然后在js那端进行监听同样事件的名字监听,就可以收到消息得到数据Android注册关键代码reactContext.getJSModule(DeviceEventManagerModule.RCT...
1 Flexbox布局1) flexDirection 可以决定布局的主轴,子元素是应该沿着水平轴(row)方向排列,还是沿着竖直轴(column)方向排列2) justifyContent 决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式 有flex-start、center、flex-end、space-around...
1 实现的功能在网上看React Native文档,我特码就想实现一个页面到另外一个页面的跳转,然后另外一个页面怎么获取参数,特么没找到一个说清楚的,要么太复杂,要么说了不理解,下面是我自己写的一个App.js文件,实现一个Home页面跳到另外Details页面,并且携带了参数怎么在Details页面获取,就是这么简单粗暴.2 测试DemoApp.js文件如下...
1 问题在一个文件构建一个对象,然后在另外一个文件里面new这个对象,通过构造方法传递参数,然后再获取这个参数2 测试代码Student.js文件如下'use strict';import React from 'react'import {NativeModules, NativeEventEmitter, DeviceEventEmitter,Ale...
1 简单部分代码export default class App extends Component&amp;lt;Props&amp;gt; { render() { return ( &amp;lt;View {styles.container}&amp;gt; &amp;lt;View {styles.welcome}&amp;gt; &amp;l...
1 怎么实现发送和接收事件理论上封装了Android原生广播的代码,需要注册和反注册,这里用DeviceEventEmitter实现//增加监听DeviceEventEmitter.addListener//取消监听//this.emitter.remove();这里可也可以通过安卓原生向页面js发送消息,可以参考我的这篇博客React Native之Android原生通过Dev...
1、Component介绍一般Component需要被其它类进行继承,Component和Android一样,也有生命周期英文图片如下2 具体说明1)、挂载阶段constructor()//构造函数,声明之前先调用super(props)componentWillMount()//因为它发生在render()方法前,因此在该方法内同步设置状态...
1 触摸事件普通点击我们可以使用onPress方法,我们可以使用Touchable 系列控件设计我们的按钮TouchableHighlight 背景会在用户手指按下时变暗TouchableNativeFeedback用户手指按下时形成类似墨水涟漪的视觉效果TouchableOpacity指按下时降低按钮的透明度,而不会改变背景的颜色TouchableWithoutFeedbac...
1 问题部分代码如下class HomeScreen extends React.Component { render() { return ( &amp;lt;View {{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&amp;gt; &amp;lt;Text&amp;gt;Home Scre...
1 Props(属性)和State(状态)和简单样式简单使用App.js代码如下/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow */import React, {Component} from 'react';import {Pla...