// // OpenGLView.m // OpenGLES22 // // Created by stephen.xing on 13/6/14. // Copyright (c) 2014 IDREAMSKEY. All rights reserved. // #import "OpenGLView.h" #import "CC3GLMatrix.h" /* typedef struct { float Position[3]; float Color[4]; } Vertex; */ // Add texture coordinates to Vertex structure as follows typedef struct { float Position[3]; float Color[4]; float TexCoord[2]; // New } 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}} }; // 每个顶点的信息 2.0 可以让我们方便的组织顶点的数据 方式不限了 const GLubyte Indices[] = { 0, 1, 2, 2, 3, 0 }; // 数据有了,,下面我们需要把他们传递给openGL */ /* const Vertex Vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {1, 0, 0, 1}}, {{-1, 1, 0}, {0, 1, 0, 1}}, {{-1, -1, 0}, {0, 1, 0, 1}}, {{1, -1, -1}, {1, 0, 0, 1}}, {{1, 1, -1}, {1, 0, 0, 1}}, {{-1, 1, -1}, {0, 1, 0, 1}}, {{-1, -1, -1}, {0, 1, 0, 1}} }; */ #define MAX_TEXTURE_COORD 4 const Vertex Vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}, {MAX_TEXTURE_COORD, 0}}, {{1, 1, 0}, {1, 0, 0, 1}, {MAX_TEXTURE_COORD, MAX_TEXTURE_COORD}}, {{-1, 1, 0}, {0, 1, 0, 1}, {0, MAX_TEXTURE_COORD}}, {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}}, {{1, -1, -1}, {1, 0, 0, 1}, {MAX_TEXTURE_COORD, 0}}, {{1, 1, -1}, {1, 0, 0, 1}, {MAX_TEXTURE_COORD, MAX_TEXTURE_COORD}}, {{-1, 1, -1}, {0, 1, 0, 1}, {0, MAX_TEXTURE_COORD}}, {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}} }; const GLubyte Indices[] = { // Front 0, 1, 2, 2, 3, 0, // Back 4, 6, 5, 4, 7, 6, // Left 2, 7, 3, 7, 6, 2, // Right 0, 4, 1, 4, 1, 5, // Top 6, 2, 1, 1, 6, 5, // Bottom 0, 3, 7, 0, 7, 4 }; // 1) Add to top of file 贴小鱼需要的 顶点 const Vertex Vertices2[] = { {{0.5, -0.5, 0.01}, {1, 1, 1, 1}, {1, 1}}, {{0.5, 0.5, 0.01}, {1, 1, 1, 1}, {1, 0}}, {{-0.5, 0.5, 0.01}, {1, 1, 1, 1}, {0, 0}}, {{-0.5, -0.5, 0.01}, {1, 1, 1, 1}, {0, 1}}, }; const GLubyte Indices2[] = { 1, 0, 2, 3 }; @implementation OpenGLView - (void)setupDisplayLink { // 注册刷新屏幕时候的回调 // render 函数必须有一个CADisplayLink* 类型的参数 CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)]; [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } - (GLuint)setupTexture:(NSString *)fileName { // 得到image的引用 使用文件名为参数的 构造函数构造UIImage对象 然后拿到CGImage 属性就是图片的引用了 CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage; if (!spriteImage) { NSLog(@"Failed to load image %@", fileName); exit(1); } // 为了创建bitmap 上下未能我们必须自己分配空间 得到图片的长宽 size_t width = CGImageGetWidth(spriteImage); size_t height = CGImageGetHeight(spriteImage); // *4 是因为每个像素占用4个字节 RGBA GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte)); // 参数8 代表每个 成分占用多少字节 CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast); // 把图片绘制到context中去 CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage); // 实际的填充spriteData 空间 CGContextRelease(spriteContext); // 下面是将像素数据传递到OpenGL中去 GLuint texName; glGenTextures(1, &texName); // 创建OpenGL对象,,去一个唯一的名字 glBindTexture(GL_TEXTURE_2D, texName); // 绑定,以后我说 TEXTURE_2D 就是说 texName 了 // 设置纹理的参数 // TEXTURE_MIN_FILTER: 对于远处的物体需要收缩纹理 // NEAREST: 绘制一个顶点的时候选择相关纹理上面距离最近的那个像素的颜色 // 如果不是使用mipmaps都必须使用 GL_TEXTURE_MIN_FILTER 参数 我们的例子就不是mipmaps glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // 将图片数据发送给gpu glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData); free(spriteData); // CPU这边的内存可以释放了 openGL会在gpu那边替我们存储这些数据 return texName; // 返回tex的唯一标示 } // 这里解释一个增加了纹理之后我们的vertex shader的变化 // 为每一个顶点指定它对应的纹理坐标---输入参数 //attribute vec2 TexCoordIn; // New // varying类型的变量是OpenGL 将会在调用fragment shader之前,为我们做插值处理 // 例如 左下角是(0,0) 右下角是(1,0) 那么达到frament shader的时候 他们中间一点的纹理坐标 TexCoordOut的值将会是(0.5, 0) //varying vec2 TexCoordOut; // New //TexCoordOut = TexCoordIn; // New // // 这里解释一下 fragment shader 的变化 //varying lowp vec2 TexCoordOut; // New //uniform sampler2D Texture; // New // gl_FragColor = DestinationColor; 顶点是什么颜色,中间的像素就根据顶点颜色插值 // 下面的方式 // 我们用顶点插值得到的颜色的基础上 乘以 像素对应的纹理坐标上面的纹理的颜色 // texture2D 是一个内建函数 为我们从Texture纹理上面得到TexCoordOut坐标上面的颜色值 //gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut); // New - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { // Initialization code [self setupLayer]; [self setupContext]; [self setupDepthBuffer]; [self setupRenderBuffer]; [self setupFrameBuffer]; [self compileShaders]; [self setupVBOs]; // [self render]; [self setupDisplayLink]; // 改掉只渲染一次为 开启一个CADisplayLink的刷新定时器 // 生成两个纹理 并且拿到纹理的名字 _floorTexture = [self setupTexture:@"tile_floor.png"]; _fishTexture = [self setupTexture:@"item_powerup_fish.png"]; } return self; } /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ // 修改view绑定layer的类型 + (Class)layerClass { // 我们通过重写这个方法就能保证View的layer是一种特殊类型的layer CAEAGLLayer return [CAEAGLLayer class]; } // 是指layer为 不透明 - (void)setupLayer { // 默认情况下CAEAGLLayer是透明的,但是那样影响性能,所以我们设置为不透明 _eaglLayer = (CAEAGLLayer*) self.layer; _eaglLayer.opaque = YES; } // 创建 context对象 - (void)setupContext { // context 用于管理iOS使用OpenGl所需要的所有信息 EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; // context是和版本号有关的 _context = [[EAGLContext alloc] initWithAPI:api]; if (!_context) { NSLog(@"Failed to initialize OpenGLES 2.0 context"); exit(1); } if (![EAGLContext setCurrentContext:_context]) { NSLog(@"Failed to set current OpenGL context"); exit(1); } } // create render buffer 颜色缓冲区 - (void)setupRenderBuffer { glGenRenderbuffers(1, &_colorRenderBuffer); // 用于产生一个缓冲区对象,该函数为_colorRenderBuffer赋值 // 以后我说GL_RENDERBUFFER指的就_colorRenderBuffer glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer); // 为渲染缓冲区非配存储空间,通过context对象的renderbufferStorage函数生成这个对象 [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; } // 深度缓冲区 开启 - (void)setupDepthBuffer { glGenRenderbuffers(1, &_depthRenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer); // 注意申请内存的方式和render/color 缓冲区不同 不是使用context对象来分配内存了 glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, self.frame.size.width, self.frame.size.height); } // create frame buffer - (void)setupFrameBuffer { GLuint framebuffer; // 帧缓冲区 包含 渲染缓冲区和颜色缓冲区 蒙版缓冲区 glGenFramebuffers(1, &framebuffer); glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); // 将renderbuffer 附着到 framebuffer的GL_COLOR_ATTACHMENT0 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer); // 将 代表深度的缓冲区 和 帧缓冲区的GL_DEPTH_ATTACHMENT 关联起来 // 可见 帧缓冲区真的包含着一系列 的其他类型的缓冲区 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); } - (GLuint) compileShader:(NSString*) shaderName withType:(GLenum) shaderType{ NSString* shaderFileExt = [[NSString alloc] initWithFormat:@"%@", @"vs"]; if( shaderType == GL_FRAGMENT_SHADER){ shaderFileExt = [[NSString alloc] initWithFormat:@"%@", @"fs" ]; } // 不同shader 的后缀名不同 // 把shader文件的内存读取出来 保存到shaderString 这个字符串中 NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:shaderFileExt]; NSError* error; NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error]; if (!shaderString) { NSLog(@"Error loading shader: %@", error.localizedDescription); exit(1); } // 创建一个代表shader的OpenGL对象,传入参数需要说明是 vs 还是 fs GLuint shaderHandle = glCreateShader(shaderType); // 把shader 的内容传递给 OpenGL const char * shaderStringUTF8 = [shaderString UTF8String]; int shaderStringLength = [shaderString length]; glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength); // 让OpenGL为我们编译shader glCompileShader(shaderHandle); // 如果编译失败就输出失败原因--方便我们修改shader,,失败就退出 GLint compileSuccess; glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); if (compileSuccess == GL_FALSE) { GLchar messages[256]; glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } return shaderHandle; // 成功则返回shader的句柄 } // 编译通过了,我们还需要几个步骤 // 1. 链接shader // 2. 让OpenGL真正的使用这些shader // 3. 得到传入参数(attribute)的指针,然后将输入参数传递进去 - (void)compileShaders { // 分别编译 vertex shader 和 fragment shader GLuint vertexShader = [self compileShader:@"SimleAddTexture" //@"SimpleWithoutComment" withType:GL_VERTEX_SHADER]; GLuint fragmentShader = [self compileShader:@"SimleAddTexture" //@"SimpleWithoutComment" withType:GL_FRAGMENT_SHADER]; // 链接 GLuint programHandle = glCreateProgram(); glAttachShader(programHandle, vertexShader); glAttachShader(programHandle, fragmentShader); glLinkProgram(programHandle); // 如果链接失败则输出失败原因 并退出程序 GLint linkSuccess; glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); if (linkSuccess == GL_FALSE) { GLchar messages[256]; glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]); NSString *messageString = [NSString stringWithUTF8String:messages]; NSLog(@"%@", messageString); exit(1); } // 让OpenGL开始使用这个shader glUseProgram(programHandle); // 得到输入参数的指针,以便传值进去 _positionSlot = glGetAttribLocation(programHandle, "Position"); _colorSlot = glGetAttribLocation(programHandle, "SourceColor"); // 使OpenGL 允许我们向输入参数传值 glEnableVertexAttribArray(_positionSlot); glEnableVertexAttribArray(_colorSlot); _projectionUniform = glGetUniformLocation(programHandle, "Projection"); // 拿到 投影矩阵的 指针 句柄 _modelViewUniform = glGetUniformLocation(programHandle, "Modelview"); // 从opengl中拿到输入变量的指针 _texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn"); glEnableVertexAttribArray(_texCoordSlot); _textureUniform = glGetUniformLocation(programHandle, "Texture"); } // 我们在compileShaders 后面马上调用render 所以用的是同一个program - (void)render:(CADisplayLink*) displayLink { // 使小鱼和边缘的黑色和 瓷砖纹理 混合起来 // GL_ONE : 源颜色 全部都要取 小鱼是源颜色 // GL_ONE_MINUS_SRC_ALPHA: 目标颜色 如果源颜色没有设置 取全部的目标颜色 小鱼躯干以外---取瓷砖底色 // 如果源颜色设置了 目标颜色不要去 小鱼的躯干 -----小鱼已经设置了,不再取瓷砖颜色 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0); // 设置清屏色 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 执行真正的清屏动作--只是清空 rendre/color buffer glEnable(GL_DEPTH_TEST); // 生成投影矩阵 CC3GLMatrix *projection = [CC3GLMatrix matrix]; float h = 4.0f * self.frame.size.height / self.frame.size.width; [projection populateFromFrustumLeft:-2 andRight:2 andBottom:-h/2 andTop:h/2 andNear:4 andFar:10]; // openGL默认是向z轴的负方向观察的 glUniformMatrix4fv(_projectionUniform, 1, 0, projection.glMatrix); // 生成模型视图矩阵 CC3GLMatrix *modelView = [CC3GLMatrix matrix]; // CACurrentMediaTime 得到当前时间,根据这个时间左右移动模型 [modelView populateFromTranslation:CC3VectorMake(0 /*sin(CACurrentMediaTime())*/, 0, -7)];// 移动我们将会绘制的东西 // 在移动的基础上添加旋转效果 _currentRotation += displayLink.duration * 90; [modelView rotateBy:CC3VectorMake(_currentRotation, _currentRotation, 0)]; // 模型视图矩阵传递到openGL glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix); // 我们在UIView的多大比例中渲染,这里我们全UIView进行渲染,我们也可以指渲染一部分 glViewport(0, 0, self.frame.size.width, self.frame.size.height); // 说明是 立方体的顶点缓冲区 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer); // 把当前的顶点值 传递给shader的输入参数 glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); // 参数解析: // shader输入参数的 句柄 glGetAttribLocation 得到的 // 每个顶点的Position 参数是一个3元组 // 3元组的每个元素是float类型 // 每个顶点的数据结构的 内存大小 // 顶点结构中 位置信息 距离结构开始的偏移量 glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3)); // 向纹理坐标传递至 到opengl glVertexAttribPointer( _texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7)); /*GLuint _floorTexture; glGenTextures(1, &_floorTexture); glBindTexture(GL_TEXTURE_2D, _floorTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);//spriteData是cpu中的图像数据 */ // glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _floorTexture); // cpu 里面的纹理的名字 glUniform1i(_textureUniform, 0); // gpu 里面需要的纹理的指针 // 把最终的图形画出来 glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0); // 参数解释: // 绘制的方式 点 线 三角形 // 顶点数目 // 顶点下标数组的数据类型 byte // ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////// // 绘制小鱼 // 绑定顶点缓冲区 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer2); // 加载小鱼纹理 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _fishTexture); glUniform1i(_textureUniform, 0); // 模型视图矩阵不需要改变,,所以下面这句话可用可不用 //glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix); glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3)); glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7)); glDrawElements(GL_TRIANGLE_STRIP, sizeof(Indices2)/sizeof(Indices2[0]), GL_UNSIGNED_BYTE, 0); [_context presentRenderbuffer:GL_RENDERBUFFER]; // render/color buffer 指的是一个buffer } // 向OpenGL传值的最好方式 叫做定点缓冲区对象 VBO // VBO 是OpenGL中为我们存储顶点数据的缓冲区 我们可以通过函数调用将顶点信息从cpu ---> gpu // VBO 有两种 1保存的是每个顶点的信息 2保存的是顶点的序号信息 - (void)setupVBOs { /* GLuint vertexBuffer; glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW); GLuint indexBuffer; glGenBuffers(1, &indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW); */ // 顶点缓冲区的 变量不再是局部变量而是 成员变量了 glGenBuffers(1, &_vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW); glGenBuffers(1, &_indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW); glGenBuffers(1, &_vertexBuffer2); glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2); glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices2), Vertices2, GL_STATIC_DRAW); glGenBuffers(1, &_indexBuffer2); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer2); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices2), Indices2, GL_STATIC_DRAW); } // 默认情况下 OpenGL 是2维的----右上角(1,1) 左下角(-1, -1) // 如果我们要在2D屏幕上达到 3D的效果 需要使用投影矩阵 // 投影矩阵的主要概念是 *面和远平面 物体越接近*面我们把它缩的越小 物体越接近远平面 我们把它放的越大 - (void) dealloc { // arc 不需要 手工释放内存,,编译器替我们干了 } @end
// // OpenGLView.h // OpenGLES22 // // Created by stephen.xing on 13/6/14. // Copyright (c) 2014 IDREAMSKEY. All rights reserved. // //#import <UIKit/UIKit.h> // //@interface OpenGLView : UIView // //@end #import <UIKit/UIKit.h> #import <QuartzCore/QuartzCore.h> #include <OpenGLES/ES2/gl.h> #include <OpenGLES/ES2/glext.h> @interface OpenGLView : UIView { CAEAGLLayer* _eaglLayer; EAGLContext* _context; GLuint _colorRenderBuffer; GLuint _positionSlot; GLuint _colorSlot; GLuint _projectionUniform; GLuint _modelViewUniform; float _currentRotation; // 我们绘制的立方体看起来怪怪的,有时候可以看到正方体的内部 // 我们可以通过开启深度测试修正这个问题----- // 深度测试的意思---只有这个物体是离我们的眼睛最近的我们才绘制 GLuint _depthRenderBuffer; GLuint _floorTexture; GLuint _fishTexture; GLuint _texCoordSlot; // vertex shader 中的TexCoordIn GLuint _textureUniform; // fragment shader 中的 Texture // 为小鱼贴图新增的 // 以前我们只有一个 顶点以及序号的缓冲区,所以我们不需要 拿到他们 // 现在我们我们将有两个 顶点以及序号的缓冲区了(1个是原来的立方体 1个是需要绘制小鱼平面),所以我们需要 索引去拿到他们 GLuint _vertexBuffer; GLuint _indexBuffer; GLuint _vertexBuffer2; GLuint _indexBuffer2; } @end
attribute vec4 Position; attribute vec4 SourceColor; varying vec4 DestinationColor; uniform mat4 Projection; uniform mat4 Modelview; attribute vec2 TexCoordIn; // New varying vec2 TexCoordOut; // New void main(void) { DestinationColor = SourceColor; gl_Position = Projection * Modelview * Position; TexCoordOut = TexCoordIn; // New }
varying lowp vec4 DestinationColor; varying lowp vec2 TexCoordOut; // New uniform sampler2D Texture; // New void main(void) { gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut); // New }