Android开发之OpenGL的画笔工具GL10

简介: 这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。

上一篇文章介绍了OpenGL绘制三维图形的流程,其实没有传说中的那么玄乎,只要放平常心把它当作一个普通控件就好了,接下来继续介绍OpenGL具体的绘图操作,这项工作得靠三维图形的画笔GL10来完成了。

GL10作为三维空间的画笔,它所描绘的三维物体却要显示在二维平面上,显而易见这不是一个简单的伙计。为了理顺物体从三维空间到二维平面的变换关系,有必要搞清楚OpenGL关于三维空间的几个基本概念。下面就概括介绍一下GL10编码的三类常见方法:

一、颜色的取值范围

Android中的三原色,不管是红色还是绿色还是蓝色,取值范围都是0到255,对应的十六进制数值则为00到FF,颜色数值越小表示亮度越弱,数值越大表示亮度越强。但在OpenGL之中,颜色的取值范围却是0.0到1.0,其中0.0对应Android标准的0,1.0对应Android标准的255,同理,OpenGL值为0.5的颜色对应Android标准的128。

GL10与颜色有关的方法主要有两个,说明如下:

glClearColor : 设置背景颜色。以下代码表示给三维空间设置白色背景:

        // 设置白色背景。四个参数依次为透明度alpha、红色red、绿色green、蓝色blue
        gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glColor4f : 设置画笔颜色。以下代码表示把画笔颜色设置为橙色:

        // 设置画笔颜色为橙色
        gl.glColor4f(0.0f, 1.0f, 1.0f, 0.0f);

二、三维坐标系

三维空间用来表达立体形状,需要三个方向的坐标,分别为水平方向的x轴和y轴,以及垂直方向的z轴。如下图的三维坐标系所示,三维空间有个M点,该点在x轴上的投影为P点,在y轴上的投影为Q点,在z轴上的投影为R点,因此M点的坐标位置就是(P, Q, R)。

sanwei.png

既然三维空间中的每个点都存在x、y、z三个方向的坐标值,那么与物体位置有关的方法均需提供x、y、z三方向的数值。比如物体的旋转方法glRotatef、平移方法glTranslatef、缩放方法glScalef,要分别指定物体在三个坐标轴上的旋转方向、平移距离、缩放倍率。具体的方法调用例子如下所示:

        // 沿着y轴的负方向旋转90度
        gl.glRotatef(90, 0, -1, 0);
        // 沿x轴方向移动1个单位
        gl.glTranslatef(1, 0, 0);
        // x,y,z三方向各缩放0.1倍
        gl.glScalef(0.1f, 0.1f, 0.1f);

三、坐标矩阵变换

有了三维坐标系,还要把三维物体投影到二维平面上,才能在手机屏幕中绘制三维图形。这个投影操作主要有三个步骤,下面分别展开叙述:

1、设置绘图区域

前面说过OpenGL使用GLSurfaceView这个控件作为绘图场所,于是允许绘制的区域范围自然落在GLSurfaceView内部。设置绘图区域的方法是glViewport,它指定了该区域左上角的平面坐标,以及区域的宽度和高度。当然一般OpenGL的绘图范围与GLSurfaceView的大小重合,所以倘若GLSurfaceView控件的宽度为width,高度为height,则设置绘图区域的方法调用示例如下:

        // 设置输出屏幕大小
        gl.glViewport(0, 0, width, height);

2、调整镜头参数

框住了绘图区域,还要把三维物体在二维平面上的投影一点一点描绘进去才行,这中间的坐标变换计算由OpenGL内部自行完成,开发者无需关注具体的运算逻辑。好比日常生活中的拍照,用户只管拿起手机咔嚓一下,根本不用关心摄像头怎么生成照片。用户所关心的照片效果,不外乎景物是大还是小,是远还是近;用专业一点的术语来讲,景物的大小由镜头的焦距决定,景物的远近由镜头的视距决定。

对于镜头的焦距而言,拍摄同样尺寸的照片,广角镜头看到的景物比标准镜头看到的景物更多,这意味着单个景物在广角镜头中会比较小,从而照片面积不增大、容纳的景物却变多了。

