欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenGL = Hello World

程序员文章站 2022-06-29 11:51:05
...

 OpenGL中的大多数函数使用了一种 基于状态 的方法,大多数OpenGL对象都需要在使用前把该对象绑定到context上。这里有两个新名词——OpenGL对象和Context

 

Context

Context是一个非常抽象的概念,我们姑且把它理解成一个包含了所有OpenGL状态的对象。如果我们把一个Context销毁了,那么OpenGL也不复存在。

 

OpenGL对象

我们可以把OpenGL对象理解成一个状态的集合,它负责管理它下属的所有状态。当然,除了状态,OpenGL对象还会存储其他数据。注意。这些状态和上述context中的状态并不重合,只有在把一个OpenGL对象绑定到context上时,OpenGL对象的各种状态才会映射到context的状态。因此,这时如果我们改变了context的状态,那么也会影响这个对象,而相反地,依赖这些context状态的函数也会使用存储在这个对象上的数据。

因此,OpenGL对象的绑定既可能是为了修改该对象的状态(大多数对象需要绑定到context上才可以改变它的状态),也可能是为了让context渲染时使用它的状态。

 

画了一个图,仅供理解。图中灰色的方块代表各种状态,箭头表示当把一个OpenGL对象绑定到context上后,对应状态的映射。


OpenGL = Hello World
            
    
    博客分类: OpenGL
 

 

 

 

OpenGL就是一个 “状态机” 。那些各种各样的API调用会改变这些状态,或者根据这些状态进行操作。但我们要注意的是,这只是说明了OpenGL是怎样被定义的,但硬件是否是按状态机实现的就是另一回事了。不过,这不是我们需要担心的地方。

OpenGL对象 包含了下面一些类型:Buffer ObjectsVertex Array ObjectsTexturesFramebuffer Objects等等。我们下面会讲到 Vertex Array Objects这个对象。

这些对象都有三个相关的重要函数:

        void glGen*(GLsizei n, GLuint *objects);

负责生成一个对象的name。而name就是这个对象的引用。

void glDelete*(GLsizei n, const GLuint *objects);

负责销毁一个对象。

void glBind*(GLenum target, GLuint object);

将对象绑定到context上。

关于OpenGL对象还有很多内容,这里就不讲了。可以参见 官方wiki  

在开始第一个程序之前,我们还要了解一些图形名词。

