重新自学学习openGL 之第一个简单openGL 工程

以前也写过openGL 简单的理论知识,可是真要看openGL 相关人写的代码的时候,发现自己还是太菜,因此,想写个完整的一系列博客来记录下学习的点滴.

创建第一个工程

工程主要代码

#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@interface FirstViewController : GLKViewController

@end

NS_ASSUME_NONNULL_END


#import "FirstViewController.h"
#import "OpenGLUtilsHeader.h"
@interface FirstViewController ()
@property (nonatomic ,strong) EAGLContext * eagcontext;
@property (nonatomic ,assign) GLuint  program;
@property (nonatomic ,strong) Shader * shader ;
@property (nonatomic ,assign) GLint vertexcolor;
@property (nonatomic ,strong) Vertex * vertex ;
@end

@implementation FirstViewController



-(void)createEagContext{
    self.eagcontext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:self.eagcontext];
}

-(void)configure{
    GLKView *view = (GLKView*)self.view;
    view.context = self.eagcontext;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
}

-(void)createShader{
    self.shader = [Shader new];
    [self.shader compileLinkSuccessShaderName:@"Shader" completeBlock:^(GLuint program) {
                glBindAttribLocation(program, 0, "position");  // 0代表枚举位置
                _vertexcolor =  glGetUniformLocation(program, "color");
    }];

}

-(void)loadVertex{
    self.vertex = [Vertex new];
    [self.vertex allocVertexNum:3 andEachVertexNum:2];
    GLfloat vertex[2];
    vertex[0]=1;
    vertex[1]=0;
    [self.vertex setVertex:vertex index:0];
    vertex[0]=0;
    vertex[1]=1;
    [self.vertex setVertex:vertex index:1];
    vertex[0]=0;
    vertex[1]=0;
    [self.vertex setVertex:vertex index:2];
    
    [self.vertex bindBufferWithUsage:GL_STATIC_DRAW];
    [self.vertex enableVertexInVertexAttrib:GLKVertexAttribPosition numberOfCoordinates:2 attribOffset:0];
    
}




-(void)viewDidLoad{
    [super viewDidLoad];
    [self createEagContext];
    [self configure];
    [self createShader];
    [self loadVertex];
    
}

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
    static NSInteger count = 0;
    // 清除颜色缓冲区
    glClearColor(1, 1, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    count ++;
    if (count > 50 ) {
        count = 0;
        // 根据颜色索引值,设置颜色数据,就是刚才我们从着色器程序中获取的颜色索引值
        glUniform4f(_vertexcolor,   arc4random_uniform(255)/225.0, arc4random_uniform(255)/225.0, arc4random_uniform(255)/225.0, 1);
    }
    // 使用着色器程序
    glUseProgram(self.shader.program);
    // 绘制
    [self.vertex drawVertexWithMode:GL_TRIANGLES startVertexIndex:0 numberOfVertices:3];
}


@end


//Shader.fsh

varying lowp vec4 colorVarying;
void main()
{
    gl_FragColor = colorVarying; // 设置片段着色器中的颜色
}

//Shader.vsh
attribute vec4 position; // 属性,如果绑定了这个属性,
uniform  vec4 color; // 需要从程序中传入的值,一会我们会不断改变这个值
varying lowp vec4 colorVarying; // 这个值需要和片段着色器的声明相同

void main()
{
    gl_Position = position; // 设置顶点位置
    colorVarying = color;  // 设置顶点颜色
}

//OpenGLUtilsHeader.h

#ifndef OpenGLUtilsHeader_h
#define OpenGLUtilsHeader_h
#import "Shader.h"
#import "Vertex.h"
#endif /* OpenGLUtilsHeader_h */

#import 
#import 
NS_ASSUME_NONNULL_BEGIN



@interface Shader : NSObject
@property (nonatomic ,readonly ) GLuint program;

-(BOOL)compileLinkSuccessShaderName:(NSString *)shader completeBlock:(void(^)(GLuint program))completeBlock;

@end

NS_ASSUME_NONNULL_END

#import "Shader.h"

@interface Shader ()
@property (nonatomic ,readwrite) GLuint program;
@end

@implementation Shader

- (instancetype)init
{
    self = [super init];
    if (self) {
         self.program = glCreateProgram();
    }
    return self;
}

