文章目錄
- 關于 glDrawElements
- 基本概念
- 使用場景
- mode 繪制模式
- type 索引數據類型
- indices 索引緩沖區
- 工作原理
- 繪制正方體實例
- 視口
- 透視投影(Perspective Projection)
- 正交投影(Orthographic Projection)
- 正交投影和透視投影對比
關于 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 繪制模式
模式 | 描述 |
---|---|
GL_POINTS | 將每個頂點作為一個點繪制。 |
GL_LINES | 將頂點兩兩連接為線段(v0-v1, v2-v3, …)。 |
GL_LINE_STRIP | 連接所有頂點為連續線段(v0-v1-v2-…)。 |
GL_LINE_LOOP | 連接所有頂點為閉合線段(最后一個點連回第一個點)。 |
GL_TRIANGLES | 將頂點每三個一組構成三角形(v0-v1-v2, v3-v4-v5, …)。 |
GL_TRIANGLE_STRIP | 相鄰三個頂點構成三角形(v0-v1-v2, v1-v2-v3, …)。 |
GL_TRIANGLE_FAN | 以第一個頂點為中心,與后續相鄰頂點構成扇形三角形(v0-v1-v2, v0-v2-v3, …)。 |
count 索引數量
需要繪制的索引總數。例如,渲染一個立方體需要 36 個索引(12 個三角形 × 3 個頂點)。
type 索引數據類型
類型 | 描述 |
---|---|
GL_UNSIGNED_BYTE | 8 位無符號整數(范圍:0~255)。 |
GL_UNSIGNED_SHORT | 16 位無符號整數(范圍:0~65535)。 |
GL_UNSIGNED_INT | 32 位無符號整數(范圍:0~4294967295)。 |
頂點數 ≤ 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.GLSurfaceView
import android.opengl.GLU
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10class CubeRenderer : GLSurfaceView.Renderer {private val vertexBuffer: FloatBufferprivate val indexBuffer: ShortBufferprivate val colorBuffer: FloatBuffer// 正方體的8個頂點坐標private val vertices = floatArrayOf(-0.5f, -0.5f, -0.5f, // 左下后 V00.5f, -0.5f, -0.5f, // 右下后 V10.5f, 0.5f, -0.5f, // 右上后 V2-0.5f, 0.5f, -0.5f, // 左上后 V3-0.5f, -0.5f, 0.5f, // 左下前 V40.5f, -0.5f, 0.5f, // 右下前 V50.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 = 0fprivate var angleY = 0finit {// 初始化頂點緩沖區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() / heightGLU.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.0fangleY += 0.5fgl.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:使用 glFrustumf
glFrustumf(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.0fangleY += 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()}
效果圖如下,只能看到最前面的一個正方體
正交投影和透視投影對比
特性 | 正交投影 | 透視投影 |
---|---|---|
特性 | 正交投影 | 透視投影 |
視覺效果 | 無近大遠小,深度感弱 | 近大遠小,深度感強 |
投影矩陣 | 線性變換(僅縮放和平移) | 非線性變換(包含除法) |
深度精度 | 均勻分布 | 近平面精度高,遠平面精度低 |
物體大小 | 與距離無關 | 隨距離增加而減小 |
適用場景 | 2D 游戲、UI、地圖、CAD | 3D 游戲、VR、真實感渲染 |