简识OpenGL + OpenGLES+GPUImage+Metal的基础原理和代码实施

OpenGL:

OpenGL是使用客户端--服务端的形式实现的。

客户端(我们编写的程序)发起图像处理请求,计算机图像硬件厂商提供的OpenGL实现图形绘制操作。

光栅化:实际绘制或填充每个顶点之间的像素形成线路

着色:沿着顶点之间改变颜色值,能够轻松创建光照 照射到一个立方体的效果

纹理贴图:将纹理图片附着到你绘图的图像上。

混合:颜色混合效果。

画一个三角形的流程:

1、设置工作目录

scss 复制代码
gltSetWorkingDirectory(argv[0])

2、初始化GLUT库,这个函数只是传送命令参数并且初始化glut库。

scss 复制代码
glutInit(&argc, argv);

3、初始化渲染方式

/*GLUT_DOUBLE:双缓冲区、GLUT_RGBA:颜色模式、GLUT_DEPTH:深度测试、GLUT_STENCIL:模板缓冲区

*/

glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH|GLUT_STENCIL);

4、设置窗口大小 标题

glutInitWIndowSize(width, height);

glutCreateWindow("title");

5、GLUT内部运行一个本地消息循环,拦截适当的消息。然后调用不同时间注册的回调函数。

**窗口size改变时的回调

glutReshapeFunc(##函数指针##);

**注册显示函数,包含渲染的回调函数

glutDisplayFunc(##函数指针##);

6、初始化一个GLEW库,确保OpenGL API对程序完全可用。在试图做任何渲染之前,药检查确定驱动程序的初始化过程中没有任何问题。

GLenum status = glewInit();

7、开始渲染

7.1 设置清屏颜色

glClearColor(...);

初始化固定管线(也有可变管线)

GLShaderManager shaderManager;

//初始化一个渲染管理器

shaderManager.InitializeStockShaders()

//定义一个批次容器,是GLTools的一个简单的容器类。

//GLBatch.h 三角形批次类,帮助类。利用它可以传输顶点/光照/纹理/颜色数据到存储着色器中

GLBatch batch;

GLfloat verts[] = {

//三个顶点数据

//(x,y,z)表示一个顶点

};

batch.Begin(GL_TRIANGLES, 3);

batch.CopyVertexData3f(顶点数据);

batch.End();

8.重绘回调

8.1 清除一个或者一组特定的缓冲区。

/* 缓冲区是一块存图像信息的储存空间,rgb和alpha分量通常一起分量作为颜色缓存区或像素缓冲区引用。*/

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

设置一组颜色浮点数组 GLFloat vColor[] = {0.5, 0.5, 0, 1.0f};

//传递到存储着色器,即GLT_SHADER_IDENTITY着色器,这个着色器只是使用指定颜色以默认笛卡尔坐标题在屏幕上渲染几何图形

shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vColor);

//提交着色器

batch.Draw();

//交换前后缓冲区

glutSwapBuffers();

着色器:

顶点着色器 :Vertex Shader。为了渲染共有3个顶点的三角形,顶点着色器将执行3次,也就是为了每个顶点执行一次。硬件上有多个执行单元可同时执行。在这之后,进行primitive Assembly, 此阶段三个顶点已经组合到一起,而三角形已经逐个片段到进行了光栅化。每个片段通过执行片元着色器进行填充。片元着色器将绘制的最终颜色值

片元着色器 :Fragment Shader。

向OpenGL着色器传递渲染数据的方式有3种:

1,属性 Attribute:就是对每一个顶点都要做改变的数据元素。顶点位置本身就是一个属性。可以是浮点数、整数、布尔数据。总是以思维向量的形式在内部存储。属性还可以是纹理坐标、颜色值、光照计算表面法线、

只提供给顶点着色器使用。

2,uniform值:每个批次改变一次,但可以无数次限制的使用,可以设置整个表面的单个颜色值也可时间值。常用于顶点渲染中设置变换矩阵,顶点着色器喝片元着色器都可以使用uniform变量。

3,纹理数据:

在顶点着色器、片段着色器中都可以对纹理数据进行采样和筛选。

在典型的应用场景中:片段着色器对一个纹理值进行采样,然后在一个三角形表面应用渲染纹理数据。

纹理数据:不仅仅表现在图形,很多图形文件格式都是以无符号字节(每个颜色通道8位)形式对颜色分量进行存储。

GLShaderManager::UserStockShader 会选择一个存储着色器并提供这个着色器的uniform值。

单位(Identity)着色器 :只是简单地使用笛卡尔坐标系(坐标范围: -1到1)。所有片段都应用同一个颜色,几何图形为实心和未渲染的。GLT_ATTRIBUTE_VERTEX(顶点分量)

平面着色器:GLT_SHADER_FLAT

上色着色器GLT_SHADER_SHADED:需要设置存储着色器的GLT_ATTRIBUTE_VERTEX(顶点分量)和GLT_ATTRIBUTE_COLOR(颜色分量)2个属性。颜色值将被平滑地插入顶点之间。

默认光源着色器:GLT_SHADER_DEFAULT_LIGHT。这种着色器是对象产生阴影喝光照的效果。需要设置存储着色器的GLT_ATTRIBUTE_VERTEX(顶点分量)和GLT_ATTRIBUTE_NORMAL(表面法线)

**点光源着色器:GLT_SHADER_DEFAULT_LIGHT_DIEF ** 点光源着色器和默认光源着色器很相似,区别在于:光源位置是特定的。同样需要设置存储着色器的GLT_ATTRIBUTE_VERTEX(顶点分量) 和 GLT_ATTRIBUTE_NORMAL(表面法线)

纹理替换矩阵:GLT_SHADER_TEXTURE_REPLACE

着色器通过给定的模型视图投影矩阵,使用绑定到nTextureUnit(纹理单元)指定纹理单元的纹理对几何图形进行变化。片段颜色:直接从纹理样本中获取。需要设置存储着色器的GLT_ATTRIBUTE_VERTEX(顶点分量)

和GLT_ATTRIBUTE_NORMAL(表面法线)

纹理调整着色器: GLT_SHADER_TEXTURE_MODULATE

纹理光源着色器: GLT_SHADER_TEXTURE_POINT_LIGHT_DIEF

点:

scss 复制代码
glPointSize(4); //点大小
glGetFloatv(GL_POINT_SIZE_RANGE, {1,2});
CGFloat step = 1;//最小步长
glGetFloatv(GL_POINT_GRAULARITY, &step);

//通过使用程序点大小模式设置点大小
glEnable(GL_PROGRAM_POINT_SIZE);

//这种模式下允许我们通过编程在顶点着色器或几何着色器中设置点大小
//着色器内建变量: gl_PointSize 并且可以在着色器源码直接写

线带:设置独立线段宽度

scss 复制代码
glLineWidth(2.f);

环绕:定义逆时针方向环绕点多边形是正面。顺时针为背面。

scss 复制代码
//定义前向(正面)和背向的多边形
glFontFace(mode);
GL_CCW: 表示传入的mode会选择逆时针为前向。默认此值
GL_CW: 表示顺时针为前向

三角地带:GL_TRIANGLE_FAN组建一组围绕一个中心点的相连三角形。

矩阵堆栈GLMatrixStack:变化管线使用矩阵堆栈。GLMatrixStack构造函数允许制定堆栈的最大深度、默认的堆栈深度为64。这个矩阵堆在初始化时已经在堆栈中包含了单位矩阵。

矩阵的工具类 可以利于GLMatrixStack加载单元矩阵/矩阵/矩阵相乘/压栈/出栈/缩放/平移/旋转

OpenGL6种坐标系:

1,物体/模型坐标系 Object/model coordinates

2,世界坐标系 world ...

3,眼坐标系或者相机坐标系 Eye or Camera ...

4,投影(裁剪)坐标系 Clip ...

5,标准设备坐标系 Normalized device ...

6,屏幕坐标系 Window (or screen) coordinates

OpenGL的重要功能之一就是将三维的世界坐标系经过变换、投影等计算,最终计算出它在显示设备上的对应的位置,这个位置就是设备坐标。

OpenGL只使用设备的一部分进行绘制,这个部分称为视口或视区。投影得到的是视区内的坐标(投影坐标),从投影坐标到设备坐标的计算过程就是设备变换

1、物体或模型坐标系Objec/Model Cooordiantes对应的modelMatrix。

从object坐标系到world再到camera坐标系的变换,在OpenGL中统一称为model-view转换

初始化的时候,object和world和camera坐标系的坐标重合在原点,变换矩阵都为Identity,所以在OpenGL中用glLoadIdentity()初始化变换矩阵栈。

matrix 转换points、vectorsd到camera坐标系。

2、世界坐标系对应的是WorldMatrix的matrix。

OpenGL中,世界坐标系是以屏幕中心为原点(0,0,0),且始终不变的。窗口长度范围固定的,左下角是(-1,-1),右上角(1,1)。这是采用了归一化的结果。使用GL_MODELVIEW矩阵和Object坐标相乘所得。GL_ModelView将对象空间变换到视觉空间。

GL_MODELVIEW矩阵是模型矩阵(model matrix)和视觉矩阵(view matrix)的组合(View矩阵*model矩阵)。

model变换指的是Object Space转换到World Space(三维空间)

view变换是将world space变换到eye(camera)space。

3、眼坐标或相机坐标系对应的是ViewMatrix。

以视点为原点,以视线的方向为Z轴正方向的坐标系。OpenGL管道会将世界坐标系变换到眼坐标,然后进行裁剪,只有在视线范围之内的场景才会进入下一阶段的计算

4、剪切坐标系对应的ProjectionMatrix

由眼坐标系可知,OpenGL管道首先会将目标从世界坐标系变换到眼坐标,然后对视线范围外的部分进行裁剪。裁剪过程中用到了投影变换矩阵栈(ProjectionMatrix),栈顶矩阵就是当前投影变换矩阵 ,负责将场景各坐标变换到眼坐标,所得到的结果就是裁剪后到场景部分,称为裁剪坐标。

5、标准设备坐标系(Normalized device coordinates: NDC)

一旦你的顶点坐标系已经在顶点着色器中处理过,他们就应该是标准化设备坐标了,标准化设备坐标是x、y、z在[-1, 1]的一段空间,任何落在范围外的坐标都会被丢弃或剪辑,不会显示在屏幕上。屏幕中心是(0,0)忽略z轴。

6、屏幕坐标系(Window/screen coordinates)

屏幕坐标系有2种,iOS的Core Graphics是以左下角为原点,向右为x轴,向上为Y轴。

7、视口坐标系

与屏幕坐标系息息相关,它是将Game视图的屏幕坐标系单位化,即左下角(0,0) 右上角(1,1)。假如屏幕坐标系左下角(0,0)右上角(1000,800)。如果在屏幕坐标系上有一个点坐标(200,400),那么该点视口坐标系点的位置就是(200/1000 = 0.2,400/800=0.5, z = 0)(默认z轴为0),

8,纹理坐标系以纹理左下角为坐标原点,向右为x轴正方向,向上为y轴正方向。总长度为1,即左上(0,1) 左下(0, 0) 右上(1, 1) 右下(1, 0)。

9、齐次坐标系: 略

总结:

ModelMatrix :就是将模型坐标变换到世界坐标系的matrix;

ViewMatrix: 用于直接将World坐标系下的坐标转换到Camera坐标系。已知相机坐标系和相机在世界空间下的坐标,就可以求出ViewMatrix。

ModelViewMatrix: 模型视图矩阵是视图变换矩阵与模型变换矩阵相乘的结果。

ProjectionMatrix: 投影矩阵将视图坐标系中的顶点转化到平面上。

根据投影矩阵x视图矩阵x模型矩阵 可求出模型视图投影矩阵, 顶点坐标乘以该矩阵就直接可获得其在规范立方体中的坐标了。这个矩阵通常作为一个整体出现在着色器中。

layout(location = 0) in vec3 aPos;

gl_Postion = projectionMatrix * viewMatrix * ModelMatrix * vec4(aPos, 1.0f);

图形渲染的画法基础认识:

隐藏面消除:剔除不可见的部分。

油画画法:由远及近的先后顺序画,但是无法处理相互交错的情况。

正背面剔除:对于一个平面的正背面,一个时刻,只能看到一个面。OpenGL可以做到检查所有正面朝向观察者的面,并渲染。丢弃背面朝向的面,从而节约片元着色器的性能。通过分析顶点数据的顺序来确定正面还是背面。

深度:其实就是像素点在3D世界中距离摄像机的距离Z值。

深度缓冲区:就是一块内存区域,专门储存着每个像素点(绘制在屏幕上的)深度值,深度值越大,距离摄像机就越远。有了深度缓冲区,则不需要关注物体的先后顺序,只要根据深度缓冲区,OpenGL都会把像素的深度值写入到缓冲区,除非glDepthMask(GL_FALSE)禁用。

深度缓冲区一般由窗口管理系统GLFW创建,深度值一般由16位 24位 32位值表示,位数越高,深度精确度越好。

深度测试:在决定是否会知一个物体表面时,首先将表面对应的像素的深度与当前深度缓冲区中的值进行比较,如果大于深度缓冲区的值,则丢弃这部分。否则利用这个像素对应的深度值和颜色值,分别更新深度缓冲区和颜色缓冲区。这个过程称为"深度测试"。

开启深度测试:glEnable(GL_DEPTH_TEST);

在绘制场景前,清除颜色缓冲区、深度缓冲

glClearColor(0,0,0,1.f);

glClear(GL_Color_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glDepthMask(GL_TRUE);//开启深度缓冲区写入 否则GL_FALSE

产生问题:开启深度测试后,OpenGL不会再去绘制模型被遮挡的部分。但是由于深度缓冲区精度的限制,对于深度相差无几的情况(例如统一平面绘制2次),OpenGL就可能出现不能正确判断两者的深度值,会导致深度测试的结果不可预测。显示出来的现象时交错闪烁。

解决方式:1,启用Polygon offset方式解决。让深度值 之间产生间隔,如果2个图形之间有间隔,就不会产生干涉,可以理解为在执行深度测试前,将立方体的深度值做一些细微的增加,于是就能将重叠的2个图形深度值有所区分。

2,指定偏移量:通过glPolygon Offset来指定。glPolygonOffset需要2个参数:factor、units。每个深度值都会增加这个偏移量:Offset = m*factor + r * units;

m:多边形深度的斜率的最大值,理解一个多边形越是近裁面平行, m越接近0。

r: 能产生于窗口坐标系的深度值中可分辨的差异最小值。r是由OpenGL平台指定的一个常量。

一个大于0的offset会把模型推离你向远的位置,否则反之。

一般设置为m = -1 和 r = 0.0 即可满足。

裁剪技术:OpenGL有一种提高渲染的方式:只刷新屏幕上发生变化的部分。OpenGL允许将要进行渲染的窗口指定一个裁剪框,用于渲染绘制区域。通过此技术在屏幕中(帧缓冲区)指定一个矩形区域。启用裁剪后,只有在此矩形区域内的片元才有可能进入帧缓冲。实际效果就是:屏幕中开了一个小窗口,在其中进行指定内容的绘制。

//开启or关闭裁剪测试

glEnable(GL_SCISSOR_TEST);

glDisable(GL_SCISSOR_TEST);

//指定裁剪窗口

glScissor(x,y,w,h);

窗口:当前显示界面。

视口:窗口中用来显示图形的一块矩形区域,一块不大于窗口的区域。只有绘制在视口区域中的图形才能被显示。

裁剪区域(平行投影) :视口矩形区域的最小最大x坐标(left、right)和最小最大y坐标(bottom、top),而不是窗口的最小最大x 、y坐标。通过glOrtho()函数设置,这个函数还需要指定最近最远z坐标,形成立体的裁剪区域。

混合: OpenGL渲染时会把颜色值存在颜色缓冲区中,每个片段的深度值也是放在深度缓冲区。当深度缓冲区被关闭后,新的颜色将简单的覆盖原来颜色缓冲区存在的颜色值,当深度缓冲区再次被打开时,新的颜色片段只是它们比原来的值更接近邻近的裁剪平面才会更换原来的颜色片段。

glEnable(GL_BLEND);

组合颜色:

目标颜色(已经存储在颜色缓冲区的颜色值)

源颜色(作为当前渲染命令结果进入颜色缓冲区的颜色值)

当混合功能被启动后,源颜色和目标颜色的组合方式就是混合方程式控制的。

cf = (cs * s) + (Cd * D)

cf: 最终计算参数的颜色; cs: color source(源颜色) cd:color destination 目标颜色

s:源混合因子; D: 目标混合因子

设置混合因子:glBlendFunc()

混合函数经常用于实现一些 在不透明的物体前绘制不同透明度物体的效果

声明一个 uniform sampler2D 把一个纹理添加到片段着色器中,这个uniform不需要手动赋值。

csharp 复制代码
//texture.fsh
varying vec4 v_vertexColor;
varying vec2 v_textureCoord;
 
uniform sampler2D u_myTexture; //纹理采样器, 它不需要手动赋值,在onDraw()中绑定纹理时自动赋值。
void main()
{
    gl_FragColor = texture2D(u_myTexture, v_textureCoord); //使用GLSL内建的texture2D函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。
}

//绑定纹理, 它会自动把纹理赋值给片段着色器texture.fsh的采样器v_myTexture.因为一个纹理的默认纹理单元是0,它是默认的激活纹理单元。

GL::bindTexture2D(textureId);

glBindVertexArray(vao);

// 在调用glDrawElements 之前绑定纹理了,它会自动把纹理赋值给片段着色器的采样器:

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glBindVertexArray(0);

一个纹理的位置值通常称为一个纹理单元(Texture Uint)。一个纹理的默认纹理单位是0,它是默认的激活纹理单元,所以当只定义了一个纹理单元采样器时,并不需要手动赋值,而是默认分配。

scss 复制代码
GLuint texture;
glGenBuffer(&texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以当只声明要绑定一个纹理单元时,无需激活任何纹理单元。

OpenGL至少保证16个纹理单元供使用,可以手动激活从GL_TEXTURE0到GL_TEXTURE15,按顺序定义的,也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8。

当创建2个纹理时:

ini 复制代码
uniform sampler2D u_myTexture1; //纹理采样器
uniform sampler2D u_myTexture2;
scss 复制代码
GL::BindTexture2DN(0, textureId);
glUniform1i(glGetUniformLocation(program), "u_myTexture1", 0);

GL::bindTexture2DN(1, textureId2);
glUniform1i(glGetUniformLocation(program), "u_myTexture2", 1);

OpenGLES:

顶点着色程序与片元着色程序是如何实现编译、绑定和链接的?

1、指定属性:加载顶点着色程序和片元着色器的文件(内部定义了属性)

2、设置源代码:

ini 复制代码
//创建着色器对象(顶点、片元)
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

3、将着色器源文件送入到着色器对象中

scss 复制代码
//顶点程序加载
gltLoadShaderFile("vertexs.sh", vertexShader);

//片段程序加载
fltLoadShaderFile("vertexf".fh, fragmentShader) ;

4、编译着色器、然后判断是否有错误

scss 复制代码
//编译顶点着色器、片元着色器
glCompileShader(vertexShader);
glCompileShader(fragmentShader);

GLint testVal, testVal1;
//检查顶点着色器程序错误
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &testVal);
glGetShaderiv(fragmentShader, GL_COMPILE_SATUS, &testVal1);
if(testVal == GL_FALSE || testVal1 == GL_FALSE){
	char infoLog[1024];
	glGetShaderInfoLog(vertexShader, 1024, NULL, infolog);
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);
	return;
}

5、进行连接&绑定

scss 复制代码
//创建最终的程序对象、连接着色器
GLuint program = glCreateProgram();
glAttachShader(program, vertextShader);
glAttachShader(program, fragmentShader);

//将参数名绑定指定的参数位置列表上
va_list options;
va_start(options, ##);
glBindAttribLocation(program, ...);
va_end(options);

6、链接着色器

scss 复制代码
//尝试链接
glLinkProgram(program);
//生成运行文件后可删除
glDeleteShader(vertextShader);
glDeleteShader(fragmentShader);

//确认链接有效
glGetProgramiv(program, GL_LINK_STATUS, &testVal);

scss 复制代码
#import <GLKit/GLKit.h>
GLKViewController
GLKView(Delegate)
//EAGLContent是苹果在iOS平台下实现的opengles渲染层,
//用于渲染结果在surface上的更新
EAGContext *ctx; 
//着色器 或光照
GLKBaseEffect *mEffect;

#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>


//创建OpenGL ES上下文
//   kEAGLRenderingAPIOpenGLES1 = 1, 固定管线
     kEAGLRenderingAPIOpenGLES2 和 kEAGLRenderingAPIOpenGLES3 区别不大

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

kv.context = [[EAGContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES3];
//配置视图创建的渲染缓冲区
//颜色缓冲区用以存储将在屏幕上显示的颜色。你可以使用其属性设置缓冲区中的每一个像素颜色格式
//默认是GLKViewDrawableColorFormatRGBA8888: 即缓存区的每个像素(最小组成部分RGBA)使用32个bit,即4个字节
kv.drawableColorFromat = GLKViewDrawableColorFormatRGBA8888;
//深度缓冲区:通过深度测试,把需要绘制的颜色点添加到深度缓冲区和颜色缓冲区
kv.drawableDepthFormat = GLKViewDrawableDepthFormat24;
//模板缓冲区:它用于把绘制区域限定到屏幕的一个特定部分。比如影子,可以使用stencil缓冲区确保影子投射到地板
kv.drawableStencilFormat = GLKViewDrawableStencilFormat8;
//启用多重采样:
//多重采样缓冲区:如果使用OpenGL画线并优化锯齿状线,multisampling就可以帮助你处理以前对于每个像素,
//都会调用一次fragment shader(片段着色器)。而drawableMultisample基本就替代了这个工作,它就像一个像素分成更小的单元,
//并在更细微的层面上多次调用fragment shader。之后它将返回的颜色合并,生成更光滑的几何边缘效果。比较占用app处理时间和内存。
//默认GLKViewDrawableMultisampleNone。
kv.drawableMultisample = GLKViewDrawableMultisample4X;

[EAGLContext setCurrentContext: kv.context];
//开启深度测试,
glEnable(GL_DEPTH_TEST);
glClearColor(0,0,0,1.f);

//默认情况下,出于性能考虑,所有顶点着色器的属性(attribute)变量都是关闭的。
//意味着数据在着色器是不可见的,即便数据已经上传到GPU。由glEnableVertexAttribArray启用指定属性
//才可在顶点着色器中访问逐顶点的属性数据。
//glVettexAttribPointer或VB0只是建立CPU和GPU之间的逻辑连接,从而实现CPU数据上传到GPU。
//但是数据在GPU端是否可见,即着色器能否读取到数据,由是否启用了对应的属性决定,这就是glEnableVertexAttribArray的功能
//允许顶点着色器读取GPU(服务器端)的数据
glEnableVertexAttribArrat(GLKVertexAttribPosition);


//用来上传顶点数据到GPU的方法,设置合适的格式从buffer中读取数据
//1: 在使用VBO的情况下,首先要glBinderBuffer,以后ptr指定的就不是具体的数据了。因为数据已经在缓冲区了。
参数ptr指向的是缓冲区数据的偏移量。这里偏移量是整型,但是需要强制转换为const GLVoid * 类型
//2:不使用VBO的情况下,参数ptr指向的是需要上传到顶点数据指针。通常是数组名的偏移量。

//参数normalized:是否需要显卡帮忙把数据归一化到-1到+1区间,这里不需要,所以设置GL_FALSE
glVertexAttribPointer(GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr)
//工作原理:通过index得到着色器对应的变量,OpenGL会把数据复制给着色器的变量
//index参数列举: GLKVertexAttribPosition(顶点类型索引) GLKVertexAttribTexCoord0(纹理类型索引)


//加载纹理
NSDictionary *options = @{
	GLKTextureLoaderOriginalBottomLeft : @1
};
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFie: filepath options: options error: NULL];


//着色器
GLKBaseEffect *eff = [[GLKBaseEffect alloc] init];
eff.texture2d0.enabled = GL_TRUE;
//纹理名字,相当于读取纹理的索引地址
eff.texture2d0.name = textureInfo.name;

//启动着色器
[eff prepareToDraw];
/*glDrawArrays 传输或指定的数据是最终的 真实数据,在绘制时 效能更好。顶点和纹理buffer已经写好
从数组数据中提取数据渲染基本图元
* glDrawElements: 是真实的数据的调用的索引,在内存/显存上更节省
*/
glDrawArrays(...);

OpenGL ES的版本:1.x是针对固定功能流水管线硬件

2.x是针对可编程流水管线硬件,

3.x是2.x的扩展。

On-Screen Rendering(当前屏幕渲染):指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区进行的。

Off-Screen Rendering(离屏渲染):指的是GPU在当前品目缓冲区以外开辟一个缓冲区进行渲染操作。

离屏渲染:1、创建新的缓冲区 2、上下文切换

drawRect会触发离屏渲染

FrameBuffer 和 RenderBuffer的关系

一个renderBuffer对象是通过应用分配的一个2D图像缓存区。renderbuffer能够被用来分配和存储颜色、深度、模板值。也能够在一个frameBuffer被用作颜色、深度、模板的附件。一个renderBuffer是一个类似于屏幕窗口系统提供可绘制的表面。比如pBuffer。一个renderBuffer并不能直接被当作一个GL纹理。

一个frameBuffer对象(通常被称为一个FBO),是一个收集颜色、深度、和模板缓冲区的附着点。描述属性的状态,例如颜色、深度、模板缓存区的大小和格式,都关联到FBO(Frame Buffer Object)。并且纹理的名字和renderBuffer对象也都是关联于FBO。各种各样的2D图形能够被附着frameBuffer对象的颜色附着点。他们包含了renderBuffer对象存储的颜色值、一个2D纹理或立方体贴图或一个mip_level的二维切面在3D纹理。同样,各种各样的2D图形包含了当时的深度值,可以附加到一个FBO的深度附着点中去。仅仅被关联到FBO的模板附着点的2D图像,可以作为一个renderBuffer对象存储了模板值。

GL Shaper Language:

3个特殊的变量限定符:

uniform:

外部application传递给vertex/fragment shader变量修饰。

1、通过glUniform1i(...)

2、在vertex、fragment shader程序内部, Uniform 和const类型 表示它不能被shader修改。

被uniform修饰的变量,只能被shader使用read。不能write

如果uniform在vertex/fragment两者的声明方式一样,则它们可以被vertex/fragment 共享。可理解为是vertex和fragment的全局变量

使用场景:变换矩阵、材质、光照、颜色等。

attribute:

1、只能在vertex shader中使用。

2、在application中,glBindAttribLocation()绑定每个变量的位置,然后再使用glVertexAttribPointer()为变量赋值。可以使用glGetAttribLocation()来获取变量。

使用场景:顶点坐标、法线、纹理坐标、顶点颜色等

ini 复制代码
attribute vec4 a_position;
gl_Position = viewProjMatrix * a_position;

varying:

1、是vertex与fragment shader之间做数据传递使用。如果需要做传递,则必须保证vertex shader 与fragment shader中两者的声明必须保持一致(修饰符、类型、变量名)否则,不可实现数据传递。

使用场景:纹理坐标、顶点颜色

光栅化操作:这一步会进行多种测试,假如测试都通过,片段才会被显示在屏幕上

片段与相关数据---》像素所有权测试--->裁剪测试--->Alpha测试--->模板测试(通过进入模板缓存区)--->深度测试(通过进入深度缓存区) ---> 混合--->抖动显示--->逻辑操作--->颜色缓存区。

几何处理阶段:主要负责大部分多边形操作和顶点操作,包括顶点着色、坐标变换、生成图元、投影、裁剪、屏幕映射过程的,其中顶点着色、坐标变换由定点着色器完成。

ini 复制代码
GLKTextureInfo *textureInfo = [GLKTextureLoader textureWithContentsOfFile: filePath options:@{} error:nil];

GLKBaseEffect *effect = [[GLKBaseEffect alloc] init];
effect.texture2d0.enabled = GL_TRUE;
effect.texture2d0.name = textureInfo.name;
effect.transform.projectionMatrix = #;
effect.transform.modelviewMatrix = #;
[effect prepareToDraw];
css 复制代码
//从指定的声音文件中创建系统声音效果对象
[[SoundEffect alloc]initWithContentsOfFile:aPath];

VAO(顶点数据对象): Vertex Array Buffer

scss 复制代码
GLuint VAO;
glGenVertexArrays(1, &VAO);
glBindBuffer(##Glenum target##, ##Gluint buffer##);

VBO (顶点数据对象):顶点数据属性

scss 复制代码
//三个步骤:
1,使用glBenBuffer(GLsizei n, GLuint *buffers)生成新缓存对象,并返回缓存对象的标识符
2,使用glBindBuffer(GLenum target, GLuint buffer)绑定缓存对象,
	当缓存对象创建后,再使用缓存对象之前,需要将缓存对象连接到相应的缓存中。target高速VBO该缓存对象将保存顶点数组数据还是索引数组数据:GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY.
任何顶点属性,如顶点坐标、纹理坐标都是用GL_ARRAY_BUFFER。用于glDrawElements()的索引数据需要使用GL_ELEMENTS_ARRAY绑定。
注意target标志帮助VBO确定缓存系统将索引保存AGP或系统内存中,将顶点保存在显卡内存中。
3,使用glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage)将顶点数据拷贝到缓存对象中,

VBO中usage标志有9个枚举值,例如GL_STATIC_DRAW、GL_DYNAMIC_COPY、GL_STREAM_READ等。static表示VBO中的数据将不会被改动(一次制定多次使用), "dynamic"表示数据将会被频繁改动(反复指定与使用),
"stream"表示每帧数据都要改变(一次指定一次使用)。"draw"表示数据将被发送GPU以待绘制;"read"表示数据将被客户端程序读取。copy表示数据可用于绘制与读取。

仅仅draw标识对VBO有用。copy与read标志对顶点/帧缓存对象(PBO或FBO)更有意义。

GL_STATIC_DRAW 与 GL_STREAM_DRAW使用显卡内存, GL_DYNAMIC使用AGP内存,READ相关缓存更适合在系统内存或AGP内存,因为这样数据更易访问

索引缓冲对象(Element Buffer Object, EBO)

索引缓冲对象EBO相当于OpenGL中的顶点数组对概念,是为了解决同一个顶点多次重复调用对问题,可以减少内存空间浪费,提高执行效率。当需要使用重复的顶点时,通过定点的位置索引来调用顶点。而不是对重复的顶点信息重复记录和调用,。

EBO中存储的内容就是顶点位置的索引indices, EBO跟VBO类似,也是在显存中的一块内存缓冲器,只不过EBO保存的 是顶点的索引。

scss 复制代码
GLuint EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
//indices 包含顶点索引的数组,一个顶点坐标(x,y, z)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

当EBO绑定顶点索引的方式绘制模型时,需要使用glDrawElements而不是glDrawArrays。

复制代码
GLKBaseEffect、GLKReflectionMapEffect、GLKSkyboxEffect 
GLKBaseEffect类实现OpenGL ES 1.1着色和照明模型的关键子集。

GLKReflectionMapEffect类将基本效果扩展为 包含反射、映射支持

GLKSkyboxEffect类提供了一个实现skyBox效果。
makefile 复制代码
GLKNamedEffect: 提供基于着色器的OpenGL渲染效果的对象的标准界面

GLKBaseEffect: 用于基于着色器的OpenGL渲染中的一个简单的照明和着色系统

GLKRefectionMapEffect: 用于基于着色器的OpenGL渲染的支持反射 映射的照明和着色器系统

GLKSkyboxEffect: 用于基于着色器的OpenGL渲染的一个简单的天空盒子视觉效果
arduino 复制代码
GLSL vertex shader 内建输入变量  简单列举

gl_Color
gl_Narmal //顶点法线
gl_Vertex //顶点位置
gl_MultiTexCoord0 //纹理单元(一共16组, 0-15)
arduino 复制代码
//内建的输出变量
GLSL Vertext Shader 
//用来设置顶点转化为屏幕坐标的位置, vertex shader:一定要对该变量赋值
vec4 gl_Position
//点的大小
float gl_pointSize
vec4 gl_ClipVertex
arduino 复制代码
gl_FrontColor //主要颜色
gl_BackColor //背面颜色
gl_FrontSecondDaryColor //固定的镜面颜色
arduino 复制代码
gl_FragColor //画面所需要填充的颜色 gl_FragColor必须赋值
gl_FrData  //填入画面的颜色
gl_FragDeth

GPUImage处理过程:

GPUImage采用链式方法处理图片。通过添加对象到链中,处理一个target就会把上一个环节处理好的图像作为数据传递给下一个target中处理。

GPUImage四大基础类作为响应链的起点,解决将图像作为数据传递给OpenGL ES处理。将纹理以及顶点数据再次传递一个响应链中target对象。

每一个输入源,滤镜、输出源、独立的管道。

GPUImage使用的MVP设计模式

GPUImage最大的优势"自定义滤镜"

GPUImage本身的所有封装思想就是基于OpenGL ES的着色器流程上封装的

GPUImage处理环节:

source(视频/图片源) ---》filter(滤镜)---〉final target(处理好的视频/图片)

1、Source 环节(数据源)

GPUImageVideoCamera: 摄像头(用于实时拍摄视频)

GPUImageStillCamera: 摄像头(用于拍照照片)

GPUImagePicture:用于处理已经拍摄完成的图片

GPUImageMovie: 用于处理已经拍摄好的视频。

2、Filter环节(滤镜)

GPUImageFilter:用来接收图形源,通过自定义顶点/片元着色器来渲染新的图像。完成滤镜处理交给响应链中下一个对象。

3、Final环节:

GPUImageView、

GPUImageMovieWrite、

OpenGL ES处理图片的步骤:

初始化OpenGL ES环境, 编译/链接顶点着色器/片元着色器

缓存顶点/纹理坐标数据, 传输相关的数据到GPU

图片绘制帧缓存区

再从帧缓存区绘制图像

Metal Command Objects之间的关系:

命令缓存区是从命令队列中创建,

命令编码器将命令汇编到命令缓存区中,

命令缓存区将此提交并发送到GPU

GPU执行命令并将结果渲染和绘制。

Metal优化:

1、CPU低开销。Metal旨在减少或消除许多CPU端性能瓶颈,只有当按照建议使用Metal API时,应用才能受益于此设计。

2、最佳GPU性能。Metal允许您创建并向GPU提交命令。要优化GPU性能,您的应用优化这些命令的配置和组织。

3、连续的处理器并行性。Metal旨在最大限度地提高CPU和GPU的并行性。您的应用程序应该让这些处理器保持忙碌并同时工作。

4、有效的资源管理。Metal为您的资源对象提供了简单而强大的接口。您的应用应该有效的管理这些资源,以减少内存消耗,提高访问速度。

lua 复制代码
Metal语言介绍:
Metal着色语言是一个用来编写3D图形渲染逻辑和并行计算核心逻辑的变成语言,编写Metal框架的APP需要需要使用
Metal 着色语言程序。

Metal着色语言与Metal框架配合使用, Metal框架管理Metal着色语言的运行和可编译选项。Metal着色器语言使用
Clang和LLVM,编译器对于在GPU上的代码执行效率有更好的控制。

Metal这门语言是基于C++ 11.0标准设计的。它的C++基础是行多了一些拓展和限制。

Metal着色语言是有对于指针的使用限制。
Metal图形和并行计算函数用到的入参,如果是指针,使用地址空间修饰符(device、threadgroup、constant)

不支持函数指针
Metal函数名不能命名为Main函数。

缓存buffer:
在Metal中实现缓存靠的是一个指针。它指向一个在Device或者constant地址空间中的内建变量
或是开发者自定义的数据块。缓存可以被定在程序域中,或是当做函数的参数传递。

纹理Textures:
纹理数据类型是一个句柄。它是一个一维/二维/三维纹理数据。而纹理数据对应着一个纹理的某个level的mipmap的全部或者一部分。

Metal着色器语言支持下列的函数修饰符:
kernel:表示该函数是一个数据并行计算着色函数,它将被分配在一个一维/二维/三维的线程组网格中执行。
vertex:表示该函数是一个顶点着色函数,它将为顶点数据流中的每个顶点数据执行一次,然后为每个顶点生成数据输出到绘制管线中去。
fragment:表示该函数是一个片元着色函数,它将片元数据流中的每个片元和其关联的数据执行一次然后为每个片元生成数据输出到绘制管线中去。

变量与参数的地址空间修饰符
Metal着色语言使用"地址空间修饰符号",来表示一个函数变量或是参数被分于哪块内存区域。下面这些修饰符描述了不相交叠地址空间:
device: 设备地址空间
threadgroup-线程组地址空间
constant:常量地址空间
thread:thread地址空间

函数参数和变量:
图形绘制或是并行计算着色函数的输入/输出都需要通过参数传递(除了常量地址空间变量和程序域名定义的采样器以外)。参数可以是如下之一:
device buffer-设备缓存
constant buffer -常量缓存
texture object -纹理对象
sample object -采样器对象
threadgroup-线程共享的缓存

用于寻址缓存、纹理、采样器属性修饰符:
为每个参数、指定一个属性修饰符、指定明确的缓冲、纹理位置。
device和constan buffers - [[buffer(index)]]
texture - [[texture(index)]]
sampler - [[sampler(index)]]
threadgroup buffer - [[threadgroup(index)]]

CoreGraphic 是作用在 CPU 之上的,因此调用 CG 开头的方法消耗的是 CPU 资源。

**拓展:关于滤镜,ffmpeg同样有相应操作,笔者在接下里的文章中,深入讲述讲滤镜的原理+不同库下的几种实现方式的代码

相关推荐
Cang_Wang32 分钟前
Android opengles深入渲染优化
opengl
音视频牛哥10 天前
十年打磨,属于我们的技术进化与系统化演进之路
音视频开发·视频编码·直播
aqi0012 天前
FFmpeg开发笔记(六十八)Windows给FFmpeg集成AV1解码器libdav1d
ffmpeg·音视频·直播·流媒体
aqi0013 天前
FFmpeg开发笔记(六十七)Windows给FFmpeg集成支持RIST协议的librist
ffmpeg·音视频·直播·流媒体
byxdaz13 天前
OpenGL和OpenGL ES区别
opengl
FinelyYang15 天前
web和uniapp接入腾讯云直播
腾讯云·直播
byxdaz18 天前
在Qt中使用OpenGL显示大量点(点云)
opengl
二进制人工智能19 天前
【OpenGL学习】(四)统一着色和插值着色
c++·opengl
aqi0019 天前
FFmpeg开发笔记(六十六)Windows给FFmpeg集成LC3音频的编码器liblc3
ffmpeg·音视频·直播·流媒体
音视频牛哥19 天前
Android平台如何高效移动RTMP|RTSP直播流的录像文件?
音视频开发·视频编码·直播