OpenGL ES绘制3D图形以及设置视口_opengl es 画osd
文章目录
关于 glDrawElements
基本概念
glDrawElements 是 OpenGL 中用于渲染图元的核心函数之一,它允许你使用索引缓冲区(Index Buffer)来指定顶点的绘制顺序,从而实现高效的渲染。这个函数在处理需要重复使用顶点数据的场景时特别有用,比如 3D 模型渲染。
使用场景
- 渲染包含大量重复顶点的复杂模型(如立方体、地形网格)。
- 共享顶点属性(如位置、法线、纹理坐标)。
void glDrawElements( int mode, // 绘制模式(如 GL_TRIANGLES、GL_LINES) int count, // 索引数量 int type, // 索引数据类型(如 GL_UNSIGNED_SHORT) java.nio.Buffer indices // 索引缓冲区);
mode 绘制模式
count 索引数量
需要绘制的索引总数。例如,渲染一个立方体需要 36 个索引(12 个三角形 × 3 个顶点)。
type 索引数据类型
顶点数 ≤ 255:使用 GL_UNSIGNED_BYTE。顶点数 ≤ 65535:使用 GL_UNSIGNED_SHORT(最常用)。顶点数 > 65535:使用 GL_UNSIGNED_INT(需 OpenGL ES 3.0+)
indices 索引缓冲区
存储顶点索引的缓冲区对象(如 java.nio.Buffer)。索引值对应顶点数组中的位置。
工作原理
- 顶点数组:定义所有顶点的属性(如位置、颜色)。
- 索引数组:指定顶点的绘制顺序。
- glDrawElements:根据索引从顶点数组中提取顶点,并按指定模式绘制。
绘制正方体实例
先看效果
先上全部代码
package com.e.openglimport android.opengl.GLSurfaceViewimport android.opengl.GLUimport java.nio.ByteBufferimport java.nio.ByteOrderimport java.nio.FloatBufferimport java.nio.ShortBufferimport javax.microedition.khronos.egl.EGLConfigimport javax.microedition.khronos.opengles.GL10class CubeRenderer : GLSurfaceView.Renderer { private val vertexBuffer: FloatBuffer private val indexBuffer: ShortBuffer private val colorBuffer: FloatBuffer // 正方体的8个顶点坐标 private val vertices = floatArrayOf( -0.5f, -0.5f, -0.5f, // 左下后 V0 0.5f, -0.5f, -0.5f, // 右下后 V1 0.5f, 0.5f, -0.5f, // 右上后 V2 -0.5f, 0.5f, -0.5f, // 左上后 V3 -0.5f, -0.5f, 0.5f, // 左下前 V4 0.5f, -0.5f, 0.5f, // 右下前 V5 0.5f, 0.5f, 0.5f, // 右上前 V6 -0.5f, 0.5f, 0.5f // 左上前 V7 ) // 正方体12个三角形的顶点索引(两个三角形组成一个面) private val indices = shortArrayOf( 0, 1, 2, 0, 2, 3, // 后面 1, 5, 6, 1, 6, 2, // 右面 5, 4, 7, 5, 7, 6, // 前面 4, 0, 3, 4, 3, 7, // 左面 3, 2, 6, 3, 6, 7, // 上面 4, 5, 1, 4, 1, 0 // 下面 ) // 每个顶点的颜色(RGBA) private val colors = floatArrayOf( 0.0f, 0.0f, 0.0f, 1.0f, // V0黑色 1.0f, 0.0f, 0.0f, 1.0f, // V1红色 1.0f, 1.0f, 0.0f, 1.0f, // V2黄色 0.0f, 1.0f, 0.0f, 1.0f, // V3绿色 0.0f, 0.0f, 1.0f, 1.0f, // V4蓝色 1.0f, 0.0f, 1.0f, 1.0f, // V5紫色 1.0f, 1.0f, 1.0f, 1.0f, // V6白色 0.0f, 1.0f, 1.0f, 1.0f // V7青色 ) private var angleX = 0f private var angleY = 0f init { // 初始化顶点缓冲区 val vbb = ByteBuffer.allocateDirect(vertices.size * 4) vbb.order(ByteOrder.nativeOrder()) vertexBuffer = vbb.asFloatBuffer() vertexBuffer.put(vertices) vertexBuffer.position(0) // 初始化索引缓冲区 val ibb = ByteBuffer.allocateDirect(indices.size * 2) ibb.order(ByteOrder.nativeOrder()) indexBuffer = ibb.asShortBuffer() indexBuffer.put(indices) indexBuffer.position(0) // 初始化颜色缓冲区 val cbb = ByteBuffer.allocateDirect(colors.size * 4) cbb.order(ByteOrder.nativeOrder()) colorBuffer = cbb.asFloatBuffer() colorBuffer.put(colors) colorBuffer.position(0) } override fun onSurfaceCreated(gl: GL10, config: EGLConfig) { // 设置清屏颜色为灰色 gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f) // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST) // 启用顶点和颜色数组 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY) gl.glEnableClientState(GL10.GL_COLOR_ARRAY) } override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { // 设置视口大小 gl.glViewport(0, 0, width, height) // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION) gl.glLoadIdentity() // 设置透视投影 val aspectRatio = width.toFloat() / height GLU.gluPerspective(gl, 45.0f, aspectRatio, 0.1f, 1000.0f) // 设置模型视图矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW) gl.glLoadIdentity() } override fun onDrawFrame(gl: GL10) { // 清除颜色和深度缓冲区 gl.glClear(GL10.GL_COLOR_BUFFER_BIT or GL10.GL_DEPTH_BUFFER_BIT) // 设置模型视图矩阵 gl.glLoadIdentity() gl.glTranslatef(0.0f, 0.0f, -5.0f) // 将正方体移到屏幕中央前方 // 旋转正方体 angleX += 1.0f angleY += 0.5f gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f) // 绕X轴旋转 gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f) // 绕Y轴旋转 // 设置顶点和颜色指针 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer) gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer) // 使用 glDrawElements 绘制正方体 gl.glDrawElements( GL10.GL_TRIANGLES, indices.size, GL10.GL_UNSIGNED_SHORT, indexBuffer ) }}
代码中都有标注,核心代码在 onDrawFrame 中,最后一行通过
gl.glDrawElements( GL10.GL_TRIANGLES, indices.size,GL10.GL_UNSIGNED_SHORT, indexBuffer)
读取顶点数据等信息,绘制正方体。
注意: 创建Buffer时,vbb.order(ByteOrder.nativeOrder()) 一般得加上。它是 Java NIO 缓冲区操作中的关键步骤,用于设置字节序(Byte Order),确保数据在内存中的存储方式与设备硬件一致。不添加这行有时候你会发现绘制的没啥错,就是不显示图像!!!
视口
透视投影(Perspective Projection)
基本概念模拟人眼视觉:远处物体看起来更小,产生 \"近大远小\" 的效果。视锥体(Frustum):由近平面、远平面和四个侧面组成的截头四棱锥,只有视锥体内的物体可见。
// 方法 1:使用 glFrustumfglFrustumf(left, right, bottom, top, near, far);// 方法 2:使用 GLU.gluPerspective 内部也是使用 glFrustumf 来实现GLU.gluPerspective(fovy, aspect, zNear, zFar);
fovy(视野角度):角度越大,视野越宽广(类似广角镜头);角度越小,视野越狭窄(类似长焦镜头)。aspect(宽高比):需与视口宽高比匹配,否则会导致图像拉伸。near 和 far:影响深度精度和可见距离,比值过大会导致深度冲突(Z-Fighting)
修改上面的代码,使用循环多绘制一些正方体
override fun onDrawFrame(gl: GL10) { // 清除颜色和深度缓冲区 gl.glClear(GL10.GL_COLOR_BUFFER_BIT or GL10.GL_DEPTH_BUFFER_BIT) // 旋转正方体 angleX += 1.0f angleY += 0.5f // 设置顶点和颜色指针 gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer) gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer) for (i in 0..10) { // 使用 glDrawElements 绘制正方体 // 设置模型视图矩阵 gl.glLoadIdentity() gl.glTranslatef(0.0f, -1f, -(5.0f * i.toFloat())) // 修改平移距离使绘制看起来远近的效果 gl.glRotatef(angleX, 1.0f, 0.0f, 0.0f) // 绕X轴旋转 gl.glRotatef(angleY, 0.0f, 1.0f, 0.0f) // 绕Y轴旋转 gl.glDrawElements( GL10.GL_TRIANGLES, indices.size, GL10.GL_UNSIGNED_SHORT, indexBuffer ) } }
效果如下图,有一种越远越小的感觉。
正交投影(Orthographic Projection)
基本概念平行投影:光线从无限远处平行照射物体,物体大小与距离无关。保持比例:物体的真实尺寸和角度在投影后保持不变,平行线投影后仍平行。
// 方法 1:3D 正交投影(OpenGL ES 1.x/2.0)void glOrthof(float left, float right, float bottom, float top, float near, float far);// 方法 2:2D 正交投影(OpenGL ES 1.x,简化版)void GLU.gluOrtho2D(float left, float right, float bottom, float top);
将上面透视投影的方法换成正交投影,同样绘制多个正方体。
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) { // 设置视口大小 gl.glViewport(0, 0, width, height) // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION) gl.glLoadIdentity() // 设置透视投影 val aspectRatio = width.toFloat() / height // GLU.gluPerspective(gl, 45.0f, aspectRatio, 0.1f, 1000.0f) // 正交投影 GLU.gluOrtho2D(gl, -5F, 5F, -5F, 5F) // 设置模型视图矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW) gl.glLoadIdentity() }
效果图如下,只能看到最前面的一个正方体