OpenGL : Open Graphics Library
OpenGL ES : 针对嵌入式系统设计的OpenGL的子集。
在Android中使用OpenGL ES 2.0需要在AndroidManifest.xml添加:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
GLSurfaceView :
android.opengl提供的渲染界面,是SurfaceView的子类,封装了一些使用OpenGL ES需要的配置。
管理Surface和EGL。
(EGL是OpenGL和设备屏幕之间做桥接的类)
允许自定义渲染器Renderer.
让渲染器在独立线程里运作,和UI主线程分离。
支持按需渲染和连续渲染。
OpenGL绘制流程
由三角形组成各种图形。
OpenGL绘制流程
OpenGL坐标系
OpenGL坐标系坐标映射
坐标映射Shader着色器
着色器是运行在GPU上的小程序。
顶点着色器(vertex shader)
如何处理顶点、法线等数据的小程序
片元着色器(fragment shader)
如何处理光、阴影、遮挡、环境等对物体表面的影响。
OpenGL函数命名格式
函数库:gl
根命令:Color
参数个数:3
参数类型:f
glColor3f(...)
Open GL语言 Open GL Shading Language GLSL
数据类型
float / vec2 / vec4 / sampler2D
修饰符
- attribute : 属性变量,只能用于顶点着色器中。一般用该变量表示一些顶点数据,如:顶点坐标、纹理坐标、颜色等。
- uniforms : 一致变量,在着色器执行期间值不变的。与const常量不同的是,该值在编译时是不确定的,是运行时由着色器外部初始化的。
- varying : 易变变量,从顶点着色器传递给片元着色器的数据变量。
内建函数
texture2D(采样器,坐标) : 采样指定位置的纹理
内建变量
gl_Position vec4类型,表示顶点着色器中顶点位置
gl_FragColor vec4类型,表示片元着色器中的颜色
精度控制
precision lowp 低精度
precision mediump 中精度
precision highp 高精度
顶点着色器 Vertex Shader
//把顶点坐标给这个变量,确定要画画的形状
attribute vec4 vPosition;
//接收纹理坐标,接收采样器采样图片的坐标
attribute vec4 vCoord;
//变换矩阵,需要将原本的vCoord(01,11,00,10)与矩阵相乘,才能得到surfacetexture的正确采样坐标
uniform mat4 vMatrix;
//传递给片元着色器,像素点
varying vec2 aCoord;
void main() {
//内置变量gl_position
//把顶点数据赋值给这个变量,opengl就知道要画什么形状了
gl_Position = vPosition;
//和设备相关
aCoord = vCoord.xy;
}
片元着色器
//预览相机的着色器,顶点着色器不变,需要修改片元着色器
//不再用sampler2D采样,需要用samplerExternalOES纹理采样器
//需要在头部增加使用扩展纹理的声明#extension GL_OES_EGL_image_external : require
#extension GL_OES_EGL_image_external : require
//设置float采用的数据精度
precision mediump float;
//采样点坐标
varying vec2 aCoord;
//采样器
uniform samplerExternalOES vTexture;
void main() {
//gl_FragColor 变量接收像素值
//texture2D() : 采样器,采集aCoord的像素
gl_FragColor = texture2D(vTexture,aCoord);
}
OpenGL实例:渲染Camera数据
项目代码放在GitHub上,地址为:
https://github.com/Hujunjob/AdvanceAndroid/tree/master/opengldemo
1、自定义GLSurfaceView
用于设置EGL版本号、设置自定义的渲染器、设置渲染模式
class MyGLSurface(context: Context?, attrs: AttributeSet? = null) : GLSurfaceView(context, attrs) {
init {
//1、设置EGL版本
setEGLContextClientVersion(2)
//2、设置渲染器
setRenderer(MyGLRenderer(this))
//3、设置渲染模式
renderMode = RENDERMODE_WHEN_DIRTY
}
}
2、自定义渲染器
/**
* 核心类,自定义渲染器
*/
class MyGLRenderer(var myGLSurface: MyGLSurface) : GLSurfaceView.Renderer,
SurfaceTexture.OnFrameAvailableListener {
private lateinit var mTextureIds: IntArray
private lateinit var screenFilter: ScreenFilter
var cameraEngine: ICameraEngine? = null
var mSurfaceTexture:SurfaceTexture? = null
var mtx = FloatArray(16)
override fun onDrawFrame(gl: GL10?) {
//在这里画画
//1、清空画布,设置画布颜色,这里设置为红色
GLES20.glClearColor(255f,0f,0f,0f)
//设置清理模式
//GL_COLOR_BUFFER_BIT 颜色缓存区
//GL_DEPTH_BUFFER_BIT 深度缓冲区
//GL_STENCIL_BUFFER_BIT 模型缓冲区
GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT)
//2、输出摄像头数据
//更新纹理
mSurfaceTexture?.updateTexImage()
//获取旋转矩阵
mSurfaceTexture?.getTransformMatrix(mtx)
screenFilter.onDrawFrame(mTextureIds[0],mtx)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
cameraEngine?.openCamera(true)
//设置gl窗口
// GLES20.glViewport(0,0,width,height)
screenFilter.onReady(width,height)
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
cameraEngine =
CameraFactory.createCameraEngine(CameraFactory.CameraType.CAMERA1, myGLSurface.context)
//准备画布
mTextureIds = IntArray(1)
//通过OpenGL创建纹理id
GLES20.glGenTextures(mTextureIds.size,mTextureIds,0)
//通过纹理id创建画布
mSurfaceTexture = SurfaceTexture(mTextureIds[0])
mSurfaceTexture?.setOnFrameAvailableListener(this)
cameraEngine?.addSurfaceView(mSurfaceTexture!!)
screenFilter = ScreenFilter(myGLSurface.context)
}
//画布有可用数据时回调
override fun onFrameAvailable(surfaceTexture: SurfaceTexture?) {
//请求渲染
myGLSurface.requestRender()
}
}
3、绘制类
class ScreenFilter(var mContext: Context) {
private var mVertexBuffer: FloatBuffer
private var mTextureBuffer: FloatBuffer
private var vTexture: Int
private var vMatrix: Int
private var vCoord: Int
private var vPosition: Int
private var mWidth = 0
private var mHeight = 0
var mProgramId = 0
fun onReady(width: Int, height: Int) {
mWidth = width
mHeight = height
}
fun onDrawFrame(textureId: Int, mtx: FloatArray) {
//1、设置视窗的宽高
GLES20.glViewport(0,0,mWidth,mHeight)
//2、使用着色器程序
GLES20.glUseProgram(mProgramId)
//3、渲染传值
// 先传递顶点
mVertexBuffer.clear()
// C function void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLint offset )
// fun glVertexAttribPointer(
// indx: Int,
// size: Int,
// type: Int,
// normalized: Boolean,
// stride: Int,
// ptr: Buffer?
// ): Unit
//参数分别为:顶点坐标的索引,每个值的长度,值类型,是否归一化,步进(每次取完size后跳过多少个值取下一次值),数据
GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,mVertexBuffer)
//传递值后需要激活
GLES20.glEnableVertexAttribArray(vPosition)
//传递纹理
mTextureBuffer.clear()
GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,mTextureBuffer)
GLES20.glEnableVertexAttribArray(vCoord)
//4、变换矩阵
GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0)
//片元着色器
//首先激活图层
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
//绑定纹理
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,textureId)
//然后再传递片元着色器参数
GLES20.glUniform1f(vTexture,0f)
//通知OpenGL绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4)
}
init {
//顶点着色器
val vertext = TextResourceReader.read(mContext, R.raw.camera_vertex)
//片元着色器
val fragment = TextResourceReader.read(mContext, R.raw.camera_fragment)
//一、配置顶点着色器
//1、创建着色器
val vShaderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
//2、绑定代码到着色器上面,把着色器里的代码加载的着色器里
GLES20.glShaderSource(vShaderId, vertext)
//3、编译着色器代码
GLES20.glCompileShader(vShaderId)
var status = IntArray(1)
//4、主动获取编译是否成功状态
GLES20.glGetShaderiv(vShaderId, GLES20.GL_COMPILE_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
throw IllegalStateException("配置顶点着色器失败")
}
//一、配置片元着色器
//1、创建着色器
val fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
//2、绑定代码到着色器上面,把着色器里的代码加载的着色器里
GLES20.glShaderSource(fShaderId, fragment)
//3、编译着色器代码
GLES20.glCompileShader(fShaderId)
//4、主动获取编译是否成功状态
GLES20.glGetShaderiv(fShaderId, GLES20.GL_COMPILE_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
throw IllegalStateException("配置片元着色器失败")
}
//三、着色器程序
//1. 创建新的OpenGL程序
mProgramId = GLES20.glCreateProgram()
//2. 将着色器附加到程序,两个着色器都附加
GLES20.glAttachShader(mProgramId, vShaderId)
GLES20.glAttachShader(mProgramId, fShaderId)
//3. 链接程序
GLES20.glLinkProgram(mProgramId)
//4. 获取链接状态
GLES20.glGetShaderiv(fShaderId, GLES20.GL_LINK_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
throw IllegalStateException("链接片元着色器失败")
}
GLES20.glGetShaderiv(vShaderId, GLES20.GL_LINK_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
throw IllegalStateException("链接顶点着色器失败")
}
//四、释放、删除着色器
//链接完成后,着色器都放到了OpenGL程序Program里,则着色器可以删除了
GLES20.glDeleteShader(vShaderId)
GLES20.glDeleteShader(fShaderId)
//五、获取着色器程序变量的索引,通过索引来赋值
//1. 顶点各个变量的索引
vPosition = GLES20.glGetAttribLocation(mProgramId, "vPosition")
vCoord = GLES20.glGetAttribLocation(mProgramId, "vCoord")
vMatrix = GLES20.glGetUniformLocation(mProgramId, "vMatrix")
//2、片元各个变量的索引
vTexture = GLES20.glGetUniformLocation(mProgramId, "vTexture")
//六、赋值
//1、顶点坐标赋值
mVertexBuffer = ByteBuffer
.allocateDirect(4 * 4 * 2) // 屏幕一共4个点,每个点有x/y两个坐标,坐标是Float类型,4个字节,则为4*4*2
.order(ByteOrder.nativeOrder()) //设置使用硬件本地字节序列,保证数据排序
.asFloatBuffer() //创建FloatBuffer
mVertexBuffer.clear()
//采用OpenGL的坐标系
//屏幕边缘4个点
//不能采用顺时针或逆时针依次获取4个点,因为这样无法无法组成闭合的矩形
// 例如顺时针四个点分别为 a b c d,则输入进去的点为a b d c
val v = floatArrayOf(
-1f, 1f,
-1f, -1f,
1f, 1f,
1f, -1f
)
mVertexBuffer.put(v)
//2、纹理坐标赋值
mTextureBuffer = ByteBuffer
.allocateDirect(4 * 4 * 2) // 屏幕一共4个点,每个点有x/y两个坐标,坐标是Float类型,4个字节,则为4*4*2
.order(ByteOrder.nativeOrder()) //设置使用硬件本地字节序列,保证数据排序
.asFloatBuffer() //创建FloatBuffer
mTextureBuffer.clear()
//纹理的坐标系采用Android系统坐标系
//屏幕边缘4个点
//需要跟顶点坐标的顺序一一对应,且需要是OpenGL坐标系和Android屏幕坐标系对应
// val t = floatArrayOf(
// 0f, 0f,
// 0f, 1f,
// 1f, 0f,
// 1f, 1f
// )
//后摄顺时针旋转90度
// val t = floatArrayOf(
// 0f, 1f,
// 1f, 1f,
// 0f, 0f,
// 1f, 0f
// )
//前摄逆时针旋转90度
// val t = floatArrayOf(
// 1f, 0f,
// 0f, 0f,
// 1f, 1f,
// 0f, 1f
// )
//前摄逆时针旋转90度后再左右镜像
val t = floatArrayOf(
1f, 1f,
0f, 1f,
1f, 0f,
0f, 0f
)
mTextureBuffer.put(t)
}
}
4、配套工具类
将raw文件里的文件读取为String输出。
class TextResourceReader {
companion object {
fun read(context: Context, resourceId: Int):String {
val sb = StringBuilder()
val inputStream = context.resources.openRawResource(resourceId)
val inputStreamReader = InputStreamReader(inputStream)
val bufferReader = BufferedReader(inputStreamReader)
var s = bufferReader.readLine()
while (s!=null){
sb.append(s)
sb.append("\n")
s = bufferReader.readLine()
}
return sb.toString()
}
}
}
网友评论