从零开始学OpenGLES开发——第四章

第四章 矩阵变换

这一章主要讲变换,所以能物体的外观什么的就不在乎了。前面三章的演示程序里面,视角视线都是固定死的。

既然视线和视角都要运动,那么就需要一个变量因子随时间变化而变化,然后动态修改每一次渲染的时候我们的视角和视线。
为了容易观察,我们需要设置一个参考系(默认屏幕背景全黑,根本感觉不到哪是天,哪是地)。在3D世界里唯一的参考系就是坐标是死的,其他的任何模型都是人加进去的,所以不准确。


所以我们在画任何物体之前,先把 x y z的坐标线画出来,分别用不同的颜色表示,红,绿,蓝。
这样如果是我自己视角变化,还是世界旋转,就能够区分得出来了。


基于第三章的代码来改吧,第三章的代码最后画的是一个球,这个球有点大,基本上和屏幕等宽了,需要改小一点,我们把半径改成50.然后 分块个数也降低到60和30(这样可以提高加载速度),但是球看上去凹凸不平的,没关系了,咱研究的是变换嘛。


并且为了没有视角盲点,我们也把灯光关掉,使用无光模式渲染,可以看到任何物体。(注释掉灯光那三个函数即可)
然后直接运行的截图如下:(白色了,可以看到材质信息无效了!)


这一章的演示不需要法线和材质,把相关代码全部删掉(最后我们贴完整代码)





画出轴线,并且去掉法线和材质的代码如下:

