NDK OpenGL ES 3.0 开发(三):YUV 渲染

简介: YUV 渲染原理

作者:字节流动

来源:https://blog.csdn.net/Kennethdroid/article/details/97153407


YUV 渲染原理

前面文章一文掌握 YUV 图像的基本处理介绍了 YUV 常用的基本格式,本文以实现 NV21/NV12 的渲染为例。

前文提到,YUV 图不能直接用于显示,需要转换为 RGB 格式,而 YUV 转 RGB 是一个逐像素处理的耗时操作,在 CPU 端进行转换效率过低,这时正好可以利用 GPU 强大的并行处理能力来实现 YUV 到 RGB 的转换。

YUV 与 RGB 之间的转换公式。

image.png

需要注意的是 OpenGLES 的内置矩阵实际上是一列一列地构建的,比如 YUV 和 RGB 的转换矩阵的构建是:

mat3 convertMat = mat3(1.0, 1.0, 1.0,      //第一列
                       0.0,-0.338,1.732, //第二列
                       1.371,-0.698, 0.0);//第三列

OpenGLES 实现 YUV 渲染需要用到 GL_LUMINANCE 和 GL_LUMINANCE_ALPHA 格式的纹理,其中 GL_LUMINANCE 纹理用来加载 NV21 Y Plane 的数据,GL_LUMINANCE_ALPHA 纹理用来加载 UV Plane 的数据。

OpenGLES 常用纹理的格式类型

image.png

GL_LUMINANCE 纹理在着色器中采样的纹理像素格式是(L,L,L,1),L 表示亮度。GL_LUMINANCE 纹理在着色器中采样的纹理像素格式是(L,L,L,A),A 表示透明度。

YUV 渲染实现

YUV 渲染步骤:

  • 生成 2 个纹理,编译链接着色器程序;
  • 确定纹理坐标及对应的顶点坐标;
  • 分别加载 NV21 的两个 Plane 数据到 2 个纹理,加载纹理坐标和顶点坐标数据到着色器程序;
  • 绘制。

片段着色器脚本

#version 300 es                                     
precision mediump float;                            
in vec2 v_texCoord;                                 
layout(location = 0) out vec4 outColor;             
uniform sampler2D y_texture;                        
uniform sampler2D uv_texture;                        
void main()                                         
{                                                   
vec3 yuv;                   
yuv.x = texture(y_texture, v_texCoord).r;   
yuv.y = texture(uv_texture, v_texCoord).a-0.5;  
yuv.z = texture(uv_texture, v_texCoord).r-0.5;  
vec3 rgb =mat3( 1.0,       1.0,         1.0,          
                0.0,    -0.344,   1.770,          
                1.403,  -0.714,       0.0) * yuv;       
outColor = vec4(rgb, 1);            
}

y_texture 和 uv_texture 分别是 NV21 Y Plane 和 UV Plane 纹理的采样器,对两个纹理采样之后组成一个(y,u,v)三维向量,之后左乘变换矩阵转换为(r,g,b)三维向量。

Java 层 Load NV21 数据

    private void LoadNV21Image() {
        InputStream is = null;
        try {
            is = getAssets().open("YUV_Image_840x1074.NV21");
        } catch (IOException e) {
            e.printStackTrace();
        }
        int lenght = 0;
        try {
            lenght = is.available();
            byte[] buffer = new byte[lenght];
            is.read(buffer);
            mGLSurfaceView.getNativeRender().native_SetImageData(IMAGE_FORMAT_NV21, 840, 1074, buffer);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try
            {
                is.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
    }

Native 层转换为 NativeImage

void MyGLRenderContext::SetImageData(int format, int width, int height, uint8_t *pData)
{
  LOGCATE("MyGLRenderContext::SetImageData format=%d, width=%d, height=%d, pData=%p", format, width, height, pData);
  NativeImage nativeImage;
  nativeImage.format = format;
  nativeImage.width = width;
  nativeImage.height = height;
  nativeImage.ppPlane[0] = pData;
  switch (format)
  {
    case IMAGE_FORMAT_NV12:
    case IMAGE_FORMAT_NV21:
      nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
      break;
    case IMAGE_FORMAT_I420:
      nativeImage.ppPlane[1] = nativeImage.ppPlane[0] + width * height;
      nativeImage.ppPlane[2] = nativeImage.ppPlane[1] + width * height / 4;
      break;
    default:
      break;
  }
  if (m_Sample)
  {
    m_Sample->LoadImage(&nativeImage);
  }
}
//copy 到 sample
void NV21TextureMapSample::LoadImage(NativeImage *pImage)
{
  LOGCATE("NV21TextureMapSample::LoadImage pImage = %p", pImage->ppPlane[0]);
  if (pImage)
  {
    m_RenderImage.width = pImage->width;
    m_RenderImage.height = pImage->height;
    m_RenderImage.format = pImage->format;
    NativeImageUtil::CopyNativeImage(pImage, &m_RenderImage);
  }
}

加载 NV21 的 2 个 Plane 数据到纹理,ppPlane[0] 表示 Y Plane 的指针,ppPlane[1] 表示 UV Plane 的指针,注意 2 个纹理的格式和宽高。

//upload Y plane data
glBindTexture(GL_TEXTURE_2D, m_yTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_RenderImage.width, m_RenderImage.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
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);
//update UV plane data
glBindTexture(GL_TEXTURE_2D, m_uvTextureId);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, m_RenderImage.width >> 1, m_RenderImage.height >> 1, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[1]);
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);

