iOS OpenGL ES2.0教程 Lesson03 旋转
注:可供翻译的课程只有前两课。从这节课起,我试着根据我对OpenGL ES的理解写接下去的课程,希望能和大家一起学习。
在上节课中,我们绘制了一个三角形。在这一课里,我们学习如何旋转。我们创建一个正方形,并让它沿Y轴旋转。
绘制一个长方形
首先,我们要在屏幕上绘制一个长方形,如下图所示:
要注意的是,在用OpenGL ES绘制图形的时候,是不能直接绘制矩形或其他多边形的,只能绘制三角形。所以,我们这里需要将通过绘制两个三角形来得到一个长方形。在图中分别绘制由顶点(0, 1, 2)和顶点(2, 3, 0)构成的三角形。
如果我们用上节课所使用的方法,每个三角形需要指定3个顶点的信息,那么两个三角形则需要指定6个顶点。这6个顶点中有重复的数据,实际上只需要指定4个不同的顶点就可以了。我们可以用新的方法来实现这个功能。在文件Lesson03.mm中:
- //---------------------------------------------------
- //create a rectangle
- std::vector<float> geometryData;
- //vertex 0, left/buttom
- geometryData.push_back(-0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
- // 1, right/buttom
- geometryData.push_back(0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
- // 2, right/ups
- geometryData.push_back(0.5); geometryData.push_back(0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
- // 3, left/up
- geometryData.push_back(-0.5); geometryData.push_back(0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
- //generate an ID for our geometry buffer in the video memory and make it the active one
- glGenBuffers(1, &m_geometryBuffer);
- glBindBuffer(GL_ARRAY_BUFFER, m_geometryBuffer);
- //send the data to the video memory
- 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屏幕的中间,其他点以此类推。
- //create a color buffer, to make our triangle look pretty
- std::vector<float> colorData;
- //3 floats define one color value (red, green and blue) with 0 no intensity and 1 full intensity
- //each color triplet is assigned to the vertex at the same position in the buffer, so first color -> first vertex
- //vertex 0 is red
- colorData.push_back(1.0); colorData.push_back(0.0); colorData.push_back(0.0);
- // 1 is blue
- colorData.push_back(0.0); colorData.push_back(0.0); colorData.push_back(1.0);
- // 2 is green
- colorData.push_back(0.0); colorData.push_back(1.0); colorData.push_back(0.0);
- // 3 is blue
- colorData.push_back(0.0); colorData.push_back(0.0); colorData.push_back(1.0);
- //generate an ID for the color buffer in the video memory and make it the active one
- glGenBuffers(1, &m_colorBuffer);
- glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
- //send the data to the video memory
- glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(float), &colorData[0], GL_STATIC_DRAW);
指定4个顶点的颜色,并将数据传递到VBO中。
- //create vertex indices
- std::vector
indexData; - //3 byte define 1 triangle
- indexData.push_back(0); indexData.push_back(1); indexData.push_back(2);
- indexData.push_back(2); indexData.push_back(3); indexData.push_back(0);
- m_indexNumber = indexData.size();
- //generate an ID for the index buffer in video memory
- glGenBuffers(1, &m_indexBuffer);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
- //send data to video memory
- 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绘制图元的时候,会到这个缓冲区来
获得顶点的索引值。
- //initiate the drawing process, we want a triangle, start at index 0 and draw 3 vertices
- glDrawArrays(GL_TRIANGLES, 0, 3);
- //initiate the drawing process
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
- 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文件:
- // modelview
- uniform mat4 modelview;
- //the shader entry point is the main method
- void main()
- {
- colorVarying = color; //save the color for the fragment shader
- 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的位置。
- //modelview uniform in the shader
- int m_modelview;
- Lesson03.mm:
- //get the p_w_upload points for modelview
- m_modelview = glGetUniformLocation(m_shader->getProgram(), "modelview");
- //check
- if (m_modelview < 0) {
- NSLog(@"Could not query uniform location");
- }
调用glGetUniformLoaction函数,将着色器程序和变量的名字传给函数,函数会返回该变量的位置。这个值我们保存起来,之后要用到。
再来看draw()函数,增加了如下代码:
- //modelview
- CC3GLMatrix *mvMatrix = [CC3GLMatrix matrix];
- [mvMatrix populateFromTranslation:CC3VectorMake(0.2, 0 , 0)];
- m_currentRotation += 1.0;
- [mvMatrix rotateBy:CC3VectorMake(0, m_currentRotation, 0)];
- 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的。