public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
        initOpenGLVertexs() ; 
    }  
      
    private FloatBuffer vertexBuffer   = null ;  
    private int         vertexCount    = 0 ;  
    
    private FloatBuffer coodsBuffer   = null ;  
    
    private void initOpenGLVertexs(){  
  
  
        int slices  = 60 ;  
        int stacks  = 30 ;  
          
        float radius  = 50f  ;  
          
        double perAngleW = 2 * Math.PI / slices;  
        double perAngleH = Math.PI / stacks;  
          
        float[] vertexArray  = new float[slices*stacks*2*3*3];  


        int     vertexIndex  = 0 ;  


        for (int a = 0; a < stacks; a++) {  
            for (int b = 0; b < slices; b++) {  
                  
                float x1 = (float) (radius * Math.sin(a * perAngleW) * Math.cos(b* perAngleH));  
                float z1 = (float) (radius * Math.sin(a * perAngleW) * Math.sin(b* perAngleH));  
                float y1 = (float) (radius * Math.cos(a * perAngleW));  
  
  
                float x2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos(b * perAngleH));  
                float z2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin(b * perAngleH));  
                float y2 = (float) (radius * Math.cos((a + 1) * perAngleW));  
  
  
                float x3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos((b + 1) * perAngleH));  
                float z3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin((b + 1) * perAngleH));  
                float y3 = (float) (radius * Math.cos((a + 1) * perAngleW));  
  
  
                float x4 = (float) (radius * Math.sin(a * perAngleW) * Math.cos((b + 1) * perAngleH));  
                float z4 = (float) (radius * Math.sin(a * perAngleW) * Math.sin((b + 1) * perAngleH));  
                float y4 = (float) (radius * Math.cos(a * perAngleW));  
  
  
                vertexArray[vertexIndex++] = (x1);  
                vertexArray[vertexIndex++] = (y1);  
                vertexArray[vertexIndex++] = (z1);  
  
  
                vertexArray[vertexIndex++] = (x2);  
                vertexArray[vertexIndex++] = (y2);  
                vertexArray[vertexIndex++] = (z2);  
  
  
                vertexArray[vertexIndex++] = (x3);  
                vertexArray[vertexIndex++] = (y3);  
                vertexArray[vertexIndex++] = (z3);  
  
  
                vertexArray[vertexIndex++] = (x3);  
                vertexArray[vertexIndex++] = (y3);  
                vertexArray[vertexIndex++] = (z3);  
  
  
                vertexArray[vertexIndex++] = (x4);  
                vertexArray[vertexIndex++] = (y4);  
                vertexArray[vertexIndex++] = (z4);  
  
  
                vertexArray[vertexIndex++] = (x1);  
                vertexArray[vertexIndex++] = (y1);  
                vertexArray[vertexIndex++] = (z1);    
            }  
        }  
          
        vertexCount  = vertexArray.length / 3;  
          
        vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();  
        vertexBuffer.put(vertexArray);  
        vertexBuffer.position(0);  
          


        float[] coodsArray = new float[]{
        		-1000, 0, 0, 
        		 1000, 0, 0,  /*两个点,x轴-1000到1000连成直线*/
        		
        		 0,-1000, 0, 
        		 0, 1000, 0,  /*两个点,y轴-1000到1000连成直线*/
        		
        		 0, 0,-1000, 
        		 0, 0, 1000,  /*两个点,z轴-1000到1000连成直线*/
        };
        
        coodsBuffer  = ByteBuffer.allocateDirect(coodsArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();  
        coodsBuffer.put(coodsArray);
        coodsBuffer.position(0); 
    } 
    
    public void onSurfaceChanged(GL10 gl, int width, int height) {  
    	  
    	  
        GLES10.glViewport(0, 0, width, height); // 设置视口宽度高度。  
        GLES10.glEnable(GLES10.GL_DEPTH_TEST); // 开启深度测试  
        // {修改投影矩阵  
        GLES10.glMatrixMode(GLES10.GL_PROJECTION); // 修改投影矩阵  
        GLES10.glLoadIdentity(); // 复位,将投影矩阵归零  
        GLU.gluPerspective(gl, 60.0f, ((float) width) / height, 0.1f, 400f);   
        // }  
              
        //{  
        GLES10.glMatrixMode(GLES10.GL_MODELVIEW);  
        GLES10.glLoadIdentity();//归零模型视图  
        GLU.gluLookAt(gl, 0, 0, 300, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);  
        //}  
       
    }  




	public void onDrawFrame(GL10 gl) {
		GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空场景为黑色。  
	    GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相关缓存。  
	  
	    //No.1 画出坐标轴的线
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); 
	    GLES10.glDisableClientState(GLES10.GL_NORMAL_ARRAY); //由于我们画轴的时候没有法线数据输入,所以禁用法线数组功能。
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, coodsBuffer);  
	    
	    //1,画X轴
	    GLES10.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//使用红色,OpenGL中颜色以浮点数表示,1.0表示红色满色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 0, 2); //连接两个顶点
	    
	    //2,画Y轴
	    GLES10.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 2, 2); //连接两个顶点
	    
	    //3,画Z轴
	    GLES10.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 4, 2); //连接两个顶点
	    
	    
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);  
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer);  
	      


	    //No.2 画球,给球画上颜色,黄色
	    GLES10.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);//使用黄色
	    GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, vertexCount);  
	      
	    GLES10.glFlush();
	}




运行截图如下:



可以看到只有横竖两条线,那是因为我们位置刚好在Z轴上,所以看不到Z的线,


我们修改一下静止的视角(行对后面视角变换来说),从侧面看,


将 gluLookAt的参数改一改,改一下眼睛位置,


GLU.gluLookAt(gl, 100, 100, 300, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);  


然后再运行,效果如图:




可以看到,Z轴里面部分看不到了,那是因为我们最远距离设置的不够长,改大一点就好了。 改成1000吧


GLU.gluPerspective(gl, 60.0f, ((float) width) / height, 0.1f, 1000f); 


这里就不发截图了哈。




首先进行最简单的变换,整个世界,围绕原点,水平旋转。眼睛位置不变。(是世界发生变换,那么眼睛看到的就是轴线发生旋转噢,水平旋转的话,就是绿色的线不变,其他的线围绕绿色的线旋转)先明确了我们想要实现的效果。


先加一个变量因子,加一个成员变量 float  angle 。注意OpenGL里面表示度数是用的弧度,2PI为一周


GLU.gluPerspective这个不是OpenGL标准库,它用了度数,它里面才调用了OpenGL库,所以要注意(这个函数可以进去看它的源码的)




