zoukankan      html  css  js  c++  java
  • OpenGL ES入门

    1. Things OpenGL Can Render

    图中展示了OpenGL 能够渲染三种类型的物体:点、线和三角形

    2. Everything's a Triangle

    虽然能够渲染三种类型,但是最终复杂的图形通常由三角形构成,图中的矩形和圣诞树都是由三角形构成的:

    接下来我们尝试理解一个简单的三角形是如何完成的

    3. Storing Vertex Info

    在我们的屏幕中,坐标构成如上图。即屏幕中间为(0,0),范围从-1 到1。所以首先我们为坐标创建一个结构体:

    typedef struct {
      GLFloat position[3]; // 分别代表x, y, z
    }TestVertex;
    

    结构体实例如下:

    const static TestVertex vertices[] = {
      {{-1.0, -1.0, 0}},  // 左下
      {{1.0, -1.0, 0}},   // 右下
      {{0,  0, 0}},   // 上
    }
    

    4. Sending Vertex Info to GPU

    需要经过三个方法:

    glGenBuffer()  // 在GPU中创建一个VertexBuffer
    glBindBuffer()  // 绑定并激活VertexBuffer
    glBufferData()  // 将CPU中数据传输至VertexBuffer,供后续处理
    

    5. Drawing Geometry

    glBindBuffer(GL_ARRAY_BUFFER,  _vertexBuffer)  // 将VertexBuffer至ArrayBuffer
    glDrawArrays(GL_TRIANGLES, 0, 3) // 绘制真正发生的地方
    

    到这里绘制就完成了,但是GPU是如何通过顶点来绘制图像呢,其背后的原理就涉及到了shader

    6. Shader

    Shader是通过OpenGL ES Shading Language(GLSL)来操作的,这是一个C-like语言。
    Shader包括两种类型:

    • Vertex Shader:输入顶点,并为顶点输出最终的位置信息
    • Fragment Shader:输入像素,输出最终颜色信息

    6.1 Vertex Shader

    vertex shader的输入是顶点信息,随后通过一系列的transform,输出最终的位置信息,这里为了简单,我们并不做复杂的transform,他的GLSL代码如下:

    attribute vec4 a_Positon;
    
    void main(void) {
      gl_position = a_Position;
    }
    

    6.2 Fragment Shader

    fragment shader的输入通常来自于vertex shader,这里为了简化,我们免去了输入,直接输出像素的颜色

    void main(void) {
      gl_FragColor = vec4(1, 1, 1, 1); // 将一切像素输出为白色
    }
    

    7 Demo:Rendering a Triangle

    7.1 Add Helper Code

    作者提供了一些resource来封装对shader的操作,以及GLSL的实现。需要将这些代码引入工程,可供我们使用+学习。下载地址在此

    7.2 Setup vertex buffer

    viewDidLoad代码如下

    - (void)viewDidLoad
    {
      [super viewDidLoad];
    	GLKView *view = (GLKView *)self.view;
      view.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    
      [EAGLContext setCurrentContext:view.context];
      
      [self setupShader];
      [self setupVertexBuffer];
    }
    

    可以看到,viewDidLoad主要做了两件事,就是setupShader和setupVertexBuffer,那么我们一起来看看内部的逻辑:

    - (void)setupVertexBuffer {
    
      const static RWTVertex vertices[] = {
        {{-1.0, -1.0, 0}}, 	// 左下
        {{1.0, -1.0, 0}},   	// 右下
        {{0, 0, 0}},            // 上方
      };
      
      // 按照上面说的将CPU数据传给GPU的三个步骤
      glGenBuffers(1, &_vertexBuffer);	// 生成
      glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);	// 绑定
      glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 绘制
    
    }
    

    setupVertexBuffer主要功能就是初始化顶点,随后将顶点从CPU传输至GPU

    - (void)setupShader {
      _shader = [[RWTBaseEffect alloc] initWithVertexShader:@"RWTSimpleVertex.glsl" fragmentShader:@"RWTSimpleFragment.glsl"];
    }
    

    setupShader主要调用了作者提供的初始化函数来对shader进行初始化,稍后我们对初始化函数再进行分析

    7.3 Enable/disable vertex attributes

    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
      glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
      glClear(GL_COLOR_BUFFER_BIT);
      
      [_shader prepareToDraw];
      
      // enable vertexAttribute
      glEnableVertexAttribArray(RWTVertexAttribPosition); 
      // 指定顶点属性在vertex buffer中的偏移,告诉GPU该如何取顶点数据
      glVertexAttribPointer(RWTVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(RWTVertex), (const GLvoid *) offsetof(RWTVertex, Position));
      
      glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
      glDrawArrays(GL_TRIANGLES, 0, 3);
      
      // disable
      glDisableVertexAttribArray(RWTVertexAttribPosition);
      
    }
    

    7.4 Helper Code分析

    接下来看看作者提供的初始化函数的内部,实际内容就是对shader进行compile的操作

    - (void)compileVertexShader:(NSString *)vertexShader
                 fragmentShader:(NSString *)fragmentShader {
      // 载入事先写好的GLSL代码,并编译shader
      GLuint vertexShaderName = [self compileShader:vertexShader
                                           withType:GL_VERTEX_SHADER];
      GLuint fragmentShaderName = [self compileShader:fragmentShader
                                             withType:GL_FRAGMENT_SHADER];
      
      // 创建glProgram,shader导入
      _programHandle = glCreateProgram();
      glAttachShader(_programHandle, vertexShaderName);
      glAttachShader(_programHandle, fragmentShaderName);
      
      glBindAttribLocation(_programHandle, RWTVertexAttribPosition, "a_Position");
      
      // 将glProgram与shader绑定
      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);
      }
    }
    

    看到作者是通过compileShader函数对shader进行compile的,我们看看这个函数内部:

    - (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
      // 载入事先写好的GLSL
      NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:nil];
      NSError* error;
      NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
      if (!shaderString) {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        exit(1);
      }
      
      // 创建shader
      GLuint shaderHandle = glCreateShader(shaderType);
      
      const char * shaderStringUTF8 = [shaderString UTF8String];
      int shaderStringLength = [shaderString length];
      // 将GLSL导入shader中
      glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
      
      // 编译
      glCompileShader(shaderHandle);
      
      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;
    }
    

    整体代码地址可以到作者主页去下载

    8. 总结

    我们来总结下绘制三角形都需要哪些步骤:

    • 配置GLContext
    • 初始化顶点,将其从CPU传输至CPU
    • 通过GLSL编译shader,并绑定只GLProgram上
    • 在glkView回调函数中,渲染背景色,enable vertex attribute(配置顶点在vertexbuffer中的偏移),随后执行绘制

    最终运行效果如下:

    image

  • 相关阅读:
    简单工厂
    Asp.Net Catch的应用
    ADO和ADO.NET的区别
    PLSql语句学习(三)
    三层结构的B/S系统(收藏)
    MVC 模式
    利用.net反射动态调用指定程序集的中的方法
    .NET反射的简单示例
    ToString格式化
    HTML:关于位置的几个概念
  • 原文地址:https://www.cnblogs.com/DaiShuSs/p/14748628.html
Copyright © 2011-2022 走看看