OpenGL ES(一)绘制三角形

OpenGL好庞大,画个三角形画的吐血,理论里面涉及到的数学知识比较多,主要是三角函数以及矩阵论里面的东西,不过反复用来用去也就那么多,补充一下这方面的知识也能很快上手,好吧,为了以后能够随心所欲的画图,我忍了!!!

1,创建布局,加载SurfaceView控件

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyGLsurfaceView view = new MyGLsurfaceView(this);
        //renderer:渲染器
        view.setRenderer(renderer);
        //设置视图
        setContentView(view);
    }


        class MyGLsurfaceView extends GLSurfaceView{
            public MyGLsurfaceView(Context context) {
                super(context);

            }

            public MyGLsurfaceView(Context context, AttributeSet attrs) {
                super(context, attrs);

            }

        }

2,自定义渲染器MyRenderer

Renderer是个接口,OpenGL开发,Renderer是最重点的,重中之重!!!SurfaceView只是个载体.
因此可以view.setRenderer(new MyRenderer());
Ctrl进入setRenderer可以查到Rebderer来自类

android.opengl.GLSurfaceView.Renderer

自定义渲染器

MyRenderer implements android.opengl.GLSurfaceView.Renderer

实现里面的三个方法:

onSurfaceCreated,onSurfaceChanged,onDrawFrame

class MyRender implements android.opengl.GLSurfaceView.Renderer{

        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
        }

        @Override
        public void onDrawFrame(GL10 gl) {
        }

    }

2.1,onSurfaceCreated表层创建

            //表层创建时
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                //设置背景色,透明度1为完全不透明
                gl.glClearColor(0, 0, 0, 1);

清屏,设置SurfaceView的背景色

gl.glClearColor(red, green, blue, alpha);

2.2,onSurfaceChanged画面大小改变时调用

            //表层size改变时,即画面的大小改变时调用
            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                //设置视口,输出画面的区域,在控件的什么区域来输出,x,y是左下角坐标
                gl.glViewport(0, 0, width, height);
                float ratio =(float) width /(float) height;

                //矩阵模式,投影矩阵,openGL基于状态机
                gl.glMatrixMode(GL10.GL_PROJECTION);
                //加载单位矩阵
                gl.glLoadIdentity();
                //平截头体
                gl.glFrustumf(-1f, 1f, -ratio, ratio, 3, 7);
            }

设置视口,即输出画面的区域,一般来说按照SurfaceView控件的大小来输出,也可在SurfaceView里面的一个小区域输出
x,y是左下角的坐标(0,0)点,

gl.glViewport(x, y, width, height);

设置平截头体(左,右,下,上,近平面,远平面)

gl.glFrustumf(left, right, bottom, top, zNear, zFar);

设置平截头体时里面不是一个具体的值,而是一个数字,一个单位,这个单位是相对的,不管这个平截头体有多大,里面物体有多少,有多少都可以要多少,因此设置实际距离没有任何意义(即使设置1cm也可以洗下同样大的照片),设置的数字用来约束在平截头体里画图时物体的值的一个比例,可以高画面不至于失真
把整个画面冲洗到视口上,可以计算它的宽高比,来决定墙上口的宽高的值

ratio = width / height;

整数的没有意义,不是0就是1,因此要转成float

float ratio = (float)width / (float)height;

宽度看成1,高度就是ratio

因此有gl.glFrustumf(-1, 1, -ratio, ratio, zNear, zFar);

zNear:到眼睛的近平面(墙孔到相机的距离);zFar:到眼睛的远平面(投影到相机的距离),这里分别设置为3和7,因此有:

gl.glFrustumf(-1f, 1f, -ratio, ratio, 3, 7);

实际拍摄需要有一个坐标系,设置平截头体跟坐标没有任何关系,都是一些标量值.
如果把相机放在原点位置(笛卡尔坐标系),相机坐标即为(0,0,0),相机朝向z轴的负方向,近平面z轴坐标就会是-3,远平面-7,因此用起来很不方便,为了使用方便直观我们把平截头体的中心位置放在(0,0,0)点,平截头体的厚度为7-3=4(近平面到远平面的距离)
因此,此时相机(眼球)的位置就会是(0,0,5)点

