OpenGL 绘制你的梦想

1 目标

记录一下OpenGL的开发过程。

2 环境

使用Google的angle中抽取的opengl模块,分为EGL和GLES两部分,EGL 用于将窗口和OpenGL项目进行绑定,GLES就是真正的OpenGL渲染库。

3 EGL

调用一系列的接口来将一个窗口句柄绑定到GL渲染库上。

3.1 成员变量EGLConfig m_config;EGLSurface m_surface;EGLContext m_context;EGLDisplay m_display;HWND m_wnd;//窗口句柄GLuint m_programId;GLuint m_vShaderId; //订单着色器GLuint m_fShaderId; //片段着色器GLuint m_textures[3];//纹理3.2 初始化//初始化m_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);if (m_display == EGL_NO_DISPLAY){return false;}EGLBoolean b = eglInitialize(m_display, &vMajor, &vMinor);if (!b) {return false;}3.3 EGL绑定窗口const EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE };const EGLint configAttribs[] = {//EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,EGL_RED_SIZE, 5,EGL_GREEN_SIZE, 6,EGL_BLUE_SIZE, 5,EGL_ALPHA_SIZE, EGL_DONT_CARE,EGL_DEPTH_SIZE, EGL_DONT_CARE,EGL_STENCIL_SIZE,EGL_DONT_CARE,EGL_SAMPLE_BUFFERS,0,EGL_NONE};eglChooseConfig(m_display, configAttribs, m_config, 1, &cfgSize);if (!b || cfgSize <= 0) {EGLint err = eglGetError();return false;}//m_wnd 就是要绑定的窗口句柄m_surface = eglCreateWindowSurface(m_display, m_config, m_wnd, NULL);if (m_surface == EGL_NO_SURFACE) {EGLint err = eglGetError();return false;}m_context = eglCreateContext(m_display, m_config, EGL_NO_CONTEXT, contextAttribs);if (m_context == EGL_NO_CONTEXT) {EGLint err = eglGetError();return false;}//将渲染切换到当前线程,如果有多线程绘制,可以每次在绘制一帧前调用切换上下文b = eglMakeCurrent(m_display, m_surface, m_surface, m_context);if (!b) {EGLint err = eglGetError();return false;}3.4 销毁if (m_surface) {eglDestroySurface(m_display, m_surface);m_surface = nullptr;}if (m_context) {eglMakeCurrent(m_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(m_display, m_context);m_context = nullptr;}if(m_display){eglTerminate(m_display);m_display=nullptr;}4 进入正题

上面的EGL过程在移动端不需要考虑,系统已经帮你绑定到了activity,可以直接进入正题。

4.1 着色器

OpenGL的绘制需要两个着色器,一个叫顶点着色器,另一个叫片段着色器。

顶点着色器用来确定绘制图形的边界,片段着色器用来填充图形。

着色器有着一套自己的语法,类似一种编程语言,初始化的过程就是将着色器的代码编译进OpenGL的Program中,之后着色器代码将在GPU中运行。

顶点着色器

#version 300 eslayout(location = 0) in vec4 vPosition;layout(location = 1) in vec2 aTexCoord;out vec2 TexCoord;void main(){gl_Position = vPosition;TexCoord = aTexCoord;}

片段着色器

#version 300 esprecision mediump float;out vec4 FragColor;//I420数据的平面uniform sampler2D SamplerY;uniform sampler2D SamplerU;uniform sampler2D SamplerV;//NV12数据的平面uniform sampler2D SamplerNV12_Y;uniform sampler2D SamplerNV12_UV;//纹理坐标in highp vec2 TexCoord;//0 代表 I420, 1 代表 NV12uniform int yuvType;//用来做YUV –> RGB 的变换矩阵const vec3 delyuv = vec3(-0.0 / 255.0, -128.0 / 255.0, -128.0 / 255.0);const vec3 matYUVRGB1 = vec3(1.0, 0.0, 1.402);const vec3 matYUVRGB2 = vec3(1.0, -0.344, -0.714);const vec3 matYUVRGB3 = vec3(1.0, 1.772, 0.0);void main(){vec3 CurResult;highp vec3 yuv;if (yuvType == 0) {//因为是YUV的一个平面,所以采样后的r,g,b,a这四个参数的数值是一样的yuv.x = texture(SamplerY, TexCoord).r;yuv.y = texture(SamplerU, TexCoord).r;yuv.z = texture(SamplerV, TexCoord).r;}else {yuv.x = texture(SamplerNV12_Y, TexCoord).r;//因为NV12是2平面的,对于UV平面,在加载纹理时,会指定格式,让U值存在r,g,b中,V值存在a中。yuv.y = texture(SamplerNV12_UV, TexCoord).r;yuv.z = texture(SamplerNV12_UV, TexCoord).a;}//读取值得范围是0-255,读取时要-128回归原值yuv += delyuv;//用数量积来模拟矩阵变换,转换成RGB值CurResult.x = dot(yuv, matYUVRGB1);CurResult.y = dot(yuv, matYUVRGB2);CurResult.z = dot(yuv, matYUVRGB3);//输出像素值给光栅器FragColor = vec4(CurResult.rgb, 1);};

