一.OPENGL基础
一)基础方法讲解
1.坐标系
采用三维笛卡尔坐标系,如图
2.不同的绘制方法
2)索引法绘制三角形:具有索引缓冲数组
3)顶点法绘制三角形:无索引缓冲数组,直接根据顶点顺序排序。
3.缓存消除
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);//消除颜色和深度缓冲
4.矩阵模式切换
gl.glMatrixMode(GL10.GL_MODELVIEW|GL10.GL_PROJECTION|...);
gl.glLoadIdentity();//将当前模式的矩阵重置为单位矩阵
5.投影
1)正交投影
2)透视投影
Public void glFrustumf(int left,int right,int top,int bottom,int near,int far) 功能: 透视投影的设置,可视区域为[near,far],投影中心为z轴上的点(0,0,R) 参数: Left:视口左侧对应的x坐标 Rigth:视口右侧对应的x坐标 Top:视口上侧对应的y坐标 Bottom:视口下侧对应的y坐标 Near:最近端的z坐标 Far:最远端的z坐标 相关计算: 水平方向的视角 a = 2arctg(left,near);//即left/near的反切 垂直方向的视角 a = 2arctg(top,near);// 一般-left=right = ScreenWidth/ScreenHeight; -top = bottom = 1 Left,right,top,bottom的设定对图像的影响 原理:视角变大,视野变大。系统应该调整视口中的图像,即缩放。 如果水平即x轴的视角变大,则说明视野更加开阔,可见的东西应该要更多,所以视图会进行调整,即当前视口中图像按比例缩小,这样就可以表现出视野开阔了的效果。a增大x轴上会进行缩放,b增大会在y轴上进行缩放,near,far会在z轴上缩放。 如下的效果 正常比例的三角形 水平视角增大则x轴上图像缩小 垂直视角增大y轴图像缩小 水平和垂直视角都增大 |
6.视口
显示GLSurfaceView的窗口
7.绘制相关的函数
public void drawSelf(GL10 gl){ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用顶点,颜色数组,默认是关闭的,不启用的话不能调用glDrawElements()绘制 gl.glRotatef(yAngle,0,1,0);//根据yAngle的角度值,绕y轴旋转yAngle //gl.glRotatef(zAngle,0,0,1); gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);//指明图形绘制的坐标数据 gl.glColorPointer(4, GL10.GL_FIXED, 0, myColorBuffer);//指明顶点着色数据 gl.glDrawElements(GL10.GL_TRIANGLES, vCount, GL10.GL_UNSIGNED_BYTE, myIndexBuffer); } |
8.去除抖动
gl.glDisable(GL10.GL_DITHER);
10.Hint设置
设置GL的相关项,如设置GL透视纠正为最有效率的形式
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);//gl的指示及模式选择
11.设背景色
颜色区间[0-1]0表示最暗-黑色,1表示最亮-白色
gl.glClearColor(0,0,0,0);
基本图形三角形的绘制过程
绘制流程图如下
开始:
this.setRenderer(render); this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); |
初始化
gl.glDisable(GL10.GL_DITHER);//去除抖动 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);//gl的指示及模式选择,设置GL的透视纠正为最有效率 gl.glClearColor(0,0,0,0);//设置背景色 |
初始化图形
public Triangle(){ vCount=3;//一个三角形,3个顶点 final int UNIT_SIZE=10000;//缩放比例 int []vertices=new int[] { -8*UNIT_SIZE,6*UNIT_SIZE,0, -8*UNIT_SIZE,-6*UNIT_SIZE,0, 8*UNIT_SIZE,-6*UNIT_SIZE,0 }; //创建顶点坐标数据缓存,由于不同平台字节顺序不同,数据单元不是字节的(上面的事整型的缓存),一定要经过ByteBuffer转换,关键是通过ByteOrder设置nativeOrder() ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4);//一个整数四个字节,根据最新分配的内存块来创建一个有向的字节缓冲 vbb.order(ByteOrder.nativeOrder());//设置这个字节缓冲的字节顺序为本地平台的字节顺序 mVertexBuffer=vbb.asIntBuffer();//转换为int型缓冲 mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据 mVertexBuffer.position(0);//设置缓冲区的起始位置 final int one=0xffff;//使用十六进制表示全色彩通道 int []colors=new int[]//顶点颜色值数组,每个顶点4个色彩值RGBA { one,0,0,0, 0,one,0,0, 0,0,one,0 }; ByteBuffer cbb=ByteBuffer.allocateDirect(colors.length*4); cbb.order(ByteOrder.nativeOrder()); myColorBuffer=cbb.asIntBuffer(); myColorBuffer.put(colors); myColorBuffer.position(0); //为三角形构造索引数据初始化 iCount=3; byte []indices=new byte[] { 0,1,2 }; //创建三角形构造索引数据缓冲 myIndexBuffer=ByteBuffer.allocateDirect(indices.length); myIndexBuffer.put(indices); myIndexBuffer.position(0); } |
绘制初始化
public void onSurfaceChanged(GL10 gl, int width, int height) { // TODO Auto-generated method stub gl.glViewport(0, 0, width, height);//设置视口 gl.glMatrixMode(GL10.GL_PROJECTION);//切换为投影矩阵 gl.glLoadIdentity();//将投影矩阵重置为单位矩阵 float ratio=(float)width/height; //gl.glOrthof(-ratio, ratio, -1, 1, 1, 10);//正交投影 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100);//设置投影方式为透视投影 } |
绘制
public void onDrawFrame(GL10 gl) { // TODO Auto-generated method stub gl.glEnable(GL10.GL_CULL_FACE); //gl.glCullFace(GL10.GL_FRONT);//绘制正面 //gl.glFrontFace(GL10.GL_CCW);//设置逆时针为正面 gl.glShadeModel(GL10.GL_SMOOTH);//着色模型为光滑着色,默认 gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);//消除颜色和深度缓冲 gl.glMatrixMode(GL10.GL_MODELVIEW);//选用视图举证模式 gl.glLoadIdentity();//充值矩阵 gl.glTranslatef(0, 0, -2.0f);// tr.drawSelf(gl);//绘制 } |
public void drawSelf(GL10 gl){ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用顶点,颜色数组,默认没有开启,所以默认不能调用glDrawElements()绘制 gl.glRotatef(yAngle,0,1,0);//根据yAngle的角度值,绕向量(0,1,0)y轴旋转yAngle //gl.glRotatef(zAngle,0,0,1); gl.glVertexPointer(3, GL10.GL_FIXED, 0, mVertexBuffer);//指明图形绘制的坐标数据 gl.glColorPointer(4, GL10.GL_FIXED, 0, myColorBuffer);//指明顶点着色数据 gl.glDrawElements(GL10.GL_TRIANGLES, vCount, GL10.GL_UNSIGNED_BYTE, myIndexBuffer);//绘制 } |
绘制基本图形
Opengl es 只支持三角形绘制,所以其他的基本图形都只有通过三角形来拼凑而成,其中注意顶点的个数问题,如果顶点数目不对将不能正确绘制出图形。
如正方形:正方形由2个三角形组成,每个三角形3个顶点,所以一个正方形共需要6个顶点数。
注:
1.如果使用索引法绘制图形,建议使用索引数量来确定共多少个顶点,带入glDrawElements();
2.指定顶点的时候如果使用的是int,则需要指定缩放指数10000,glVertexPointer()对应type是GL_FIXED;如果使用时float,则type是GL_FLOAT,而且不需要指定type
绘制三维图形
球体的绘制
原理:按照地球的经纬度来绘制球体,以赤道把球南北分为N,S极。以子午线(经度为0)左右为界分为西经和东经,
纬度:N极[0~90],S极为[-90~0];
经度:W[0~-180],E[0~180]
然后按照指定的切分角度angleSpan横纵分别切分。的切分行数row = 180/angleSpan;col = 360/angleSpan.切分后供row*col个长方形,2*row*col个三角形。
坐标计算:
final int angleSpan=18; //将小球进行单位切分的角度 for (int vAngle = -90; vAngle <= 90; vAngle=vAngle+angleSpan) { //垂直方向angleSpan度一份 for (int hAngle = 0; hAngle <360; hAngle=hAngle+angleSpan ) { //水平方向angleSpan度一份 //纵向横向各到一个角度后计算对应的此点在球面上的坐标 double xozLength=scale*UNIT_SIZE*Math.cos(Math.toRadians(vAngle));//xozLength是上图中的BO,即半径投影在xoz面上的长度 int x=(int) (xozLength*Math.cos(Math.toRadians(hAngle)));//hAngle是纬度,vAngle是经度 int y=(int) (xozLength*Math.sin(Math.toRadians(hAngle))) ; int z=(int) (scale*UNIT_SIZE*Math.sin(Math.toRadians(vAngle))); alVertex.add(x); alVertex.add(y); alVertex.add(z); } } |
索引
int row=(180/angleSpan)+1;//球面切分的行数 int col=360/angleSpan;//球面切分的列数 for(int i=0;i<row;i++){//对每一行循环 if(i>0&&i<row-1){ //中间行 for(int j=-1;j<col;j++){ //中间行的两个相邻点与下一行的对应点构成三角形 int k=i*col+j;//当前点的索引值求解,当前点在i行j列 alIndex.add(k+rol); alIndex.add(k+1); alIndex.add(k); } for(int j=0;j<col+1;j++){ //中间行的两个相邻点与上一行的对应点构成三角形 int k=i*col+j; alIndex.add(k-col); alIndex.add(k-1); alIndex.add(k); }}} |
绘制球的步骤
注:必须在调用glDrawElements()之前先消除缓存即上图的步骤5,否则会图像会乱
图像初始化
public void initBuffer() { // TODO Auto-generated method stub getVertex();//计算出所有坐标的xyz值存入vertex数组中,计算出所有索引值存入index数组中 ByteBuffer bbf = ByteBuffer.allocateDirect(vCount * 4 *3);//每个顶点由3个int数据组成,(int) = 4*(byte) bbf.order(ByteOrder.nativeOrder());//转化成本地字节顺序 mVertexBuffer = bbf.asIntBuffer(); mVertexBuffer.put(vertex); mVertexBuffer.position(0);//顶点坐标数组缓冲 ByteBuffer nbb = ByteBuffer.allocateDirect(vCount*4*3);//法向量缓冲 nbb.order(ByteOrder.nativeOrder()); mNormalBuffer = nbb.asIntBuffer(); mNormalBuffer.put(vertex); mNormalBuffer.position(0); mIndexBuffer = ByteBuffer.allocateDirect(iCount); mIndexBuffer.order(ByteOrder.nativeOrder()); mIndexBuffer.put(index); mIndexBuffer.position(0); } public void getVertex() { // TODO Auto-generated method stub int angleSpan=16;//球面切分角度 int row = 180/angleSpan +1;//横向切面的数量 int col= 360/angleSpan+1;//纵向切的次数,切面数量 float vAngle=0,hAngle = 0;//纬度和经度 ArrayList<Integer>alVertex = new ArrayList<Integer>(); for(vAngle =-90;vAngle<=90;vAngle+=angleSpan){ for(hAngle = 0;hAngle<360;hAngle+=angleSpan){ double xozLength = R * UNIT_SIZE * Math.cos(Math.toRadians(vAngle));//xoz面的投影长度 int x = (int) (xozLength * Math.cos(Math.toRadians(hAngle))); int z = (int) (xozLength * Math.sin(Math.toRadians(hAngle))); int y = (int) (R * UNIT_SIZE * Math.sin(Math.toRadians(vAngle))); //for(int i = 0;i<3;i++){ alVertex.add(x); alVertex.add(y); alVertex.add(z);//将xyz顶点数据存入vertex数组中 //} } } vCount = alVertex.size()/3; vertex=new int[vCount*3]; for(int i = 0;i<alVertex.size();i++){ vertex[i]= alVertex.get(i); } ArrayList<Integer>alIndex = new ArrayList<Integer>(); for(int i = 0;i<row;i++){ if(i>0&&i<row-1){ for(int j = 0;j<col;j++){ int c = i*col+j;//当前点的索引值 alIndex.add(c+col); alIndex.add(c+1); alIndex.add(c);//当前点的与上方和右方的相邻点构成当前点上面一个三角形 } for(int j = 0;j<col;j++){ int c = i*col+j;//当前点的索引值 alIndex.add(c-col); alIndex.add(c-1); alIndex.add(c);//当前点的与下方和左方的相邻点构成当前点下面的一个三角形 } }} iCount = alIndex.size(); index = new byte[iCount]; for(int i = 0;i<iCount;i++){ index[i] = alIndex.get(i).byteValue(); } } |
绘制初始化
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { // TODO Auto-generated method stub gl.glViewport(0, 0, width, height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity();//重置为投影单位矩阵 float ratio =(float) width/height; gl.glFrustumf(-ratio, ratio, -1, 1, 1, 100);//投影方式 ball = new Ball(); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // TODO Auto-generated method stub gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); gl.glDisable(GL10.GL_DITHER); gl.glShadeModel(GL10.GL_SMOOTH);//着色模式为光滑着色,FLAT为平滑着色,反射光线是是单一反射 gl.glClearColor(0, 0, 0, 0);//背景为黑色 //启用深度测试,关闭了有些像素将不能绘制出来 gl.glEnable(GL10.GL_DEPTH_TEST); } |
绘制
@Override public void onDrawFrame(GL10 gl) { // TODO Auto-generated method stub gl.glShadeModel(GL10.GL_SMOOTH); gl.glMatrixMode(GL10.GL_MODELVIEW); gl.glLoadIdentity(); gl.glEnable(GL10.GL_LIGHTING);//开灯 initLight(gl); initMeterial(gl); //设定Light0光源的位置 float[] positionParamsGreen={-6,6,-6,1};//最后的1表示是定位光 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, positionParamsGreen,0); ball.drawSelf(gl); gl.glLoadIdentity(); } public void initLight(GL10 gl) { // TODO Auto-generated method stub gl.glEnable(GL10.GL_LIGHT0); //环境光 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[]{0.4f,0.4f,0.4f,1.0f},0); //散射光 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[]{0.5f,0.5f,0.5f,1.0f},0); //反射光 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, new float[]{1.0f,1.0f,1.0f,1.0f},0); //高光反射 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SHININESS, new float[]{1.5f,1.5f,1.5f,1.0f},0); } public void initMeterial(GL10 gl) { // TODO Auto-generated method stub gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, new float[]{0.4f,0.4f,0.4f,1.0f},0);//环境光为白色 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, new float[]{1.0f,0.0f,0.0f,1.0f},0);//三色光为红色 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, new float[]{0.0f,1.0f,0.0f,1.0f},0);//反射光为绿色 gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SHININESS, new float[]{1.5f},0);//高光反射区,光强越大,范围越小 } |
正方体的绘制
顶点法--使用面法向量
CubeVertex.java public class CubeVertex { private FloatBuffer mVertexBuffer;//顶点坐标数据缓冲 private FloatBuffer mNormalBuffer;//法向量数据缓冲 public float mOffsetX; public float mOffsetY;//绕Y轴旋转 float scale; //立方体高度 int vCount;//顶点数量 public CubeVertex(float scale,float length,float width) { this.scale=scale; vCount=36; float UNIT_SIZE=0.5f; float UNIT_HIGHT=0.5f; float[] verteices= { //顶面 -UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, //后面 -UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, //前面 -UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, //下面 -UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, //左面 -UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, -UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width, -UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, //右面 UNIT_SIZE*length,UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,-UNIT_SIZE*width, UNIT_SIZE*length,UNIT_HIGHT*scale,UNIT_SIZE*width, UNIT_SIZE*length,-UNIT_HIGHT*scale,UNIT_SIZE*width }; ByteBuffer vbb=ByteBuffer.allocateDirect(verteices.length*4); //创建顶点坐标数据缓冲 vbb.order(ByteOrder.nativeOrder());//设置字节顺序 mVertexBuffer=vbb.asFloatBuffer();//转换为float型缓冲 mVertexBuffer.put(verteices);//向缓冲区中放入顶点坐标数据 mVertexBuffer.position(0);//设置缓冲区起始位置 float[] normals= { //顶面顶点法向量 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, //后面顶点法向量 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, //前面顶点法向量 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, //下面顶点法向量 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, //左面顶点法向量 -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, //右面顶点法向量 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 }; ByteBuffer nbb=ByteBuffer.allocateDirect(normals.length*4);//创建颜色坐标数据缓冲 nbb.order(ByteOrder.nativeOrder());//设置字节顺序 mNormalBuffer=nbb.asFloatBuffer();//转换为float型缓冲 mNormalBuffer.put(normals);//向缓冲区中放入顶点坐标数据 mNormalBuffer.position(0);//设置缓冲区起始位置 } public void drawSelf(GL10 gl) { gl.glRotatef(mOffsetX, 1, 0, 0); gl.glRotatef(mOffsetY, 0, 1, 0); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//允许使用顶点数组 //为画笔指定顶点坐标数据 gl.glVertexPointer ( 3, //每个顶点的坐标数量为3 xyz GL10.GL_FLOAT, //顶点坐标值的类型为 GL_FIXED 0, //连续顶点坐标数据之间的间隔 mVertexBuffer //顶点坐标数据 ); gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);//允许使用法向量数组 gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalBuffer);//为画笔指定顶点法向量数据 gl.glDrawArrays(GL10.GL_TRIANGLES, 0, vCount);//绘制图形 } } |
MySurfaceView.java public class MySurfaceView extends GLSurfaceView{ private SceneRenderer mRenderer;//声明渲染器 float cx=0;//摄像机x位置 float cy=3;//摄像机y位置 float cz=40;//摄像机z位置 float tx=0;目标点x位置 float ty=0;//目标点y位置 float tz=0;//目标点z位置 public MySurfaceView(Context context) { super(context); // TODO Auto-generated constructor stub mRenderer = new SceneRenderer(); //创建场景渲染器 setRenderer(mRenderer); //设置渲染器 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//设置渲染模式为主动渲染 } private class SceneRenderer implements GLSurfaceView.Renderer { CubeVertex cubeVertex;//声明顶点法立方体 @Override public void onDrawFrame(GL10 gl) { // TODO Auto-generated method stub //采用平滑着色 gl.glShadeModel(GL10.GL_SMOOTH); //设置为打开背面剪裁 gl.glEnable(GL10.GL_CULL_FACE); //清除颜色缓存于深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); //设置当前矩阵为模式矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); //设置当前矩阵为单位矩阵 gl.glLoadIdentity(); //设置camera位置 GLU.gluLookAt ( gl, cx, //人眼位置的X cy, //人眼位置的Y cz, //人眼位置的Z tx, //人眼球看的点X ty, //人眼球看的点Y tz, //人眼球看的点Z 0, 1, ); gl.glPushMatrix();//获取坐标系 gl.glRotatef(45, 0, 1, 0);//绕Y轴旋转45度 gl.glRotatef(45, 1, 0, 0);//绕X轴旋转45度 cubeVertex.drawSelf(gl);//绘制立方体 gl.glPopMatrix();//恢复坐标系 } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { // TODO Auto-generated method stub //设置视窗大小及位置 gl.glViewport(0, 0, width, height); //设置当前矩阵为投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); //设置当前矩阵为单位矩阵 gl.glLoadIdentity(); //计算透视投影的比例 float ratio = (float) width / height; //调用此方法计算产生透视投影矩阵 gl.glFrustumf(-ratio, ratio, -1.0f, 1.0f, 8, 100); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { // TODO Auto-generated method stub //关闭抗抖动 gl.glDisable(GL10.GL_DITHER); //设置特定Hint项目的模式,这里为设置为使用快速模式 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST); //设置屏幕背景色黑色RGBA gl.glClearColor(0,0,0,0); //设置着色模型为平滑着色 gl.glShadeModel(GL10.GL_SMOOTH); //启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); //设置为打开背面剪裁 gl.glEnable(GL10.GL_CULL_FACE); //允许使用光照 gl.glEnable(GL10.GL_LIGHTING); //初始化光源 initLight(gl); //初始化材质光源 initMaterial(gl); cubeVertex=new CubeVertex(2.5f,2.5f,2.5f); new Thread() { public void run() { while(true) { try { sleep(100);//睡眠0.1秒 }catch(Exception e) { e.printStackTrace(); } cubeVertex.mOffsetY+=2.0f;//每次旋转 } } }.start(); } } public void initLight(GL10 gl) { // TODO Auto-generated method stub gl.glEnable(GL10.GL_LIGHT0);//打开0号光源 //环境光设置 float[] ambientParams={0.46f,0.21f,0.05f,1.0f};//光参数 RGBA gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, ambientParams,0); //散射光设置 float[] diffuseParams={0.46f,0.21f,0.05f,1.0f}; gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, diffuseParams,0); //镜面光设置 float[] specularParams={0.46f,0.21f,0.05f,1.0f}; gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, specularParams,0); //指定光源位置 float[] directionParams={-1f,1f,1f,0};//定向光 gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, directionParams,0); } public void initMaterial(GL10 gl) { // TODO Auto-generated method stub //环境光为白色材质 float ambientMaterial[] = {0.6f, 0.6f, 0.6f, 1.0f}; gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, ambientMaterial,0); //散射光为白色材质 float diffuseMaterial[] = {1.0f, 1.0f, 1.0f, 1.0f}; gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, diffuseMaterial,0); //高光材质为白色 float specularMaterial[] = {1f, 1.0f, 1f, 1.0f}; gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, specularMaterial,0); } } |
三)光照
1. 光照模型
环境光——经过多次反射而来的光称为环境光,无法确定其最初的方向,但当特定的光源关闭后,它们将消失.
全局环境光——每个光源都能对场景提供环境光。此外,还有一个环境光,它不来自任何特定的光源,称之为全局环境光。这也就是为什么我们不加任何自定义光源,也能看见绘制的物体的原因。
散射光——来自同一方向,照射到物体表面后,将沿各个方向均匀反射,因此,无论从哪个方向观察,表面的亮度都相同. 主要通过设置GLfloat light_diffuse[] = { 0.0, 1.0, 0.0, 1.0 }来发射绿光镜面反射光——来自特定方向,也被反射到特定方向.镜面反射度与之相关.
材质发射光——用于模拟发光物体.在OpenGL光照模型中,表面的发射光增加了物体的亮度,它不受光源的影响,另外,发射光不会给整个场景中增加光线.
当光照射到一个物体表面上时,会出现三种情形。首先,光可以通过物体表面向空间反射, 产生反射光。其次,对于透明体,光可以穿透该物体并从另一端射出,产生透射光。最后,部分光将被物体表面吸收而转换成热。在上述三部分光中,仅仅是透射光和反射光能够进入人眼产生视觉效果。这里介绍的简单光照模型只考虑被照明物体表面的反射光影响,假定物体表面光滑不透明且由理想材料构成,环境假设为由白光照明。
一般来说,反射光可以分成三个分量,即环境反射、漫反射和镜面反射。环境反射分量假定入射光均匀地从周围环境入射至景物表面并等量地向各个方向反射出去,通常物体表面还会受到从周围环境来的反射光(如来自地面、天空、墙壁等的反射光)的照射,这些光常统称为环境光(Ambient Light);漫反射分量表示特定光源在景物表面的反射光中那些向空间各方向均匀反射出去的光,这些光常称为漫射光(Diffuse Light);镜面反射光为朝一定方向的反射光,如一个点光源照射一个金属球时会在球面上形成一块特别亮的区域,呈现所谓“高光(Highlight)”,它是光源在金属球面上产生的镜面反射光(Specular Light)。对于较光滑物体,其镜面反射光的高光区域小而亮;相反,粗糙表面的镜面反射光呈发散状态,其高光区域大而不亮。
2. OpenGL 光照模效果的原理
OpenGL的光照模型是用来模拟现实生活中的光照的。它根据顶点的法线向量和光源的位置决定顶点的明暗程度,根据顶点的材质和光源中三原色的成分来决定物体将表现出怎样的颜色。
值得一提的是材质。OpenGL中的材质并非我们平常所说的组成物体的元素(如木材、金属材质),而是指一个物体对不同颜色的光的反射和吸收程度。比如,在光照系统中,如果一个物体表现为红色,则是因为这个物体吸收了从光源放射出来的绿色和蓝色光,而将绝大多数红色的光反射了出来。正因如此,一旦你开启了光照系统,就要通过指定物体的材质来决定这个物体是什么颜色。既然这样,你可能会想到怎样表现类似金属、玻璃等物质质感,但这些除了要使用光照系统并为它们指定合适的材质外,还要使用纹理贴图来表现质感。
使用OpenGL的光照模型包括以下几个步骤:
· 设置光源的种类、位置和方向(对于平行光源)
· 为每个图元的每个顶点指定它的法线向量
· 为各个图元指定它的材质
2.1 局部视点和无穷远视点
视点位置能影响镜面反射中高光的计算。
即顶点的高光强度不仅取决于顶点法向量,而且取决于顶点到光源的方向以及顶点到视点的方向。
对于无穷远视点,视点到任何顶点的方向都是相同的。
而对于局部视点,视点到每个顶点的方向是不同的。
所以需要计算视点到每个顶点的方向,因而性能降低了,但效果质量提高了。
缺省时,使用的是无穷远视点。
glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER , GL_TRUE ); // 将视点设置为局部视点
glLightModeli( GL_LIGHT_MODEL_LOCAL_VIEWER , GL_FALSE ); // 将视点设置为无穷远视点
2.2 双面光照
光照计算是对所有多边形(包括其正面和背面)进行的。而一般情况下,只设置正面光照条件,而忽略背面。但有时候需要看到物体内侧的光照效果,这就需要用到双面光照。
glLightModeli( LIGHT_MODEL_TWO_SIDE , GL_TRUE ); // 启用双面光照
glLightModeli( LIGHT_MODEL_TWO_SIDE , GL_FALSE ); // 禁用双面光照
2.3 光源衰减
真实的光,离光源越远则光强越小。环境光、漫反射光和镜面光的强度都衰减,辐射光和全局环境光的强度不衰减。
方向光源是无穷远光源,因此距离对光强没有影响,所以方向光没有衰减,但位置光则有衰减。
OpenGL是通过光源光强乘以衰减系数来计算衰减光照的。
衰减系数 = 1 / ( Kc + Kl*d + Kq*d*d ) ,d = 光源位置到物体顶点的距离
Kc = GL_CONSTANT_ATTENUATION 常数衰减因子
Kl = GL_LINER_ATTENUATION 线性衰减因子
Kq = GL_QUADRATIC_ATTENUATION 二次衰减因子,缺省时衰减因子为( 1 , 0 , 0 ) 即不进行衰减
glLightf( GL_LIGHT0 , GL_CONSTANT_ATTENUATION , 1.0 );
glLightf( GL_LIGHT0 , GL_LINEAR_ATTENUATION , 0.0 );
glLightf( GL_LIGHT0 , GL_QUADRATIC_ATTENUATION , 0.0 );
3.设置光源
3.1 光源的种类
环境光
环境光是一种无处不在的光。环境光源放出的光线被认为来自任何方向。因此,当你仅为场景指定环境光时,所有的物体无论法向量如何,都将表现为同样的明暗程度。
点光源
由这种光源放出的光线来自同一点,且方向辐射自四面八方。
平行光
平行光又称镜面光,这种光线是互相平行的。从手电筒、太阳等物体射出的光线都属于平行光。
聚光灯
这种光源的光线从一个锥体中射出,在被照射的物体上产生聚光的效果。使用这种光源需要指定光的射出方向以及锥体的顶角α。
3.2 光的成分
对于每一种光源,都有漫射光和平行光两种成分。在OpenGL中,环境光也被作为一种特殊的光源的成分来看待。漫射光是指在光源中能够被漫反射的光的颜色成分(白色则包含所有颜色),而平行光是指光源中所有能够被镜面反射的光的颜色成分。通过指定这两种成分的颜色,就能决定光源是平行光源还是点光源。
3.3 设置光源成分
OpenGL可以同时为我们提供8个有效的光源。也就是说,我们最多可以同时启用8个光源。它们分别是GL_LIGHT0,GL_LIGHT1,GL_LIGHT2 …… 其中,GL_LIGHT0是最特殊的一个光源。我们可以为GL_LIGHT0指定环境光成分。
LIGHT0:白色光
LIGHT1:红色
LIGHT2:蓝色
LIGHT3:绿色
LIGHT4:LIGHT5:LIGHT6:LIGHT7黄色
3.3.1 设置环境光
对于GL_LIGHT0,我们可以为其指定环境光成分。 调用
glLightfv(GL_LIGHT0,GL_AMBIENT,@ambientLight);
方法不懂见函数详解
3.3.2 设置漫射光成分
通过对漫射光成分的设置,我们可以产生一个点光源。方法和设置环境光成分相似,只需调用
glLightfv(GL_LIGHT0,GL_DIFFUSE,@DiffuseLight);
即可。其中DiffuseLight是漫射光的颜色成分。一般情况下也为(1,1,1,1)。
3.3.3 设置镜面光成分
通过对镜面光成分的设置,我们可以产生一个平行光源。方法和设置漫射光成分相似,只需调用
glLightfv(GL_LIGHT0,GL_SPECULAR,@SpecularLight);
即可。其中SpecularLight是漫射光的颜色成分。可以根据不同需要指定不同的颜色。
3.4 设置光源的位置
对于点光源和平行光源,我们常常需要指定光源的位置来产生需要的效果。方法仍然是调用glLightfv函数,仅仅是换换参数而已:
glLightfv(GL_LIGHT0,GL_POSITION,@LightPosition);
其中,LightPosition也是一个四维数组,四维数组的前3项依次为光源位置的X,Y,Z分量,第四个值很特殊,一般为1或-1。当LightPosition[4]=-1的时候,表示光源位于距离场景无限远的地方,无论前面设置的X,Y,Z是什么值。当LightPosition[4]=1时,光源的位置就是前三项所指定的位置。
3.5 创建聚光灯
聚光位置光源可以定义成聚光形式,即将光的形状限制在一个圆锥内。用来模拟现实世界中的聚光灯。聚光的具体使用步骤如下:
1 设置聚光源位置 GLfloat light_position[]={ 1.0 , 1.0 , 1.0 , 1.0 };
glLightfv( GL_LIGHT0 , LIGHT_POSITION , light_position );
2 设置聚光最大散布角 聚光最大散布角就是光锥的轴与中心线的夹角,也就是光锥顶角的一半。
聚光最大散布角可以选择在[ 0.0 , 90.0 ]之间的值,以及一个特定值:180.0。缺省时最大散布角为180.0,即顶角为360度,光源照向所有方向。即一个点光源。 glLightf( GL_LIGHT0,GL_SPOT_CUTOFF , 45.0 );
3 设置聚光方向 聚光方向决定光锥的轴,缺省值为( 0.0 , 0.0 , -1.0 ) , 即指向负Z轴。 GLfloat spot_direction[]={ -1.0 , -1.0 , 0.0 };
glLightfv( GL_LIGHT0 , GL_SPOT_DIRECTION , spot_direction );
4 设置聚光指数 聚光指数控制光的集中程度,光锥中心的光强最大,越靠边的光强越小。
缺省时为0,即均匀照射。 glLightf( GL_LIGHT0 , GL_SPOT_EXPONENT , 2.0 );
4. 材质
4.1 材质颜色
OpenGL用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。象光源一样,材料颜色也分成环境、漫反射和镜面反射成分,它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。在进行光照计算时,材料对环境光的反射率与每个进入光源的环境光结合,对漫反射光的反射率与每个进入光源的漫反射光结合,对镜面光的反射率与每个进入光源的镜面反射光结合。对环境光与漫反射光的反射程度决定了材料的颜色,并且它们很相似。对镜面反射光的反射率通常是白色或灰色(即对镜面反射光中红、绿、蓝的反射率相同)。镜面反射高光最亮的地方将变成具有光源镜面光强度的颜色。例如一个光亮的红色塑料球,球的大部分表现为红色,光亮的高光将是白色的。
4.2 材质定义
材质的定义与光源的定义类似。其函数为:
void glMaterial{if}[v](GLenum face,GLenum pname,TYPE param);
定义光照计算中用到的当前材质。face可以是GL_FRONT、GL_BACK、GL_FRONT_AND_BACK,它表明当前材质应该应用到物体的哪一个面上;pname说明一个特定的材质;
param是材质的具体数值,若函数为向量形式,则param是一组值的指针,反之为参数值本身。非向量形式仅用于设置GL_SHINESS。
pname参数值具体内容见下表。另外,参数GL_AMBIENT_AND_DIFFUSE表示可以用相同的RGB值设置环境光颜色和漫反射光颜色。
___________________________________________________________________
参数名 缺省值 说 明___________________________________________________________________
GL_AMBIENT (0.2,0.2,0.2,1.0) 材料的环境光颜色
GL_DIFFUSE (0.8,0.8,0.8,1.0) 材料的漫反射光颜色
GL_AMBIENT_AND_DIFFUSE 材料的环境光和漫反射光颜色
GL_SPECULAR (0.0,0.0,0.0,1.0) 材料的镜面反射光颜色
GL_SHINESS 0.0 镜面指数(光亮度)
GL_EMISSION (0.0,0.0,0.0,1.0) 材料的辐射光颜色
GL_COLOR_INDEXES (0,1,1) 材料的环境光、漫反射光和镜面光颜色__________________________________________________________________
4.3 材质RGB值和光源RGB值的关系
材质的颜色与光源的颜色有些不同。对于光源,R、G、B值等于R、G、B对其最大强度的百分比。若光源颜色的R、G、B值都是1.0,则是最强的白光;若值变为0.5,颜色仍为白色,但强度为原来的一半,于是表现为灰色;若R=G=1.0,B=0.0,则光源为黄色。对于材质,R、G、B值为材质对光的R、G、B成分的反射率。比如,一种材质的R=1.0,G=0.5,B=0.0,则材质反射全部的红色成分,一半的绿色成分,不反射蓝色成分。也就是说,若OpenGL的光源颜色为(LR,LG,LB),材质颜色为(MR,MG,MB),那么,在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为(LR*MR,LG*MG,LB*MB)。同样,如果有两束光,相应的值分别为(R1,G1,B1)和(R2,G2,B2),则OpenGL将各个颜色成分相加,得到(R1+R2,G1+G2,B1+B2),若任一成分的和值大于1(超出了设备所能显示的亮度)则约简到1.0。
光源
1.开启与关闭
gl.glEnable(GL10.LIGHTING)
gl.glDisable(GL10.LIGHTING)
2.数量设定
gl.glEnalbe(GL10.LIGHTING0~7)//最多开8盏灯
3.定向光
定向光:光源在无穷远处,光的方向相同且平行,
gl.glLightf(int light,int pname,float[]params,int offset)
参数:
Light:灯的选定,GL_LIGHT0~7
pname:光源属性,定向光是GL_POSITION,
params:(x,y,z,w)共4个值,前3个表示定向光的方向向量,指向原点,w=0表示定向光,w = 1表示定位光
Offset:params数组中第一个相对第0个的偏移量
4.定位光
定位光:指定光源位置的光
函数同上glLightf(),不过params中(x,y,z,w)表示的是光源的坐标,w = 1;
5.光源颜色
1)环境光GL_AMBIENT经过多次反射而来的光称为环境光,无法确定其最初的方向,但当特定的光源关闭后,它们将消失.
2)散射光GL_DIFFUSE来自同一方向,照射到物体表面后,将沿各个方向均匀反射,因此,无论从哪个方向观察,表面的亮度都相同.
3)反射光GL_SPECULAR来自特定方向,也被反射到特定方向.镜面反射度与之相关.
4)自发光GL_EMMISION
6.设定光的颜色
gl.glLightf(int light,int pname,float[]params,int offset)
pname指定光源颜色,p
arams[]=(R,G,B,A)指定灯光颜色的rgba
材料反光属性-材质和法向量
1.法向量
为每个顶点设定法向量决定了光照是的反射情况,如果没有为顶点设定法向量,则光照系统不能正常工作。
法向量就是三维物体表面的法线方向,用向量表示
2.面法向量
即每个面的法向量,根据顶点的数量来确定共多少个面法向量,如正方体共6各面,每个面4个顶点,所以共4*6 = 24个面法向量。
3.点平均法向量
由该顶点相邻的几个面的法向量合成,如下图
光源设定和材质设定相同的环境光和散射光和反射光是的表现
1)光源设定
什么颜色的光照射到白色的材质上则显示什么颜色
环境光为红色,其他为白色 散射光为红色,其他为白色 反射光为红色,其他为白色
2)材质设定
白光射来,什么颜色的材质显示什么颜色
环境光为红色,其他为白色 散射光为红色,其他为白色 反射光为红色,其他为白色
绘制注意事项集合
1.视角
视角变大,视野变大。系统应该调整视口中的图像,即缩放。
如果水平即x轴的视角变大,则说明视野更加开阔,可见的东西应该要更多,所以视图会进行调整,即当前视口中图像按比例缩小,这样就可以表现出视野开阔了的效果。a增大x轴上会进行缩放,b增大会在y轴上进行缩放,near,far会在z轴上缩放。
2.绘制方式
1.如果使用索引法绘制图形,建议使用索引数量来确定共多少个顶点,带入glDrawElements();
2.指定顶点的时候如果使用的是int,则需要指定缩放指数10000,glVertexPointer()对应type是GL_FIXED;如果使用时float,则type是GL_FLOAT,而且不需要指定type
3.调用glDrawElements()之前
·必须在调用glDrawElements()之前先消除缓存,否则会图像会乱
4.绘制绕轴运动的物体
如果想要绘制绕y轴自动旋转的球体,使得坐标系绕y轴转动即可,调用gl.glRotatef()。
5.绘制正面方向选取(背面剪裁)
调用glFrontFace(mode)既可以选择正面的方向,默认是逆时针==GL_CCW,顺时针方向是GL_CW,
6.索引法绘制图形时索引的顺序
如果正面是逆时针则索引数组按照逆时针构成三角形,如果顺时针则按顺时针构成三角形。如下:0
逆时针:(0,1,2)构成三角形V0V1V2,(2,1,3)构成三角形V2V1V3
顺时针则构不成三角形
绘制哪一个面,那么就要将该面当成正面那样绘制,否则会出错
四)纹理映射
1.须知
纹理坐标系
坐标范围[0~1],如下三角形的st纹理坐标是(0,1,0,.5,0,1,0)
2D:ST坐标
3D:STR坐标
2.绘制步骤
注:使用顶点法绘制映射?