好吧,我们进入正题吧。以下解释主要基于iOS。使用其他操作系统的同学可能不够友好。
首先,我们来说一下编写openGLES代码的基本流程。
图片.png
image.png 在iOS中,最直接的渲染形式是UIView,图层、CGContext等也必须基于它。 openGLES 也是如此。你不需要把它想得那么复杂,它和我们正常的编码习惯类似。
首先,我们需要创建一个继承自UIView 的新类。接下来,我们需要重写这个子类View的+(Class)layerClass{}类方法。该方法默认返回[CALayer Class]。我们使用openGLES。这里我们必须要返回[CAEAGLLayer类]。对此,没什么好说的。规定我们继续完善这个CAEAGLLayer,看下面的图层属性设置。看看吧。它设置的属性并不是为了保持渲染内容和颜色格式为RGBA8。从CAEAGLLayer, CA 可以看出,它也属于Core Animation。
_eaglLayer=(CAEAGLLayer *)self.layer;
_eaglLayer.opaque=YES;
_eaglLayer.drawableProperties=@{
kEAGLDrawablePropertyRetainedBacking:@(NO),
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
};
_eaglLayer.contentsScale=screenScale;完成这一步后,我们需要配置openGLES渲染上下文EAGLContext,
_context=[[EAGLContext 分配]initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:_context]; api 就是版本。我们可以选择ES1、ES2或ES3。您可以根据自己的需要进行选择。虽然ES1可能不是所有人都会用,但是EAGLContext是openGLES的渲染上下文。
接下来是buffer,renderBuffer和frameBuffer,它们是OpenGL非常重要的部分。
renderBuffer:renderbuffer对象是应用程序分配的2D图像缓冲区。渲染缓冲区可用于分配和存储颜色、深度或模板值。渲染缓冲区类似于离屏窗口系统提供的可绘制表面。简单来说,就是渲染图形的基本外观。
FrameBuffer:帧缓冲区对象(通常称为FBO)是颜色、深度和模板缓冲区连接点的集合;描述属性的状态,例如颜色、深度以及附加到FBO 的模板缓冲区的大小和格式;以及附加到FBO 的纹理和渲染缓冲区对象的名称。
简单理解,frameBuffer就像一个管理器,管理着所有支持渲染的RenderBuffer和Textures(纹理)。 FBO 有许多附着点。我们使用Attachment 将实际工作并占用实际内存空间的渲染缓冲区和纹理附加到FBO。优越的。如下所示,我们将frameBuffer附加到renderBuffer上。我们先把纹理放在一边,有机会再讨论。
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
做完以上之后,就是最后一步了,绘制我们想要绘制的图形了。
我们设置清屏颜色,类似于UIView的backgroundColor。可以理解为openGL的背景色。清晰的屏幕颜色默认为黑色。其代码表示为
glClearColor(0.3, 0.5, 0.8, 1.0); RGBA类型。
然后glClear(GL_COLOR_BUFFER_BIT);
glClear 指定要清除的缓冲区
总共可以设置三个选项:GL_COLOR_BUFFER_BIT(颜色)、GL_DEPTH_BUFFER_BIT(深度)和GL_STENCIL_BUFFER_BIT(模板)
也可以组合如:glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
这里我们只使用颜色缓冲区,所以我们只需要清除GL_COLOR_BUFFER_BIT
最后,[_context PresentRenderbuffer:_renderBuffer];将renderBuffer渲染到上下文中并提交最终的渲染结果。做完这一步之后,我们的OpenGLES绘图就有结果了,虽然只是一个清晰的屏幕颜色,即背景颜色。
至此,openGLES绘图的基本流程就完成了。让我们回顾一下。事实上,这个过程只包含几个步骤:
View—Layer—Context—Buffer—Render而且这些步骤也很简单,所以如果你想制作一个openGLES应用程序,不要想得太复杂,过程还是很简单的。
如果我们想做一些稍微复杂的功能,我们只需在这个过程中添加代码即可。这里提一下,可能涉及到:个shader程序配置、深度缓存、混色、纹理处理、矩阵变换处理等,需要什么就加什么,不用担心。
好了,基本流程说完了,我们来说说一些基本概念。
来,我们来说说openGL的坐标系。
在绘制图形之前,我们首先要向OpenGL输入一些顶点数据。 (VA0,VBO),OpenGL是一个3D图形库,所以我们在OpenGL中指定的所有坐标都是3D坐标(x,y,z坐标)。 OpenGL 并不简单地将所有3D 坐标转换为屏幕上的2D 像素; OpenGL 仅处理所有3 个轴(即x、y 和z)上的3D 坐标均在-1.0 到1.0 范围内的情况。这是标准化的设备坐标。只有该范围内的坐标最终才会显示在屏幕上(超出该范围的坐标将不会显示)。所以必然涉及到坐标变换。
我们要绘制的物体通常都有自己的坐标范围,比如建筑物。它自己的坐标可能是实际的长、宽、高。我们获取它的长度、宽度和高度,然后将这些坐标转换为标准化设备坐标。这些标准化的设备坐标然后被传递到光栅化器(Rasterizer)中,然后将它们转换为屏幕上的二维坐标或像素并显示出来。
将原始坐标转换为标准化设备坐标,再转换为屏幕坐标的过程涉及到以下五个重要的坐标系:
1.局部空间(Local Space,也称Object Space),即物体本身的坐标(如建筑物的长、宽、高)
2.世界空间,(比如某个区域的建筑物的x,y,z,比如它在一个广场的坐标和它在一个城市的坐标,是不同的)
3.观察空间(View Space,或者Eye Space)(你可以想象一下我们眼睛看到的这个建筑的xyz坐标)
4. Clip Space(什么意思?由于视觉效果的原因,有些物体可能不在可视范围内,导致只能看到部分物体。比如我们在建筑物下面,看不到整个建筑物)
5. 屏幕空间(屏幕空间是向我们显示的内容,在设备上它是设备屏幕)。
这个过程涉及到几个矩阵,模型矩阵(Model)、观察矩阵(View)、投影矩阵(Projection),也就是MVP矩阵。
最终的裁剪空间就是视口(ViewPort),所以我们写代码的时候很可能会有这样的代码:glViewport(0, 0, self.frame.size.width, self.frame.size.height);来确定视觉空间。
接下来简单说一下MVP的三个矩阵:
投影矩阵投影矩阵分为正交投影和透视投影。具体细节我就不分析了。它们之间的区别是:
正交投影矩阵直接将坐标映射到屏幕的二维平面上。从人的视觉效果来看,会产生不切实际的结果。但透视投影远处的顶点看起来更小,这与人眼看近处和远处的物体是一致的。影响较小,所以我们一般选择透视投影。我们来看一个直观的效果。
图片.png
如图所示,投影矩阵确实更符合人类视觉效果。让我们看一个现实中的好例子:火车轨道。两条轨道之间的距离看似近的大,远的小,但实际上是一样的:
image.png 投影矩阵在代码: 中的行为如下
浮动方面=self.frame.size.width/self.frame.size.height;
_projectionMatrix=GLKMatrix4MakePerspective(45.0*M_PI/180.0, 方面, 0.0001, 100);
glUniformMatrix4fv(_projectionSlot, 1, GL_FALSE, _projectionMatrix.m);GLKMatrix4MakePerspective(float fovyRadians, 浮动纵横比, 浮动nearZ, 浮动farZ)
fovyRadians 是视角,它接受弧度值、纵横比、nearZ 视角和farZ 视角。
glUniformMatrix4fv 将矩阵信息传输到我们的着色器程序。它是我们的代码和着色器代码之间的沟通桥梁。类似的还有很多,比如将顶点信息、颜色信息等传递到着色器程序中。这里有一个小总结提一下。
观察矩阵(摄像机矩阵)如下图所示,我们可以直观地了解到相机就是我们的眼睛,眼睛会移动,你看到的物体也会发生变化。大家应该都能明白,当你从不同的角度和方向看物体时,物体给你的呈现是不同的。
图片.png
代码中观测矩阵的表现如下:eyeX=SXYZ * sinf(RZ) * cosf(RX);
眼睛Y=SXYZ * sinf(RX);
眼睛Z=SXYZ * cosf(RZ) * cosf(RX);
眼睛X +=TX;
眼睛Y+=TY;
眼睛Z+=TZ;
_camaraMatrix=GLKMatrix4MakeLookAt(eyeX, eyeY, eyeZ, TX, TY, TZ, 0, 1, 0);
glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, _camaraMatrix.m);GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ,float centerX, float centerY, float centerZ, float upX, float upY, float upZ) 这个方法表示眼睛在( eyeX,eyeY,eyeZ)坐标,向(centerX,centerY,centerZ)看,眼睛坐标系的y轴正方向为(upX,upY,upZ)。就像在我的代码中一样,眼睛坐标xyz 朝向对象的中心。坐标xyz,眼睛坐标系(0,1,0)的y轴正方向。
模型矩阵模型矩阵是物体相对于自身的变化,如图:
图片.png
我们可以看到图中的茶壶先旋转再平移,最终的结果和先平移再旋转是不同的,因为它是基于物体本身的。学完线性代数,我们就会知道矩阵乘法不满足交换律。模型矩阵在代码中表示如下: _poiTitleModelViewMatrix=GLKMatrix4MakeTranslation(titlePoint.x, poiPlaneTop+0.005, titlePoint.y);
_poiTitleModelViewMatrix=GLKMatrix4RotateY(_poiTitleModelViewMatrix, RY);
_poiTitleModelViewMatrix=GLKMatrix4RotateX(_poiTitleModelViewMatrix, -RX);
camara_position_distance=sqrtf(pow(eyeX-titlePoint.x, 2)+pow(eyeY-(poiPlaneTop+0.005), 2)+pow(eyeZ-titlePoint.y, 2));
_poiTitleModelViewMatrix=GLKMatrix4Scale(_poiTitleModelViewMatrix, camara_position_distance, camara_position_distance, camara_position_distance);似乎没什么可说的。基于它本身的平移、旋转和缩放,我们普通的视图已经做到了这一点,这里就不解释了。
好了,到这里我们对矩阵的介绍就差不多结束了。其实每个人根据自己的需要使用不同的矩阵。
接下来,我们来谈谈着色器的概念。我们来谈谈顶点着色器和片段着色器。
顶点着色器在openGL 编程中是必需的。我们开始使用它们是因为我们还没有绘制图形。顶点着色器的功能是:
1.使用矩阵进行顶点位置变换
2.常态改造、常态工程常态化
3.纹理坐标生成与变换
4.计算每个顶点的光照
5. 颜色计算
其实它只是对顶点数据做了一些处理。
属性vec4 位置;
属性vec2 TexCoordIn;
改变vec2 TexCoordOut;
统一bool isLocate;
统一mat4locateModel;
统一mat4投影;
统一mat4 视图;
统一mat4模型;
无效主(){
if (isLocate) {
gl_Position=投影* 视图*locateModel * 位置;
}别的{
gl_Position=投影* 视图* 模型* 位置;
}
//gl_Position=位置;
TexCoordOut=vec2(TexCoordIn.x, 1.0-TexCoordIn.y);
让我们看一下这段代码。此代码包含输入顶点数据Position 和输出顶点数据gl_Position。 gl_Position 经历了一些转变。比如这段代码中,根据外部变量isLocate,处理成屏幕上不同的变化。协调。我们还可以看到它的变换经历了透视投影矩阵、观察矩阵和模型矩阵。
这段代码还有纹理坐标的输入和输出。与顶点坐标为xyzw 四维向量不同,它只有两个坐标xy。输入为TexCoordIn,输出为TexCoordOut。 1.0-TexCoordIn.y是因为纹理坐标的y坐标与我们设备屏幕的y坐标相反。它的y从上到下都是负值,是笛卡尔坐标系。我们的屏幕从上到下都是正的。
我们再看一下片段着色器:
片段着色器将顶点着色器的数据处理为实际屏幕坐标处的像素颜色。
片段着色器的功能如下:
1. 计算颜色
2.获取纹理值
3.用颜色值填充像素(纹理值/颜色值)
这张图是定制的Fragment.glsl:
精密中型浮子;
改变mediump vec4 OutColor;
统一布尔is_side;
统一浮动边颜色;
统一bool is_sprite;
无效主()
{
如果(is_sprite){
if (length(gl_PointCoord-vec2(0.5)) 0.45) //0.5 会弹出
丢弃;
}别的
gl_FragColor=输出颜色;
gl_FragColor=vec4(gl_FragColor.r*OutColor.r,gl_FragColor.g*OutColor.g,gl_FragColor.b*OutColor.b,gl_FragColor.a*OutColor.a);
如果(是侧){
//gl_FragColor=gl_FragColor * vec4(sideColor,sideColor,sideColor,1.0);
gl_FragColor=gl_FragColor;
}
数据的精度必须在片段着色器中指定,因此有precisionmediumpfloat;这一行指定数据类型为浮点型、中等精度,当然还有低精度和高精度。不同的精度消耗不同的性能。
这个片段着色器输出颜色,vec4 OutColor,输出是rgba。我们可以在这里对颜色进行一些处理和修改。
接下来我们要讲一下可编程渲染管线的概念,这个概念在openGL中非常重要。看这张图:
1).Vertex Array/Buffer对象
顶点数据源,这是渲染管线的顶点输入,VAO VBO是不同风格的顶点存储,它们的绘制方式也不同。
2).顶点着色器
顶点着色器执行基于顶点的操作,例如通过矩阵变换位置、计算光照公式以生成每个顶点的颜色以及生成或变换纹理坐标。
3).原始装配
图元组装经过着色器处理后的顶点在图像组装阶段被组装成基本图元。 OpenGL ES支持三种基本图元:点、线和三角形,可以通过OpenGL ES进行渲染。
4).光栅化
光栅化。在光栅化阶段,基本图元被转换为二维片段。片段代表可以渲染到屏幕上的像素。它包含位置、颜色、纹理坐标等信息,这些值是由图元的顶点决定的。该信息是通过插值计算获得的。然后这些片段被发送到片段着色器进行处理。这是从顶点数据到可以在显示设备上渲染的像素的定性转换。
5).片段着色器
片段着色器以可编程的方式实现对每个片段的操作。在此阶段,它接受光栅化片段、颜色、深度值和模板值作为输入。片段着色器可以丢弃片段或生成一个或多个颜色值作为输出。
6).Per-Fragment Operations(每片段操作)
它包括像素所有权测试、剪刀测试、模板和深度测试、混合、抖动等片段处理。我们在渲染3D 图形时经常使用它们。
7).Framebuffer:这是管道的最后阶段。 Framebuffer存储可用于渲染到屏幕或纹理的像素值。
总计:
1、我们的顶点数据经过顶点着色器的处理,转化为我们想要绘制的顶点数据;
2. 使用原语再次组装。这些顶点应该用点、线还是三角形组装?
3.接下来就是光栅化,将图形变成我们可以在屏幕上显示的像素,其中包括坐标、颜色等;
4.然后使用片段着色器在这些顶点、颜色等上创建我们想要的效果;
5.然后通过Per-Fragment Operations,是否要对绘制的图形进行深度、裁剪或混合;
6.处理完最终提交后,我们就得到了最终想要渲染的像素。通过提交,我们就可以得到我们想要绘制的图形了。
最后,让我们看一些代码。流程一般是这样的
[自行设置图层];
[自我设置上下文];
[自行设置DepthBuffer];
[自行设置渲染缓冲区];
[自行设置FrameBuffer];
[自行设置程序]; //配置程序
[自行设置矩阵];
[自行设置渲染数据];
[自我渲染];我们看一下render :中的处理
-(无效)渲染
{
[EAGLContext setCurrentContext:_context];
glClearColor([backColorArr[0] floatValue], [backColorArr[1] floatValue], [backColorArr[2] floatValue], 1);
//glEnable(GL_POLYGON_OFFSET_FILL); //解决z_fighting问题
//glPolygonOffset(1.0f, 1.0f);
glEnable(GL_BLEND); //允许混合
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
//MSAA处理
glBindFramebuffer(GL_FRAMEBUFFER, mMSAAFramebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, mMSAARenderbuffer);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, self.frame.size.width*2, self.frame.size.height*2);
[自我更新CubeCenterToScreenPoint]; //确定边界
glUseProgram(_programHandle); //使用某种颜色处理器程序
[自行设置CubeProjectionAndCamara];
[自画外线]; //绘制轮廓
[自画TopPoi]; //绘制顶部poi,其有多个深度测试的开闭。
glDisable(GL_DEPTH_TEST); //关闭深度测试
if (naviPathArr.count) {
对于(int i=0; i
好了,关于深入浅出解析OpenGLES:小白必读指南和的问题到这里结束啦,希望可以解决您的问题哈!
【深入浅出解析OpenGLES:小白必读指南】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
我一直想学习一下OpenGLES,这个白话版的理解真是太棒了!
有20位网友表示赞同!
终于找到一本能让我轻松理解OpenGLES的书啦!
有9位网友表示赞同!
用通俗语言解释技术细节,这真的很不错啊!
有14位网友表示赞同!
希望能详细讲解一些OpenGLES常用的技术点。
有16位网友表示赞同!
学习OpenGLES一直是一个很大的挑战,希望这本书能指点迷津。
有14位网友表示赞同!
学习图形编程一直是我的目标,这篇白话理解太适合我入门了!
有6位网友表示赞同!
以前对OpenGLES完全懵逼,看懂这个标题感觉很有望。
有17位网友表示赞同!
终于不用再啃那些晦涩的专业文档了,太感谢作者了!
有6位网友表示赞同!
期待能从这篇白话篇中学会一些实践技能!
有6位网友表示赞同!
OpenGLES看起来真的好复杂呀,这篇文章能帮我明白吗?
有14位网友表示赞同!
希望能有更多图文结合的讲解,更容易理解。
有9位网友表示赞同!
这个标题很有吸引力,一定得来看看!
有10位网友表示赞同!
想要学习开发游戏,OpenGLES是必修课吧?
有12位网友表示赞同!
以前做过一些简单的2D图形编程,想进一步学习OpenGLES。
有6位网友表示赞同!
感觉这个白话篇会非常实用的!
有19位网友表示赞同!
希望作者能解释一下OpenGLES与其他图形库的区别。
有7位网友表示赞同!
现在是移动设备盛行的时代,了解OpenGLES相当关键啊!
有10位网友表示赞同!