编译着色器

//创建着色器//type有GL_VERTEX_SHADER – 顶点着色器 GL_FRAGMENT_SHADER – 片段着色器shaderId = glCreateShader(type);if (!glIsShader(shaderId)) {//检查是否创建成功return false;}//shaderSrc就是上面着色器代码的字符串glShaderSource(shaderId, 1, &shaderSrc, NULL);//开始编译glCompileShader(shaderId);//检查是否编译成功GLint compiled = 0;glGetShaderiv(shaderId, GL_COMPILE_STATUS, &compiled);if (!compiled) {GLint infoLen = 0;glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &infoLen);if (infoLen > 1){//获取编译失败的信息infoLog = new char[infoLen]();glGetShaderInfoLog(shaderId, infoLen, NULL, infoLog);printf(“Error compiling shader:%s”, infoLog);delete[] infoLog;}glDeleteShader(shaderId);return false;}//编译成功,得到shaderId4.2 创建Programm_programId = glCreateProgram();if (m_programId == 0) {close();return false;}//连接上面编译好的两个着色器glAttachShader(m_programId, m_vShaderId);glAttachShader(m_programId, m_fShaderId);glLinkProgram(m_programId);//检查一下连接是否成功GLint linked = 0, infoLen = 0;glGetProgramiv(m_programId, GL_LINK_STATUS, &linked);if (!linked) {glGetProgramiv(m_programId, GL_INFO_LOG_LENGTH, &infoLen);if (infoLen > 1){//连接失败原因char* infoLog = new char[sizeof(char) * infoLen]();glGetProgramInfoLog(m_programId, infoLen, NULL, infoLog);printf(“Error linking program:%s”, infoLog);delete[] infoLog;}return false;}//开始使用glUseProgram(m_programId);//一般会在绘制前先刷一下底色glClearColor(1.0f, 1.0f, 1.0f, 0.0f);4.3 创建纹理

纹理是绘制片段着色器时使用的贴图,示例中绘制的是YUV数据,所以我们分布对Y U V创建三个纹理。

