下面我们终于可以使用opengl命令,绘制图形了.我们将使用GLKBaseEffect类绘制矩形,然后我们利用GLKMath库实现矩形的旋转.终于可以绘制东西了.
创建矩形的顶点数据
我们先画矩形,假设矩形的顶点如上图所示.OpenGlES实际上只能绘制三角形,拼接成为矩形.我们需要创建两个三角形(0, 1, 2)和(2, 3, 0)
在OpenGlEs2.0中你不用关心顶点坐标的方向问题.在HelloGLKitViewController.m文件中增加一个结构体.
|
创建顶点缓冲区实体
向OpenGL发送数据最好的方式是使用叫做顶点缓冲区的实体.我们可以这样理解在OpenGl中用一些实体为我们保存顶点数据的缓冲区.我们通过一些函数调用把数据传递到OpenGL那里.顶点缓冲区有两类数据:一类用来保存每个顶点的数据,一类用来保存每个三角形的顶点的下标.
在HelloGLKitViewController的category中增加两个成员变量:
GLuint _vertexBuffer; GLuint _indexBuffer; |
Then add a method above viewDidLoad to create these:
|
首先要做的时是指上下文, glGenBuffers用来创建缓冲区对象, glBindBuffer来告诉OpenGL我们的指代关系,当我说GL_ARRAY_BUFFER的时候,我指的是_vertexBuffer. glBufferData帮我们把数据传输到OpenGl内部.
再添加个方法:
|
And before we forget, add this to the bottom of viewDidLoad:
|
And this inside viewDidUnload, right after the call to [superviewDidUnload]:
|
介绍一下 GLKBaseEffect
在2.0 中渲染任何的形状都要创建两个叫做渲染器的程序. 渲染器使用的是类C语言GLSL.这个教程中我们不需要用到他们.渲染器有两种:
· Vertex shaders 点渲染器,对于场景中的每个点,点渲染器都会被调用,如果我们要画一个正方形,要4个顶点,那么点渲染器会被调用4次,点渲染器的工作时计算光照,顶点位置移动变换.点渲染器输出的结果是顶点的最终位置,同时将一些数据传递到段渲染器.
· Fragment shaders 段渲染器是对于场景中的每个像素都要调用.如果你要绘制一个简单正方形,那么正方形所覆盖到的每个像素上都会调用一次段渲染器.段渲染器可以用来计算光照,但是最主要的工作是用来设置像素的最终显示颜色.
GLKBaseEffect is a helper class that implements some commonshaders for you. The goal of GLKBaseEffect is to provide most of thefunctionality available in OpenGL ES 1.0, to make porting apps from OpenGL ES1.0 to OpenGL ES 2.0 easier.
GLKBaseEffect是一个帮助类,为我们实现了通用的一些渲染器,GLKBaseEffect的目标是提供1.0能够提供的功能,是应用可以平滑的从1.0过渡到2.0.
GLKBaseEffect类的使用流程:
1. Create a GLKBaseEffect. Usually you create one ofthese when you create your OpenGL context. You can (and should) re-use the sameGLKBaseEffect for different geometry, and just reset the properties. Behind thescenes, GLKBaseEffect will only propogate the properties that have changed toits shaders. 通常我们在上下文的同时创建GLKBaseEffect对象.我们
2. Set GLKBaseEffect properties. Here you can configurethe lighting, transform, and other properties that the GLKBaseEffect’s shaderswill use to render the geometry.设置GLKBaseEffect的属性
3. Call prepareToDraw on theGLKBaseEffect. Any time you change aproperty on the GLKBaseEffect, you need to call prepareToDraw prior to drawingto get the shaders set up properly. This also enables the GKBaseEffect’sshaders as the current shader program.只要修改了属性就要在绘制之前调用prepareToDraw函数.
4. Enable pre-defined attributes. Usually when you make your own shaders, they take parameterscalled attributes and you write code to get their IDs. For GLKBaseEffect’sbuilt in shaders, these are already predefined as constants such asGLKVertexAttribPosition or GLKVertexAttribColor. So you need to enable anyparameters that you want to pass in to the shaders, and give them pointers todata.使能属性,然后将属性数值传递过去.
5. Draw your geometry. Once you have everything set up, you can use normal OpenGL drawcommands such as glDrawArrays or glDrawElements, and it will be rendered usingthe effect you’ve set up!
The nice thing about GLKBaseEffect is if you use them, you don’tnecessarily have to write any shaders at all! Of course you’re still welcome toif you’d like – and you can mix and match and render some things withGLKBaseEffect, and some with your own shaders. If you look at the OpenGLtemplate project, you’ll see an example of exactly that!
In this tutorial, we’re going to focus on just usingGLKBaseEffect, since the entire point is to get you up-to-speed with the newGLKit functionality – plus it’s plain easier!
So let’s walk through the steps one-by-one in code.
1) Create a GLKBaseEffect创建BLKBaseEffect对象
The first step is to simply create a GLKBaseEffect. Up in yourprivate HelloGLKitViewController category, add a property for a GLKBaseEffect:增加一个成员.
|
And synthesize it after the @implementation below:
|
Then in setupGL, initialize it right after calling [EAGLContextsetCurrentContext:...]:
|
And set it to nil at the bottom of tearDownGL:
|
Now that we’ve created the effect, let’s use it in conjunctionwith our vertex and index bufferes to render the square. The first step is toset our effect’s projection matrix!
2) Set GLKBaseEffect properties 设置属性
投影矩阵决定了3维物体是怎样映射成为2维的.可以想象从你的眼睛发射出一束光线,当光线与哪个点相撞,那个点的颜色就是该像素投射到屏幕上面的颜色.
GLKit provides you with some handy functions to set up aprojection matrix. The one we’re going to use allows you to specify the fieldof view along the y-axis, the aspect ratio, and the near and far planes:我们可以通过几个元素确定投影矩阵:长宽比,近平面,远平面.
这个和摄像头的相投很像.
The field of view is similar to camera lenses. A small field ofview (for example 10) is like a telephoto lens – it magnifies images by“pulling” them closer to you. A large field of view (for example 100) is like awide angle lens – it makes everything seem farther away. A typical value to usefor this is around 65-75.
The aspect ratio is the aspect ratio you want to render to (i.e.the aspect ratio of the view). It uses this in combination with the field ofview (which is for the y-axis) to determine the field of view along the x-axis.
The near and far planes are the bounding boxes for the “viewable”volume in the scene. So if something is closer to the eye than the near planeor further away than the far plane, it won’t be rendered. This is a commonproblem to run into – you try and render something and it doesn’t show up. Onething to check is that it’s actually between the near and far planes.
Let’s try this out – add the following code to the bottom ofupdate:
|
In the first line, we get the aspect ratio of the GLKView.
In the second line, we use a built in helper function in the GLKitmath library to easily create a perspective matrix for us – all we have to dois pass in the parameters discussed above. We set the near plane to 4 unitsaway from the eye, and the far plane to 10 units away.
In the third line, we just set the projection matrix on the effect’stransform property!
We need to set one more property now – the modelViewMatrix. ThemodelViewMatrix is the transform that is applied to any geometry that theeffect renders.
下面开始设置模型试图矩阵,模型试图矩阵决定了该效果绘制的所有几何图形的空间移动属性.
The GLKit math library once again comes to the rescue here withsome really handy functions that make performing translations, rotations, andscales easy, even if you don’t know much about matrix math. To see what I mean,add the following lines to the bottom of update:
|
If you remember back to where we set up the vertices for thesquare, remember that the z-coordinate for each vertex was 0. If we tried torender it with this perspective matrix, it wouldn’t show up because it’s closerto the eye than the near plane!
So the first thing we need to do is to move this backwards. So inthe first line, we use the GLKMatrix4MakeTranslation function to create amatrix for us that translates 6 units backwards.
Next, we want to make the cube rotate for fun. So we increment aninstance variable that keeps track of the current rotation (which we’ll add ina second), and use the GLKMatrix4Rotate method to modify the currenttransformation by rotating it as well. It takes radians, so we use theGLKMathDegreesToRadians method to that conversion. Yes, this math library hasjust about every matrix and vector math routine you’ll need!
Finally, we set the model view matrix on the effect’s transformproperty.
Before we forget, add the rotation instance variable to yourHelloGLKitViewController’s private category:
|
We’ll play around with more GLKBaseEffect properties later, sincethere’s a lot of cool stuff and we’ve barely scratched the surface here. Butlet’s continue on for now, so we can finally get something rendering!
3) Call prepareToDraw on the GLKBaseEffect
This step is about as simple as it gets. Add the following line tothe bottom of glkView:drawInRect:
|
w00t! Just remember that you need to call this after any time youchange properties on a GLKBaseEffect, before you draw with it.
4) Enable pre-defined attributes
Next add this code to the bottom of glkView:drawInRect:
|
If you’ve programmed with OpenGL ES 2.0 before this will lookfamiliar to you, but if not let me explain.
Every time before you draw, you have to tell OpenGL which vertexbuffer objects you should use. So here we bind the vertex and index buffers wecreated earlier. Strictly, we didn’t have to do this for this app (becausethey’re already still bound from before) but usually you have to do thisbecause in most games you use many different vertex buffer objects.
Next, we have to enable the pre-defined vertex attributes we wantthe GLKBaseEffect to use. We use the glEnableVertexAttribArray to enable twoattributes here – one for the vertex position, and one for the vertex color.GLKit has predefined constants we need to use for these –GLKVertexAttribPosition adn GLKVertexAttribColor.
Next, we call glVertexAttribPointer to feed the correct values tothese two input variables for the vertex shader.
This is a particularly important function so let’s go over how it workscarefully.
· The first parameter specifies the attribute name to set. We justuse the predefined constants GLKit set up.
· The second parameter specifies how many values are present foreach vertex. If you look back up at the Vertex struct, you’ll see that for theposition there are three floats (x,y,z) and for the color there are four floats(r,g,b,a).
· The third parameter specifies the type of each value – which isfloat for both Position and Color.
· The fourth parameter is always set to false.
· The fifth parameter is the size of the stride, which is a fancyway of saying “the size of the data structure containing the per-vertex data”.So we can simply pass in sizeof(Vertex) here to get the compiler to compute itfor us.
· The final parameter is the offset within the structure to findthis data. We can use the handy offsetof operator to find the offset of aparticular field within a structure.
So now that we’re passing on the position and color data to theGLKBaseEffect, there’s only one step left…
5) Draw your geometry
This is a very simple one-liner at this point. Add this to thebottom of glkView:drawInRect:
|
This is also an important function so let’s discuss each parameterhere as well.
· The first parameter specifies the manner of drawing the vertices.There are different options you may come across in other tutorials likeGL_LINE_STRIP or GL_TRIANGLE_FAN, but GL_TRIANGLES is the most genericallyuseful (especially when combined with VBOs) so it’s what we cover here.
· The second parameter is the count of vertices to render. We use aC trick to compute the number of elements in an array here by dividing thesizeof(Indices) (which gives us the size of the array in bytes) bysizeof(Indices[0]) (which gives us the size of the first element in the arary).
· The third parameter is the data type of each individual index inthe Indices array. We’re using an unsigned byte for that so we specify thathere.
· From the documentation, it appears that the final parameter shouldbe a pointer to the indices. But since we’re using VBOs it’s a special case –it will use the indices array we already passed to OpenGL-land in theGL_ELEMENT_ARRAY_BUFFER.
Guess what – you’re done! Compile and run the app and you shouldsee a pretty rotating square on the screen!
//
// HelloWorldGLViewController.m
// HelloOpenGL
//
// Created by stephen.xing on 12/6/14.
// Copyright (c) 2014 IDREAMSKEY. All rights reserved.
//
#import "HelloWorldGLViewController.h"
#import <GLKit/GLKit.h>
typedef struct {
float Position[3];
float Color[4];
} Vertex; // 定义每个顶点的结构
const Vertex Vertices[] = {
{{1, -1, 0}, {1, 0, 0, 1}},
{{1, 1, 0}, {0, 1, 0, 1}},
{{-1, 1, 0}, {0, 0, 1, 1}},
{{-1, -1, 0}, {0, 0, 0, 1}}
}; // 具体每个顶点的数据
const GLubyte Indices[] = { 0, 1, 2, 2, 3, 0 }; // 顶点的下标情况
@interface HelloWorldGLViewController () {
float _curRed;
BOOL _increasing; // 自己定义的私有成员
float _rotation; // 成员变量,记录旋转的角度
GLuint _vertexBuffer;
GLuint _indexBuffer;
}
@property (strong, nonatomic) GLKBaseEffect *effect;
@property (strong, nonatomic) EAGLContext *context;
@end // category
@implementation HelloWorldGLViewController
@synthesize context = _context;
@synthesize effect = _effect;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)setupGL
{
[EAGLContext setCurrentContext:self.context];
self.effect = [[GLKBaseEffect alloc] init]; // 初始化帮助类
glGenBuffers(1, &_vertexBuffer); //为_vertexBuffer 生成值
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW); // 把数据发送到 gpu
glGenBuffers(1, &_indexBuffer); // 为_indexBuffer 生成值
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}
- (void)tearDownGL {
[EAGLContext setCurrentContext:self.context];
glDeleteBuffers(1, &_vertexBuffer);
glDeleteBuffers(1, &_indexBuffer);
self.effect = nil; // 释放effect对象
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!self.context) {
NSLog(@"Failed to create ES context");
}
GLKView *view = (GLKView *)self.view;
view.context = self.context;
[self setupGL];
}
- (void)viewDidUnload
{
[super viewDidUnload];
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
self.context = nil;
[self tearDownGL];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
#pragma mark - GLKViewDelegate
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
glClearColor(_curRed, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
[self.effect prepareToDraw];
glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer); // 我们这个程序中只有一个下标缓冲区,可以不绑定,但是大型项目中都有多个缓冲区需要绑定
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Position));
glEnableVertexAttribArray(GLKVertexAttribColor);
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Color));
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);
}
#pragma mark - GLKViewControllerDelegate
- (void)update {
if (_increasing) {
_curRed += 1.0 * self.timeSinceLastUpdate;
} else {
_curRed -= 1.0 * self.timeSinceLastUpdate;
}
if (_curRed >= 1.0) {
_curRed = 1.0;
_increasing = NO;
}
if (_curRed <= 0.0) {
_curRed = 0.0;
_increasing = YES;
}
float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height); // 计算长宽比
GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 4.0f, 10.0f);
// 65是焦距 10 很小相当于长焦,100 就大了
// aspect是长宽比
// 4.0 是近平面
// 10.0 是远平面
self.effect.transform.projectionMatrix = projectionMatrix; // 设置投影矩阵
GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -6.0f); // 我们的顶点z坐标是0,近平面是4.0,所以顶点不可见,所以我们平移6个单位
_rotation += 90 * self.timeSinceLastUpdate;
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 0, 1);
self.effect.transform.modelviewMatrix = modelViewMatrix;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"timeSinceLastUpdate: %f", self.timeSinceLastUpdate);
NSLog(@"timeSinceLastDraw: %f", self.timeSinceLastDraw);
NSLog(@"timeSinceFirstResume: %f", self.timeSinceFirstResume);
NSLog(@"timeSinceLastResume: %f", self.timeSinceLastResume);
self.paused = !self.paused;
}
@end