正交投影:
在OPENGL ES中只支持正交投影与透视投影,正交投影是平行投影的一种,特点是和观察者的视线是平行的,不会产生真 实世界中远大近小的的透视效果。在此做个假设:
I与Z是一个分别具有二阶矩的 n维和m唯的随机向量。如果存在一个与I同纬的随机向量“X;”,如果满足下列三个条件则将“X;”称 为 是 I在Z上的正交投影。
(1)线性表示,“X;”=A+BZ;
(2)无偏性,E(“X;”)=E(I);
(3)I-&“X;”与Z正交,即E[(I-Icirc;)ZT]=0;
透视投影:
透视投影属于非平行投影,特点是观察这的实现在远处是相交的,当视线相交时表示灭点。因为通过透视投影可以产生现实 世界中近大远小的效果,所以使用透视投影可以得到一个更加真实的3D感受,所以现实游戏中一般采用透视投影效果。
两种投影的区别:
在正交投影中,图形沿平行线变换到投影面上。对透视投影来说,图形沿收敛于某一点的直线变换到投影面上,这个点被称为 投影中心,相当与观察点,也被称为视点。
正交投影与透视投影的区别在与透视投影的投影中心到投影面之间的距离是有限的,二正交投影的投影中心到投影面的之间的 距离是无限的。当投影中心在无限远时,投影线相互平行,所以定义正交投影时,给出投影线的方向就可以了,而定义透视 投影时,需要制定投影中心的具体位置。
正交投影保持物体的有关比例不变,这是三维绘图中产生比例图画的方法,物体的各个面的精确试图可以由平行投影得到。 另一方面,虽然透视投影不会保持相关比例,但是能够生成真实感的视图。对同样大小的物体来说,离投影面较远的物体 比离投影面较近的物体的投影图像要小,会产生远大近小的梦幻效果。
1.MyActiyity.java
package com.scout.eeeeeee; import android.app.Activity; import android.os.Bundle; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.LinearLayout; import android.widget.ToggleButton; public class MyActivity extends Activity { private MySurfaceView mSurfaceView; //声明MySurfaceView对象 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); //设置布局 mSurfaceView = new MySurfaceView(this); //创建 mSurfaceView.requestFocus(); //获取焦点 //MySurfaceView对象 mSurfaceView.setFocusableInTouchMode(true);//设置为可触控 LinearLayout ll = (LinearLayout) findViewById(R.id.main_liner); //获得布局引用 ll.addView(mSurfaceView); //在布局中添加MySurfaceView对象 //控制是否打开背面剪裁的ToggleButton ToggleButton tb = (ToggleButton) this.findViewById(R.id.ToggleButton01);//获得按钮引用 tb.setOnCheckedChangeListener(new MyListener()); //为按钮设置监听器 } class MyListener implements OnCheckedChangeListener { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // TODO Auto-generated method stub mSurfaceView.isPerspective = !mSurfaceView.isPerspective;//在正交投影与透视投影之间切换 mSurfaceView.requestRender();//重新绘制 } } @Override protected void onResume() { super.onResume(); mSurfaceView.onResume(); } @Override protected void onPause() { super.onPause(); mSurfaceView.onPause(); } }
2.MySufaceView.java
定义构造器,创建时设置渲染器和渲染模式。定义触摸回调实现屏幕触控功能,实现滑动翻转场景。定义渲染器内部类,实 现对图像的渲染。
package com.scout.eeeeeee; /** * Created by liuguodong on 2017/10/26. */ import android.content.Context; import android.opengl.GLSurfaceView; import android.view.MotionEvent; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; class MySurfaceView extends GLSurfaceView { private final float TOUCH_SCALE_FACTOR = 180.0f/320; //角度缩放比例 private SceneRenderer mRenderer; //场景渲染器 public boolean isPerspective=false; //投影标志位 private float mPreviousY; //上次的触控位置Y坐标 public float xAngle=0; //整体绕x轴旋转的角度 public MySurfaceView(Context context) { super(context); mRenderer = new SceneRenderer(); //创建场景渲染器 setRenderer(mRenderer); //设置渲染器 setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//主动渲染 } //触摸事件回调方法 @Override public boolean onTouchEvent(MotionEvent e) { float y = e.getY(); switch (e.getAction()) { //获取动作 case MotionEvent.ACTION_MOVE: //判断是否是滑动 float dy = y - mPreviousY; //计算触控笔Y位移 xAngle+= dy * TOUCH_SCALE_FACTOR; //设置沿x轴旋转角度 requestRender(); //重绘画面 } mPreviousY = y; //作为上一次触点的Y坐标 return true; } private class SceneRenderer implements GLSurfaceView.Renderer { touCH[] ha=new touCH[]{ //六边形数组 new touCH(0), new touCH(-2), new touCH(-4), new touCH(-6), new touCH(-8), new touCH(-10), new touCH(-12), }; public SceneRenderer(){} //渲染器构造类 @Override public void onDrawFrame(GL10 gl) { gl.glMatrixMode(GL10.GL_PROJECTION); //设置当前矩阵为投影矩阵 gl.glLoadIdentity(); //设置当前矩阵为单位矩阵 float ratio = (float) 320/480; //计算透视投影的比例 if(isPerspective){ gl.glFrustumf(-ratio, ratio, -1, 1, 1f, 10);//调用此方法计算产生透视投影矩阵 } else{ gl.glOrthof(-ratio, ratio, -1, 1, 1, 10);//调用此方法计算产生正交投影矩阵 } gl.glEnable(GL10.GL_CULL_FACE); //设置为打开背面剪裁 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, 0f, -1.4f); //沿z轴向远处推 gl.glRotatef(xAngle, 1, 0, 0); //绕x轴旋转制定角度 for(touCH th:ha){ th.drawSelf(gl); //循环绘制六边形数组中的每个六边形 } } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { gl.glViewport(0, 0, width, height); //设置视窗大小及位置 } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glDisable(GL10.GL_DITHER); //关闭抗抖动 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_FASTEST);//设置Hint模式 gl.glClearColor(0,255,255,0); //设置屏幕背景色黑色 gl.glEnable(GL10.GL_DEPTH_TEST); //启用深度测试 }}}
3.touCH.java
先声明顶点缓存,顶点颜色缓存、顶点索引缓存、顶点数、索引数等变量。在构造器中初始化六边形的顶点数据缓存,
颜色数据缓存和索引数据缓存。定义具体实现场景物体实现的绘制方法。
package com.scout.eeeeeee; /** * Created by liuguodong on 2017/10/26. */ import java.nio.ByteBuffer; //引入相关包 import java.nio.ByteOrder; //引入相关包 import java.nio.IntBuffer; //引入相关包 import javax.microedition.khronos.opengles.GL10; public class touCH { private IntBuffer mVertexBuffer; //顶点坐标数据缓冲 private IntBuffer mColorBuffer; //顶点着色数据缓冲 private ByteBuffer mIndexBuffer; //顶点构建索引数据缓冲 int vCount=0; //图形顶点数量 int iCount=0; //索引顶点数量 public touCH(int zOffset){ //顶点坐标数据的初始化 vCount=7; final int UNIT_SIZE=10000; int vertices[]=new int[]{ 0*UNIT_SIZE,0*UNIT_SIZE,zOffset*UNIT_SIZE, 2*UNIT_SIZE,3*UNIT_SIZE,zOffset*UNIT_SIZE, 4*UNIT_SIZE,0*UNIT_SIZE,zOffset*UNIT_SIZE, 2*UNIT_SIZE,-3*UNIT_SIZE,zOffset*UNIT_SIZE, -2*UNIT_SIZE,-3*UNIT_SIZE,zOffset*UNIT_SIZE, -4*UNIT_SIZE,0*UNIT_SIZE,zOffset*UNIT_SIZE, -2*UNIT_SIZE,3*UNIT_SIZE,zOffset*UNIT_SIZE }; //创建顶点坐标数据缓冲 ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder());//设置字节顺序 mVertexBuffer = vbb.asIntBuffer();//转换为int型缓冲 mVertexBuffer.put(vertices);//向缓冲区中放入顶点坐标数据 mVertexBuffer.position(0);//设置缓冲区起始位置 //顶点着色数据的初始化 final int one = 65535; int colors[]=new int[]//顶点颜色值数组,每个顶点4个色彩值RGBA { 0,0,one,0, 0,one,0,0, 0,one,one,0, one,0,0,0, one,0,one,0, one,one,0,0, one,one,one,0 }; //创建顶点着色数据缓冲 ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4); cbb.order(ByteOrder.nativeOrder());//设置字节顺序 mColorBuffer = cbb.asIntBuffer();//转换为int型缓冲 mColorBuffer.put(colors);//向缓冲区中放入顶点着色数据 mColorBuffer.position(0);//设置缓冲区起始位置 //三角形构造索引数据初始化 iCount=18; byte indices[]=new byte[]{ 0,2,1, 0,3,2, 0,4,3, 0,5,4, 0,6,5, 0,1,6 }; //创建三角形构造索引数据缓冲 mIndexBuffer = ByteBuffer.allocateDirect(indices.length); mIndexBuffer.put(indices);//向缓冲区中放入三角形构造索引数据 mIndexBuffer.position(0);//设置缓冲区起始位置 } public void drawSelf(GL10 gl){ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用顶点坐标数组 gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用顶点颜色数组 gl.glVertexPointer(//为画笔指定顶点坐标数据 3, //每个顶点的坐标数量为3 xyz GL10.GL_FIXED, //顶点坐标值的类型为 GL_FIXED 0, //连续顶点坐标数据之间的间隔 mVertexBuffer //顶点坐标数据 ); gl.glColorPointer(//为画笔指定顶点着色数据 4, //设置颜色的组成成分,必须为4—RGBA GL10.GL_FIXED, //顶点颜色值的类型为 GL_FIXED 0, //连续顶点着色数据之间的间隔 mColorBuffer //顶点着色数据 ); gl.glDrawElements(//索引法绘制图形 GL10.GL_TRIANGLES, //以三角形方式填充 iCount, //一共icount/3个三角形,iCount个顶点 GL10.GL_UNSIGNED_BYTE, //索引值的尺寸 mIndexBuffer //索引值数据 ); }}
4.布局文件
xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_liner" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ToggleButton android:textOff="正交投影" android:textOn="透视投影" android:checked="false" android:id="@+id/ToggleButton01" android:layout_width="fill_parent" android:layout_height="wrap_content"> ToggleButton> LinearLayout>
效果图: