这次要做的是一个立方体,在学完纹理之后其实大家应该能自己实现一个立方体,也就是把Z轴利用上就可以实现了。唯一的难点是这里涉及一个深度测试,如果不加深度测试渲染出来的立方体会是一个有缺失面的立方体。现在我们进入正题,和以前一样我们用两套代码来实现。
学习的代码都在我的github仓库欢迎大家学习指教!
不使用GLKit的方式
一、设置顶点和纹理数据
为了避免重复同样的话,前面讲过的知识点将不再说明,我们是在上一篇代码的基础之上进行修改的。前面我们已经做出了一个纹理,只要用六个面就可以拼一个立方体,和单个纹理面的区别是空间坐标不一样而已。现将顶点坐标数组写成以下方式:
// 顶点结构体
typedef struct{
GLfloat position[3];
GLfloat texturePosion[2];
} Vertex;
const Vertex vertexes[] = {
// 顶点 纹理
// 前面
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左上 0
{{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左下 1
{{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右下 2
{{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右上 3
// 后面
{{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后左上 4
{{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后左下 5
{{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后右下 6
{{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后右上 7
// 左面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左上 8
{{-0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左下 9
{{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前左上 10
{{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前左下 11
// 右面
{{0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前右上 12
{{0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前右下 13
{{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右下 14
{{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右上 15
// 上面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左上 16
{{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左上 17
{{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右上 18
{{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右上 19
// 下面
{{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左下 20
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右下 21
{{-0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左下 22
{{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右下 23
};
// 顶点索引
const GLbyte indexes[] = {
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 5, 6,
4, 6, 7,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
16, 17, 18,
16, 18, 19,
// 下面
20, 22, 23,
20, 23, 21,
};
你们可能已经看到了,这次的顶点坐标和上一篇笔记的不一样,这里是把顶点对应的纹理坐标也写在一起了。这样写我们也可以学习另一种方式设置顶点和纹理坐标,这里我们还用到了索引,这样有可以实现顶点数据的重复利用。
二、设置VBO(顶点缓存)
这次绘制立方体的时候有没有会发现顶点数据比原来多很多!其实这还算是少的。简单的图形我们可以通过手写顶点数据来实现,但是如果是一个复杂的图形就不行了,我们在玩大型游戏的时候都看过人物的结构很复杂,这个用我们手写顶点数据是不可能实现的!一般是由设计师用建模工具来实现的,最终的文件还是顶点数据。这些数据是在内存中的,只有在绘制的时候才会从CPU传给GPU,这种数据传递本身的性能也不是很高,当顶点数据很大的时候对性能的影响更明显。一般的优化方案就是将顶点数据缓存的GPU的显存里,这样不用每次从CPU传递顶点数据到GPU从而提高图形的性能。下面我们就用代码实现VBO:
/**
设置顶点缓存
*/
- (void)setupVBO{
// 设置VBO
// 设置顶点缓存
GLuint bufferVBO;
glGenBuffers(1, &bufferVBO);
glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
// 设置索引缓存
GLuint bufferIndex;
glGenBuffers(1, &bufferIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
// 设置顶点
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 设置纹理坐标
glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}
三、引入矩阵变换的库
在图像的世界里图像的放大、缩小、位移都是通过图片乘以一个矩阵来实现的(通过矩阵相乘效率更高)你在iOS开发中是否遇到过图形的transform属性,你打印一下看看里面的结构你就会明白,这也是一个矩阵数据。这个涉及线性代数的东西,你大学里学的线性代数的东西是不是已经忘记了?不过不用怕网上已经有很多矩阵计算相关的库,我们直接用就可以了。其实GLKit里也有矩阵计算的库,因为我们这里不打算用GLKit所以我们用一个别人写好的库。可以从这里下载这个库。下载图片中的这几个文件。
四、实现触摸来滚动立方体
如果我们不添加让立方体滚动的功能就只能看到一个面,看不出立方体的效果,而且深度测试的效果也看不出来。滚动的实现方式也很简单让其乘以一个矩阵即可,我们用两个变量来记录手指在屏幕上划动的量变。代码如下:
// 限制上下转动的角度
#define kLimitDegreeUpDown 40.0
@interface ViewController ()
// 记录x轴方向上的变量
@property(nonatomic,assign)GLfloat degreeX;
// 记录y轴方向上的变量
@property(nonatomic,assign)GLfloat degreeY;
@end
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint previousPoint = [touch previousLocationInView:self.view];
self.degreeX += previousPoint.y - currentPoint.y;
// 限制上下转动的角度
if (self.degreeX > kLimitDegreeUpDown) {
self.degreeX = kLimitDegreeUpDown;
}
if (self.degreeX < -kLimitDegreeUpDown) {
self.degreeX = -kLimitDegreeUpDown;
}
self.degreeY += previousPoint.x - currentPoint.x;
}
五、设置视角
可能你会在上一个笔记中看到我们创建的纹理是一个长方形的,但我们设置的坐标应该出现的是一个正方形才对。其实我以前也纠结这个问题,查了很多资料都没找到相关说明,后来在我无意间设置完视角发现一切都正常了,所以我们要对代码进行以下更改:
- 将顶点着色器文件修改如下
attribute vec4 myPosition;
// 添加物体运动的变换矩阵
uniform mat4 modelView;
// 添加视角的变换矩阵
uniform mat4 projection;
attribute vec2 textureCoordsIn;
varying vec2 textureCoordsOut;
void main()
{
// 将原来的顶点乘以矩阵数据,注意乘的前后顺序
gl_Position = projection * modelView * myPosition;
textureCoordsOut = textureCoordsIn;
}
- 添加相应的槽
// 添加以下成员变量
GLuint _modelViewSlot; // 物体变换的槽
GLuint _projectionSlot; // 摄像机的槽
// 在原来设置槽的位置添加以下代码
_modelViewSlot = glGetUniformLocation(_program, "modelView");
_projectionSlot = glGetUniformLocation(_program, "projection");
- 用一个方法来传递视角数据和用一个方法来传递图形的变换矩阵,该方法在render方法里调用
- (void)setupPerspactive{
GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
ksMatrix4 tempMatrix;
ksMatrixLoadIdentity(&tempMatrix);
// 设置视角矩阵
ksPerspective(&tempMatrix, 60, aspect, 0.1f, 10.0f);
// 传递视角矩阵数据
glUniformMatrix4fv(_projectionSlot, 1 , GL_FALSE, (GLfloat *)&tempMatrix.m[0][0]);
}
/**
设置图形的变换矩阵
*/
- (void)setupModelViewMatrix{
// 设置物体的变换
ksMatrixLoadIdentity(&_matrix4);
// 远离视野,不然是在视角会在立方体的中心
ksMatrixTranslate(&_matrix4, 0, 0, -3);
// x方向旋转
ksMatrixRotate(&_matrix4, self.degreeX, 1, 0, 0);
// y方向旋转
ksMatrixRotate(&_matrix4, self.degreeY, 0, 1, 0);
glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat *)&_matrix4.m[0][0]);
}
六、实现视图的刷新
前面我们的代码都是只渲染一次,在修改了数据后视图是不会改变的。为了能及时渲染每次修改的数据我们要用CADisplayLink来不停渲染数据。实现代码正如下:
@interface ViewController ()
@property (nonatomic,strong)CADisplayLink * link;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 设置视图刷新
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
修改渲染方法,因为我们改成了使用索引来绘制图形,所以绘制方法也需要修改,修改后的渲染方法如下:
/**
渲染
*/
- (void)render {
[self setupPerspactive];
[self setupModelViewMatrix];
glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);
glClearColor(1.0, 1.0, 1.0, 1.0);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 用索引要设置要绘制的方法
glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
七、实现深度测试
通过以上步骤我们已经可实现一个半成品了,我们先运行看一下结果。
没有深度测试.gif
你可能已经看到了问题:有的面有缺失。对于该问题是因为后渲染的面覆盖了先前渲染的面所造成的,解决方法是设置深度测试即可。这样在渲染的时候会检测各个面的前后关系,在后面被挡住的面将不会渲染,从而来解决此问题。深度测试的代码如下:
- 在原来设置渲染缓存的方法里添加深度测试缓存代码
- (void)setupFrameAndRenderBuffer {
// 申请渲染缓存
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
// 该方法最好在绑定渲染后立即设置,不然后面会被绑定为深度渲染缓存
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_glLayer];
// 设置深度调试
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
// 申请深度渲染缓存
glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
// 设置深度测试的存储信息
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
// 申请帧缓存
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
// 将渲染缓存挂载到GL_DEPTH_ATTACHMENT这个挂载点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
// GL_RENDERBUFFER绑定的是深度测试渲染缓存,所以要绑定回色彩渲染缓存
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
// 检查帧缓存状态
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Error: Frame buffer is not completed.");
exit(1);
}
}
- 在渲染方法里将原来的清空缓存区数据的方法改成以下方法
glEnable(GL_DEPTH_TEST);
// 清空色彩数据的同时清空深度数据
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
运行结果如下:
添加深度测试.gif
实现只有线连成的方法体
我在评论区看到有人想问的问题可能是在问怎么实现用线绘制立方体,就在这里补充一下评论区里的问题的答案。我们目前都是通过将顶点绘制成三角形的方式来实现一些图形效果,除此之外我们还可以将顶点数据绘制成点(也可以说成点精灵)或者直线。这次我们用直线实现,首先将顶点和索引数据要修改如下:
const GLfloat vertexes[] = {
-0.5f, 0.5f, 0.5f, // 前左上 0
-0.5f, -0.5f, 0.5f, // 前左下 1
0.5f, -0.5f, 0.5f, // 前右下 2
0.5f, 0.5f, 0.5f, // 前右上 3
// 后面
-0.5f, 0.5f, -0.5f, // 后左上 4
-0.5f, -0.5f, -0.5f, // 后左下 5
0.5f, -0.5f, -0.5f, // 后右下 6
0.5f, 0.5f, -0.5f // 后右上 7
};
const GLbyte indexes[] = {
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7
};
由于我们的顶点数据没有了纹理坐标,指针的跨度由5变成了3所以我们要将设置顶点参数的代码修改如下:
glVertexAttribPointer(_vertexSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, NULL);
我们的片元着色器也得修改,因为原来是从纹理中获取颜色数据而现在我们直接设置颜色数据,将着色器修改如下:
precision mediump float;
void main()
{
// 我们在这里将颜色设成了红色,下面4个数分别对应RGBA
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
上面说过我们这次要绘制成直线,所以在绘制的方法里将GL_TRIANGLES修改成GL_LINES,表示用线绘制。
glDrawElements(GL_LINES, sizeof(indexes) / sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
好了,现在运行一下代码看看效果吧。代码也同步更新了,可以在我的github下载。
立方体.gif知识扩展
后面会讲到天空盒子,其实天空盒子就是一个立方体只是视角在立方体的中心而已,还记得我们设置图形变换矩阵的时候有一行这样的代码:
// 远离视野,不然是在视角会在立方体的中心
ksMatrixTranslate(&_matrix4, 0, 0, -3);
这是为了看到图形的全貌才设置的让其在Z轴上远离我们,如果将这行代码注释了你就会看到我们的视角在立方体的中心,平面是双面显示的所以在立方体中心也能看到美女的图片(后面我们会讲到面剔除让其只在一面显示从而提高性能),你可以设置每个面都不同就可以实现天空盒子了,这个先留给大家尝试一下吧!
使用GLKit的方式
使用GLKit的方法要简单很多,简单到不用再写shader了。上面的原理能讲的都已经讲完了,这里就只提供相关代码和注释。需要说明的会在代码注释里。
// 限制上下转动的角度
#define kLimitDegreeUpDown 40.0
// 顶点结构体
typedef struct{
GLfloat position[3];
GLfloat texturePosion[2];
} Vertex;
const Vertex vertexes[] = {
// 顶点 纹理
// 前面
{{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左上 0
{{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左下 1
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右下 2
{{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右上 3
// 后面
{{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后左上 4
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后左下 5
{{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后右下 6
{{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后右上 7
// 左面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左上 8
{{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左下 9
{{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前左上 10
{{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前左下 11
// 右面
{{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前右上 12
{{0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前右下 13
{{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右下 14
{{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右上 15
// 上面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左上 16
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左上 17
{{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右上 18
{{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右上 19
// 下面
{{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左下 20
{{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右下 21
{{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左下 22
{{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右下 23
};
const GLbyte indexes[] = {
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 5, 6,
4, 6, 7,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
16, 17, 18,
16, 18, 19,
// 下面
20, 22, 23,
20, 23, 21,
};
@interface ViewController ()
@property (nonatomic,strong)GLKBaseEffect * effect;
@property(nonatomic,assign)GLfloat degreeX;
@property(nonatomic,assign)GLfloat degreeY;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GLKView * glView = (GLKView *)self.view;
EAGLContext * contex = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!contex) {
NSLog(@"context创建失败");
}
if (![EAGLContext setCurrentContext:contex]) {
NSLog(@"设置当前context失败");
}
glView.context = contex;
glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
self.effect = [[GLKBaseEffect alloc] init];
// 加载纹理图片
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];
NSError * error;
CGImageRef image = [UIImage imageNamed:@"girl"].CGImage;
GLKTextureInfo * textureInfo = [GLKTextureLoader textureWithCGImage:image options:options error:&error];
if (error) {
NSLog(@"%@", error);
}
// 设置纹理可用
self.effect.texture2d0.enabled = GL_TRUE;
// 传递纹理信息
self.effect.texture2d0.name = textureInfo.name;
[self setupVBO];
[self setupPerspective];
glClearColor(1.0, 1.0, 1.0, 1.0);
glEnable(GL_DEPTH_TEST);
}
/**
设置顶点缓存
*/
- (void)setupVBO{
// 设置VBO
GLuint bufferVBO;
glGenBuffers(1, &bufferVBO);
glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
GLuint bufferIndex;
glGenBuffers(1, &bufferIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
// 设置顶点
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 设置纹理坐标
glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}
- (void)setupPerspective{
GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
GLKMatrix4 perspective = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(60), aspect, 0.1, 200);
self.effect.transform.projectionMatrix = perspective;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint previousPoint = [touch previousLocationInView:self.view];
self.degreeX += currentPoint.y - previousPoint.y;
// 限制上下转动的角度
if (self.degreeX > kLimitDegreeUpDown) {
self.degreeX = kLimitDegreeUpDown;
}
if (self.degreeX < -kLimitDegreeUpDown) {
self.degreeX = -kLimitDegreeUpDown;
}
self.degreeY += currentPoint.x - previousPoint.x;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 远离视野,不然是在正方体内部
GLKMatrix4 modelMat = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -3);
modelMat = GLKMatrix4RotateX(modelMat, GLKMathDegreesToRadians(self.degreeX));
modelMat = GLKMatrix4RotateY(modelMat, GLKMathDegreesToRadians(self.degreeY));
self.effect.transform.modelviewMatrix = modelMat;
[self.effect prepareToDraw];
glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
}
网友评论