Android GlSurfaceView 入门教程 : 绘制一个三角形

1. GlSurfaceView是什么

GlSurfaceViewAndroid中的一个类,继承自SurfaceView,是用于显示OpenGL ES图形渲染的一个View
OpenGL ES是一种跨平台的图形API,用于渲染2D3D图形,也可以将相机的画面显示到GlSurfaceView上,从而实现滤镜的效果。
GlSurfaceView提供了一个可以在Android应用程序中绘制OpenGL ES图形的接口,允许开发者将复杂的3D图形、动画和视觉效果嵌入到应用程序中。
GlSurfaceView处理了OpenGL ES渲染环境的创建、维护和更新,以及与其他Android视图和事件系统的交互。

2. android中怎使用GlSurfaceview

2.1 添加OpenGL ES版本支持

新建Android项目,在AndroidManifest.xml文件中,添加 OpenGL ES 版本支持。例如,要使用 OpenGL ES 2.0,请添加以下代码:

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

如果需要使用 OpenGL ES 3.0 则需要以下声明:0x00030000
如果需要使用 OpenGL ES 3.2 则需要以下声明:0x00030002

2.2 新建自定义的GlSurfaceView

新建一个MyGlSurfaceView类,继承自 GLSurfaceView,并进行初始化

import android.content.Context
import android.opengl.GLSurfaceView
import android.util.AttributeSet

class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {

    init {
        // 设置 OpenGL ES 版本
        setEGLContextClientVersion(2)

        // 设置渲染器
        val renderer = MyGLRenderer()
        setRenderer(renderer)
    }
}

2.3 新建自定义的Renderer

创建一个自定义的渲染器MyRenderer类,实现 GLSurfaceView.Renderer 接口,并实现其中的方法

class MyGLRenderer : GLSurfaceView.Renderer {
    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        //设置清除屏幕时使用的颜色
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
    }

    override fun onDrawFrame(gl: GL10?) {
        // 清除屏幕
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 设置视口大小
        GLES20.glViewport(0, 0, width, height)
    }
}

2.4 使用MyGLSurfaceView

Activityxml中使用MyGLSurfaceView

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.heiko.myglsurfaceviewtest.MyGLSurfaceView
        android:id="@+id/gl_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

2.5 运行看下效果

运行后,可以看到一个使用 OpenGL ES 绘制的黑色屏幕

在这里插入图片描述

3. 在自定义的MyGLRenderer中绘制三角形

3.1 添加顶点着色器和片段着色器的代码

MyGLRenderer 类中,添加顶点着色器和片段着色器的代码。在这里,我们创建一个简单的顶点着色器和片段着色器,它们将顶点位置传递给渲染管线并使用固定颜色进行渲染:

private val vertexShaderCode = "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}"

private val fragmentShaderCode = "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}"

3.2 添加编译和链接着色器的方法

MyGLRenderer 类中,添加一个方法来编译和链接着色器,然后返回着色器程序的 ID

private fun loadShaderProgram(vertexCode: String, fragmentCode: String): Int {
    // 编译顶点着色器
    val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
    GLES20.glShaderSource(vertexShader, vertexCode)
    GLES20.glCompileShader(vertexShader)

    // 编译片段着色器
    val fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
    GLES20.glShaderSource(fragmentShader, fragmentCode)
    GLES20.glCompileShader(fragmentShader)

    // 链接着色器程序
    val program = GLES20.glCreateProgram()
    GLES20.glAttachShader(program, vertexShader)
    GLES20.glAttachShader(program, fragmentShader)
    GLES20.glLinkProgram(program)
    return program
}

3.3 加载并创建着色器程序

MyGLRenderer 类中,添加成员变量来存储顶点数据、顶点缓冲区对象(VBO)和着色器程序 ID

private val vertexData = floatArrayOf(
    0.0f, 0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    0.5f, -0.5f, 0.0f
)

private var vertexBufferId = 0
private var shaderProgramId = 0