对于镜头的视距而言,它表示镜头的视力好坏,即最近能看到多近的景物,最远能看到多远的景物。在日常生活当中,每个人的睫毛离自己的眼睛太近了,这么近的东西能看得清楚吗?所以必须规定一下,最近只能看清楚比如离眼睛十厘米的物体。很遥远的景物自然也是看不清楚的,所以也要规定一下,比如最远只能看到一公里之内的人影。这个能看清景物的最近距离和最远距离,就构成了镜头的视距。

所以,镜头的焦距是横向的,它反映了画面的广度;而镜头的视距是纵向的,它反映了画面的深度。在OpenGL中,这些镜头参数的调节依赖于GL10的gluPerspective方法,具体的参数调整代码举例如下:

        // 设置投影矩阵,对应gluPerspective(调整相机参数)、glFrustumf(调整透视投影)、glOrthof(调整正投影)
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // 重置投影矩阵,即去掉所有的参数调整操作
        gl.glLoadIdentity();
        // 设置透视图视窗大小。第二个参数是焦距的角度,第四个参数是能看清的最近距离,第五个参数是能看清的最远距离
        GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f);

3、挪动观测方位

调整好了镜头的拍照参数,要不要再摆个POSE,来个花式摄影?比如用户跃上好几级台阶,居高临下拍摄;也可俯下身子,从下向上拍摄;还能把手机横过来拍或者倒过来拍。要是怕摄影家累坏了,不妨叫摆拍的模特自己挪动身影,或者走进或者走远,往左靠一点或者往右靠一点,还可以躺下来甚至倒立过来。

