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 es
layout(location = 0) in vec4 vPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 TexCoord;
void main()
{
gl_Position = vPosition;
TexCoord = aTexCoord;
}
片段着色器
#version 300 es
precision 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 代表 NV12
uniform 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);
};