渲染(Rendering :计算机从模型到创建一张图像的过程。OpenGL仅仅是其中一个渲染系统。它是一个基于光栅化的系统,其他的系统还有光线追踪(但有时也会用到OpenGL)等。

模型(Models)或者对象(Objects :这里两者的含义是一样的。指从几何图元——点、线、三角形中创建的东西,由顶点指定。

Shaders :这是一类特殊的函数,是在图形硬件上执行的。 我们可以理解成,Shader是一些为图形处理单元(GPU)编译的小程序。OpenGL包含了编译工具来把我们编写的Shader源代码编译成可以在GPU上运行的代码。在OpenGL中,我们可以使用四种shader阶段。最常见的就是vertex shaders——它们可以处理顶点数据;以及fragment shaders,它们处理光栅化后生成的fragments vertex shadersfragment shaders是每个OpenGL程序必不可少的部分。

像素(pixel :像素是我们显示器上的最小可见元素。我们系统中的像素被存储在一个帧缓存(framebuffer)中。帧缓存是一块由图形硬件管理的内存空间,用于供给给我们的显示设备。

 

OpenGL坐标系

OpenGL坐标系不同于UIKit坐标系,其实它是这样的


OpenGL = Hello World
            
    
    博客分类: OpenGL
 

 

除了方向,还有一点需要注意,默认情况各个方向坐标值范围为(-11

 

 

需要补充一点,默认情况下,GLKViewController渲染RunLoop并非NSRunLoopCommonModes,而是NSDefaultRunLoopMode,因此在UIKit中使用GLKViewController,当滑动界面时,OpenGL是不会渲染的。

可以自定义修改:

#import <UIKit/UIKit.h>

#import <GLKit/GLKView.h>

 

@class CADisplayLink;

 

@interface HJGLKViewController : UIViewController

 

@property (nonatomic, readonly) GLKView *glkView;

 

@property (nonatomic) NSInteger preferredFramesPerSecond;

 

@property (nonatomic, getter=isPaused) BOOL paused;

 

@end

 

 

 

#import "HJGLKViewController.h"

 

static const NSInteger HJGLKDefaultFramesPerSecond = 30;

 

@interface HJGLKViewController () <GLKViewDelegate>

 

@property (nonatomic, strong) GLKView       *glkView;

@property (nonatomic, strong) CADisplayLink *displayLink;

 

@end

 

@implementation HJGLKViewController

 

- (void)dealloc {

    self.paused = YES;

}

 

- (void)viewDidLoad {

    [super viewDidLoad];

    

    self.view.backgroundColor = [UIColor whiteColor];

    self.glkView = [[GLKView alloc] initWithFrame:self.view.frame];

    self.glkView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    self.glkView.delegate = self;

    [self.view addSubview:self.glkView];

    

    self.preferredFramesPerSecond = HJGLKDefaultFramesPerSecond;

    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];

    self.displayLink.frameInterval = MAX(1, 60.0f / _preferredFramesPerSecond);

    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

}

 

#pragma mark -

- (void)setPreferredFramesPerSecond:(NSInteger)preferredFramesPerSecond {

    _preferredFramesPerSecond = preferredFramesPerSecond;

    self.displayLink.frameInterval = MAX(1, 60.0f / _preferredFramesPerSecond);

}

 

- (BOOL)isPaused {

    return self.displayLink.paused;

}

 

- (void)setPaused:(BOOL)paused {

    self.displayLink.paused = paused;

}

 

- (void)drawView {

    [self update];

    [self.glkView display];

}

 

- (void)update {

    

}

 

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

    

}

 

@end

 

 

 

HelloWorld的实现过程:

1. 控制器

 

: openGL里面很多的属性值和宏都是在ios的基础上加了GL前缀,所以当你看到GL_TRUE, GL_FLOAT的时候,其实他就是true float 别被吓到。

 

 

为了简便省时,我决定直接在ViewController中修改代码,首先我们先导入GLKit.h头文件,紧接着那个将ViewController的类型修改为GLKViewController.

 

 

 

然后在Main.storyboard修改ViewControllerview的类型为GLKView.如图所示.

 

 

 

上面的准备工作已经是做完了,那么接下来,就是正题部分了,我们现在ViewController.m中声明两个属性.一个是OpenGL ES 上下文属性的EAGLContext对象,一个是矩阵相关的GLKBaseEffect对象.

2. 两个控件

//openGL渲染上下文, 请类比CGContextRef

@property(nonatomic,strong)EAGLContext *mContext;

//这个东西主要负责视觉效果

@property(nonatomic,strong)GLKBaseEffect *mEffect;

 

通过官方的API文档,我们知道,EAGLContext对象管理一个OpenGL ES渲染环境状态信息,命令,以及使用OpenGL ES的所需要资源。OpenGL ES执行任何命令之前,都需要通过EAGLContext对象来实现。同时官方文档也提到,绘制一个上下文之前,你必须完成framebuffer对象绑定到上下文。

GLKBaseEffect这个类实现了OpenGL ES 1.0公共(common)的shading行为,简化(从1.0)到OpenGL ES 2.0的转化。它们也提供了让光和纹理(lighting and texturing)工作的简单方法。GLKBaseEffect对象提供了着色器这一功能.其实着色器应该算的上是OpenGL ES的一大特色,但是GLKBaseEffect已经包含了这一功能,相当于封装了着色器.现在只是知道有着色器就行.

 

3. 初始化,配置OpenGL ES 上下文信息

 //设置当前使用的context使用的api版本

    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

    //设置当前的context

    [EAGLContext setCurrentContext:self.context];

 

    //设置视图的渲染属性

    GLKView *view = (GLKView *)self.view;

    view.context = self.context;

    //颜色渲染格式

    view.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;//默认值可以不设置

    //模板渲染格式

    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;

 

 

    //初始化视觉管理对象

    self.effect = [[GLKBaseEffect alloc] init];

    //光照效果

    self.effect.light0.enabled = GL_TRUE;

    //设置光照颜色 RGBA格式

    self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 1.0f, 1.0f, 1.0f);

 

 

iOS支持的OpenGL版本

typedef NS_ENUM(NSUInteger, EAGLRenderingAPI)

{

    kEAGLRenderingAPIOpenGLES1 = 1,

    kEAGLRenderingAPIOpenGLES2 = 2,

    kEAGLRenderingAPIOpenGLES3 = 3,

};

 

4. 图形

因为本文的目标是实现一个简单的静态图形,所以构造如下一个图形

    //从左到右, 依次是  顶点X, Y, Z, 法线X, Y, Z, 纹理S, T

    //顶点位置用于确定在什么地方显示,法线用于光照模型计算,纹理则用在贴图中。

    GLfloat squareVertexData[48] = {

        0.5f, 0.5f, -0.9f,      0.0f, 0.0f, 1.0f,       1.0f, 1.0f,

        -0.5f, 0.5f, -0.9f,     0.0f, 0.0f, 1.0f,       0.0f, 1.0f,

        0.5f, -0.5f, -0.9f,     0.0f, 0.0f, 1.0f,       1.0f, 0.0f,

        0.5f, -0.5f, -0.9f,     0.0f, 0.0f, 1.0f,       1.0f, 0.0f,

        -0.5f, 0.5f, -0.9f,     0.0f, 0.0f, 1.0f,       0.0f, 1.0f,

        -0.5f, -0.5f, -0.9f,    0.0f, 0.0f, 1.0f,       0.0f, 0.0f,

    };

GLfloat的一个数组,其实就是一个 float的数组,数组中的值对应的概念看注释
注意,此时手机/模拟器的原点是在屏幕的中心位置,我们先搞平面效果,所以暂时不考虑Z
只关注每一行的前2个坐标点. 可以在坐标轴上大概画出这个图形,是一个正方形
至于为什么会有重复点,在OpenGL ES只能绘制三角形,不能绘制多边形,但是在OpenGL中确实可以直接绘制多边形.

纹理坐标系的取值范围是[0, 1],原点是在左下角。故而点(0, 0)在左下角,点(1, 1)在右上角。

 

5.设置渲染缓冲区

设置一个渲染的缓冲区域,概念不清楚的请类比于一个缓存, context对象要从这个缓存里面拿数据来进行渲染.

    //声明一个缓冲区的标识(GLuint类型)

    GLuint buffer;

    //OpenGL自动分配一个缓冲区空间

    glGenBuffers(1, &buffer);

    //绑定这个缓冲区到当前“Context

    glBindBuffer(GL_ARRAY_BUFFER, buffer);

    //将我们前面预先定义的顶点数据“squareVertexData”复制进这个缓冲区中。

    //注:参数“GL_STATIC_DRAW”,它表示此缓冲区内容只能被修改一次,但可以无限次读取。

    glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);