for (int i = 0; i < 3; i++) {glGenTextures(1, &m_textures[i]);glBindTexture(GL_TEXTURE_2D, m_textures[i]);// 为当前绑定的纹理对象设置环绕、过滤方式glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(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);}4.4 绘制4.4.1 切换上下文//切换上下文eglMakeCurrent(m_display, m_surface, m_surface, m_context);glUseProgram(m_programId);glViewport(x, y, cx, cy);4.4.2 构建顶点数据//index=0 对应顶点着色器代码中的vPosition变量glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), vVertices);glEnableVertexAttribArray(0);//index=1 对应顶点着色器代码中的aTexCoord变量glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), vVertices + 3); glEnableVertexAttribArray(1);

顶点数据

static const GLfloat vertices[] ={-1.0f, 1.0f, 0.0f,0.0f, 1.0f, 0.0f, //左上这里是 一个顶点坐标+一个纹理坐标1.0f, 1.0f, 0.0f,1.0f, 1.0f, 0.0f,//右上-1.0f, -1.0f, 0.0f,0.0f, 0.0f, 0.0f,//左下1.0f, -1.0f, 0.0f, 1.0f,0.0f, 0.0f //右下};

顶点数据包含了顶点坐标和纹理坐标,顶点坐标用来确定物体绘制形状的边界,纹理坐标用来给物体刷漆时框定的边界,但是他们具有不同的坐标系:

顶点坐标的坐标系

纹理坐标的坐标系

当按照这个标准把YUV图形渲染上去的时候你会惊奇地发现图像是倒着的,具体原因我没去详细了解过,应该是跟图片的读取有关,最简单的解决办法就是在顶点数组里调整一下纹理坐标,纹理倒着刷,图像不就变正了么,千万别自己去把YUV数据倒过来,这样太耗性能了。

glVertexAttribPointer

index:绑定顶点着色器里的第几个属性

size:一个顶点所有数据的个数,这里每个顶点包含了xyz三个浮点数所以是3

type:顶点描述数据的类型,这里position数组中的数据全部为float,所以是GL_FLOAT

normalized:是否需要显卡帮忙把数据归一化到-1到+1区间,这里不需要,所以设置GL_FALSE

stride:一个顶点到下一个顶点的字节数,这里为3+3个元素(顶点xyz+纹理xyz)两个float,所以是sizeof(float)*6

pointer:顶点数组中第一个元素的指针

glEnableVertexAttribArray

我们能声明的顶点属性是有上限的,它一般由硬件来决定。OpenGL确保至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询glGetIntegerv(GL_MAX_VERTEX_ATTRIBS,&n); 来获取具体的上限。默认情况下,出于性能考虑,所有顶点着色器的属性(Attribute)变量都是关闭的,所以需要通过glEnableVertexAttribArray来打开。

4.4.3 构建纹理数据//传纹理的像素格式给fragment shaderGLint yuvType = glGetUniformLocation(m_programId, “yuvType”);glUniform1i(0, yuvType);GLint samplerY = glGetUniformLocation(m_programId, “SamplerY”);GLint samplerU = glGetUniformLocation(m_programId, “SamplerU”);GLint samplerV = glGetUniformLocation(m_programId, “SamplerV”);glUniform1i(samplerY, 0);glUniform1i(samplerU, 1);glUniform1i(samplerV, 2);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D,m_textures[0]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->width, frame->height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[0]);glActiveTexture(GL_TEXTURE1);glBindTexture(GL_TEXTURE_2D, m_textures[1]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->width/2, frame->height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[1]);glActiveTexture(GL_TEXTURE2);glBindTexture(GL_TEXTURE_2D, m_textures[2]);glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, frame->width/2, frame->height/2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame->data[2]);

glGetUniformLocation

从片段着色器中获取uniform变量的引用

glUniform1i

给uniform变量的引用赋int类型的值

glActiveTexture

启用纹理槽,OpenGL支持32个纹理槽,GL_TEXTURE0 – GL_TEXTURE31

glBindTexture

将本地代码里创建的纹理绑定到系统的纹理槽上

glTexImage2D

在纹理槽上填充纹理图像数据

4.4.4 呈现glClear(GL_COLOR_BUFFER_BIT);glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);eglSwapBuffers(m_display, m_surface);

glClear

清理画布

glDrawArrays

绘制顶点,在前面加入的顶点数组中连贯地画连续4个点的连线。

GL_POINTS – 单独的将顶点画出来。

GL_LINES – 单独地将直线画出来。行为和 GL_TRIANGLES 类似。

GL_LINE_STRIP – 连贯地将直线画出来。行为和 GL_TRIANGLE_STRIP 类似。

GL_LINE_LOOP – 连贯地将直线画出来。行为和 GL_LINE_STRIP 类似,但是会自动将最后一个顶点和第一个顶点通过直线连接起来。

GL_TRIANGLES – 这个参数意味着OpenGL使用三个顶点来组成图形。所以,在开始的三个顶点,将用顶点1,顶点2,顶点3来组成一个三角形。完成后,在用下一组的三个顶点(顶点4,5,6)来组成三角形,直到数组结束。

GL_TRIANGLE_STRIP – OpenGL的使用将最开始的两个顶点出发,然后遍历每个顶点,这些顶点将使用前2个顶点一起组成一个三角形。

无论多复杂的图像,OpenGL都是靠这种一个一个的小三角形拼接而成的。

eglSwapBuffers

交换缓存,图像正式渲染到前台。

4.5 销毁

每一帧图像的呈现都经过了上面的绘制过程,直到播放完毕或者软件退出。

//销毁着色器glDeleteShader(m_vShaderId);glDeleteShader(m_fShaderId);//销毁纹理glBindTexture(GL_TEXTURE_2D, 0);for (int i = 0; i0) {glDeleteTextures(1, &m_textures[3]);m_textures[i] = 0;}}//销毁项目glDeleteProgram(m_programId);//最后销毁前面的EGL绑定5 结语

最后开始你的表演吧,在隔壁邻居家的墙上绘制你的梦想。

本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系admin#jikehao.com删除。
(0)
极客号的头像极客号
上一篇 2022年 9月 30日
下一篇 2022年 9月 30日

相关推荐

合作联系:1152QQ041010