iOS OpenGL ES2.0教程    Lesson03  旋转

 

注:可供翻译的课程只有前两课。从这节课起,我试着根据我对OpenGL ES的理解写接下去的课程,希望能和大家一起学习。

 

 

在上节课中,我们绘制了一个三角形。在这一课里,我们学习如何旋转。我们创建一个正方形,并让它沿Y轴旋转。

 

 

 

绘制一个长方形

 

首先,我们要在屏幕上绘制一个长方形,如下图所示: 

 

 

要注意的是,在用OpenGL ES绘制图形的时候,是不能直接绘制矩形或其他多边形的,只能绘制三角形。所以,我们这里需要将通过绘制两个三角形来得到一个长方形。在图中分别绘制由顶点(0, 1, 2)和顶点(2, 3, 0)构成的三角形。

 

如果我们用上节课所使用的方法,每个三角形需要指定3个顶点的信息,那么两个三角形则需要指定6个顶点。这6个顶点中有重复的数据,实际上只需要指定4个不同的顶点就可以了。我们可以用新的方法来实现这个功能。在文件Lesson03.mm中:

 

 

 
    
  1. //--------------------------------------------------- 
  2. //create a rectangle 
  3. std::vector<float> geometryData; 
  4.  
  5. //vertex 0, left/buttom 
  6. geometryData.push_back(-0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0); 
  7.  
  8. // 1, right/buttom 
  9. geometryData.push_back(0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);   
  10.  
  11. // 2, right/ups 
  12. geometryData.push_back(0.5); geometryData.push_back(0.5); geometryData.push_back(0.0); geometryData.push_back(1.0); 
  13.  
  14. // 3, left/up 
  15. geometryData.push_back(-0.5); geometryData.push_back(0.5); geometryData.push_back(0.0); geometryData.push_back(1.0); 
  16.  
  17. //generate an ID for our geometry buffer in the video memory and make it the active one 
  18. glGenBuffers(1, &m_geometryBuffer); 
  19. glBindBuffer(GL_ARRAY_BUFFER, m_geometryBuffer); 
  20.  
  21. //send the data to the video memory 
  22. glBufferData(GL_ARRAY_BUFFER, geometryData.size() * sizeof(float), &geometryData[0], GL_STATIC_DRAW); 

 

 

指定4个顶点的数据,顶点按照添加的顺序,其索引值分别为0, 1, 2, 3。然后将顶点数据传送给OpenGL,和上一课用的方法一样。屏幕显示范围是从(-1, 1),中心点位置是(0, 0),所以 (0.5,0.5)指的是右上1/4屏幕的中间,其他点以此类推。

 

 

 

 
    
  1. //create a color buffer, to make our triangle look pretty 
  2. std::vector<float> colorData; 
  3.  
  4. //3 floats define one color value (red, green and blue) with 0 no intensity and 1 full intensity 
  5. //each color triplet is assigned to the vertex at the same position in the buffer, so first color -> first vertex 
  6.  
  7. //vertex 0 is red 
  8. colorData.push_back(1.0); colorData.push_back(0.0); colorData.push_back(0.0);     
  9. // 1 is blue 
  10. colorData.push_back(0.0); colorData.push_back(0.0); colorData.push_back(1.0);     
  11. // 2 is green 
  12. colorData.push_back(0.0); colorData.push_back(1.0); colorData.push_back(0.0); 
  13. // 3 is blue 
  14. colorData.push_back(0.0); colorData.push_back(0.0); colorData.push_back(1.0); 
  15.  
  16. //generate an ID for the color buffer in the video memory and make it the active one 
  17. glGenBuffers(1, &m_colorBuffer); 
  18. glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer); 
  19. //send the data to the video memory 
  20. glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(float), &colorData[0], GL_STATIC_DRAW); 

 

指定4个顶点的颜色,并将数据传递到VBO中。

 

 

 

 

 
    
  1. //create vertex indices 
  2. std::vector indexData; 
  3.  
  4. //3 byte define 1 triangle 
  5. indexData.push_back(0); indexData.push_back(1); indexData.push_back(2); 
  6. indexData.push_back(2); indexData.push_back(3); indexData.push_back(0);     
  7.  
  8. m_indexNumber = indexData.size(); 
  9.  
  10. //generate an ID for the index buffer in video memory 
  11. glGenBuffers(1, &m_indexBuffer); 
  12. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer); 
  13. //send data to video memory 
  14. glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexData.size() * sizeof(GLubyte), &indexData[0], GL_STATIC_DRAW); 

 

要注意这里新添加的内容。新创建一个数组indexData, 保存的是组成每个三角形的顶点的索引。比如第一个三角形由顶点0,1,2组成,第二个三角形由

顶点2,3,1组成,注意顶点的指定顺序要按照逆时针次序。定义了一个变量m_indexNumber,用来保存绘制图形一共需要使用的元素的数目。

然后在图像内存中生产一个缓冲区,绑定这个缓冲区。这里用的是GL_ELEMENT_ARRAY_BUFFER,表示之后用glDrawELements绘制图元的时候,会到这个缓冲区来

