04-初始OpenGL ES-用GLSL简单实现一个三角形金字塔旋转的案例

前言

  • 实现OpenGS 实现三角形金字塔旋转的案例有两种方法,一种是用GLKit框架实现,一种是用GLSL来实现。
  • 两种实现方式的区别:
  • GLKit 封装了GLSL的代码,提供了一些常用的方法。用起来相对比较简单,但是GLKit只能在屏幕上只支持3个光源和2个纹理的渲染。
  • GLSL可以自由发挥,没有GLKit的限制。但是GLSL需要自己实现顶点着色器和片元着色器的渲染工作。相对比较麻烦

1. 使用GLSL之前,需要了解的几个知识点

1.1 坐标系详细概念

  • 世界坐标系

  • 坐标系统主要⽤于计算机图形场景中的所有图形对象的空间定位和定义

  • 局部坐标系

  • 独⽴于世界坐标系来定义物体⼏何特性

  • 观察坐标

  • 观察坐标系通常是以视点的位置为原点,通过⽤户指定的⼀个向上的观察向量来定义整个坐标系统,观察坐标系主要⽤于从观察者的⻆角度对整个世界坐标系内的对象进⾏重新定位和描述,从⽽简化⼏何物体在投影⾯的成像的数学推导和计算

  • 投影坐标系

  • 物体从世界坐标描述转换到观察坐标后,可将三维物体投影到⼆维表⾯上,即投影到虚拟摄像机的胶⽚上,这个过程就是投影变换。以胶⽚中⼼为参考原点的空间坐标系称为投影坐标系,物体在投影坐标系中的坐标称为投影坐标。

  • 设备坐标系

  • 是图形设备上采⽤的与具体设备相关的坐标系。设备坐标系⼀般采⽤整数坐标,其坐标范围由具体设备的分辨率决定。设备坐标系上的⼀个点⼀般对应图形设备上的⼀个像素。由于具体设备的限制,设备坐标系的坐标范围⼀般是有限的。

  • 规格化设备坐标系

  • 是为了避免设备相关性⽽定义的⼀种虚拟的设备坐标系。规格化坐标系的坐标范围⼀般从0到1,也有的是从-1到+1。采⽤规格化设备坐标系的好处是屏蔽了具体设备的分辨率,使得图形处理能够尽量避开对具体设备坐标的考虑。实际图形处理时,先将世界坐标转换成对应的规格化设备坐标,然后再将规格化设备坐标映射到具体的设备坐标上去。

  • 屏幕坐标系统

  • 也称设备坐标系统,它主要⽤于某⼀特殊的计算机图形显示设备(如光栅显示器)的表⾯的点的定义,在多数情况下,对于每⼀个具体的显示设备,都有⼀个单独的坐标系统,在定义了成像窗⼝的情况下,可进⼀步在屏幕坐标系统中定义称为视图区(view port)的有界区域,视图区中的成像即为实际所观察到的。

1.2 三维坐标转换为二维坐标的过程

04-初始OpenGL ES-用GLSL简单实现一个三角形金字塔旋转的案例_第1张图片

1.3 通过索引绘制图形

04-初始OpenGL ES-用GLSL简单实现一个三角形金字塔旋转的案例_第2张图片
在未了解到索引绘图时,一般是通过顶点数组的位置来绘图,调用的是如下图的方法:

glDrawArrays (GLenum mode, GLint first, GLsizei count)

了解了索引绘图后,可以使用如下的方法绘图:

glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices)

索引绘图,将绘制多个三角形的步骤简略了很多。

2.用GLSL代码实现三角形金字塔的选择

思路如下:

1.设置图层
2.设置上下文
3.清空缓存区
4.设置RenderBuffer
5.设置FrameBuffer
6.绘制

2.0 项目的准备资料

  • 从网上下载Ulit类,并拖入到工程中。
  • 新建一个View,取名为”YZView“(名字自行定义)
  • 在YZView的.m中导入如下的库:
  • #import “GLESMath.h”
    #import “GLESUtils.h”
    #import

  • 设置需要的属性,如下:
@interface YZView ()

@property (nonatomic, strong) CAEAGLLayer *myEagLayer;
@property (nonatomic, strong) EAGLContext *myContext;

//渲染缓存区
@property (nonatomic, assign) GLuint myColorRenderBuffer;
//帧缓存区
@property (nonatomic, assign) GLuint myColorFrameBuffer;
//编译程序
@property (nonatomic, assign) GLuint myProgram;
//顶点缓存
@property (nonatomic, assign) GLuint myVertices;

@end

@interface YZView ()

@property (nonatomic, strong) CAEAGLLayer *myEagLayer;
@property (nonatomic, strong) EAGLContext *myContext;

