OpenGL好庞大,画个三角形画的吐血,理论里面涉及到的数学知识比较多,主要是三角函数以及矩阵论里面的东西,不过反复用来用去也就那么多,补充一下这方面的知识也能很快上手,好吧,为了以后能够随心所欲的画图,我忍了!!!
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);
}
}
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) {
}
}
//表层创建时
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//设置背景色,透明度1为完全不透明
gl.glClearColor(0, 0, 0, 1);
清屏,设置SurfaceView的背景色
gl.glClearColor(red, green, blue, alpha);
//表层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操作矩阵之前有个原则是在操作之前先要加载成单位矩阵*,然后再干其它.
//绘图
@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
}
在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~
此三角形为红色的,在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.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);
改变一下顶点的坐标值为
float[] coords = {
1f,0.5f,0.7f,
-1f,-1f,0f,
0.5f,-0.5f,-1f,
};
至于三角形的三个顶点坐标值具体如何设置见下文
设置三角形坐标,使其充满整个手机画面(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
}
}
}