因为视角发生了变换,所以我们需要将gluLookAt的调用位置移动到 onDrawFrame里,因为我们每一次都要明确的确定模型旋转矩阵每一次渲染都是归零了,重新设置观察角度。、


gluLookAt的位置一定要在绘制最开始时,因为它是修改的模型旋转矩阵。它本质的工作原理修改OpenGL矩阵链中的模型旋转矩阵,一旦被设置了,后面绘制的物体都会应用这个矩阵,所以它一定要在最前面(可以思考下,如果设置在最后是什么效果)


我改了好几个地方,所以把完整代码贴出来,而重点是onDrawFrame里面


public void onSurfaceCreated(GL10 gl, EGLConfig config) {  
        initOpenGLVertexs() ; 
    }  
      
    private FloatBuffer vertexBuffer   = null ;  
    private int         vertexCount    = 0 ;  
    
    private FloatBuffer coodsBuffer   = null ;  
    
    private void initOpenGLVertexs(){  
  
  
        int slices  = 60 ;  
        int stacks  = 30 ;  
          
        float radius  = 50f  ;  
          
        double perAngleW = 2 * Math.PI / slices;  
        double perAngleH = Math.PI / stacks;  
          
        float[] vertexArray  = new float[slices*stacks*2*3*3];  


        int     vertexIndex  = 0 ;  


        for (int a = 0; a < stacks; a++) {  
            for (int b = 0; b < slices; b++) {  
                  
                float x1 = (float) (radius * Math.sin(a * perAngleW) * Math.cos(b* perAngleH));  
                float z1 = (float) (radius * Math.sin(a * perAngleW) * Math.sin(b* perAngleH));  
                float y1 = (float) (radius * Math.cos(a * perAngleW));  
  
  
                float x2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos(b * perAngleH));  
                float z2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin(b * perAngleH));  
                float y2 = (float) (radius * Math.cos((a + 1) * perAngleW));  
  
  
                float x3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos((b + 1) * perAngleH));  
                float z3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin((b + 1) * perAngleH));  
                float y3 = (float) (radius * Math.cos((a + 1) * perAngleW));  
  
  
                float x4 = (float) (radius * Math.sin(a * perAngleW) * Math.cos((b + 1) * perAngleH));  
                float z4 = (float) (radius * Math.sin(a * perAngleW) * Math.sin((b + 1) * perAngleH));  
                float y4 = (float) (radius * Math.cos(a * perAngleW));  
  
  
                vertexArray[vertexIndex++] = (x1);  
                vertexArray[vertexIndex++] = (y1);  
                vertexArray[vertexIndex++] = (z1);  
  
  
                vertexArray[vertexIndex++] = (x2);  
                vertexArray[vertexIndex++] = (y2);  
                vertexArray[vertexIndex++] = (z2);  
  
  
                vertexArray[vertexIndex++] = (x3);  
                vertexArray[vertexIndex++] = (y3);  
                vertexArray[vertexIndex++] = (z3);  
  
  
                vertexArray[vertexIndex++] = (x3);  
                vertexArray[vertexIndex++] = (y3);  
                vertexArray[vertexIndex++] = (z3);  
  
  
                vertexArray[vertexIndex++] = (x4);  
                vertexArray[vertexIndex++] = (y4);  
                vertexArray[vertexIndex++] = (z4);  
  
  
                vertexArray[vertexIndex++] = (x1);  
                vertexArray[vertexIndex++] = (y1);  
                vertexArray[vertexIndex++] = (z1);    
            }  
        }  
          
        vertexCount  = vertexArray.length / 3;  
          
        vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();  
        vertexBuffer.put(vertexArray);  
        vertexBuffer.position(0);  
          


        float[] coodsArray = new float[]{
        		-1000, 0, 0, 
        		 1000, 0, 0,  /*两个点,x轴-1000到1000连成直线*/
        		
        		 0,-1000, 0, 
        		 0, 1000, 0,  /*两个点,y轴-1000到1000连成直线*/
        		
        		 0, 0,-1000, 
        		 0, 0, 1000,  /*两个点,z轴-1000到1000连成直线*/
        };
        
        coodsBuffer  = ByteBuffer.allocateDirect(coodsArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();  
        coodsBuffer.put(coodsArray);
        coodsBuffer.position(0); 
    } 
    
    public void onSurfaceChanged(GL10 gl, int width, int height) {  
    	  
    	  
        GLES10.glViewport(0, 0, width, height); // 设置视口宽度高度。  
        GLES10.glEnable(GLES10.GL_DEPTH_TEST); // 开启深度测试  
        // {修改投影矩阵  
        GLES10.glMatrixMode(GLES10.GL_PROJECTION); // 修改投影矩阵  
        GLES10.glLoadIdentity(); // 复位,将投影矩阵归零  
        GLU.gluPerspective(gl, 60.0f, ((float) width) / height, 0.1f, 1000f);   
        // }  
    }  


    private float angle = 0.0f ;
    private float[] rotateMatrix = new float[16];
    
	public void onDrawFrame(GL10 gl) {
		GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空场景为黑色。  
	    GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相关缓存。  
	  
	    //{  
        GLES10.glMatrixMode(GLES10.GL_MODELVIEW);  
        GLES10.glLoadIdentity();//归零模型视图  
        GLU.gluLookAt(gl, 100, 100, 300, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);  
        //}
        
	    angle += 0.4f ; //随时间递增
	    
	    //创建一个矩阵,然后承载着旋转的参数,然后再加入到OpenGL矩阵链中,后面绘制的物体就会附加上这个矩阵的旋转效果了
	    Matrix.setIdentityM(rotateMatrix, 0);//矩阵归位,类似 glLoadIdentity()
	    Matrix.rotateM(rotateMatrix, 0, angle, 0, 1, 0); //以Y轴为准,旋转angle弧度,将这个变换设置到矩阵中,矩阵就代表了这个变换了。
	    GLES10.glMultMatrixf(rotateMatrix, 0);//将这个矩阵加入矩阵链中。
	    //Matrix这只是一个工具类,它提供的所有函数都不会直接修改OpenGL状态,只会产生中间值。
        
        
	    //No.1 画出坐标轴的线
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); 
	    GLES10.glDisableClientState(GLES10.GL_NORMAL_ARRAY); //由于我们画轴的时候没有法线数据输入,所以禁用法线数组功能。
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, coodsBuffer);  
	    
	    //1,画X轴
	    GLES10.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//使用红色,OpenGL中颜色以浮点数表示,1.0表示红色满色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 0, 2); //连接两个顶点
	    
	    //2,画Y轴
	    GLES10.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 2, 2); //连接两个顶点
	    
	    //3,画Z轴
	    GLES10.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 4, 2); //连接两个顶点
	    
	    
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);  
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer);  
	      


	    //No.2 画球,给球画上颜色,黄色
	    GLES10.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);//使用黄色
	    GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, vertexCount);  
	      
	    GLES10.glFlush();
	}


