KxMovieGLView
该类就是展示类. 该类的图像展示是使用openGL进行绘制的.
类结构
public 方法
- (id) initWithFrame:(CGRect)frame decoder: (KxMovieDecoder *) decoder;// 初始化
- (void) render: (KxVideoFrame *) frame; ///渲染图片
OpenGL 基础知识
渲染层
+ (Class) layerClass
{
return [CAEAGLLayer class];
}
想要使用openGL进行绘制,必须使用CAEAGLLayer 作为UIView的图层才可以.
使用OpenGL基本步骤
第一步:配置layer
CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;
eagLayer.opaque = YES; // 提高渲染质量 但会消耗内存
eagLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : @(false),kEAGLColorFormatRGBA8:@(true)};
kEAGLDrawablePropertyRetainedBacking 不需要retain 已经渲染的图像
kEAGLColorFormatRGBA8 OpenGL采用的颜色空间
第二步 配置context 上下文
self.eagContext = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.eagContext];
第三步 创建一个帧缓冲区
glGenFramebuffers(1, &_framebuffer); // 为帧缓存申请一个内存标示,唯一的 1.代表一个帧缓存
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);// 把这个内存标示绑定到帧缓存上
第四步 创建渲染buffer
glGenRenderbuffers(1, &_colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer);
[self.eagContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER, _colorRenderbuffer);
第五步 规定绘制屏幕大小
CGFloat scale = [[UIScreen mainScreen] scale]; //获取视图放大倍数,可以把scale设置为1试试
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale); //设置视口大小
glClearColor(1, 1, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
第六步 渲染顶点
设置顶点 和 shader
第七步 展示数据
[self.eagContext presentRenderbuffer:GL_RENDERBUFFER];
补充知识
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
获取屏幕的宽高
Shader and Program编程基本概念
在OpenGL ES中,每个program对象有且仅有一个Vertex Shader对象和一个Fragment Shader对象连接到它。
Shader:类似于C编译器
Program:类似于C链接器
glLinkProgram操作产生最后的可执行程序,它包含最后可以在硬件上执行的硬件指令。
- 创建Shader
- 1.)编写Vertex Shader和Fragment Shader源码。
- 2.)创建两个shader 实例:GLuint glCreateShader(GLenum type);
- 3)给Shader实例指定源码。 glShaderSource
- 4)在线编译shaer源码 void glCompileShader(GLuint shader)
- 创建Program
- 1)创建program GLuint glCreateProgram(void)
- 2)绑定shader到program 。 void glAttachShader(GLuint program, GLuint shader)。每个program必须绑定一个Vertex Shader 和一个Fragment Shader。
- 3)链接program 。 void glLinkProgram(GLuint program)
- 4)使用porgram 。 void glUseProgram(GLuint program)
- 对于使用独立shader编译器编译的二进制shader代码,可使用glShaderBinary来加载到一个shader实例中。
shader(着色器) 编程
着色器,是一种较为简短的程序片段,用于告诉图形软件如何计算和输出图像。shader主要分两类:Vertex Shader(顶点着色器)和Fragment Shader(片段着色器)
shader三种变量类型(uniform,attribute和varying)
uniform变量一般用来表示:变换矩阵,材质,光照参数和颜色等信息。
uniform mat4 viewProjMatrix; //投影+视图矩阵
uniform mat4 viewMatrix; //视图矩阵
uniform vec3 lightPosition; //光源位置
attribute变量是只能在vertex shader中使用的变量。(它不能在fragment shader中声明attribute变量,也不能被fragment shader中使用)
一般用attribute变量来表示一些顶点的数据,如:顶点坐标,法线,纹理坐标,顶点颜色等。
在application中,一般用函数glBindAttribLocation()来绑定每个attribute变量的位置,然后用函数glVertexAttribPointer()为每个attribute变量赋值。
varying变量是vertex和fragment shader之间做数据传递用的。一般vertex shader修改varying变量的值,然后fragment shader使用该varying变量的值。因此varying变量在vertex和fragment shader二者之间的声明必须是一致的。application不能使用此变量。
内置变量
内置变量可以与固定函数功能进行交互。在使用前不需要声明。顶点着色器可用的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 输入属性-表示顶点的主颜色 |
gl_SecondaryColor | vec4 | 输入属性-表示顶点的辅助颜色 |
gl_Normal | vec3 | 输入属性-表示顶点的法线值 |
gl_Vertex | vec4 | 输入属性-表示物体空间的顶点位置 |
gl_MultiTexCoordn | vec4 | 输入属性-表示顶点的第n个纹理的坐标 |
gl_FogCoord | float | 输入属性-表示顶点的雾坐标 |
gl_Position | vec4 | 输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。 |
gl_ClipVertex | vec4 | 输出坐标,用于用户裁剪平面的裁剪 |
gl_PointSize | float | 点的大小 |
gl_FrontColor | vec4 | 正面的主颜色的varying输出 |
gl_BackColor | vec4 | 背面主颜色的varying输出 |
gl_FrontSecondaryColor | vec4 | 正面的辅助颜色的varying输出 |
gl_BackSecondaryColor | vec4 | 背面的辅助颜色的varying输出 |
gl_TexCoord[] | vec4 | 纹理坐标的数组varying输出 |
gl_FogFragCoord | float | 雾坐标的varying输出 |
片段着色器的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 包含主颜色的插值只读输入 |
gl_SecondaryColor | vec4 | 包含辅助颜色的插值只读输入 |
gl_TexCoord[] | vec4 | 包含纹理坐标数组的插值只读输入 |
gl_FogFragCoord | float | 包含雾坐标的插值只读输入 |
gl_FragCoord | vec4 | 只读输入,窗口的x,y,z和1/w |
gl_FrontFacing | bool | 只读输入,如果是窗口正面图元的一部分,则这个值为true |
gl_PointCoord | vec2 | 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。 |
gl_FragData[] | vec4 | 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。 |
gl_FragColor | vec4 | 输出的颜色用于随后的像素操作 |
gl_FragDepth | float | 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替 |
gl_Position 输出的是投影矩阵
矩阵变换
OpenGL中的坐标处理过程包括模型变换、视变换、投影变换、视口变换等过程.
具体变换过程可以参考这里
这里贴两张图
这里 gl_Position = 模型变换世界坐标系转换投影转换* 顶点坐标
这里 模型变换 就是自身大小,是标准矩阵
世界坐标系转换 默认把模型放在世界坐标系的原点
投影转换 是正交矩阵
顶点
顶点是啥?
顶点就是坐标位置,不管你是画直线,三角形,正方体,球体,以及3D游戏人物等,都需要顶点来确定其形状。
顶点可可以被定为为2维或者三维,这个看你的实际情况!但是你要注意,所有的内部计算都是建立在三维数据的基础之上,比如:你定义一个点(x,y) 是二维形式,OpenGL默认把它的z设置为0,看到这里你以为三维就是(x,y,z)的形式吗?不是的,OpenGL 是根据三维投影几何的齐次方程坐标进行操作的,因此在内部计算是都是用4个浮点坐标值表示(x,y,z,w) 如果w不等于0 那么这些坐标值就对应于与欧几里德三维点(x/w,y/w,z/w)。一般情况下w默认为1.0.
可以参考这里学习顶点知识
类介绍
初始化
- (id) initWithFrame:(CGRect)frame
decoder: (KxMovieDecoder *) decoder
{
self = [super initWithFrame:frame];
if (self) {
_decoder = decoder;
if ([decoder setupVideoFrameFormat:KxVideoFrameFormatYUV]) {
_renderer = [[KxMovieGLRenderer_YUV alloc] init];
LoggerVideo(1, @"OK use YUV GL renderer");
} else {
_renderer = [[KxMovieGLRenderer_RGB alloc] init];
LoggerVideo(1, @"OK use RGB GL renderer");
}
CAEAGLLayer *eaglLayer = (CAEAGLLayer*) self.layer;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
nil];
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!_context ||
![EAGLContext setCurrentContext:_context]) {
LoggerVideo(0, @"failed to setup EAGLContext");
self = nil;
return nil;
}
glGenFramebuffers(1, &_framebuffer);
glGenRenderbuffers(1, &_renderbuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer*)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
LoggerVideo(0, @"failed to make complete framebuffer object %x", status);
self = nil;
return nil;
}
GLenum glError = glGetError();
if (GL_NO_ERROR != glError) {
LoggerVideo(0, @"failed to setup GL %x", glError);
self = nil;
return nil;
}
if (![self loadShaders]) {
self = nil;
return nil;
}
_vertices[0] = -1.0f; // x0
_vertices[1] = -1.0f; // y0
_vertices[2] = 1.0f; // ..
_vertices[3] = -1.0f;
_vertices[4] = -1.0f;
_vertices[5] = 1.0f;
_vertices[6] = 1.0f; // x3
_vertices[7] = 1.0f; // y3
LoggerVideo(1, @"OK setup GL");
}
return self;
}
- 设置解码器解码成yuv 格式数据流
- 配置layer
- 配置上下文,上下文不合法返回
- 创建缓冲区
- 创建渲染buffer.并且绑定渲染buffer的宽高内存地址
- 加载shader
- 设置顶点(这里采用的顶点是二维的,四个角)
加载shader
这里我们主要看看shader yuv 格式的转换
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 modelViewProjectionMatrix;
varying vec2 v_texcoord;
void main()
{
gl_Position = modelViewProjectionMatrix * position;
v_texcoord = texcoord.xy;
}
modelViewProjectionMatrix 是应该模型矩阵,世界标准矩阵,和正交矩阵转换好的矩阵(二维矩阵没必要使用投影.)
v_texcoord 保存纹理
vec4 texture2D(sampler2D sampler, vec2 coord)
The texture2D function returns a texel, i.e. the (color) value of the texture for the given coordinates.
第一个参数代表图片纹理,第二个参数代表纹理坐标点(代表绘制方向),通过GLSL的内建函数texture2D来获取对应位置纹理的颜色RGBA值
我们看yuv片段着色器
varying highp vec2 v_texcoord;
uniform sampler2D s_texture_y;
uniform sampler2D s_texture_u;
uniform sampler2D s_texture_v;
void main()
{
highp float y = texture2D(s_texture_y, v_texcoord).r;
highp float u = texture2D(s_texture_u, v_texcoord).r - 0.5;
highp float v = texture2D(s_texture_v, v_texcoord).r - 0.5;
highp float r = y + 1.402 * v;
highp float g = y - 0.344 * u - 0.714 * v;
highp float b = y + 1.772 * u;
gl_FragColor = vec4(r,g,b,1.0);
}
这里我们知道我们采用的是yuv颜色空间,而渲染要rgb 颜色,因此需要yuv颜色空间转换成rgb颜色空间.
这里我们看见每个从texture2D 获取的rgbA颜色我们只是获取的r颜色空间.这主要是外界传入给我们的数据导致的
public 方法
- (void)render: (KxVideoFrame *) frame
{
static const GLfloat texCoords[] = {
0.0f, 1.0f,
1.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f,
};
[EAGLContext setCurrentContext:_context];
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glViewport(0, 0, _backingWidth, _backingHeight);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(_program);
if (frame) {
[_renderer setFrame:frame];
}
if ([_renderer prepareRender]) {
GLfloat modelviewProj[16];
mat4f_LoadOrtho(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, modelviewProj);
glUniformMatrix4fv(_uniformMatrix, 1, GL_FALSE, modelviewProj);
glVertexAttribPointer(ATTRIBUTE_VERTEX, 2, GL_FLOAT, 0, 0, _vertices);
glEnableVertexAttribArray(ATTRIBUTE_VERTEX);
glVertexAttribPointer(ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, 0, 0, texCoords);
glEnableVertexAttribArray(ATTRIBUTE_TEXCOORD);
#if 0
if (!validateProgram(_program))
{
LoggerVideo(0, @"Failed to validate program");
return;
}
#endif
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
这个方法就是将镇数据渲染到gl所在的屏幕上.
该类还是比较简单的,就是单纯的将yuv数据进行播放.