//渲染缓存区
@property (nonatomic, assign) GLuint myColorRenderBuffer;
//帧缓存区
@property (nonatomic, assign) GLuint myColorFrameBuffer;

@property (nonatomic, assign) GLuint myProgram;
@property (nonatomic, assign) GLuint myVertices;

@end

2.1 设置图层

关于CAEAGLLayer在上一篇中已经大概描述了一下,这一篇就不负赘了。

//1.设置图层
-(void)setupLayer
{
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    [self setContentScaleFactor:[[UIScreen mainScreen]scale]];
    
    //CALayer默认是透明的,必须将它设置为不透明才能其可见
    self.myEagLayer.opaque = YES;
    
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

2.2 设置图形上下文

//2. 设置上下文
- (void)setupContext {
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
    if (!context) {
        NSLog(@"failed to create context");
        return;
    }
    
    //设置当前上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"Failed to set current context");
        return;
    }
    
    self.myContext = context;
}

2.3 清空缓存

//3.清空缓存区
-(void)deletBuffer
{
    //1.导入框架#import 
    /*
     buffer分为frame buffer 和 render buffer2个大类。
     其中frame buffer 相当于render buffer的管理者。
     frame buffer object即称FBO,常用于离屏渲染缓存等。
     render buffer则又可分为3类。colorBuffer、depthBuffer、stencilBuffer。
  
     */
    glDeleteBuffers(1, &_myColorRenderBuffer);
    _myColorRenderBuffer = 0;
    
    glDeleteBuffers(1, &_myColorFrameBuffer);
    _myColorFrameBuffer = 0;
    
}

2.4 设置renderBuffer

//4. 设置renderBuffer(渲染缓存区)
- (void)setupRenderBuffer {
    //1.定义一个缓存区
    GLuint buffer;
    //2.申请一个缓存标识符
    glGenRenderbuffers(1, &buffer);
    //3.设置强引用
    self.myColorRenderBuffer = buffer;
    //4. 将标识符绑定到GL_RENDERBUFFER上
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    //5.为renderBuffer分配空间
    /*
     frame buffer仅仅是管理者,不需要分配空间;
     render buffer的存储空间的分配,对于不同的render buffer,使用不同的API进行分配,
     而只有分配空间的时候,render buffer句柄才确定其类型
     */
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}

2.5 设置frameBuffer

//5. 设置frameBuffer(帧缓冲区)
- (void)setupFrameBuffer {
    //1. 定义一个缓存区
    GLuint buffer;
    //2. 申请一个缓存区标识
    glGenFramebuffers(1, &buffer);
    //3. 设置强引用
    self.myColorFrameBuffer = buffer;
    //4. 将标识符绑定到GL_FRAMEBUFFER上
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    //5. 将_myColorRendBuffer 加载到GL_COLOR_ATTACHMENT0 的附着点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
    //接下来,可以调用OpenGL ES进行绘制处理,最后则需要在EGALContext的OC方法进行最终的渲染绘制。
    //这里渲染的color buffer,这个方法会将buffer渲染到CALayer上。- (BOOL)presentRenderbuffer:(NSUInteger)target;
    
}

2.6 渲染

2.6.1 实现顶点和片元着色器

渲染之前,需要直接先实现一下顶点着色器和片元着色器。在项目中新建一个空白文件,分别取名为shaderv.glslshaderf.glsl,对应“顶点着色器”和“片元着色器”,如下图:
04-初始OpenGL ES-用GLSL简单实现一个三角形金字塔旋转的案例_第3张图片
shaderv.glsl中编写顶点着色器的代码如下,其中gl_Position必须赋值:

attribute vec4 position;
attribute vec4 positionColor;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying lowp vec4 varyColor;

void main()
{
    varyColor = positionColor;
    
    vec4 vPos;
    vPos = projectionMatrix * modelViewMatrix * position;
    gl_Position = vPos;
}

shaderf.glsl中编写片元着色器的代码如下,其中gl_FragColor必须赋值:

varying lowp vec4 varyColor;

void main()
{
    gl_FragColor = varyColor;
}

2.6.3 在YZView.m中实现加载编译着色器的方法