这里vertexData 为什么这么传,是和openGL的世界坐标系相关的
在这里插入图片描述
可以看到vertexData的第一个值0.0f,第二个值0.5f这个坐标点位于Y轴偏上的位置,第三个值0.0f我们不用管,这个是Z轴,在2D图形中我们不需要Z轴,所以这个统一传0.0f就好了。
同理,第四个值-0.5f、第五个值-0.5f位于X轴坐标的偏左侧,第六个值也是Z轴,我们不用管。
第七个值是0.5f,第八个值是-0.5f位于X轴坐标的偏右侧,第九个值也是Z轴,我们不用管。
具体如下图所示

在这里插入图片描述

然后,在 onSurfaceCreated 方法中初始化这些值,然后加载并创建着色器程序

override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
    // 设置清除屏幕时使用的颜色
    GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)

    // 创建顶点缓冲区对象
    val buffers = IntArray(1)
    GLES20.glGenBuffers(1, buffers, 0)
    vertexBufferId = buffers[0]

    // 将顶点数据上传到缓冲区对象
    val vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
    vertexBuffer.put(vertexData)
    vertexBuffer.position(0)

    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId)
    GLES20.glBufferData(
        GLES20.GL_ARRAY_BUFFER,
        vertexData.size * 4,
        vertexBuffer,
        GLES20.GL_STATIC_DRAW
    )
    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)

    // 加载并创建着色器程序
    shaderProgramId = loadShaderProgram(vertexShaderCode, fragmentShaderCode);
}

3.4 绘制三角形

onDrawFrame 方法中,使用创建的着色器程序和顶点缓冲区对象来绘制三角形:

override fun onDrawFrame(gl: GL10?) {
    // 清除屏幕
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)

    // 使用着色器程序
    GLES20.glUseProgram(shaderProgramId)

    // 绑定顶点缓冲区对象并启用顶点属性
    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId)
    val positionLocation = GLES20.glGetAttribLocation(shaderProgramId, "vPosition")
    GLES20.glEnableVertexAttribArray(positionLocation)
    GLES20.glVertexAttribPointer(positionLocation, 3, GLES20.GL_FLOAT, false, 0, 0)

    // 设置片段着色器的颜色
    val colorLocation = GLES20.glGetUniformLocation(shaderProgramId, "vColor")
    GLES20.glUniform4f(colorLocation, 1.0f, 0.0f, 0.0f, 1.0f)

    // 绘制三角形
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3) // GL_TRIANGLES:三角形 GL_POINTS:点

    // 禁用顶点属性并解除顶点缓冲区对象的绑定
    GLES20.glDisableVertexAttribArray(positionLocation)
    GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
}

3.5 设置视口大小

onSurfaceChanged 方法中,还是一样,设置视口大小:

override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
    // 设置视口大小
    GLES20.glViewport(0, 0, width, height)
}

3.6 运行看下效果

在这里插入图片描述

4. Android中GLES20.java的API说明

上面我们已经实现了一个三角形的绘制,但是对于GLES20API还是不太了解,接下来再来看下常用的GLES20 API