拍摄的时候要把相机摆正,否则就会拍到别的地方去,因此还要有一个指向上方的向量(正方向),镜头也要有一个方向(镜头的朝向)

投影分透视投影和正投影,都是一个坐标的变换
* 透视投影:有深度概念,越远越小.
* 正投影:没有深度概念,相同大小.
坐标变换涉及到矩阵运算,即矩阵变换.而透视投影用的是投影矩阵.
平截头体设置棱台,是投影矩阵的变换,所以在设置平截头体之前要设置矩阵的模式为projection(投影)

gl.glMatrixMode(GL10.GL_PROJECTION);

以后所有的矩阵运算都是基于投影矩阵,其中的Frustumf就已经是在运算投影矩阵了
单位矩阵:除了对角线上为1,其它的都是0,与任何矩阵相乘都等于任何矩阵
因为任何矩阵在操作之前都要恢复成单位矩阵,在这个单位矩阵上进行再次运算,有点像矩阵的清0
要保证这个矩阵是单位矩阵,在操作之前需要把矩阵变换成单位矩阵

gl.glLoadIdentity(); //加载单位矩阵

因此有:

            gl.glMatrixMode(GL10.GL_PROJECTION);
            gl.glLoadIdentity();
            gl.glFrustumf(-1f, 1f, -ratio, ratio, 3, 7);

注:*OpenGL操作矩阵之前有个原则是在操作之前先要加载成单位矩阵*,然后再干其它.

2.3,onDrawFrame绘图

            //绘图
            @Override
            public void onDrawFrame(GL10 gl) {
                //清除颜色缓冲区
                gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

                //模型视图矩阵
                gl.glMatrixMode(GL10.GL_MODELVIEW);
                //操作新矩阵要先清0,加载单位矩阵
                gl.glLoadIdentity();
                //眼睛放的位置,eyes,eyey,eyez眼球(相机)的坐标
                //centerx,y,z镜头朝向,眼球的观察点
                //upx,upy,upz:指定眼球向上的向量,眼睛正着看
                GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);

                //画三角形
                //三角形的坐标
                float[] coords = {
                        0f,0.5f,0f,
                        -0.5f,-0.5f,0f,
                        0.5f,-0.5f,0f,
                };
                //分配字节缓冲区空间,存放顶点坐标数据,将坐标放在缓冲区中
                ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);  //直接分配字节的大小
                //设置顺序(本地顺序)
                ibb.order(ByteOrder.nativeOrder());
                //放置顶点坐标数组
                FloatBuffer fbb =  ibb.asFloatBuffer();
                fbb.put(coords);
                //定位指针的位置,从该位置开始读取顶点数据
                ibb.position(0);

                //设置绘图的颜色,红色
                gl.glColor4f(1f, 0f, 0f, 1f);
                //3:3维点,使用三个坐标值表示一个点
                //type:每个点的数据类型
                //stride:0,上,点的跨度
                //ibb:指定顶点缓冲区
                gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb);
                //绘制三角形数组
                gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);   //count以点的数量来算,为3
            }

2.3.1 指定眼球的位置lookat

在gl里找,如果找不到的话在工具类GLU中找lookat

GLU.gluLookAt(gl, eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);

gl:gl对象
eyeX,eyeY,eyeZ:眼球的坐标,根据上一小节的分析,眼球(相机)放在(0,0,5)点是最合适的,因此分别设置值0,0,5.
centerX,centerY,centerZ:是一个点,这个点决定相机的拍摄方向(镜头朝向,眼球的观察点),如指定(0,0,0),即相机朝向原点拍摄,其实在这里指定任何一个点都是有效的,只要把相机摆正位置就可以,在此demo里我们就指它为(0,0,0).
upX,upY,upZ:相机(眼球)向上的一个向量(正方向,如放反了就不能拍),设置(0,1,0),指定y轴正方向为正方向,即设置相机的正方向与y轴平行

即GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);

眼睛设置的时候操纵的是模型视图矩阵,因此要先将矩阵的模式改变为模型视图矩阵(modeView)

gl.glMatrixMode(GL10.GL_MODELVIEW);