获得顶点的索引值。

 

 

 

 
    
  1. //initiate the drawing process, we want a triangle, start at index 0 and draw 3 vertices 
  2.   glDrawArrays(GL_TRIANGLES, 0, 3); 
  3.  
  4. //initiate the drawing process 
  5. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer); 
  6. glDrawElements(GL_TRIANGLES, m_indexNumber, GL_UNSIGNED_BYTE, 0); 

 

draw()方法中,我们不再使用前一课用的glDrawArray()方法,而是调用glDrawElement()方法,从索引数据来绘制图元。

函数需要四个参数:第一个参数是要绘制的图元,我们这里画的是三角形,选择GL_TRIANGLES。第二个参数是要绘制的元素的数目,即是索引数组的元素数目,

这里我们用m_indexNumber。第三个参数是索引数组中元素的类型。最后一个参数是指向索引数组的指针。因为我们之前已经把索引数组传送到OpenGL的

GL_ELEMENT_ARRAY_BUFFER缓冲区里去了,所以这里我们直接用0。

 

编译运行程序,长方形就显示出来了。

 

 

 

 

旋转

 

下面,我们要让这个长方形沿X轴正方向移动0.5,然后沿Y轴旋转。

 

首先,看shader.vert文件:

 

 

 
    
  1. // modelview 
  2. uniform mat4 modelview; 
  3.  
  4.  
  5. //the shader entry point is the main method 
  6. void main() 
  7. {     
  8.     colorVarying = color; //save the color for the fragment shader 
  9.  
  10.     gl_Position = modelview * position; //copy the position     

 

我们增加了一个变量modelview,是一个4*4的矩阵。限定符uniform表示这个变量是从应用程序传递给着色器的,并且在绘制图元时保持为常量。

现在gl_Position的值是变换矩阵modelview乘给定的顶点位置。

 

注:关于这部分使用到的向量和矩阵库,来源于raywenderrlich写的OpenGL ES2.0 iphone开发指引,最初来源于Cocos3d框架。( http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial)

 

 

在Lesson03.h中,增加一个变量m_modelview,保存着色器程序中uniform变量modelview的位置。

 

 
    
  1. //modelview uniform in the shader 
  2. int m_modelview; 

 

 

 

 
    
  1. Lesson03.mm: 
  2.     //get the p_w_upload points for modelview 
  3.     m_modelview = glGetUniformLocation(m_shader->getProgram(), "modelview"); 
  4.     //check 
  5.     if (m_modelview < 0) { 
  6.         NSLog(@"Could not query uniform location"); 
  7.     } 

 

调用glGetUniformLoaction函数,将着色器程序和变量的名字传给函数,函数会返回该变量的位置。这个值我们保存起来,之后要用到。

 

 

再来看draw()函数,增加了如下代码:

 

 

 
    
  1. //modelview  
  2.  
  3. CC3GLMatrix *mvMatrix = [CC3GLMatrix matrix]; 
  4. [mvMatrix populateFromTranslation:CC3VectorMake(0.2, 0 , 0)]; 
  5.  
  6. m_currentRotation += 1.0; 
  7. [mvMatrix rotateBy:CC3VectorMake(0, m_currentRotation, 0)]; 
  8.  
  9. glUniformMatrix4fv(m_modelview, 1, 0, mvMatrix.glMatrix); 

 

首先,创建一个CC3GLMatrix的实例对象。关于CC3GLMatrix类,我也不是太熟悉,现在我们只要知道它有一个property成员 glMatrix,glMatrix表示的是一个4*4的浮点数矩阵数组。

然后,populateFromTranslation函数给glMatrix矩阵提供位置移动的数据,参数为指定的向量vector(x, y, z),表示在x, y, z轴上移动的距离。这样得到的矩阵glMatrix就可以通过矩阵乘法移动指定顶点的位置了。在这里,表示向x轴正方向移动0.5,其他两个方向不动。

rotateBy函数提供在x, y, z轴上的旋转数据。我们设定的值是,只沿y轴旋转,旋转角度为m_currentRotation,每次调用draw()函数,旋转角度增加1度。

    最后glUniformMatrix4fv函数用我们得到的变换矩阵来更新着色器程序里的uniform变量矩阵modelview。第一个参数是要修改的uniform变量的索引位置,第二个参数是要修改的矩阵数据,这里是1个。第三个参数是第四个参数是按行主序还是列主序指定的,这里我们用GL_FALSE(0),表明是列主序指定的。最后一个参数是我们前面设定的变换矩阵。

 

 

现在,编译并运行程序,我们就可以在屏幕上看到程序运行的结果了。长方形沿x轴正方向移动了0.2,并且沿Y轴旋转。附件是教程的工程文件。

 

还有一个问题,旋转角度在180-360度之间时,看不到我们的长方形了。那是因为在那个范围里时,按照我们顶点的指定顺序,这两个三角形的顶点都是按顺时针方向指定的,OpenGL认为这个面是背向我们的,就没有让它显示了。下一课,我们把它改造成3D的。