//解释我都写在注释里面了。。


可以看到旋转的效果了呀,是世界在原地打转


我们来修改一下顺序 gluLookAt的和glMultMatrixf的顺序 但是glLoadIdentity还是一定要在最前面。


修改后的OnDrawFrame代码如下,修改一下旋转速度,将+=0.4 改成 += 1.0 这样看上去明显点

public void onDrawFrame(GL10 gl) {
		GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空场景为黑色。  
	    GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相关缓存。  
	  
	    //{  
        GLES10.glMatrixMode(GLES10.GL_MODELVIEW);  
        GLES10.glLoadIdentity();//归零模型视图  
        //}
        
	    angle += 1.0f ; //随时间递增
	    
	    //创建一个矩阵,然后承载着旋转的参数,然后再加入到OpenGL矩阵链中,后面绘制的物体就会附加上这个矩阵的旋转效果了
	    Matrix.setIdentityM(rotateMatrix, 0);//矩阵归位,类似 glLoadIdentity()
	    Matrix.rotateM(rotateMatrix, 0, angle, 0, 1, 0); //以Y轴为准,旋转angle弧度,将这个变换设置到矩阵中,矩阵就代表了这个变换了。
	    GLES10.glMultMatrixf(rotateMatrix, 0);//将这个矩阵加入矩阵链中。
	    //Matrix这只是一个工具类,它提供的所有函数都不会直接修改OpenGL状态,只会产生中间值。
        
	    //gluLookAt的代码位置变到这里来了
	    GLU.gluLookAt(gl, 100, 100, 300, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);  
        
	    //No.1 画出坐标轴的线
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); 
	    GLES10.glDisableClientState(GLES10.GL_NORMAL_ARRAY); //由于我们画轴的时候没有法线数据输入,所以禁用法线数组功能。
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, coodsBuffer);  
	    
	    //1,画X轴
	    GLES10.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//使用红色,OpenGL中颜色以浮点数表示,1.0表示红色满色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 0, 2); //连接两个顶点
	    
	    //2,画Y轴
	    GLES10.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 2, 2); //连接两个顶点
	    
	    //3,画Z轴
	    GLES10.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);//使用绿色
	    GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 4, 2); //连接两个顶点
	    
	    
	    GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);  
	    GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer);  
	      


	    //No.2 画球,给球画上颜色,黄色
	    GLES10.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);//使用黄色
	    GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, vertexCount);  
	      
	    GLES10.glFlush();
	}


