opengl基础

OpenGL 基础摘要

2017-08-03  本文已影响78人  vedon_fu

耐心看完,你将会了解OpenGL 工作的原理(入门级别😄)。

背景知识

图形渲染管线

1 )顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。一个顶点是一个信息集合,包括空间中的位置、顶点的颜色、法线、纹理坐标等。

2 )图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元(三角形、四边形等)。

3)图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。

4)几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。

一个片段是OpenGL渲染一个像素所需的所有数据 :用来更新帧缓存
(frame buffer)中特定位置的一个像素。一个片断除了包含颜色,
还有法线和纹理坐标等属性,这些信息用来计算新的像素颜色值。

本阶段的输出包括:

5) 片段着色器(Fragment Shader)的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

此阶段的输入是经过插值的片断信息。在前一阶段已经通过插值计算了纹理坐标和一个颜色值,这个颜色在本阶段可以用来和纹理元素进行组合。此外,这一阶段还可以进行雾化处理。通常最后的输出是片断的颜色值以及深度信息。

6 )在所有对应颜色值确定以后,最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

注意混合只能在此阶段进行,因为片断纹理化和颜色化阶段不能访问帧缓存。帧缓存只能在此阶段访问。

编码基础

Name
const 限定的变量在编译时不可被修改
attribute 是应用程序传给顶点着色器用的,attribute限定符标记的是一种全局变量,该变量在顶点着色器中是只读(read-only)的,该变量被用作从OpenGL应用程序向顶点着色器中传递参数,因此该限定符仅能用于顶点着色器。
uniform 一般是应用程序用于设定顶点着色器和片断着色器相关初始化值。不允许声明时初始化,uniform限定符标记的是一种全局变量,该变量对于一个图元(primitive)来说是不可更改的 它可以从OpenGL应用程序中接收传递来的参数。
varying 用于传递顶点着色器的值给片断着色器。不允许声明时初始化,它提供了从顶点着色器向片段着色器传递数据的方法,varying限定符可以在顶点着色器中定义变量,然后再传递给光栅化器,光栅化器对数据插值后,再将每个片段的值交给片段着色器。

Demo Code

Demo的流程如下:

  • 创建顶点数据:vertices,里面包含位置,颜色和纹理坐标
  • 把当前View的layerClass改为CAEAGLLayer类型,并初始化。
  • 初始化EAGLContext。它管理着OpenGLES的渲染context,即所有绘制的状态,命令及资源信息,并控制GPU去执行渲染运算。
  • 创建渲染缓冲,调用renderbufferStorage接口函数, 为之前生成的CAEAGLLayer对象分配空间,此空间也是RenderBuffer的存储空间。
  • 创建帧缓冲,把RenderBuffer挂接到对应的帧缓冲,也就是FrameBuffer。
  • 初始化Shader
  • 创建顶点缓冲:vertexBuffer
  • 渲染

渲染结果是先输出到Framebuffer中,然后通过调用context的presentRenderbuffer,将Framebuffer上的内容提交给之前的layer。

更多请查看这里

Vertex Shader

attribute vec4 Position;
attribute vec4 SourceColor;
varying vec4 DestinationColor;
attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

void main(void) {
    DestinationColor = SourceColor;
    gl_Position = Position;
    TexCoordOut = TexCoordIn;
}

Fragment Shader

varying lowp vec4 DestinationColor; 

varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) {
     gl_FragColor = texture2D(Texture, TexCoordOut);
}

GLView

#import "GLView.h"
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>

typedef struct {
    float Position[3];
    float Color[4];
    float TexCoord[2]; // New
} Vertex;


const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, 0}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}},
    {{1, -1, -1}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, -1}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}}
};

const GLubyte Indices[] = {
    0, 1, 2,
    2, 3, 0
};

@interface GLView ()
{
    GLuint _positionSlot;
    GLuint _colorSlot;
    GLuint _floorTexture;
    GLuint _texCoordSlot;
    GLuint _textureUniform;
}
@property (nonatomic,strong) CAEAGLLayer *eagLayer;
@property (nonatomic,strong) EAGLContext *context;
@property (nonatomic,assign) GLuint colorRenderBuffer;
@end

@implementation GLView
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setupLayer];
        [self setupContext];

        //先设置RenderBuffer,再设置FrameBuffer,顺序不能互换
        [self setupRenderBuffer];
        [self setupFrameBuffer];

        [self compileShaders];
        [self setupVBOs];
        
        _floorTexture = [self setupTexture:@"1.png"];
        [self render];
    }
    return self;
}

+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

- (void)setupLayer
{
    self.eagLayer = (CAEAGLLayer *)self.layer;
    self.eagLayer.opaque = YES;
}

- (void)setupContext
{
    EAGLRenderingAPI renderApi = kEAGLRenderingAPIOpenGLES2;
    self.context = [[EAGLContext alloc] initWithAPI:renderApi];
    [EAGLContext setCurrentContext:_eaglContext]; 
}