一个与当先context绑定的缓冲区被分配出来,接下来要渲染什么图形,只需要把对应的数据放到缓冲区,这样context会在程序运行的时候不断地从缓冲区拿数据然后渲染到界面上.

 

 

glGenBuffers(GLsizei n,GLuint *buffers):任何非零的无符合整数都可以作为缓冲区对象的标识符使用。这个函数的作用就是向系统申请n个缓冲区,系统把这n个缓冲区的标识符都放进buffers数组中。还可以调用glIsBuffer()函数判断一个标识符是否正被使用。
例如,glGenBuffers(1, &index);这是是向系统申请1个缓冲区,标识符为index.

glBindBuffer(GLenum target, GLuint buffer) :把这个缓冲区绑定给顶点还是索引.通俗点,也就是定义了这个缓冲区存储的是什么.target用于决定绑定的是顶点数据(GL_ARRAY_BUFFER)还是索引数据(GL_ELEMENT_ARRAY_BUFFER).

glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage):CPU中的内存中的数组复制到GPU的内存中.target用于决定绑定的是顶点数据(GL_ARRAY_BUFFER)还是索引数据(GL_ELEMENT_ARRAY_BUFFER).size决定数据的存储长度.data则是数据信息.usage表示数据的读写方式,是一个枚举类型,这里使用的是GL_STATIC_DRAW,它表示此缓冲区内容只能被修改一次,但可以无限次读取。

 

 

6. 填充数据进缓冲区

上述声明只是告诉context要渲染一个名字叫做squareVertexData的图形,但是具体怎么渲染并没有告诉context,接下来要做的就是告诉context如何渲染,注意:索引数据不需要复制到通用顶点属性中.

因为图形坐标包括了位置,法线,纹理. 所以要分别把三部分添加到context的缓冲区.

    /**

     填充数据,参数含义分别为:

 

     顶点属性索引(这里是位置/法线/纹理)、

     3个分量的矢量、

     类型是浮点(GL_FLOAT)、

     填充时不需要单位化(GL_FALSE)、

     在数据数组中每行的跨度是32个字节(4*8=32。从预定义的数组中可看出,每行有8GL_FLOAT浮点值,而GL_FLOAT4个字节,因此每一行的跨度是4*8)。

     最后一个参数是一个偏移量的指针,用来确定“第一个数据”将从内存数据块的什么地方开始。

     */

 

 

    //位置

    glEnableVertexAttribArray(GLKVertexAttribPosition);

    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 4*8, (char *)NULL + 0);

 

    //法线

    glEnableVertexAttribArray(GLKVertexAttribNormal);

    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 4*8, (char *)NULL + 12);

 

    //纹理

    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);

    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, 4*8, (char *)NULL + 24);

    //加载纹理内容

    //GLKit加载纹理,默认都是把坐标设置在“左上角”。然而,OpenGL的纹理贴图坐标却是在左下角,这样刚好颠倒。

    NSDictionary *options = @{ GLKTextureLoaderOriginBottomLeft : @YES };

    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"222.jpg" ofType:nil];

    GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];

    self.effect.texture2d0.enabled = GL_TRUE;

    self.effect.texture2d0.name = textureInfo.name;

 