4.1 Android中GLES20 API的一些主要功能说明

  • Shaders(着色器):GLES20使用可编程的着色器来渲染图形。顶点着色器处理顶点数据,片段着色器处理像素数据。着色器需要用GLSL(OpenGL Shading Language)编写。
  • Buffers(缓冲区):GLES20使用缓冲区来存储顶点数据和索引数据。顶点缓冲区对象(VBO)存储顶点数据,元素缓冲区对象(EBO)存储索引数据。
  • Textures(纹理):GLES20支持多种纹理类型,如2D纹理、立方体贴图等。纹理用于给3D对象添加详细的表面特征
  • Framebuffers(帧缓冲区):GLES20使用帧缓冲区对象(FBO)来存储渲染结果。你可以将渲染结果渲染到纹理中,然后将纹理应用到其他对象上,实现高级渲染效果。
  • Transformations(变换):GLES20支持多种变换操作,如平移、旋转、缩放等。变换矩阵用于在顶点着色器中处理顶点数据。
  • Lighting(光照):GLES20支持基本的光照计算,如环境光、漫反射光、镜面反射光等。光照计算通常在顶点着色器或片段着色器中进行。
  • Blending(混合):GLES20支持颜色混合,用于实现透明度、半透明等效果。混合操作可以根据源颜色和目标颜色按照指定的混合因子进行计算。
  • Culling(剔除):GLES20支持面剔除,可以剔除不可见的面,提高渲染性能。面剔除可以根据面的正面或反面进行。
  • Depth Testing(深度测试):GLES20支持深度测试,用于判断像素的可见性。深度测试可以根据像素的深度值进行比较,只渲染最前面的像素。
  • Stencil Testing(模板测试):GLES20支持模板测试,用于实现遮罩、镜子等效果。模板测试可以根据模板缓冲区的值对像素进行掩盖或者保留。

4.2 GLES20 API中一些主要方法的说明

  • glGenBuffers:生成缓冲区对象,如顶点缓冲区对象(VBO)和元素缓冲区对象(EBO)。
  • glBindBuffer:绑定缓冲区对象,使其成为当前活动的缓冲区。
  • glBufferData:将数据上传到缓冲区对象。
  • glVertexAttribPointer:定义顶点属性数组,指定顶点数据在缓冲区中的布局。
  • glEnableVertexAttribArray:启用顶点属性数组。
  • glDisableVertexAttribArray:禁用顶点属性数组。
  • glUseProgram:使用某个着色器程序进行渲染。
  • glGetUniformLocation:获取着色器程序中uniform变量的位置。
  • glUniformMatrix4fv:为uniform变量设置矩阵数据。
  • glCreateShader:创建着色器对象。
  • glShaderSource:为着色器对象设置源代码。
  • glCompileShader:编译着色器对象。
  • glGetShaderiv:获取着色器对象的编译状态。
  • glAttachShader:将着色器对象附加到着色器程序。
  • glLinkProgram:链接着色器程序。
  • glGetProgramiv:获取着色器程序的链接状态。
  • glDeleteShader:删除着色器对象。
  • glGenTextures:生成纹理对象。
  • glBindTexture:绑定纹理对象。
  • glTexParameteri:设置纹理参数,如过滤模式、环绕模式等。
  • glTexImage2D:上传纹理数据。
  • glGenFramebuffers:生成帧缓冲区对象。
  • glBindFramebuffer:绑定帧缓冲区对象。
  • glFramebufferTexture2D:将纹理对象附加到帧缓冲区对象。
  • glDrawArrays:绘制顶点数组,渲染图形。
  • glDrawElements:绘制索引数组,渲染图形。
  • glEnable:启用某个OpenGL功能,如深度测试、剔除、混合等。
  • glDisable:禁用某个OpenGL功能。
  • glBlendFunc:设置混合函数。
  • glCullFace:设置剔除面的模式。
  • glClearColor:设置清除颜色缓冲区时使用的颜色。
  • glClear:清除颜色缓冲区、深度缓冲区和/或模板缓冲区。

4.3 扩展 : EGL是什么

OpenGL是一个跨平台的操作GPUAPI,但OpenGL需要本地视窗系统进行交互,这就需要一个中间控制层。
EGL就是连接OpenGL ES和本地窗口系统的接口,引入EGL就是为了屏蔽不同平台上的区别。
EGL需要做一些环境配置,而GLSurfaceView就已经帮我们配置好了,从而让我们不了解EGL也可以调用OpenGL

当然,我们也可以不使用GLSurfaceView,而是自己通过EGL来和OpenGL进行交互,但这样就比较麻烦了。

5. 开启调试

GLSurfaceView.setDebugFlags() 方法可以激活 log或者错误检测,它们可以帮助调试 OpenGL ES 调用。具体使用时,在 GLSurfaceView 的构造函数中,调用 setRender() 之前调用GLSurfaceView.setDebugFlags()就可以了。下面是个例子:

class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {

    init {
        // 设置 OpenGL ES 版本
        setEGLContextClientVersion(2)
        //打开调试和日志
        setDebugFlags(DEBUG_CHECK_GL_ERROR or DEBUG_LOG_GL_CALLS)

        // 设置渲染器
        val renderer = MyGLRenderer()
        setRenderer(renderer)
    }
}

6. 源码下载

本文源码下载 : Android使用GlSurfaceView和OpenGL绘制三角形 Demo

7. 官方文档

GLSurfaceView | Android Developers (google.cn)
SurfaceView 和 GLSurfaceView | Android 开源项目 | Android Open Source Project (google.cn)
构建 OpenGL ES 环境 | Android 开发者 | Android Developers (google.cn)

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要将GLSurfaceView绑定到Camera2中,您需要使用TextureView而不是GLSurfaceView。这是因为GLSurfaceView不支持与Camera2 API一起使用。 以下是一些步骤,可以帮助您在Android中将TextureView与Camera2 API结合使用: 1. 在布局文件中添加一个TextureView: ``` <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 在您的活动类中获取TextureView的引用: ``` TextureView textureView = findViewById(R.id.texture_view); ``` 3. 创建一个CameraDevice.StateCallback来处理CameraDevice的状态变化: ``` private CameraDevice.StateCallback cameraStateCallback = new CameraDevice.StateCallback() { @Override public void onOpened(@NonNull CameraDevice camera) { // CameraDevice已经打开,可以开始预览 } @Override public void onDisconnected(@NonNull CameraDevice camera) { // CameraDevice已经断开连接 } @Override public void onError(@NonNull CameraDevice camera, int error) { // CameraDevice遇到错误 } }; ``` 4. 获取CameraManager的实例,并使用它来打开相机: ``` CameraManager cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE); String cameraId = cameraManager.getCameraIdList()[0]; // 获取第一个后置摄像头 cameraManager.openCamera(cameraId, cameraStateCallback, null); ``` 5. 在CameraDevice.StateCallback的onOpened方法中,设置一个SurfaceTextureListener来处理TextureView的缓冲区更新: ``` @Override public void onOpened(@NonNull CameraDevice camera) { SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); surfaceTexture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight()); Surface previewSurface = new Surface(surfaceTexture); try { CaptureRequest.Builder previewRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.addTarget(previewSurface); camera.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession session) { try { CaptureRequest previewRequest = previewRequestBuilder.build(); session.setRepeatingRequest(previewRequest, null, null); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession session) { // 配置失败 } }, null); } catch (CameraAccessException e) { e.printStackTrace(); } } ``` 6. 在SurfaceTextureListener中,创建一个OpenGL ES上下文,并将其与TextureView关联: ``` textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { // 创建OpenGL ES上下文 EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); egl.eglInitialize(display, null); int[] attribList = { EGL10.EGL_RED_SIZE, 8, EGL10.EGL_GREEN_SIZE, 8, EGL10.EGL_BLUE_SIZE, 8, EGL10.EGL_ALPHA_SIZE, 8, EGL10.EGL_DEPTH_SIZE, 0, EGL10.EGL_STENCIL_SIZE, 0, EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; egl.eglChooseConfig(display, attribList, configs, 1, numConfigs); int[] contextAttribList = { EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; EGLContext context = egl.eglCreateContext(display, configs[0], EGL10.EGL_NO_CONTEXT, contextAttribList); // 将OpenGL ES上下文与TextureView关联 EGLSurface eglSurface = egl.eglCreateWindowSurface(display, configs[0], surface, null); egl.eglMakeCurrent(display, eglSurface, eglSurface, context); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { return false; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }); ``` 注意:上述步骤仅提供了一个基本的示例,用于将TextureView与Camera2 API结合使用。在实际应用中,您可能需要根据自己的需求进行修改和定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

氦客

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值