//RenderBuffer用于存储渲染的内容,renderBuffer对象本身不能直接使用,
//不能挂载到GPU上而直接输出内容的,要使用frameBuffer。
- (void)setupRenderBuffer {

    //创建渲染缓冲
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eagLayer];
}

- (void)setupFrameBuffer {
    GLuint framebuffer;
    //创建帧缓冲
    glGenFramebuffers(1, &framebuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    //glFramebufferRenderbuffer将Renderbuffer挂接到相应的Framebuffer上.
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
}
- (void)render {
    glClearColor(0, 104.0/255.0, 55.0/255.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
   // 从buffer里面读取数据到对应的缓冲区
    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));
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _floorTexture);
    
    
    //glUniform1i设置uniform采样器的位置值,或者说纹理单元。通过glUniform1i的设置,我们保证每个uniform采样器对应着正确的纹理单元
    glUniform1i(_textureUniform, 0);
    
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]),
                   GL_UNSIGNED_BYTE, 0);
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}



- (GLuint)setupTexture:(NSString *)fileName {

    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }

    GLsizei width = (GLsizei)CGImageGetWidth(spriteImage);
    GLsizei height = (GLsizei)CGImageGetHeight(spriteImage);    
    GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte));
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    CGContextRelease(spriteContext);
    
    GLuint texName;
    glGenTextures(1, &texName);
    glBindTexture(GL_TEXTURE_2D, texName);
    /*
     GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,
      而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。
      GL_LINEAR可以产生更真实的输出,但有些开发者更喜欢8-bit风格,
      所以他们会用GL_NEAREST选项。

      当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,
      比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。
      我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。这段代码
      看起来会和纹理环绕方式的设置很相似:
     
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     */
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
    free(spriteData);
    return texName;    
}
- (void)compileShaders {
    
    GLuint vertexShader = [self compileShader:@"Vertex"
                                     withType:GL_VERTEX_SHADER];
    
    GLuint fragmentShader = [self compileShader:@"Fragment"
                                       withType:GL_FRAGMENT_SHADER];

    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    
    /*
     glCreateProgram函数创建一个程序,并返回新创建程序对象的ID引用。
    现在我们需要把之前编译的着色器附加到程序对象上,然后用
     glLinkProgram链接它们
     */
    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);
    }
    
    /*
     得到的结果就是一个程序对象,我们可以调用glUseProgram函数,
     用刚创建的程序对象作为它的参数,以激活这个程序对象:
     在glUseProgram函数调用之后,每个着色器调用和渲染调用都会使用
      这个程序对象(也就是之前写的着色器)了。
     
     对了,在把着色器对象链接到程序对象以后,记得删除着色器对象,
      我们不再需要它们了:
     */

    glUseProgram(programHandle);
    _positionSlot = glGetAttribLocation(programHandle, "Position");
    _colorSlot = glGetAttribLocation(programHandle, "SourceColor");

    //开启对应的顶点属性
    glEnableVertexAttribArray(_positionSlot);
    glEnableVertexAttribArray(_colorSlot);
    
    
    _texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn");
    glEnableVertexAttribArray(_texCoordSlot);
    _textureUniform = glGetUniformLocation(programHandle, "Texture");
}

- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
    
    NSString* shaderPath = [[NSBundle mainBundle] pathForResource:shaderName
                                                           ofType:@"glsl"];
    NSError* error;
    NSString* shaderString = [NSString stringWithContentsOfFile:shaderPath
                                                       encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString) {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        exit(1);
    }

    GLuint shaderHandle = glCreateShader(shaderType);
    const char * shaderStringUTF8 = [shaderString UTF8String];
    int shaderStringLength = (int)[shaderString length];
    
    /*
     glShaderSource函数把要编译的着色器对象作为第一个参数。第二参数
    指定了传递的源码字符串数量,这里只有一个。第三个参数是顶点着色器
    真正的源码,第四个参数我们先设置为NULL。
     */
    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;   
}


- (void)setupVBOs {
    
    GLuint vertexBuffer;
    //vertexBuffer :顶点缓冲对象(Vertex Buffer Objects, VBO,
      它会在GPU内存(通常被称为显存)中储存大量顶点
    glGenBuffers(1, &vertexBuffer);

    //使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,
      从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用
      都会用来配置当前绑定的缓冲(VBO
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

    //把顶点数据从cpu内存复制到gpu内存
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
    
    GLuint indexBuffer;
    //创建索引缓冲对象
    glGenBuffers(1, &indexBuffer);

    //VBO类似,我们先绑定EBO然后用glBufferData把索引复制到缓冲里。
    同样,和VBO类似,我们会把这些函数调用放在绑定和解绑函数调用之间,
    只不过这次我们把缓冲的类型定义为GL_ELEMENT_ARRAY_BUFFER。
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
    
}

@end

参考资料 :
OpenGL Frame Buffer Object
OpenGL Vertex Buffer Object (VBO)
OpenGL Rendering Pipeline
图形流水线
GLSL基础
你好,三角形
FrameBuffer & RenderBuffer relationship
很直观的说明了FrameBuffer 和 RenderBuffer 的关系

上一篇下一篇

猜你喜欢

热点阅读