因此,不管是挪动相机的位置,还是挪动物体的位置,都会让照片里的景物发生变化。挪动相机的位置,依靠的是GL10的gluLookAt方法;至于挪动物体的位置,依靠的则是旋转方法glRotatef、平移方法glTranslatef,以及缩放方法glScalef了。下面是OpenGL挪动相机位置的方法调用代码:

        // 选择模型观察矩阵,对应gluLookAt(人动)、glTranslatef/glScalef/glRotatef(物动)
        gl.glMatrixMode(GL10.GL_MODELVIEW);
        // 重置模型矩阵,即去掉所有的位置挪动操作
        gl.glLoadIdentity();

        // 设置镜头的方位。第二到第四个参数为相机的位置坐标,第五到第七个参数为相机画面中心点的坐标,第八到第十个参数为朝上的坐标方向,比如第八个参数为1表示x轴朝上,第九个参数为1表示y轴朝上,第十个参数为1表示z轴朝上
        GLU.gluLookAt(gl, 10.0f, 8.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

注意到前面调整相机参数和挪动相机位置这两个动作,都事先调用了glMatrixMode与glLoadIdentity方法,这是什么缘故呢?其实这两个方法结合起来只不过是状态重置操作,好比把手机恢复出厂设置,接下来重新进行状态设置。glMatrixMode方法的参数指定了重置操作的类型,像GL10.GL_PROJECTION类型涵盖了所有的镜头参数调整方法,包括gluPerspective(调整相机参数)、glFrustumf(调整透视投影)、glOrthof(调整正投影)三种方法,每次重置GL10.GL_PROJECTION类型,意味着之前的这三种参数设置统统失效。而GL10.GL_MODELVIEW类型涵盖的是位置变换的相关方法,包括挪动相机的gluLookAt方法,以及挪动物体的glTranslatef/glScalef/glRotatef方法,每次重置GL10.GL_MODELVIEW类型,意味着之前的位置挪动统统失效。

现在了解了以上的三维绘图的常见方法,接下来再看OpenGL的应用代码就会比较轻松了。先来看看一个最简单的三维立方体是如何实现的,下面是OpenGL绘制立方体的代码例子片段:

public class GlCubeActivity extends Activity {
   
   
    private final static String TAG = "GlCubeActivity";
    private GLSurfaceView glsv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_gl_cube);
        initVertexs(); // 初始化立方体的顶点集合,具体说明详见下一篇文章
        glsv_content = (GLSurfaceView) findViewById(R.id.glsv_content);
        // 注册渲染器
        glsv_content.setRenderer(new GLRender());
    }
    private class GLRender implements GLSurfaceView.Renderer {
   
   
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
   
   
            // 背景:白色
            gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
            // 启动阴影平滑
            gl.glShadeModel(GL10.GL_SMOOTH);
        }
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
   
   
            // 设置输出屏幕大小
            gl.glViewport(0, 0, width, height);
            // 设置投影矩阵,对应gluPerspective(调整相机)、glFrustumf(调整透视投影)、glOrthof(调整正投影)
            gl.glMatrixMode(GL10.GL_PROJECTION);
            // 重置投影矩阵,即去掉所有的平移、缩放、旋转操作
            gl.glLoadIdentity();
            // 设置透视图视窗大小
            GLU.gluPerspective(gl, 40, (float) width / height, 0.1f, 20.0f);
            // 选择模型观察矩阵,对应gluLookAt(人动)、glTranslatef/glScalef/glRotatef(物动)
            gl.glMatrixMode(GL10.GL_MODELVIEW);
            // 重置模型矩阵
            gl.glLoadIdentity();
        }
        @Override
        public void onDrawFrame(GL10 gl) {
   
   
            // 清除屏幕和深度缓存
            gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
            // 重置当前的模型观察矩阵
            gl.glLoadIdentity();
            // 设置画笔颜色
            gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
            // 设置镜头的方位
            GLU.gluLookAt(gl, 10.0f, 8.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
            // 旋转图形
            //gl.glRotatef(angle, 0, 0, -1);
            //gl.glRotatef(angle, 0, -1, 0);
            // 沿x轴方向移动1个单位
            //gl.glTranslatef(1, 0, 0);
            // x,y,z方向缩放0.1倍
            //gl.glScalef(0.1f, 0.1f, 0.1f);
            // 绘制一个立方体,具体说明详见下一篇文章
            drawCube(gl);
        }
    }
}
相关文章
|
11天前
|
存储 安全 Android开发
安卓应用开发:构建一个高效的用户登录系统
【5月更文挑战第3天】在移动应用开发中,用户登录系统的设计与实现是至关重要的一环。对于安卓平台而言,一个高效、安全且用户体验友好的登录系统能够显著提升应用的用户留存率和市场竞争力。本文将探讨在安卓平台上实现用户登录系统的最佳实践,包括对最新身份验证技术的应用、安全性考量以及性能优化策略。
|
4天前
|
Java Android开发
Android开发--Intent-filter属性详解
Android开发--Intent-filter属性详解
|
4天前
|
物联网 Java 开发工具
安卓应用开发:打造未来移动生活
【5月更文挑战第10天】 随着科技的飞速发展,智能手机已成为我们日常生活中不可或缺的一部分。作为智能手机市场的两大巨头,安卓和iOS分别占据了一定的市场份额。在这篇文章中,我们将重点关注安卓应用开发,探讨如何利用先进的技术和创新思维,为用户打造更加便捷、智能的移动生活。文章将涵盖安卓应用开发的基本概念、关键技术、以及未来发展趋势等方面的内容。
|
5天前
|
Java API 开发工具
java与Android开发入门指南
java与Android开发入门指南
12 0
|
6天前
|
Android开发 Kotlin
Kotlin开发Android之基础问题记录
Kotlin开发Android之基础问题记录
16 1
|
7天前
|
Java Android开发
Android开发@IntDef完美替代Enum
Android开发@IntDef完美替代Enum
13 0
|
7天前
|
Java Android开发
Android 反编译工具的使用
Android 反编译工具的使用
17 0
|
7天前
|
Android开发
Android 盒子开发过程中遇到的问题及解决方法
Android 盒子开发过程中遇到的问题及解决方法
8 2
|
8天前
|
机器学习/深度学习 算法 Android开发
安卓应用开发:打造高效通知管理系统
【5月更文挑战第6天】 在现代移动应用的海洋中,用户经常面临信息过载的挑战。一个精心设计的通知管理系统对于提升用户体验至关重要。本文将探讨在安卓平台上如何实现一个高效的通知管理系统,包括最佳实践、系统架构设计以及性能优化技巧。通过分析安卓通知渠道和优先级设置,我们的目标是帮助开发者构建出既能吸引用户注意,又不会引发干扰的智能通知系统。
19 2
|
8天前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库