简单代码实现

// 编译链接着色器程序,生成 2 个纹理
void NV21TextureMapSample::Init()
{
  char vShaderStr[] =
      "#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";
  char fShaderStr[] =
      "#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 uv_texture;                        \n"
      "void main()                                         \n"
      "{                                                   \n"
      " vec3 yuv;                   \n"
      "   yuv.x = texture(y_texture, v_texCoord).r;   \n"
      "   yuv.y = texture(uv_texture, v_texCoord).a-0.5;  \n"
      "   yuv.z = texture(uv_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";
  // Load the shaders and get a linked program object
  m_ProgramObj= GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);
  // Get the sampler location
  m_ySamplerLoc = glGetUniformLocation (m_ProgramObj, "y_texture" );
  m_uvSamplerLoc = glGetUniformLocation(m_ProgramObj, "uv_texture");
  //create textures
  GLuint textureIds[2] = {0};
  glGenTextures(2, textureIds);
  m_yTextureId = textureIds[0];
  m_uvTextureId = textureIds[1];
}
// 加载 NV21 图像数据到纹理,加载纹理坐标和顶点坐标数据到着色器程序,绘制实现 YUV 渲染
void NV21TextureMapSample::Draw(int screenW, int screenH)
{
  LOGCATE("NV21TextureMapSample::Draw()");
  if(m_ProgramObj == GL_NONE || m_yTextureId == GL_NONE || m_uvTextureId == GL_NONE) return;
  //upload Y plane data
  glBindTexture(GL_TEXTURE_2D, m_yTextureId);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_RenderImage.width, m_RenderImage.height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
  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);
  //update UV plane data
  glBindTexture(GL_TEXTURE_2D, m_uvTextureId);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, m_RenderImage.width >> 1, m_RenderImage.height >> 1, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[1]);
  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);
  //glViewport(0, 0, m_RenderImage.width, m_RenderImage.height);
  GLfloat verticesCoords[] = {
      -1.0f,  0.78f, 0.0f,  // Position 0
      -1.0f, -0.78f, 0.0f,  // Position 1
      1.0f,  -0.78f, 0.0f,  // Position 2
      1.0f,   0.78f, 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, 0, 2, 3 };
  // Use the program object
  glUseProgram (m_ProgramObj);
  // Load the vertex position
  glVertexAttribPointer (0, 3, GL_FLOAT,
               GL_FALSE, 3 * sizeof (GLfloat), verticesCoords);
  // Load the texture coordinate
  glVertexAttribPointer (1, 2, GL_FLOAT,
               GL_FALSE, 2 * sizeof (GLfloat), textureCoords);
  glEnableVertexAttribArray (0);
  glEnableVertexAttribArray (1);
  // Bind the Y plane map
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, m_yTextureId);
  // Set the Y plane sampler to texture unit to 0
  glUniform1i(m_ySamplerLoc, 0);
  // Bind the UV plane map
  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_2D, m_uvTextureId);
  // Set the UV plane sampler to texture unit to 1
  glUniform1i(m_uvSamplerLoc, 1);
  glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
}

YUV 渲染结果

联系与交流

技术交流、获取源码可以扫码添加我的微信:Byte-Flow ,领取视频教程


「视频云技术」你最值得关注的音视频技术公众号,每周推送来自阿里云一线的实践技术文章,在这里与音视频领域一流工程师交流切磋。

阿里云社区.png

相关文章
|
8月前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
180 1
|
8月前
|
Android开发 开发者
Android开发之OpenGL的画笔工具GL10
这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。
97 1
Android开发之OpenGL的画笔工具GL10
|
8月前
|
前端开发 API vr&ar
Android开发之OpenGL绘制三维图形的流程
即将连载的系列文章将探索Android上的OpenGL开发,这是一种用于创建3D图形和动画的技术。OpenGL是跨平台的图形库,Android已集成其API。文章以2D绘图为例,解释了OpenGL的3个核心元素:GLSurfaceView(对应View)、GLSurfaceView.Renderer(类似Canvas)和GL10(类似Paint)。通过将这些结合,Android能实现3D图形渲染。文章介绍了Renderer接口的三个方法,分别对应2D绘图的构造、测量布局和绘制过程。示例代码展示了如何在布局中添加GLSurfaceView并注册渲染器。
234 1
Android开发之OpenGL绘制三维图形的流程
|
8月前
|
Linux API iOS开发
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
【Qt 渲染引擎】一文带你了解qt的三种 渲染引擎,包括栅格引擎(Raster)、OpenGL 和本地绘图系统
249 0
|
8月前
|
XML Java Android开发
Android App开发中OpenGL三维投影的讲解及实现(附源码和演示 简单易懂)
Android App开发中OpenGL三维投影的讲解及实现(附源码和演示 简单易懂)
152 1
|
8月前
|
JavaScript C++
从OpenGL渲染的角度排查 creator native 局部换肤的问题
从OpenGL渲染的角度排查 creator native 局部换肤的问题
104 0
|
8月前
|
XML 小程序 Java
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
【Android App】三维投影OpenGL ES的讲解及着色器实现(附源码和演示 超详细)
153 0
|
存储 编解码 算法
Opengl ES之LUT滤镜(上)
Opengl ES之连载系列
498 0
|
数据安全/隐私保护 开发者
OpenGL ES 多目标渲染(MRT)
Opengl ES连载系列
339 0
|
缓存 C++
Opengl ES之FBO
Opengl ES连载系列
162 0