-(BOOL)compileLinkSuccessShaderName:(NSString *)shader completeBlock:(void(^)(GLuint program))completeBlock
{
    NSURL *vertShaderURL, *fragShaderURL;
    GLuint vertShader, fragShader;
    GLuint luint =self.program;
    vertShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) {
        NSLog(@"Failed to compile vertex shader");
        return NO;
    }
    
    // Create and compile fragment shader.
    fragShaderURL = [[NSBundle mainBundle] URLForResource:shader withExtension:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) {
        NSLog(@"Failed to compile fragment shader");
        return NO;
    }
    // Attach vertex shader to program.
    glAttachShader(luint, vertShader);
    // Attach fragment shader to program.
    glAttachShader(luint, fragShader);
    if (![self linkProgram:luint]) {
        NSLog(@"Failed to link program: %d", luint);
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (luint) {
            glDeleteProgram(luint);
            luint = 0;
        }
        
        return NO;
    }
    completeBlock(luint);
    glUseProgram(luint);
    
    return YES;
}



- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type URL:(NSURL *)URL
{
    NSError *error;
    NSString *sourceString = [[NSString alloc] initWithContentsOfURL:URL encoding:NSUTF8StringEncoding error:&error];
    if (sourceString == nil) {
        NSLog(@"Failed to load vertex shader: %@", [error localizedDescription]);
        return NO;
    }
    
    GLint status;
    const GLchar *source;
    source = (GLchar *)[sourceString UTF8String];
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return NO;
    }
    
    return YES;
}

-(BOOL)linkProgram:(GLuint)prog
{
    GLint status;
    glLinkProgram(prog);
    
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program link log:\n%s", log);
        free(log);
    }
#endif
    
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}

- (BOOL)validateProgram:(GLuint)prog
{
    GLint logLength, status;
    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program validate log:\n%s", log);
        free(log);
    }
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    return YES;
}

@end

#import 
#import 
NS_ASSUME_NONNULL_BEGIN

@interface Vertex : NSObject


-(void)allocVertexNum:(GLsizei)vertexNum andEachVertexNum:(GLsizei)eachVertexNum;
-(void)setVertex:(CGFloat *)vertex index:(NSInteger)index;
-(void)releaseVertex;

-(void)bindBufferWithUsage: (GLenum) usage;
-(void)enableVertexInVertexAttrib:(GLuint)index   numberOfCoordinates:(GLint)count attribOffset:(GLsizeiptr)offset;
-(void)drawVertexWithMode:(GLenum)mode  startVertexIndex:(GLint)first
         numberOfVertices:(GLsizei)count;
@end

NS_ASSUME_NONNULL_END
#import "Vertex.h"

@interface Vertex()

@property (nonatomic ,assign)   GLfloat  *vertex; ;
@property (nonatomic ,assign) GLsizei vertexNum ;
@property (nonatomic ,assign) GLsizei eachVertexNum ;
@property (nonatomic, assign) GLuint vertexBuffers;
@property (nonatomic ,assign) GLenum usage ;
@end

@implementation Vertex

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self _customInit];
    }
    return self;
}

-(void)_customInit{
     glGenBuffers(1, &_vertexBuffers);
}

-(NSInteger)getAllocSpaceByteNum{
    return self.getVertexWidth*self.vertexNum;
}
-(GLsizei)getVertexWidth{
    return sizeof(GLfloat) * self.eachVertexNum;
}

-(void)allocVertexNum:(GLsizei)vertexNum andEachVertexNum:(GLsizei)eachVertexNum{
    [self releaseVertex];
    self.vertexNum = vertexNum;
    self.eachVertexNum = eachVertexNum;
     self.vertex =(GLfloat*)malloc(self.getAllocSpaceByteNum);
    memset( self.vertex, 0,  self.getAllocSpaceByteNum);
}

-(void)setVertex:(GLfloat *)vertex index:(NSInteger)index{
    if (self.vertex) {
        NSInteger offset = index * self.eachVertexNum;
        for (NSInteger i = 0; i=
             ((first + count) *sizeof(GLfloat) * self.eachVertexNum),
             @"Attempt to draw more vertex data than available.");
     glDrawArrays(mode, first, count);
}

- (void)dealloc
{
    [self releaseVertex];
}

@end


工程运行结果

重新自学学习openGL 之第一个简单openGL 工程_第1张图片
image.png

工程源码地址

github

工程解释

在第一个工程里面,我们只是绘制了一个三角形,并且不停的变化三角形的颜色.

这里我们看看我们的第一个工程里面都干了些啥事情

声明OpenGL VC

我们首先创建了GLKViewController 类型的vc.该类会定时刷新屏幕回调函数是-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect

创建上下文 和配置view

我们必须给openGL 创建OpenGL使用的上下文EAGLContext .并且设置为当前上下文.并且给当前view 使用当前上下文

这里其实就是配置OpenGL 环境而已

配置shader

shader 是OpenGL 绘制所必须依赖的.
这里我们需要知道shader 需要经过编译连接过程.和编译型语言很相似.

顶点设置

接着我们再设置顶点相关信息,我们配置了三个顶点.

绘制

我们根据shader 和顶点 进行再屏幕绘制.

简单工程可学习知识点

顶点

顶点是啥?
顶点就是坐标位置,不管你是画直线,三角形,正方体,球体,以及3D游戏人物等,都需要顶点来确定其形状。

如何绘制顶点

涉及到的api如下

  • glGenBuffers (GLsizei n, GLuint* buffers)

生成buffer指针

  • glBindBuffer (GLenum target, GLuint buffer)

绑定buffer指针指向的内存区域

  • glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage)