//加载shader
- (GLuint)loadShader:(NSString *)vert frag:(NSString *)frag {
    //创建2个临时着色器(顶点着色器和片元着色器)
    GLuint vertShader, fragShader;
    //创建一个Program
    GLuint program = glCreateProgram();
    
    //编辑文件
    /*
     编译顶点着色程序、片元着色器程序
     参数1:编译完存储的底层地址
     参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
     参数3:文件路径
     */
    [self complieShader:&vertShader type:GL_VERTEX_SHADER file:vert];
    [self complieShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    
    //创建最终的程序
    glAttachShader(program, vertShader);
    glAttachShader(program, fragShader);
    
    //释放不需要的shader
    glDeleteProgram(vertShader);
    glDeleteProgram(fragShader);
    
    return program;
}


// 链接shader
- (void)complieShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
    //读取文件路径字符串
    NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    //获取文件路径字符串(需要转化成C语言)
    const GLchar *source = (GLchar *)[content UTF8String];
    //根据type类型,创建一个shader
    *shader = glCreateShader(type);
    
    //将着色器源码附加到着色器对象上
    /*
     参数1:shader,要编译的着色器对象 *shader
     参数2:numOfStrings,传递的源码字符串数量 1个
     参数3:strings,着色器程序的源码(真正的着色器程序源码)
     参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
     */
    glShaderSource(*shader, 1, &source, NULL);
    
    //把着色器源码代码编译成目标代码
    glCompileShader(*shader);
}

2.6.4 实现渲染的发法

//6. 绘制
- (void)render {
    //清屏颜色
     glClearColor(0, 0.0, 0, 1.0);
     glClear(GL_COLOR_BUFFER_BIT);
     
     CGFloat scale = [[UIScreen mainScreen] scale];
     //设置视口
     glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
     
     //获取顶点着色程序、片元着色器程序文件位置
     NSString* vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"glsl"];
     NSString* fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"glsl"];
     
     //判断self.myProgram是否存在,存在则清空其文件
     if (self.myProgram) {
         
         glDeleteProgram(self.myProgram);
         self.myProgram = 0;
     }
     
     //加载程序到myProgram中来。
     self.myProgram = [self loadShader:vertFile frag:fragFile];
     
     //4.链接
     glLinkProgram(self.myProgram);
     GLint linkSuccess;
     
     //获取链接状态
     glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkSuccess);
     if (linkSuccess == GL_FALSE) {
         GLchar messages[256];
         glGetProgramInfoLog(self.myProgram, sizeof(messages), 0, &messages[0]);
         NSString *messageString = [NSString stringWithUTF8String:messages];
         NSLog(@"error%@", messageString);
         
         return ;
     }else {
         glUseProgram(self.myProgram);
     }
     
     //创建绘制索引数组
     GLuint indices[] =
     {
         0, 3, 2,
         0, 1, 3,
         0, 2, 4,
         0, 4, 1,
         2, 3, 4,
         1, 4, 3,
     };
     
     //判断顶点缓存区是否为空,如果为空则申请一个缓存区标识符
     if (self.myVertices == 0) {
         glGenBuffers(1, &_myVertices);
     }
     
     //顶点数组
     //前3顶点值(x,y,z),后3位颜色值(RGB)
     GLfloat attrArr[] =
     {
         -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f, //左上
         0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f, //右上
         -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f, //左下
         0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f, //右下
         0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f, //顶点
     };
     
     //-----处理顶点数据-------
     //将_myVertices绑定到GL_ARRAY_BUFFER标识符上
     glBindBuffer(GL_ARRAY_BUFFER, _myVertices);
     //把顶点数据从CPU内存复制到GPU上
     glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
     
    // glBindBuffer(GL_ARRAY_BUFFER, _myVertices);
     
     //将顶点数据通过myPrograme中的传递到顶点着色程序的position
     //1.glGetAttribLocation,用来获取vertex attribute的入口的.2.告诉OpenGL ES,通过glEnableVertexAttribArray,3.最后数据是通过glVertexAttribPointer传递过去的。
     //注意:第二参数字符串必须和shaderv.vsh中的输入变量:position保持一致
     GLuint position = glGetAttribLocation(self.myProgram, "position");
     
     //3.设置读取方式
     //参数1:index,顶点数据的索引
     //参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
     //参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
     //参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
     //参数5:stride,连续顶点属性之间的偏移量,默认为0;
     //参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
     glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, NULL);
     
     //2.设置合适的格式从buffer里面读取数据
     glEnableVertexAttribArray(position);
     
     //--------处理顶点颜色值-------
     ////1.glGetAttribLocation,用来获取vertex attribute的入口的.
     //注意:第二参数字符串必须和shaderv.glsl中的输入变量:positionColor保持一致
     GLuint positionColor = glGetAttribLocation(self.myProgram, "positionColor");
     
     //3.设置读取方式
     //参数1:index,顶点数据的索引
     //参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
     //参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
     //参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
     //参数5:stride,连续顶点属性之间的偏移量,默认为0;
     //参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
     glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (float *)NULL + 3);
     
     //2.设置合适的格式从buffer里面读取数据
     glEnableVertexAttribArray(positionColor);
     
     //注意,想要获取shader里面的变量,这里记得要在glLinkProgram后面,后面,后面!
     /*
      一个一致变量在一个图元的绘制过程中是不会改变的,所以其值不能在glBegin/glEnd中设置。一致变量适合描述在一个图元中、一帧中甚至一个场景中都不变的值。一致变量在顶点shader和片断shader中都是只读的。首先你需要获得变量在内存中的位置,这个信息只有在连接程序之后才可获得
      */
     //找到myProgram中的projectionMatrix、modelViewMatrix 2个矩阵的地址。如果找到则返回地址,否则返回-1,表示没有找到2个对象。
     GLuint projectionMatrixSlot = glGetUniformLocation(self.myProgram, "projectionMatrix");
     GLuint modelViewMatrixSlot = glGetUniformLocation(self.myProgram, "modelViewMatrix");
     
     float width = self.frame.size.width;
     float height = self.frame.size.height;
     
     //创建4 * 4矩阵
     KSMatrix4 _projectionMatrix;
     
     //获取单元矩阵
     ksMatrixLoadIdentity(&_projectionMatrix);
     
     //计算纵横比例 = 长/宽
     float aspect = width / height; //长宽比
     
     //获取透视矩阵
     /*
      参数1:矩阵
      参数2:视角,度数为单位
      参数3:纵横比
      参数4:近平面距离
      参数5:远平面距离
      参考PPT
      */
     ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f); //透视变换,视角30°
     
     //设置glsl里面的投影矩阵
     /*
      void glUniformMatrix4fv(GLint location,  GLsizei count,  GLboolean transpose,  const GLfloat *value);
      参数列表:
      location:指要更改的uniform变量的位置
      count:更改矩阵的个数
      transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
      value:执行count个元素的指针,用来更新指定uniform变量
      */
     glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
     
     //开启剔除操作效果
     glEnable(GL_CULL_FACE);
     
     //创建一个4 * 4 矩阵,模型视图
     KSMatrix4 _modelViewMatrix;
     //获取单元矩阵
     ksMatrixLoadIdentity(&_modelViewMatrix);
     //平移,z轴平移-10
     ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
     
     //创建一个4 * 4 矩阵,旋转矩阵
     KSMatrix4 _rotationMatrix;
     //初始化为单元矩阵
     ksMatrixLoadIdentity(&_rotationMatrix);
     
     //旋转
     ksRotate(&_rotationMatrix, xDegree, 1.0, 0.0, 0.0); //绕X轴
     ksRotate(&_rotationMatrix, yDegree, 0.0, 1.0, 0.0); //绕Y轴
     ksRotate(&_rotationMatrix, zDegree, 0.0, 0.0, 1.0);//绕Z轴
     
     //把变换矩阵相乘,注意先后顺序 ,将平移矩阵与旋转矩阵相乘,结合到模型视图
      ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