可以看到,这次不是世界旋转了,而是人的眼睛在原地打转了。。呵呵,这是为什么呢??


简单例子来说吧,YY一下。你在一个棋盘上,


你先向左转90度,再往前走2步。
你先向前走两步,再左转90度。


这两种变换的结果,最后能一样吗??是吧,明白了噻


在OpenGL中,这种差别的表现就是矩阵链中的矩阵顺序。转换顺序不一样,结果就不一样。


从我的肤浅实际经验来看,OpenGL中矩阵链其实是一个堆栈结构。先设置进去的,后应用变换,后设置的,先应用变换。(那是因为OpenGL的矩阵应用的是乘法,并且是左乘法,后面解释)


比如第一个现象,世界旋转,眼睛位置没动,那是因为我先确定了眼睛的位置,实际上先告诉了3D世界的一个变换过程(为了满足我的视线视角要求)。
而后面我附加了一个旋转变换进去,这个旋转变换在视线变换之后,那么旋转变换就先生效,实现变换后生效,换句话说就是世界先经历了旋转,后才适当平移,满足我的视线视角需要。


如果顺序反了,就会变成先满足我的视线需要的变换,(注意默认OpenGL的视角点是在(0,0,0))世界经过平移之后(模拟我的眼睛的位置的变化,是模拟的,不是真的),真实情况是球心已经不在原点了(这时候世界再旋转,那可不就是求围绕着眼睛转了嘛)




矩阵的左乘是指的


矩阵1代表旋转
矩阵2代表平移
矩阵3代表拉伸


那么一个物体要顺序的进行 旋转,平移,拉伸的顺序的话


运算过程是  新物体 = (矩阵3 x (矩阵2 x (矩阵1 x 原物体)))


就是矩阵作为左的乘数,右边是需要变换的对象,后边如果是一个矩阵,那乘法的结果是产生一个新的矩阵,并且这个矩阵同时拥有了两个变换,顺序是 右边来个在前,左边那个在后。



接下来演示一个复杂的变换。


我们现在要实现一个效果,中间的黄色的球就当是太阳,目前太阳还有点大,我们再把半径改小一半,然后加上其他元素。

然后我们向里面添加一个正方形,一个三角形。分别不同的颜色表示。

然后运动效果需要实现的是,坐标线完全固定,正方形和三角形都围绕中间的黄色球在水平平面上上旋转

同时正方形和三角形都有自转。

完整代码如下,改动比较多,直接贴上去运行吧,效果OK,经过本人验证。解释都在注释里。