给内存区域赋值

  • glEnableVertexAttribArray (GLuint index)

使能顶点属性

  • glVertexAttribPointer (GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* ptr)

设置顶点该属性使用的是内存中顶点的那些值

  • glDrawArrays (GLenum mode, GLint first, GLsizei count)

绘制顶点

具体模型如下


重新自学学习openGL 之第一个简单openGL 工程_第2张图片

glBindBuffer绑定的buffer 是GL_ARRAY_BUFFER. 这里还有其他的buffer GL_ELEMENT_ARRAY_BUFFER ,GL_ARRAY_BUFFER_BINDING ,GL_ELEMENT_ARRAY_BUFFER_BINDING.这里不探讨几种buffer.等积累到一定知识再来讨论这几种buffer.

glBufferData 写入数据的时候,我们有个参数是GL_STATIC_DRAW
该参数还有以下几个值GL_STREAM_DRAW ,GL_DYNAMIC_DRAW

这些值什么意思呢?
"Static”意味着数据不会被改变(一次修改,多次使用),"dynamic”意味着数据可以被频繁修改(多次修改,多次使用),"stream”意味着数据每帧都不同(一次修改,一次使用)。"Draw”意味着数据将会被送往GPU进行绘制,"read”意味着数据会被用户的应用读取,"copy”意味着数据会被用于绘制和读取。注意在使用VBO时,只有draw是有效的,而copy和read主要将会在像素缓冲区(PBO)和帧缓冲区(FBO)中发挥作用。

因此这里我们需要根据顶点的数据特点选择特定的样式. 我们的demo中由于只是简单的绘制三角形,没有必要进行更改就选择了GL_STATIC_DRAW 模式.

在demo中我们传入的顶点属性是 GLKVertexAttribPosition ,顶点属性还有以下值GLKVertexAttribNormal,GLKVertexAttribColor,GLKVertexAttribTexCoord0,GLKVertexAttribTexCoord1

GLKVertexAttribPosition 代表顶点在屏幕上的具体位置
GLKVertexAttribNormal 代表法向量
GLKVertexAttribColor 顶点的颜色
GLKVertexAttribTexCoord0 0位置贴图
GLKVertexAttribTexCoord1 1 位置贴图

具体的效果,在后面的章节研究.

glDrawArrays 中我们传入了GL_TRIANGLE_FAN ,该值代码点连线的方式. 还有以下其他方式GL_POINTS ,GL_LINE_LOOP ,GL_LINE_STRIP, GL_TRIANGLES,GL_TRIANGLE_FAN .这些模式具体的作用在后面的章节具体细讲.

shader 的介绍

shader 分为顶点着色器 和片段着色器.每种着色器都需要经过编译和连接两个步骤

主要api

+glCreateProgram (void)

生成program .其实就是一个指针地址,代表着色器

  • glCreateShader (GLenum type)

创建shader 值是 GL_VERTEX_SHADER 或者GL_FRAGMENT_SHADER

  • glShaderSource (GLuint shader, GLsizei count, const GLchar* const string, const GLint length)

给shader 赋值

  • glCompileShader (GLuint shader)

编译shader

  • glDeleteShader (GLuint shader)

要是shader 编译不成功需要删除shader

  • glAttachShader (GLuint program, GLuint shader)

绑定shader 到指定的标志位上.

  • glLinkProgram (GLuint program)

连接

  • glUseProgram (GLuint program)

使用着色器

具体图如下


重新自学学习openGL 之第一个简单openGL 工程_第3张图片
image.png

这里不探讨着色器的具体写法.后面有专有章节学习.

你可能感兴趣的:(重新自学学习openGL 之第一个简单openGL 工程)