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就是一个 “状态机” 。那些各种各样的API调用会改变这些状态,或者根据这些状态进行操作。但我们要注意的是,这只是说明了OpenGL是怎样被定义的,但硬件是否是按状态机实现的就是另一回事了。不过,这不是我们需要担心的地方。
OpenGL对象 包含了下面一些类型:Buffer Objects,Vertex Array Objects,Textures,Framebuffer 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 shaders和fragment shaders是每个OpenGL程序必不可少的部分。
像素(pixel) :像素是我们显示器上的最小可见元素。我们系统中的像素被存储在一个帧缓存(framebuffer)中。帧缓存是一块由图形硬件管理的内存空间,用于供给给我们的显示设备。
OpenGL坐标系
OpenGL坐标系不同于UIKit坐标系,其实它是这样的
除了方向,还有一点需要注意,默认情况各个方向坐标值范围为(-1,1)
需要补充一点,默认情况下,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修改ViewController中view的类型为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。从预定义的数组中可看出,每行有8个GL_FLOAT浮点值,而GL_FLOAT占4个字节,因此每一行的跨度是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来组成一个三角形。完成后,在用下一组的三个顶点(顶点4,5,6)来组成三角形,直到数组结束。
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的HelloWorld"通常是指一个简单的程序,它使用OpenGL库来在屏幕上显示文本“Hello, World!”,以此作为学习OpenGL编程的起点。 在开始之前,你需要确保已经安装了必要的开发环境,包括OpenGL库、GLUT...
"OpenGL Hello World"通常是指使用OpenGL库编写的一个简单的示例程序,它标志着开发者开始接触并理解OpenGL的基本概念和操作。这个"OpenGL红宝书"指的是经典的《OpenGL Programming Guide》,这本书是学习OpenGL的...
"OpenGL HelloWorld"通常是初学者接触这个图形库时编写的第一个程序,它旨在展示如何设置基本的OpenGL环境并绘制出简单的图形。在这个Xcode版本的OpenGL HelloWorld中,我们将深入探讨如何在苹果的开发环境中配置和...
在这个"Hello Window OpenGL 第一个窗口"的教程中,我们将探讨如何创建并显示一个基本的OpenGL窗口。 首先,你需要了解的是OpenGL并不直接处理窗口系统。在Windows系统中,我们通常使用GLUT(OpenGL Utility ...
根据提供的信息,我们可以总结出以下关于“OpenGL HelloWorld”程序的关键知识点: ### 1. OpenGL简介 OpenGL(Open Graphics Library)是一种用于渲染2D、3D矢量图形的跨语言、跨平台的应用程序编程接口(API)。...
在本例中,可能是简单的"hello, world"文本,或者是一个几何形状,如三角形。 5. **顶点坐标和投影**:在OpenGL中,我们需要定义顶点坐标,并可能使用视口变换和投影矩阵来将3D坐标转换为2D屏幕坐标。 6. **帧缓冲...
"webgl-3d-helloworld"项目是一个基础教程或示例,教你如何使用JavaScript和WebGL创建一个简单的3D“Hello, World!”程序。 在描述中提到的"javascript 实现的 3d helloworld",意味着这个项目将通过JavaScript代码...
本文将详细介绍安卓 OpenGL ES 2.0 的基本概念和 hello world 项目,帮助读者快速入门 OpenGL ES 2.0 的世界。 基本概念 OpenGL ES 2.0 是一种用于移动设备的图形处理 API,能够提供高性能的图形处理能力。OpenGL ...
一个用opengl着色语言(glsl)编写的犹他壶,并且可以通过鼠标点动沿三个坐标轴转动
在本实例中,"gdx-helloworld-0.9.1"是一个基于LibGDX的游戏入门项目,非常适合初学者学习和理解LibGDX的基本用法。 LibGDX的核心组件包括以下几个部分: 1. **Backend**:LibGDX提供了不同平台的后端支持,如...
"Vulkan Hello World"是学习Vulkan API的基础教程,通过一个简单的程序来展示Vulkan的基本用法和3D渲染流程。下面我们将详细讨论Vulkan API的使用以及"Vulkan Hello World"中的关键概念。 1. **Vulkan API基础**:...
"opengl.rar_world"这个标题暗示我们讨论的是如何在QT环境下用C++实现一个基础的OpenGL“Hello, World!”程序。 首先,要开始使用OpenGL,我们需要在QT项目中包含必要的库。这通常通过在.pro文件中添加`QT += ...
// Hello World_Code // #import #import "AppDelegate.h" int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }...
【vvvv的演示程序:HELLO WORLD】 vvvv是一款基于节点图界面的视觉编程语言,主要应用于实时图像处理、互动艺术以及多媒体应用开发。在这个"HELLO WORLD"演示程序中,我们将深入探讨vvvv的基本概念、工作流程以及...
Hello-GLUT:一个非常简单的“ Hello World!” GLUT应用程序演示了如何使用MinGW和MSVC用C编写OpenGL应用程序
hello-gl:OpenGL 2.0中的“ Hello World”
OpenGL的“Hello World”示例通常涉及到以下几个步骤: 1. **初始化OpenGL环境**:设置OpenGL版本、启用必要的扩展等。 2. **创建窗口**:使用GLUT或其他窗口库创建一个窗口。 3. **定义渲染函数**:编写一个简单的...
学习JOGL时,可以从简单的“Hello, World”示例开始,逐步扩展到更复杂的项目。例如,创建一个旋转的立方体,展示基本的图形绘制和变换操作。随着技能的提升,可以尝试实现光照、纹理映射、动画效果等。 **7. 社区...