//     ksMatrixMultiply(&_modelViewMatrix, &_modelViewMatrix, &_rotationMatrix);
//
     // 加载模型视图矩阵 modelViewMatrixSlot
     //设置glsl里面的投影矩阵
     /*
      void glUniformMatrix4fv(GLint location,  GLsizei count,  GLboolean transpose,  const GLfloat *value);
      参数列表:
      location:指要更改的uniform变量的位置
      count:更改矩阵的个数
      transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
      value:执行count个元素的指针,用来更新指定uniform变量
      */
     glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
     
     
     //使用索引绘图
     /*
      void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid * indices);
      参数列表:
      mode:要呈现的画图的模型
                 GL_POINTS
                 GL_LINES
                 GL_LINE_LOOP
                 GL_LINE_STRIP
                 GL_TRIANGLES
                 GL_TRIANGLE_STRIP
                 GL_TRIANGLE_FAN
      count:绘图个数
      type:类型
              GL_BYTE
              GL_UNSIGNED_BYTE
              GL_SHORT
              GL_UNSIGNED_SHORT
              GL_INT
              GL_UNSIGNED_INT
      indices:绘制索引数组

      */
     glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
     
     //要求本地窗口系统显示OpenGL ES渲染<目标>
     [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
     
}

2.7 设置xyz轴选中控制的按钮

- (IBAction)xClick:(id)sender {
    //开启d定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bX = !bX;
}

- (IBAction)yClick:(id)sender {
    //开启定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bY = !bY;
}
- (IBAction)zClick:(id)sender {
    //开启定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bZ = !bZ;
}

- (void)reDegree {
    xDegree += bX * 5;
    yDegree += bY * 5;
    zDegree += bZ * 5;
    //重新渲染
    [self render];
}

实现的效果如下:
04-初始OpenGL ES-用GLSL简单实现一个三角形金字塔旋转的案例_第4张图片

你可能感兴趣的:(OpenGL与OpenGL,SE)