7. 开始渲染

准备工作搞定,剩下的就是要开始渲染了.

//渲染代码放在这里

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {

    glClearColor(0.3f, 0.6f, 1.0f, 1.0f);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

    [self.effect prepareToDraw];

    glDrawArrays(GL_TRIANGLES, 0, 6);

 

 

}

//场景变化放在这里

- (void)update {

 

    //修改投影矩阵

    //加下面的代码,是因为如果不加,默认的屏幕长宽比和openGL的长宽比不一致,会导致有一个方向被拉伸

    CGSize size = self.view.bounds.size;

    //算出屏幕的纵横比

    float aspect = fabs(size.width / size.height);

 

    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0), aspect, 0.1f, 10.0f);

    self.effect.transform.projectionMatrix = projectionMatrix;

 

    GLKMatrix4 modelViewMatrix = GLKMatrix4Translate(GLKMatrix4Identity, 0.0f, 0.0f, -1.0f);

    self.effect.transform.modelviewMatrix = modelViewMatrix;

}

 

 

 

  • glClearColor (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) : 渲染前的“清除”操作,指定在清除屏幕之后填充什么样的颜色.四个参数就是RGB.
  • glClear (GLbitfield mask) :指定需要清除的缓冲.mask指定缓冲的类型.可以使用 | 运算符组合不同的缓冲标志位,表明需要清除的缓冲.可以使用以下标识符.

GL_COLOR_BUFFER_BIT: 当前可写的颜色缓冲

 

GL_DEPTH_BUFFER_BIT: 深度缓冲

 

GL_ACCUM_BUFFER_BIT: 累积缓冲

 

GL_STENCIL_BUFFER_BIT: 模板缓冲

 

 

[self.mEffect prepareToDraw];这个就是启动当前GLKBaseEffect对象.

  • glDrawElements (GLenum mode, GLsizei count, GLenum type, const GLvoid* indices) : 通过顶点索引绘制图像.mode指定的绘制的类型.类型展示如下,这里使用的是GL_TRIANGLES,count指定的顶点索引数组中元素的个数,type 为索引数组(indices)中元素的类型,只能是下列值之一: GL_UNSIGNED_BYTE ,GL_UNSIGNED_SHORT,GL_UNSIGNED_INT. indices指向索引数组的指针。

GL_POINTS:  单独的将顶点画出来。

 

GL_LINES:  单独地将直线画出来。

 

GL_LINE_STRIP:  连贯地将直线画出来。

 

GL_LINE_LOOP:  连贯地将直线画出来。行为和GL_LINE_STRIP类似,但是会自动将最后一个顶点和第一个顶点通过直线连接起来。

 

GL_TRIANGLES:这个参数意味着OpenGL使用三个顶点来组成图形。所以,在开始的三个顶点,将用顶点1,顶点2,顶点3来组成一个三角形。完成后,在用下一组的三个顶点(顶点456)来组成三角形,直到数组结束。

 

GL_TRIANGLE_STRIP:  OpenGL的使用将最开始的两个顶点出发,然后遍历每个顶点,这些顶点将使用前2个顶点一起组成一个三角形。

 

 

GL_TRIANGLE_FAN:  在跳过开始的2个顶点,然后遍历每个顶点,让OpenGL将这些顶点于它们前一个,以及数组的第一个顶点一起组成一个三角形。

 

 

http://www.cocoachina.com/game/20141127/10335.html

http://www.olinone.com/?p=308

http://www.jianshu.com/p/e04edce75fbf

http://www.jianshu.com/p/353b5496e494

http://www.jianshu.com/notebooks/2135411/latest

  • OpenGL = Hello World
            
    
    博客分类: OpenGL
  • 大小: 50.6 KB
  • OpenGL = Hello World
            
    
    博客分类: OpenGL
  • 大小: 15.4 KB