public void onSurfaceCreated(GL10 gl, EGLConfig config) {    
        initOpenGLVertexs() ;   
    }    
    
	private FloatBuffer coodsBuffer   = null ;    
    private FloatBuffer vertexBuffer   = null ;    
     
    private FloatBuffer squareBuffer   = null ;  //正方形的顶点buffer
    private FloatBuffer triangleBuffer   = null ; //三角形顶点的buffer
    
    private int         vertexCount    = 0 ;    
    
    private void initOpenGLVertexs(){    
    
    
        int slices  = 60 ;    
        int stacks  = 30 ;    
            
        float radius  = 20f  ;    
            
        double perAngleW = 2 * Math.PI / slices;    
        double perAngleH = Math.PI / stacks;    
            
        float[] vertexArray  = new float[slices*stacks*2*3*3];    
  
  
        int     vertexIndex  = 0 ;    
  
  
        for (int a = 0; a < stacks; a++) {    
            for (int b = 0; b < slices; b++) {    
                    
                float x1 = (float) (radius * Math.sin(a * perAngleW) * Math.cos(b* perAngleH));    
                float z1 = (float) (radius * Math.sin(a * perAngleW) * Math.sin(b* perAngleH));    
                float y1 = (float) (radius * Math.cos(a * perAngleW));    
    
    
                float x2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos(b * perAngleH));    
                float z2 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin(b * perAngleH));    
                float y2 = (float) (radius * Math.cos((a + 1) * perAngleW));    
    
    
                float x3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.cos((b + 1) * perAngleH));    
                float z3 = (float) (radius * Math.sin((a + 1) * perAngleW) * Math.sin((b + 1) * perAngleH));    
                float y3 = (float) (radius * Math.cos((a + 1) * perAngleW));    
    
    
                float x4 = (float) (radius * Math.sin(a * perAngleW) * Math.cos((b + 1) * perAngleH));    
                float z4 = (float) (radius * Math.sin(a * perAngleW) * Math.sin((b + 1) * perAngleH));    
                float y4 = (float) (radius * Math.cos(a * perAngleW));    
    
    
                vertexArray[vertexIndex++] = (x1);    
                vertexArray[vertexIndex++] = (y1);    
                vertexArray[vertexIndex++] = (z1);    
    
    
                vertexArray[vertexIndex++] = (x2);    
                vertexArray[vertexIndex++] = (y2);    
                vertexArray[vertexIndex++] = (z2);    
    
    
                vertexArray[vertexIndex++] = (x3);    
                vertexArray[vertexIndex++] = (y3);    
                vertexArray[vertexIndex++] = (z3);    
    
    
                vertexArray[vertexIndex++] = (x3);    
                vertexArray[vertexIndex++] = (y3);    
                vertexArray[vertexIndex++] = (z3);    
    
    
                vertexArray[vertexIndex++] = (x4);    
                vertexArray[vertexIndex++] = (y4);    
                vertexArray[vertexIndex++] = (z4);    
    
    
                vertexArray[vertexIndex++] = (x1);    
                vertexArray[vertexIndex++] = (y1);    
                vertexArray[vertexIndex++] = (z1);      
            }    
        }    
            
        vertexCount  = vertexArray.length / 3;    
            
        vertexBuffer = ByteBuffer.allocateDirect(vertexArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();    
        vertexBuffer.put(vertexArray);    
        vertexBuffer.position(0);    
            
  
  
        float[] coodsArray = new float[]{  
                -1000, 0, 0,   
                 1000, 0, 0,  /*两个点,x轴-1000到1000连成直线*/  
                  
                 0,-1000, 0,   
                 0, 1000, 0,  /*两个点,y轴-1000到1000连成直线*/  
                  
                 0, 0,-1000,   
                 0, 0, 1000,  /*两个点,z轴-1000到1000连成直线*/  
        };  
          
        
        coodsBuffer  = ByteBuffer.allocateDirect(coodsArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();    
        coodsBuffer.put(coodsArray);  
        coodsBuffer.position(0);   
        
        
        //构造正方形的顶点
        float[]squareArray = new float[]{
        		/*第一个三角形*/
        		-10, 10, 0, /*注意,这里我正方形z轴全为0,也就是正方形位于x-y的平面上*/
        		 10,-10, 0,
        		 10, 10, 0,
        		 
        		/*第二个三角形*/
        		-10, 10, 0,
        		-10,-10, 0,
        		 10,-10, 0,
        };
        
        squareBuffer  = ByteBuffer.allocateDirect(squareArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();    
        squareBuffer.put(squareArray);  
        squareBuffer.position(0);  
        

        //构造三角形的顶点
        float[]triangleArray = new float[]{
        		-15, 15, 0, /*注意,这里我正方形z轴全为0,也就是正方形位于x-y的平面上*/
        		 15,-15, 0,
        		 15, 15, 0,
        };
        triangleBuffer  = ByteBuffer.allocateDirect(triangleArray.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();    
        triangleBuffer.put(triangleArray);  
        triangleBuffer.position(0); 
    }   
      
    public void onSurfaceChanged(GL10 gl, int width, int height) {    
            
        GLES10.glViewport(0, 0, width, height); // 设置视口宽度高度。    
        GLES10.glEnable(GLES10.GL_DEPTH_TEST); // 开启深度测试    
        // {修改投影矩阵    
        GLES10.glMatrixMode(GLES10.GL_PROJECTION); // 修改投影矩阵    
        GLES10.glLoadIdentity(); // 复位,将投影矩阵归零    
        GLU.gluPerspective(gl, 60.0f, ((float) width) / height, 0.1f, 1500f);     
        // }    
                
    }    
  
    private float squareMaxAngle   = 0.0f ;  //正方形公转旋转弧度
    private float triangleMaxAngle = 0.0f ;  //三角形公转旋转弧度
    
    private float squareMinAngle   = 0.0f ;  //正方形自转旋转弧度
    private float triangleMinAngle = 0.0f ;  //三角形自转旋转弧度
    
    private float squareRadius	   = 100f ;  //正方形公转半径
    private float triangleRadius   = 150f ;  //三角形公转半径
    
    //设置几个运算临时矩阵,因为运算过程很多,不能在onDrawFrame里面new任何东西.
    //java GC 回收速度绝对没有  绘制的速度快的。
    private float[] tempMatrix1 = new float[16]; //运算临时矩阵1。
    private float[] tempMatrix2 = new float[16]; //运算临时矩阵2。
    private float[] tempMatrix3 = new float[16]; //运算临时矩阵3。
    private float[] tempMatrix4 = new float[16]; //运算临时矩阵3。
    
    public void onDrawFrame(GL10 gl) {  
    	
    	//改变旋转因子,并且速度不一样。
    	squareMaxAngle   += 0.2f ;
    	triangleMaxAngle += 0.3f ;
    	squareMinAngle   += 1.0f ;
    	triangleMinAngle += 1.5f ;
    	
        GLES10.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清空场景为黑色。    
        GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT | GLES10.GL_DEPTH_BUFFER_BIT);// 清空相关缓存。    
        
        //归零模型转换矩阵,则默认物体不进行任何平移旋转。
        GLES10.glMatrixMode(GLES10.GL_MODELVIEW);    
        GLES10.glLoadIdentity();//归零模型视图     

        //设置视角(必须在任何绘制之前,因为任何物体都必须满足视角的切换)
        GLU.gluLookAt(gl, 100, 100, 300, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);    
        
        //先画不动的物体,坐标线和中间的太阳
        GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY); //启用坐标数组输入
        
        {//坐标线
            GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, coodsBuffer);    
            //1,画X轴  
            GLES10.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);//使用红色,OpenGL中颜色以浮点数表示,1.0表示红色满色  
            GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 0, 2); //连接两个顶点  
              
            //2,画Y轴  
            GLES10.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);//使用绿色  
            GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 2, 2); //连接两个顶点  
              
            //3,画Z轴  
            GLES10.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);//使用蓝色  
            GLES10.glDrawArrays(GLES10.GL_LINE_STRIP, 4, 2); //连接两个顶点  
        }
        {//画太阳
        	GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, vertexBuffer); 
        	GLES10.glColor4f(1.0f, 1.0f, 0.0f, 1.0f);//使用黄色  
            GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, vertexCount);    
        }
        
        {//画正方形
        	//首先要进行的是将正方形和三角形平移到公转半径指定的位置上去
            //因为从正方形和三角形的初始坐标来看,它们中心在原点位置上。
            Matrix.setIdentityM(tempMatrix1, 0);
            Matrix.translateM(tempMatrix1, 0, squareRadius, 0, 0);//产生平移变换的矩阵,squareRadius设置给x或者y都可以,但是不能是z。
            //上面两句代码已经产生了平移矩阵了,接下来是公转旋转矩阵
            Matrix.setIdentityM(tempMatrix2, 0);
            Matrix.rotateM(tempMatrix2, 0, squareMaxAngle, 0, 1, 0);//公转以Y轴为中心
            //接下来是自传旋转矩阵
            Matrix.setIdentityM(tempMatrix3, 0);
            Matrix.rotateM(tempMatrix3, 0, squareMinAngle, 0, 0, 1);//自转以Z轴为中心
            
            //分析下我们需要的顺序,旋转和平移的顺序处理不好的话,就会结果不是我们期望的
            //从上面代码可以看到,后面公转和自传的代码调用的函数都是一样的,所以要倍加小心
            //对于公转的结果,公转必须是在平移之后。而自传必须是在平移之前。
            //因为你首先要把一个物体从中心(原点),拉出来,再旋转才会是公转。否则它就是在原地打转
            //反过来如果想要自转,就必须保证先在原地转了再拉出来,否则它就变成了公转。
            //所以顺序是 先自转,再平移,再公转
            
            Matrix.multiplyMM(tempMatrix4, 0, tempMatrix1, 0, tempMatrix3, 0);//右边的是最先的
            Matrix.multiplyMM(tempMatrix4, 0, tempMatrix2, 0, tempMatrix4, 0);//右边的是最先的
            
            //tempMatrix4就是最后的矩阵,同时拥有这些所有变换。
            GLES10.glPushMatrix();
            GLES10.glMultMatrixf(tempMatrix4, 0);
            GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, squareBuffer); 
        	GLES10.glColor4f(0.0f, 1.0f, 1.0f, 1.0f); 
            GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 6);  
            GLES10.glPopMatrix();
            
            /*
            glPushMatrix的含义是将当前系统OpenGL矩阵链,保存一个副本,临时存起来
         	然后后面调用glMultMatrixf的时候,会修改系统的矩阵,
         	然后绘制正方形的时候,应用的是新的修改之后的矩阵变换。但是我不希望这个矩阵变换对后面的其他物体起作用
         	所以到最后glPopMatrix,表示我恢复保存的那个副本,将我刚刚glMultMatrixf的操作还原。
            */
        }
        
        
        {//画三角形,这里就不写注释了,代码调用关系都和上面是一样的。
            Matrix.setIdentityM(tempMatrix1, 0);
            Matrix.translateM(tempMatrix1, 0, triangleRadius, 0, 0);

            Matrix.setIdentityM(tempMatrix2, 0);
            Matrix.rotateM(tempMatrix2, 0, triangleMaxAngle, 0, 1, 0);

            Matrix.setIdentityM(tempMatrix3, 0);
            Matrix.rotateM(tempMatrix3, 0, triangleMinAngle, 0, 0, 1);
            
            Matrix.multiplyMM(tempMatrix4, 0, tempMatrix1, 0, tempMatrix3, 0);
            Matrix.multiplyMM(tempMatrix4, 0, tempMatrix2, 0, tempMatrix4, 0);
            
            GLES10.glPushMatrix();
            GLES10.glMultMatrixf(tempMatrix4, 0);
            GLES10.glVertexPointer(3, GLES10.GL_FLOAT, 0, triangleBuffer); 
        	GLES10.glColor4f(1.0f, 0.0f, 1.0f, 1.0f); 
            GLES10.glDrawArrays(GLES10.GL_TRIANGLES, 0, 3);  
            GLES10.glPopMatrix();
        }
  
        GLES10.glFlush();  
    } 

可以看到旋转的过程很复杂。可以慢慢琢磨。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值