目录
一、前言
二、openGL渲染介绍
1、OpenGl渲染管线的流程
2、顶点着色器的介绍
3、片元着色器的介绍
三、openGL着色器语言GLSL介绍
1、数据类型
2、限定符
3、二维图像渲染会用的的内建变量
4、GLSL实现顶点着色器的代码示例
5、GLSL实现片元着色器的代码示例
四、openGL主要API介绍
五、openGL渲染yuv代码示例
《Windows平台openGL显示视频代码实现》链接:
https://edu.csdn.net/learn/38258/606145?spm=1003.2001.3001.4157
《Android平台openGL显示摄像头数据代码实现》链接:
https://edu.csdn.net/learn/38258/606149?spm=1003.2001.3001.4157
在
OpenGl还有一个主要用于嵌入式场景的OpenGl ES(OpenGL for Embedded Systems)。OpenGl ES是OpenGl的子集,本文所介绍的知识适用OpenGl ES也适用OpenGl。
下图是OpenGl渲染管线的流程图。蓝色部分代表的是可编程着色器的阶段。通常二维图像的渲染过程用的的可编程阶段主要是顶点着色器和片段(片元)着色器。
顶点着色器的输入包含了三维的顶点坐标集合;顶点着色器主要的作用是把三维坐标转为另一种三维坐标。
图元装配阶段将顶点着色器输出的所有顶点作为输入,并将所有的顶点装配成指定图元的形状,比如三角形、四边形(二维图像为四边形)。
几何着色器作用是吧把图元输出的一系列顶点的集合作为输入,产生新顶点构造出新的图元来生成其他形状。
光栅化阶段的输入是几何着色器的输出,这里它会把图元映射为最终屏幕上相应的像素,生成供片段(片元)着色器使用的片段(Fragment)。
片段着色器的主要目的是计算一个像素要显示的颜色,该阶段可以产生OpenGL高级效果产。通常,片段着色器包含三维场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
测试和混合的输入是对应颜色值确定以后的片段(像素);在这个阶段检测片段的对应的深度值,来判断这个像素在物体表面的位置;通过像素alpha(透明度)值,对物体进行混合。
顶点属性通常可以包含:顶点坐标、法线、颜色、纹理坐标如下示例(不包含法线)。在二维图像YUV/RGB显示过程中顶点属性中的颜色通常也不需要。
float vertices[] = {
// ---- 位置 ---- ---- 颜色 ---- - 纹理坐标 -
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上
};
纹理是一个二维图片,它可以用来添加物体的细节;如一面正方形的墙体,有四个顶点,墙砌好后需要在墙表面贴上瓷砖,纹理可以看作是一张绘有瓷砖的布,无缝贴合到墙面上,墙体看起来就像有瓷砖的外表了。为了能够把纹理映射(Map)到四边形上,我们需要指定到四边形的每个顶点各自对应纹理的哪个部分;这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明应该从纹理图像的哪个部分采样;只要指定每一个顶点在纹理图象中所对应的像素位置,OpenGL就会自动计算顶点以外的其它点在纹理图像中所对应的像素位置,通过插值生成对应位置上像素。在片元着色器中使用纹理坐标获取纹理颜色,这个过程也叫采样。接下来了解下坐标系;
下图是屏幕坐标系,坐标系的原点(0,0)是屏幕左上角,屏幕水平方向是x轴,右向是正方向,屏幕垂直方向是Y轴,向下是正方向。
下图是纹理坐标系,坐标系的原点(0,0)是屏幕左下角,屏幕水平方向是x轴,右向是正方向,屏幕垂直方向是Y轴,向上是正方向。
下图是顶点坐标系,坐标系的原点(0,0)是屏幕中间,屏幕水平方向是x轴,右向是正方向,屏幕垂直方向是Y轴,向上是正方向。
上面三个图可以看出顶点坐标系和纹理坐标系在Y轴上和屏幕坐标系的Y轴反向,经过顶点着色器顶点坐标会转变为屏幕坐标系。纹理坐标需要在代码中进行变换为屏幕坐标,否则会出现画面上下颠倒。
片元着色器的主要功能是计算颜色、获取纹理值,从图片中获取对应像素点的颜色值、往像素点中填充颜色值(纹理值/颜色值)。opengl显示二维图像(YUV图像)时候,片元着色器中会从顶点着色器中获取纹理坐标,根据顶点坐标和采样器获取对应位置上纹理像素Y、U、V的值将YUV转RGB,最终RGB加上alpha(透明度)合成一个完整的RGBA像素作为输出。
色器语言GLSL(OpenGL Shading Language)是一种高级的图形化编程语言,其源自使用广泛的C语言,GLSL的基本语法与C基本相同;与C语言不同GLSL支持向量和矩阵操作;GLSL通过限定符操作来管理输入输出类型的。
标量类型:在GLSL中标量只有bool、int和float三种。
向量类型:vec2、vec3、vec4,ivec2、ivec3、ivec4、bvec2、bvec3和bvec4,数字代表维数、i表示int类型、b表示bool类型;向量类型数据和矩阵相乘进行变换时,矩阵在前,向量在后。
矩阵类型:有2*2、3*3、4*4三种大小的矩阵,分别用mat2、mat3、mat4表示 。
采样器类型:sampler2D;
此外还有结构体、数组、void等和c语言类似。
GLSL中的限定符号主要有attritude、uniform、varying、const;
attritude: 顶点数据,如空间位置,法向量,纹理坐标以及顶点颜色,它是针对每一个顶点的数据。属性只在顶点着色器中才有,片元着色器中没有该属性。
uniform: uniforms保存由CPU层序传递给着色器的只读常量数据 。通常用于定义物体中所有顶点都相同的量,比如Y、U、V的数据。
varying:用于存储顶点着色器的输出数据,也存储片元着色器的输入数据,一般用于顶点着色器传递到片元着色器的量。
const:常量
gl_Position:顶点坐标;是顶点着色器的输入变量
此外还有gl_PointSize、gl_FragCoord、gl_FragFacing、gl_FragColor、gl_FragData等。
SL(Shading Language)实现的顶点着色器的代码如下,a_position和a_texCoord都是通过CPU程序传递的值,是作为顶点着色器的输入,通过in来标识;v_texCoord是顶点着色器的输出通过out来标识,在顶点着色器中将输入的纹理坐标a_texCoord(attritude)转换到v_texCoord中,v_texCoord又作为片元着色器的输入,这样片元着色器就可以拿到纹理坐标。
"#version 300 es \n"
"layout(location = 0) in vec4 a_position; \n"
"layout(location = 1) in vec2 a_texCoord; \n"
"out vec2 v_texCoord; \n"
"void main() \n"
"{ \n"
" gl_Position = a_position; \n"
" v_texCoord = a_texCoord; \n"
"} \n";
SL(Shading Language)实现的片元着色器的代码如下;precision mediump float;用来设置片元着色器中浮点数精度为中等精度,通常float默认精度为highp;精度越低执行速度越快,在不太影响渲染效果前提下,可以尽量降低运算精度。v_texCoord为片元着色器的输入纹理坐标,通过顶点着色器传递到元着色器。outColor为输出颜色。y_texture、u_texture、v_texture为采样器,接收CPU传递的YUV的像素值;SL中的texture()函数根据纹理坐标和采样器获取对应的YUV像素值,通过矩阵乘法将YUV转RGB,将RGB和alpha输出给outColor。
"#version 300 es \n"
"precision mediump float; \n"
"in vec2 v_texCoord; \n"
"layout(location = 0) out vec4 outColor; \n"
"uniform sampler2D y_texture; \n"
"uniform sampler2D u_texture; \n"
"uniform sampler2D v_texture; \n"
"void main() \n"
"{ \n"
" vec3 yuv; \n"
" yuv.x = texture(y_texture, v_texCoord).r; \n"
" yuv.y = texture(u_texture, v_texCoord).r-0.5; \n"
" yuv.z = texture(v_texture, v_texCoord).r-0.5; \n"
" highp vec3 rgb = mat3( 1, 1, 1, \n"
" 0, -0.344, 1.770, \n"
" 1.403, -0.714, 0) * yuv; \n"
" outColor = vec4(rgb, 1); \n"
"} \n";
openGL的API介绍可以从https://docs.gl/ 里面查看。
1、 GLuint glCreateShader(
GLenum shaderType)
;
该函数用于创建一个空的着色器对象,并返回一个可以引用的非零值(着色器ID)。shaderType是着色器类型:GL_VERTEX_SHADER或者GL_FRAGMENT_SHADER。
2、void glShaderSource(
GLuint shader,GLsizei count,const GLchar **string,const GLint *length)
;
该函数将着色器中的源代码设置为string指定的字符串数组中的源代码。先前存储在着色器对象中的任何源代码都将被完全替换。参数shader为着色器ID;count为该函数后面两个参数的元素个数;string为着色器源代码字符串指针数组;length为指定字符串长度的数组。
3、void glCompileShader(
GLuint shader)
;
该函数编译已存储在shader指定的着色器对象中的源代码字符串。shader为着色器ID。
4、void glGetShaderiv( GLuint shader,GLenum pname,GLint *params);
该函数从着色器对象返回一个参数(用来检测着色器编译是否成功)。参数shader为着色器ID;pname为着色器参数通常GL_INFO_LOG_LENGTH日志信息长度,GL_COMPILE_STATUS编译状态;params返回的参数指针.
5、glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
该函数用于获取着色器日志信息。参数shader为着色器ID;maxLength为指定日志缓存大小;length返回日志的字符串长度;infoLog返回日志信息的字符指针。
void glDeleteShader(
GLuint shader)
;该函数用于删除一个着色器对象。shader为着色器ID。
7、GLuint glCreateProgram(void);
该函数用于创建一个program,并返回一个program ID。
8、void glAttachShader(
GLuint program,GLuint shader)
;
该函数将着色器添加到program对象上。参数program为program ID;shader为着色器ID。
9、void glLinkProgram(GLuint program);
该函数用于连接一个program对象;用于创建将在可编程顶点着色器上运行的可执行文件、以及用于创建将在可编程片原着色器上运行的可执行文件。program为programID。
10、void glGetProgramiv( GLuint program,GLenum pname,GLint *params);
该函数用于获取着色器program的属性值。program为programID;pname为获取属性值类型比如GL_LINK_STATUS、GL_INFO_LOG_LENGTH;params返回的参数指针。
11、void glDeleteProgram(GLuint program);
该函数用于删除一个program对象。program为programID。
12、GLint glGetUniformLocation(GLuint program,const GLchar *name);
该函数用于返回指定的uniform变量的位置。program为programID;name为uniform变量名称字符串指针。
13、void WINAPI glGenTextures(GLsizei n,GLuint *textures);
该函数用于生成纹理名称。n为要生成的纹理名称数目;textures指向在其中存储生成的纹理名称的数组的第一个元素的指针。
14、void WINAPI glViewport(GLint x,GLint y,GLsizei width,GLsizei height);
该函数用于设置视见区域属性。参数x,y指定了视见区域的左下角在窗口中的位置,一般情况下为(0,0),Width和Height指定了视见区域的宽度和高度。
15、void glBindTexture(GLenum target, GLuint texture );
该函数用于将已经生成的纹理的名字绑定到对应的目标纹理上。target目标纹理,比如GL_TEXTURE_2D;texture为纹理名称,如glGenTextures生成的纹理名称,或者为GL_NONE;
16、void glTexImage2D( GLenum target, GLint level, GLint internalFormat,
GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type,
const GLvoid * data);
该函数用于根据指定的参数,生成一个2D纹理。参数target :指定纹理单元的类型是哪一种比如GL_TEXTURE_2D;level:指定纹理单元的层次,非mipmap纹理level设置为0;internalFormat:指定OpenGL是如何管理纹理单元中数据格式的,比如GL_LUMINANCE;width和height是指定纹理单元的宽和高,和输入图像的宽高一样;border:指定纹理单元的是否包含边框,如果包含边框取值为1,不包含边框取值为0;format:指定data所指向的数据的格式,如GL_LUMINANCE;type:指定data所指向的数据的类型,如GL_UNSIGNED_BYTE 无符号 char;data为输入的图像数据。
17、void WINAPI glTexParameterf(GLenum target,GLenum pname,GLfloat param);
该函数用于设置纹理映射参数。 参数target :指定纹理单元的类型是哪一种比如GL_TEXTURE_2D;pname为单个值纹理参数的符号名称,如GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T、GL_TEXTURE_MIN_FILTER、GL_TEXTURE_MAG_FILTER等;param表示pname 的设置值。
18、void glUseProgram(GLuint program);
该函数将指定的 program 程序对象来作为当前渲染状态的一部分;将OpenGL渲染管道切换到着色器模式。program为 programID.
19、void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,const GLvoid * pointer);
该函数指定了渲染时索引值为 index 的顶点属性数组的数据格式和位置。index指定要修改的顶点属性的索引值;size指定每个顶点属性的组件数量、如二维坐标(x,y)为2,三位坐标(x、y、z)为3;type指定数组中元素的数据类型;normalized数组中数据值是否应该被归一化(GL_TRUE)或者直接转换为固定点值(GL_FALSE;stride指定连续顶点属性之间的偏移量,比如二维坐标下每个坐标元素之间只有xy、则偏移量为2;pointer数组地址。
20、void glEnableVertexAttribArray(GLuint index)
该函数表示使能指定顶点属性数组。index顶点属性的索引值。
21、void glActiveTexture(GLenum texUnit);
该函数选择一个纹理单元,之后的纹理函数将作用于该纹理单元上,参数texUnit为符号常量GL_TEXTUREi ,i的取值范围为0~K-1,K是OpenGL实现支持的最大纹理单元数,可以使用GL_MAX_TEXTURE_UNITS来调用函数glGetIntegerv()获取该值。
22、void glUniform1i(GLint location, GLint v0);
该函数用于为当前着色器程序对象指定Uniform变量的值。location为uniform变量的位置;v0为uniform变量设置的值。
23、void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid *indices);
该函数用用于绘制指定的基元图形、如绘制三角形、或者通过绘制2个三角形来绘制矩形。mode要渲染的图元模式,如GL_TRIANGLES等;count要呈现的元素数;type索引中值的类型,如GL_UNSIGNED_SHORT(short类型数据);indices元素索引数组。
1、着色器代码编译
static GLuint loadShaderSourceCode(GLenum shaderType, const char* pSource)
{
GLuint shader = glCreateShader(shaderType);
if (shader)
{
glShaderSource(shader, 1, &pSource, NULL);
glCompileShader(shader);
GLint compiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if (0 == compiled)
{
GLint logLen = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char* buf = new char [logLen];
if (buf) {
glGetShaderInfoLog(shader, logLen, NULL, buf);
LOGI("Could not compile shader %d:\n%s\n",shaderType, buf);
delete [] buf;
}
glDeleteShader(shader);
return 0;
}
}
}
return shader;
}
2、连接着色器程序
static GLuint makeShaderProgram(const char* pVertexSource, const char* pFragmentSource)
{
GLuint program = glCreateProgram();//创建一个着色器程序
if (program)
{
glAttachShader(program, vertexShaderId);//将着色器绑定到程序上
glAttachShader(program, fragmentShaderId);
glLinkProgram(program);//链接着色器程序
GLint linkStatus = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);//获取程序链接状态
if (linkStatus != GL_TRUE)
{
GLint logLen = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen);
if (logLen > 0)
{
char* buf = new char [logLen];
if (buf) {
glGetProgramInfoLog(program, logLen, NULL, buf);
LOGW("Could not link program:\n%s\n", buf);
delete [] buf;
}
}
glDeleteProgram(program);
return 0;
}
}
return program;
}
3、从着色器程序中获取yuv采样器位置id
ySamplerLoc = glGetUniformLocation(programId, "y_texture" );
uSamplerLoc = glGetUniformLocation(programId, "u_texture");
vSamplerLoc = glGetUniformLocation(programId, "v_texture");
4、创建纹理生成纹理名称
GLuint textureIds[3] = {0};
glGenTextures(3, textureIds);
yTextureId = textureIds[0];
uTextureId = textureIds[1];
vTextureId = textureIds[2];
5、yuv渲染
int width = yuv.width;
int height = yuv.height;
pYTmp = (char *)yuv.pData;
pUTmp = pYTmp + width * height;
pVTmp = pUTmp + width * height / 4;
glBindTexture(GL_TEXTURE_2D, m_displayYuvMng.yTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE,width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pYTmp);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);//将GL_TEXTURE_2D 纹理解绑
glBindTexture(GL_TEXTURE_2D,m_displayYuvMng.uTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width >> 1, height >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,pUTmp);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glBindTexture(GL_TEXTURE_2D, m_displayYuvMng.vTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width >> 1, height >> 1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,pVTmp);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_displayYuvMng.yTextureId);
glUniform1i(m_displayYuvMng.ySamplerLoc, 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_displayYuvMng.uTextureId);
glUniform1i(m_displayYuvMng.uSamplerLoc, 1);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, m_displayYuvMng.vTextureId);
glUniform1i(m_displayYuvMng.vSamplerLoc, 2);
//顶点位置坐标
GLfloat verticesCoords[] = {
-1.0f, 1.0f, 0.0f, // Position 0
-1.0f, -1.0f, 0.0f, // Position 1
1.0f, -1.0f, 0.0f, // Position 2
1.0f, 1.0f, 0.0f, // Position 3
};
GLfloat textureCoords[] = {
0.0f, 0.0f, // TexCoord 0
0.0f, 1.0f, // TexCoord 1
1.0f, 1.0f, // TexCoord 2
1.0f, 0.0f // TexCoord 3
};
GLushort indices[] = { 0, 1, 2, 2, 3, 0 };
glUseProgram (m_displayYuvMng.programId);
glVertexAttribPointer (0, 3, GL_FLOAT,
GL_FALSE, 3 * sizeof (GLfloat), verticesCoords);
glVertexAttribPointer (1, 2, GL_FLOAT,
GL_FALSE, 2 * sizeof (GLfloat), textureCoords);
glEnableVertexAttribArray (0);
glEnableVertexAttribArray (1);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
glutSwapBuffers();