一般咱们学习一种新的语言,创立的工程都是从Hello world开端的。不过OpenGL ES一般是从制作一个三角形开端的。Demo
屏幕图画的显现原理
-
- 首要画三角形,假如是在一张白纸上画。那画画的过程是这样的:
1.确定3个极点(不在同一直线上) 2.把它们用线段衔接起来 3.涂色(要是有色彩的话)
-
- 那OpenGL 制作三角形的过程又是怎样的呢? 咱们先简略的来了解一下 OpenGL 的作业流程
那么大致过程和白纸绘图是一样的便是:
1.极点数据(确定3个极点) 2.传输给极点着色器,然后进行图元装备(相当于:把它们用线段衔接起来) 3.然后进行光栅化和插值(涂色)
光栅化便是将图元转化成一个二维片段的过程,而这些转化的片段将由片元着色器处理,这些二维片段便是屏幕上可制作的像素。 OpenGL 能够制作三种根本元素:点、线、 三角形 三角形是计算机图形学中常用的根本形状图元,(个人了解:能够说计算机制作的一切多边形和柱体,都是由三角形组成的)
- 3.代码完成 这儿先用GLKit完成,因为GLKit苹果封装了极点着色器和片元着色器,对于刚入门的咱们了解比较简略。
- 1.设置图层
//1.设置图层
func setupLayer() {
//创立一个OpenGL ES上下文并将其分配给从storyboard加载的视图
//注意⚠️:这儿需求把storyboard的View记得添加为GLKView, 还有把ViewController承继自: GLKViewController
glkView = self.view as? GLKView
//装备视图创立的烘托缓冲区
/*
OpenGL ES 有一个缓存区,它用以存储将在屏幕中显现的色彩。你能够运用其特点来设置缓冲区中的每个
像素的色彩格局。
默许:GLKViewDrawableColorFormatRGBA8888,即缓存区的每个像素的最小组成部分(RGBA)运用
8个bit,(所以每个像素4个字节,4*8个bit)。
GLKViewDrawableColorFormatRGB565,假如你的APP允许更小规模的色彩,即可设置这个。会让你的
APP消耗更小的资源(内存和处理时刻)
*/
glkView.drawableColorFormat = .RGBA8888
/*
OpenGL ES 另一个缓存区,深度缓冲区。协助咱们保证能够更挨近观察者的方针显现在远一些的方针前面。
(离观察者近一些的方针会挡住在它后边的方针)
默许:OpenGL把挨近观察者的方针的一切像素存储到深度缓冲区,当开端制作一个像素时,它(OpenGL)
首要查看深度缓冲区,看是否现已制作了更挨近观察者的什么东西,假如是则疏忽它(要制作的像素,
便是说,在制作一个像素之前,看看前面有没有挡着它的东西,假如有那就不用制作了)。否则,
把它增加到深度缓冲区和色彩缓冲区。
缺省值是GLKViewDrawableDepthFormatNone,意味着彻底没有深度缓冲区。
可是假如你要运用这个特点(一般用于3D游戏),你应该挑选GLKViewDrawableDepthFormat16
或GLKViewDrawableDepthFormat24。这儿的差别是运用GLKViewDrawableDepthFormat16
将消耗更少的资源,可是当方针非常挨近彼此刻,你或许存在烘托问题()
*/
glkView.drawableDepthFormat = GLKViewDrawableDepthFormat.format24
/*
你的OpenGL上下文的另一个可选的缓冲区是stencil(模板)缓冲区。它协助你把制作区
域限定到屏幕的一个特定部分。它还用于像影子一类的事物=比方你能够运用stencil缓冲
区保证影子投射到地板。缺省值是GLKViewDrawableStencilFormatNone,
意思是没有stencil缓冲区,可是你能够经过设置其值为GLKViewDrawableStencilFormat8
(仅有的其他选项)使能它
*/
// view.drawableStencilFormat = GLKViewDrawableStencilFormat8;
//启用多重采样
/*
这是你能够设置的最终一个可选缓冲区,对应的GLKView特点是multisampling。
假如你曾经尝试过运用OpenGL画线并关注过"锯齿壮线",multisampling就能够协助你处理
以前对于每个像素,都会调用一次fragment shader(片段着色器),
drawableMultisample根本上代替了这个作业,它将一个像素分红更小的单元,
并在更细微的层面上屡次调用fragment shader。之后它将回来的色彩合并,
生成更润滑的几许边缘效果。
要小心此操作,因为它需求占用你的app的更多的处理时刻和内存。
缺省值是GLKViewDrawableMultisampleNone,可是你能够经过设置其值GLKViewDrawableMultisample4X为来使能它
*/
//view.drawableMultisample = GLKViewDrawableMultisample4X;
EAGLContext.setCurrent(context)
glEnable(GLenum(GL_DEPTH_TEST)) //敞开深度测验,便是让离你近的物体能够遮挡离你远的物体。
glClearColor(1, 0, 0, 1.0)//设置surface的铲除色彩,也便是烘托到屏幕上的背景色。
}
- 2.设置上下文
//2.设置上下文
func setupContext() {
//EAGLContext 是苹果在iOS 平台下完成的OpenGLES烘托层,用于烘托成果在方针surface上的更新。
//1.新建上下文
context = EAGLContext(api: EAGLRenderingAPI.openGLES2)
if (context == nil) {
NSLog("Failed to load context")
}
EAGLContext.setCurrent(context)
//敞开深度测验,便是让离你近的物体能够遮挡离你远的物体。
glEnable(GLenum(GL_DEPTH_TEST))
//给glkView上下文赋值
glkView.context = context
}
- 3.设置极点数据(方位,色彩,纹路(烘托图片时用到))
//3.设置极点数据(方位,色彩,纹路(烘托图片时用到))
func setupVertexData() {
//第一步:设置极点数组
//OpenGL ES的国际坐标系是[-1, 1],故而点(0, 0)是在屏幕的正中间。
//极点数据,3个是极点坐标x,y,z;
let vertexArray: [GLfloat] = [ 0.5, -0.5, 0.0, //右下 -0.5, 0.5, 0.0, //左上 -0.5, -0.5, 0.0 //左下 ]
//第二步:敞开极点缓冲区
//极点缓存区
var buffer: GLuint = GLuint()
//请求一个缓存区标识符
glGenBuffers(1, &buffer)
//glBindBuffer把标识符绑定到GL_ARRAY_BUFFER上
glBindBuffer(GLenum(GL_ARRAY_BUFFER), buffer)
//glBufferData把极点数据从cpu内存仿制到gpu内存
glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * vertexArray.count, vertexArray, GLenum(GL_STATIC_DRAW))
//第三步:设置合适的格局从buffer里边读取数据)
/*
默许情况下,出于性能考虑,一切极点着色器的特点(Attribute)变量都是关闭的,意味着数据在着色器端是不可见的,哪怕数据现已上传到GPU,由glEnableVertexAttribArray启用指定特点,才可在极点着色器中拜访逐极点的特点数据。glVertexAttribPointer或VBO仅仅建立CPU和GPU之间的逻辑衔接,从而完成了CPU数据上传至GPU。可是,数据在GPU端是否可见,即,着色器能否读取到数据,由是否启用了对应的特点决定,这便是glEnableVertexAttribArray的功能,允许极点着色器读取GPU(服务器端)数据。
*/
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
//glVertexAttribPointer 运用来上传极点数据到GPU的办法(设置合适的格局从buffer里边读取数据)
// index: 指定要修改的极点特点的索引值
// size : 指定每个极点特点的组件数量。必须为1、2、3或许4。初始值为4。(如position是由3个(x,y,z)组成,而色彩是4个(r,g,b,a))
// type : 指定数组中每个组件的数据类型。可用的符号常量有GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, 和 GL_FLOAT,初始值为GL_FLOAT。
// normalized : 指定当被拜访时,固定点数据值是否应该被归一化(GL_TRUE)或许直接转换为固定点值(GL_FALSE)
// stride : 指定接连极点特点之间的偏移量。假如为0,那么极点特点会被了解为:它们是严密摆放在一起的。初始值为0
// ptr : 指定一个指针,指向数组中第一个极点特点的第一个组件。初始值为0 这个值受到VBO的影响
/*
VBO,极点缓存方针
在不运用VBO的情况下:作业是这样的,ptr便是一个指针,指向的是需求上传到极点数据指针。通常是数组名的偏移量。
在运用VBO的情况下:首要要glBindBuffer,今后ptr指向的就不是具体的数据了。因为数据现已缓存在缓冲区了。这儿的ptr指向的是缓冲区数据的偏移量。这儿的偏移量是整型,可是需求强制转换为const GLvoid *类型传入。注意的是,这儿的偏移的意思是数据个数总宽度数值。
比方说:这儿寄存的数据前面有3个float类型数据,那么这儿的偏移便是,3*sizeof(float).
最终解释一下,glVertexAttribPointer的作业原理:
首要,经过index得到着色器对应的变量openGL会把数据仿制给着色器的变量。
今后,经过size和type知道当时数据什么类型,有几个。openGL会映射到float,vec2, vec3 等等。
因为每次上传的极点数据不止一个,或许是一次4,5,6极点数据。那么经过stride便是在数组中间隔多少byte字节拿到下个极点此类型数据。
最终,经过ptr的指针在迭代中获得一切数据。
那么,最最终openGL怎样知道ptr指向的数组有多长,读取几回呢。是的,openGL不知道。所以在调用制作的时分,需求传入一个count数值,便是告知openGL制作的时分迭代几回glVertexAttribPointer调用。
*/
//(GLfloat *)NULL + 0 指针,指向数组首地址
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 3), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
}
- 4.设置着色器:极点着色器和片元着色器 (苹果GLKit现已将这两个着色器封装了)
//4.设置着色器:极点着色器和片元着色器 (苹果GLKit现已将这两个着色器封装了)
func setupEffect() {
//着色器
myEffect = GLKBaseEffect()
}
- 5.制作烘托(GLKViewDelegate 完成)
//5.制作烘托
override func glkView(_ view: GLKView, drawIn rect: CGRect) {
glClearColor(0, 0, 1.0, 1.0)
//铲除surface内容,康复至初始状况
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
//发动着色器
myEffect.prepareToDraw()
/*
定义:
void glDrawArrays( GLenum mode, GLint first, GLsizei count);
参数:
mode:
需求烘托的图元类型,包括 GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN ,GL_TRIANGLES。
first:
从数组缓存中的哪一位开端制作,一般为0.
count:
数组中极点的数量.
*/
glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
}
至此,咱们就完成了从极点数据到三角形的制作了。接下来咱们来看看怎样制作一张图片呢?
华丽的分割线
上面咱们说过:OpenGL 能够制作三种根本元素:点、线、 三角形 那么画一张图片的过程是怎样样的呢?
1.画一个四边形 2.加载纹路(图片)
- 1.画一个四边形 一个四边形由两个三角形组成,画三角形上面咱们现已讲过,所以画两个就行。 那么极点数据就变成这样:
//2个三角形构成
let vertexArray: [GLfloat] = [
0.5, -0.5, 0.0, //右下
0.5, 0.5, 0.0, //右上
-0.5, 0.5, 0.0, //左上
0.5, -0.5, 0.0, //右下
-0.5, 0.5, 0.0, //左上
-0.5, -0.5, 0.0, //左下
]
还有制作烘托那里的数据也变一下,极点数据变为:6
glDrawArrays(GLenum(GL_TRIANGLES), 0, 6)
运行成果:会得出一个白色的四边形来。 思考题:怎样得出如下的四边形来呢? 提示:仔细的同学,或许会发现(极点数据能够包括:方位,色彩,纹路(烘托图片时用到))
那么极点数据中参加色彩值,还有设置一下色彩特点就行了。如下
let vertexArray: [GLfloat] = [ //方位:(x, y, z)色彩:(r, g, b) 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, //右下 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, //右上 -0.5, 0.5, 0.0, 1.0, 0.0, 0.0, //左上 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, //右下 -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, //左上 -0.5, -0.5, 0.0, 0.0, 1.0, 0.0, //左下 ]
...
//极点方位,极点个数为:3,起点为:0,步长为:6
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
//极点色彩,极点个数为:3,起点为:3,步长为:6
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.color.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.color.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 3))
这就完成了极点和极点色彩的烘托了。那么纹路(图片)的烘托道理也是差不多的,接下来让咱们学习怎样烘托纹路吧。 那么极点的数据就多加上纹路坐标一般为(s, t),那么极点数据就变成这样了:
//第一步:设置极点数组
//OpenGL ES的国际坐标系是[-1, 1],故而点(0, 0)是在屏幕的正中间。
//极点数据,前3个是极点坐标x,y,z;后边2个是纹路坐标。
//纹路坐标系的取值规模是[0, 1],原点是在左下角。故而点(0, 0)在左下角,点(1, 1)在右上角
//2个三角形构成
let vertexArray: [GLfloat] = [ //方位:(x, y, z) 色彩:(r, g, b) 纹路:(s, t) 0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, //右下 0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, //右上 -0.5, 0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, //左上 0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, //右下 -0.5, 0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, //左上 -0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 //左下]
...
//极点方位,极点个数为:3,起点为:0,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.position.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.position.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 0))
//极点色彩,极点个数为:3,起点为:3,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.color.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.color.rawValue), 3, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 6), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 3))
//纹路,极点个数为:2,起点为:6,步长为:8
glEnableVertexAttribArray(GLuint(GLKVertexAttrib.texCoord0.rawValue))
glVertexAttribPointer(GLuint(GLKVertexAttrib.texCoord0.rawValue), 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(MemoryLayout<GLfloat>.size * 8), UnsafeRawPointer(bitPattern: MemoryLayout<GLfloat>.size * 6))
有了极点数据有了纹路坐标之后,可是从始至终都没有加载过纹路,所以咱们还需求: 加载纹路。
- 加载纹路
//加载纹路
func setupTexture() {
//第一步,获取纹路图片保存途径
let filePath = Bundle.main.path(forResource: "cTest", ofType: "jpg")
//GLKTextureLoaderOriginBottomLeft,纹路坐标是相反的
let options: [String : NSNumber] = [GLKTextureLoaderOriginBottomLeft : 1]
let textureInfo: GLKTextureInfo = try! GLKTextureLoader.texture(withContentsOfFile: filePath!, options: options)
//第一个纹路特点
myEffect.texture2d0.enabled = GLboolean(GL_TRUE)
//纹路的姓名
myEffect.texture2d0.name = textureInfo.name
}
图片被压缩了,怎样办呢?咱们能够把图片按照比例缩放至屏幕上。相当于UIImageView的scaleAspectFit。咱们能够经过:
AVMakeRectWithAspectRatioInsideRect(CGSize aspectRatio, CGRect boundingRect) 这个办法有什么用呢? 这个办法便是计算在一个rect里,假如需求保持一个size比例不变,这个size的真实方位。
那么极点数据就变为这样:
let image = UIImage(named: "cTest.jpg")!
let realRect = AVMakeRect(aspectRatio: image.size, insideRect: self.view.bounds)
let widthRatio: Float = Float(realRect.size.width/self.view.bounds.size.width)
let heightRatio: Float = Float(realRect.size.height/self.view.bounds.size.height)
let vertexArray: [GLfloat] = [
//方位:(x, y, z) 色彩:(r, g, b) 纹路:(s, t)
widthRatio, -heightRatio, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, //右下
widthRatio, heightRatio, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, //右上
-widthRatio, heightRatio, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, //左上
widthRatio, -heightRatio, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, //右下
-widthRatio, heightRatio, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, //左上
-widthRatio, -heightRatio, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 //左下
]
愉快的学习记载到此就完毕了,下一步咱们将学习不运用GLKit,直接用GLSL进行烘托。