Android Studio基于OpenGL 1.0实现从中心左右移动的无畸变圆绘制
问题根源分析
图形畸变原因
- 第一段代码的比例修正逻辑完全失效:
- 硬编码
width=100、height=50算出的ratio=2,和实际屏幕宽高比完全不匹配;同时在onSurfaceChanged中同时调用GLU.gluPerspective和gl.glOrthof,后调用的正交投影会覆盖透视投影配置,没有形成稳定的投影映射。 - 自定义
DrawCircle类的流程残缺:draw()方法仅生成顶点数组,既没有将顶点传入OpenGL缓冲区,也没有触发实际的绘制指令;同时类中的顶点索引变量c没有在每次绘制前重置为0,多次调用后会出现数组越界、顶点数据错乱的问题,根本无法生成有效图形。
- 硬编码
- 第二段代码的畸变来源:
- 透视投影参数配置错误:设置
GLU.gluPerspective时近裁剪面设为-1、远裁剪面设为-10,不符合OpenGL近远裁剪面必须为正值的规则;同时没有为x、y轴设置等比例的可视映射范围,x方向和y方向的单位长度在屏幕上的实际占比不匹配,最终正圆被拉伸为椭圆。
- 透视投影参数配置错误:设置
动画实现失败原因
- 代码全程没有维护圆形的位置状态变量,没有每帧更新位置的逻辑,也没有边界检测和方向翻转的处理流程,缺少动画运行的核心驱动逻辑。
可行实现方案
核心修正思路
- 采用正交投影替代错误的透视/混用投影配置,在
onSurfaceChanged中根据真实屏幕宽高比设置等比例的可视范围,保证x、y方向的单位长度在屏幕上完全相等,从根源上避免图形畸变。 - 正圆的顶点坐标仅在初始化时生成一次,避免每帧重复分配内存造成性能损耗。
- 独立维护圆形的中心x坐标、移动方向参数,在每帧绘制前更新坐标,检测到边界时翻转移动方向,实现往返移动效果。
完整可运行代码
package pl.varkame; import android.opengl.GLSurfaceView; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; public class MyRenderer implements GLSurfaceView.Renderer { // 圆形顶点配置 private final int SEG_COUNT = 720; private FloatBuffer circleVertexBuffer; private final float circleRadius = 0.1f; // 移动动画配置 private float circleX = 0.0f; private float moveSpeed = 0.01f; private int moveDirection = 1; // 1为向右,-1为向左 private float maxXOffset = 0.0f; // 可移动的x轴最大偏移量 @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // 初始化正圆顶点:x、y方向半径完全相等,无畸变 float[] vertices = new float[SEG_COUNT * 2 + 2]; int index = 0; // TRIANGLE_FAN模式需要第一个点为圆心,才能正确生成扇形填充 vertices[index++] = 0.0f; vertices[index++] = 0.0f; for (int i = 0; i < SEG_COUNT; i++) { double rad = Math.PI * 2 * i / SEG_COUNT; vertices[index++] = (float) Math.cos(rad) * circleRadius; vertices[index++] = (float) Math.sin(rad) * circleRadius; } // 把顶点数据传入OpenGL直接缓冲区 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4); vbb.order(ByteOrder.nativeOrder()); circleVertexBuffer = vbb.asFloatBuffer(); circleVertexBuffer.put(vertices); circleVertexBuffer.position(0); // 配置背景为绿色 gl.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); // 计算真实屏幕宽高比,设置等比例正交投影 float screenRatio = (float) width / (float) height; gl.glOrthof(-screenRatio, screenRatio, -1.0f, 1.0f, -1.0f, 1.0f); // 计算可移动的最大x偏移:保证圆形完全处于屏幕可视范围内 maxXOffset = screenRatio - circleRadius; } @Override public void onDrawFrame(GL10 gl) { // 更新圆形位置 circleX += moveSpeed * moveDirection; // 检测到达边界,翻转移动方向实现往返效果 if (circleX > maxXOffset || circleX < -maxXOffset) { moveDirection = -moveDirection; } // 清屏 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 设置圆形颜色为蓝色 gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f); // 重置模型视图矩阵,应用圆形的x轴偏移 gl.glLoadIdentity(); gl.glTranslatef(circleX, 0.0f, 0.0f); // 提交顶点数据绘制圆形 gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(2, GL10.GL_FLOAT, 0, circleVertexBuffer); gl.glDrawArrays(GL10.GL_TRIANGLE_FAN, 0, SEG_COUNT + 1); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); }
实现效果说明
- 投影配置为等比例正交映射,绘制的圆形不会出现椭圆畸变
- 圆形初始位置为屏幕中心,沿x轴向右移动到屏幕右侧边界后自动折返,向左移动到屏幕左侧边界后再次折返,实现要求的往返动画
- 圆形顶点仅初始化一次,每帧无需重新计算顶点数组,性能开销更低
内容的提问来源于stack exchange,提问作者Varkame
相关产品推荐
相关产品推荐