操纵新矩阵前要先加载单位矩阵(清0)

gl.glLoadIdentity();

因此有:

            gl.glMatrixMode(GL10.GL_MODELVIEW);
            gl.glLoadIdentity();
            GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);

下面要画三角形啦O(∩_∩)O~

2.3.2 绘制三角形

此三角形为红色的,在z=0的平面上(xy平面)
调用方法glDrawArray

gl.glDrawArrays(mode, first, count);

drawArray:画数组
mode:指定画三角形,GL_TRIANGLES(三角形集合)

即gl.glDrawArrays(GL10.GL_TRIANGLES, first, count);

画之前先把坐标给出来,三角形有三个点,给出这三个点的坐标
在哪里画这个三角形?
平截头体中,gl.glFrustumf(-1f, 1f, -ratio, ratio, zNear, zFar);
三角形尽量不要超出平截头体的范围,因为一量超出范围就会被截断,因此设置的小一点,比如X(0,0.5,0),Y(-0.5,-0.5,0),Z(0.5,-0.5,0)三个点(看看是不是在xy平面上啊?很显然z的值都是0)
这三个点的坐标类型为float,因此有

float[] coords = {
                        0f,0.5f,0f,
                        -0.5f,-0.5f,0f,
                        0.5f,-0.5f,0f,
                };

把这个坐标放到缓冲区里面去,状态机里有顶点数据(所有物体都是由点构成),所有的点都放到一个缓冲区,即顶点缓冲区vertexBuffer,变换到缓冲区中这里用的是字节缓冲区ByteBuffer
用它做一个分配

ByteBuffer ibb = ByteBuffer.allocateDirect(capacity);

allocateDirect直接分配字节的大小,一个字节是8位,三角形坐标用float类型来指定的,一个浮点数4个字节,用字节缓冲区来存放浮点数据,每个浮点数得用4个字节,在分配大小的时候需要分配多大的空间?
每个点是三个坐标,每个坐标是浮点数,每个浮点数用4个字节,所以capacity = coords .length *4(其中length为3 * 3 = 9)

即ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);

ibb设置顺序为本地顺序(跟操作系统有关,看你当前的处理器是怎么处理顺序的,不用管)

ibb.order(ByteOrder.nativeOrder());

在顶点缓冲区中添加数据
先转成floatBuffer

FloatBuffer fbb = ibb.asFloatBuffer();

然后把坐标放入fbb中

fbb.put(coords);

然后把指针定位到第0个位置,从此处开始读取顶点数据

ibb.position(0);

再来看画三角形数组的方法DrawArrays:

gl.glDrawArrays(GL10.GL_TRIANGLES, first, count);

first: 0
count: 由于是以点的数量来画的,要画三个点,count值就为3,即绘制三角形

即gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

指定绘制的颜色glColor4f

gl.glColor4f(red, green, blue, alpha);

此处设置为红色,不透明

gl.glColor4f(1f, 0f, 0f, 1f);

绘制三角形小结:

//顶点坐标
float[] coords = {
                    0f,0.5f,0f,
                    -0.5f,-0.5f,0f,
                    0.5f,-0.5f,0f,
            };

            //设置顶点缓冲区
            ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);
            ibb.order(ByteOrder.nativeOrder());

            //往顶点缓冲区中添加数据
            FloatBuffer fbb = ibb.asFloatBuffer();
            fbb.put(coords);
            ibb.position(0);

            //绘制三角形
            gl.glColor4f(1f, 0f, 0f, 1f);
            gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);

2.4 查漏补缺,特别要注意的地方

2.4.1,启用顶点数组要告诉它你要启用顶点数组,因此在create时需要启用客户端状态,设置它为顶点数组gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);即:

            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                //设置背景色,透明度1为完全不透明
                gl.glClearColor(0, 0, 0, 1);
                //启用客户端状态,启用顶点缓冲区
                gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            }

2.4.2,设置过清屏的颜色,但是却没有清屏,可以在onDraw的时候先进行清屏

gl.glClear(mask);

mask掩码可以是很多数据,比如顶点缓冲区要清除,颜色缓冲区要清除,这里设置为COLOR_BUFFER_BIT(颜色缓冲区)

gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

2.4.3,在设置颜色之后指定缓冲区的数据顶点指针

gl.glVertexPointer(size, type, stride, pointer);

最后一个参数pointer就是buffer,即ibb
size:维度,这里是3维的点(3个坐标值表示一个点),它的值就为3
type: 每个顶点是用什么数据来组成的,浮点数即gl_Float
stride: 跨度,设置为0

即gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb);

3.运行结果:

OpenGL ES(一)绘制三角形_第1张图片

改变一下顶点的坐标值为

float[] coords = {
                    1f,0.5f,0.7f,
                    -1f,-1f,0f,
                    0.5f,-0.5f,-1f,
            };

所得结果:
OpenGL ES(一)绘制三角形_第2张图片

至于三角形的三个顶点坐标值具体如何设置见下文
设置三角形坐标,使其充满整个手机画面(OpenGL)

本文完整代码:

package com.gigi.opengl01_triangle;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Menu;

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyGLsurfaceView view = new MyGLsurfaceView(this);
        //renderer:渲染器
        view.setRenderer(new MyRenderer());
        //设置视图
        setContentView(view);
    }


        class MyGLsurfaceView extends GLSurfaceView{
            public MyGLsurfaceView(Context context) {
                super(context);

            }

            public MyGLsurfaceView(Context context, AttributeSet attrs) {
                super(context, attrs);

            }

        } 

        //自定义渲染器,最重点的
        class MyRenderer implements android.opengl.GLSurfaceView.Renderer{
            //表层创建时
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                //设置背景色,透明度1为完全不透明
                gl.glClearColor(0, 0, 0, 1);
                //启用客户端状态,启用顶点缓冲区
                gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
            }

            //表层size改变时,即画面的大小改变时调用
            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                //设置视口,输出画面的区域,在控件的什么区域来输出,x,y是左下角坐标
                gl.glViewport(0, 0, width, height);
                float ratio =(float) width /(float) height;

                //矩阵模式,投影矩阵,openGL基于状态机
                gl.glMatrixMode(GL10.GL_PROJECTION);
                //加载单位矩阵
                gl.glLoadIdentity();
                //平截头体
                gl.glFrustumf(-1f, 1f, -ratio, ratio, 3, 7);
            }

            //绘图
            @Override
            public void onDrawFrame(GL10 gl) {
                //清除颜色缓冲区
                gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

                //模型视图矩阵
                gl.glMatrixMode(GL10.GL_MODELVIEW);
                //操作新矩阵要先清0,加载单位矩阵
                gl.glLoadIdentity();
                //眼睛放的位置,eyes,eyey,eyez眼球(相机)的坐标
                //centerx,y,z镜头朝向,眼球的观察点
                //upx,upy,upz:指定眼球向上的向量,眼睛正着看
                GLU.gluLookAt(gl, 0, 0, 5, 0, 0, 0, 0, 1, 0);

                //画三角形
                //三角形的坐标
                float[] coords = {
                        0f,0.5f,0f,
                        -0.5f,-0.5f,0f,
                        0.5f,-0.5f,0f,
                };
                //分配字节缓冲区空间,存放顶点坐标数据,将坐标放在缓冲区中
                ByteBuffer ibb = ByteBuffer.allocateDirect(coords.length * 4);  //直接分配字节的大小
                //设置顺序(本地顺序)
                ibb.order(ByteOrder.nativeOrder());
                //放置顶点坐标数组
                FloatBuffer fbb =  ibb.asFloatBuffer();
                fbb.put(coords);
                //定位指针的位置,从该位置开始读取顶点数据
                ibb.position(0);

                //设置绘图的颜色,红色
                gl.glColor4f(1f, 0f, 0f, 1f);
                //3:3维点,使用三个坐标值表示一个点
                //type:每个点的数据类型
                //stride:0,上,点的跨度
                //ibb:指定顶点缓冲区
                gl.glVertexPointer(3, GL10.GL_FLOAT, 0, ibb);
                //绘制三角形数组
                gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);   //count以点的数量来算,为3
            }

        }


}

你可能感兴趣的:(OpenGL,ES)