前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使用OpenGL渲染YUV数据

如何使用OpenGL渲染YUV数据

作者头像
雪月清
发布2020-06-23 16:20:46
6K0
发布2020-06-23 16:20:46
举报
文章被收录于专栏:雪月清的随笔雪月清的随笔

本篇文章主要描述如何使用OpenGL ES来渲染i420(YUV420P)和nv21(YUV420SP)

首先准备yuv数据文件,使用ffmpeg对图片进行格式转换

原图大小为800x480:

ffmpeg转化为nv21和i420格式的yuv文件

代码语言:javascript
复制
// convert to nv21 
ffmpeg -i test.png -s 800x480 -pix_fmt nv21 test.yuv
// convert to i420
ffmpeg -i test.png -s 800x480 -pix_fmt yuv420p yuv420p.yuv

在OpenGL中,片元着色器最后输出的都是rgba的数据,所以使用OpenGL来渲染YUV数据的关键还是将YUV数据传递给着色器,并在着色器中将YUV转化为RGB

在我们创建一个2D纹理并使用glTexImage2D来填充数据的时候可以指定internalformat

代码语言:javascript
复制
public static native void glTexImage2D(
        int target,  // 目标纹理,此处必须为GL_TEXTURE_2D
        int level,   // 执行细节级别,0是最基本的图像级别,n表示第N级贴图细化级别
        int internalformat,  // 指定纹理中的颜色组件
        int width,  // 指定纹理图像的宽度,
        int height, //  指定纹理图像的高度,
        int border, // 指定边框的宽度。必须为0
        int format, // 像素数据的颜色格式
        int type, // 指定像素数据的数据类型
        java.nio.Buffer pixels // 指定内存中指向图像数据的指针
    );

internalformat这个参数指定纹理中的颜色组件,可选的值有GL_RGB,GL_RGBA,GL_LUMINANCE,GL_LUMINANCE_ALPHA 等

通常使用的GL_RGBA这种internalformat,它会单独保存R,G,B,A四个数据,而在渲染YUV数据的时候,我们使用GL_LUMINANCE和GL_LUMINANCE_ALPHA

使用GL_LUMINANCE的时候,可以将Y分量存储到像素的各个通道内,这样在着色器中,我们可以通过R,G,B任意一个分量来获取到Y值。U,V分量同理

使用GL_LUMINANCE_ALPHA的时候,首先存储亮度,然后是alpha值,利用这一点可以将U值存储到像素的A通道,V值存储到R,G,B通道

渲染i420

在使用GL渲染i420格式的YUV数据时,需要使用三个2D纹理,每个纹理的颜色组件采用GL_LUMINANCE

代码语言:javascript
复制
private fun textureLuminance(imageData: ByteBuffer, width: Int, height: Int, textureId: Int) {
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexImage2D(
            GLES20.GL_TEXTURE_2D, 0,
            GLES20.GL_LUMINANCE, width, height, 0,
            GLES20.GL_LUMINANCE,
            GLES20.GL_UNSIGNED_BYTE, imageData
        )
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
  }

首先把i420数据从文件中读取出来,然后创建3个2D纹理和buffer,并填充数据到buffer中,关键代码如下

代码语言:javascript
复制
// read i420 data
imageBytes = Util.read("yuv420p.yuv", context!!)
yBuffer = ByteBuffer.allocateDirect(imageWidth * imageHeight)
                .order(ByteOrder.nativeOrder())
yBuffer.put(imageBytes, 0, imageWidth * imageHeight)
yBuffer.position(0)

 // y texture
textureLuminance(yBuffer, imageWidth, imageHeight, yTextureId)

// u, v的流程是一样的,只是填充数据的时候要注意offset和纹理的宽高
uBuffer.put(imageBytes, imageWidth * imageHeight, imageWidth * imageHeight / 4)
textureLuminance(uBuffer, imageWidth / 2, imageHeight / 2, uTextureId)

vBuffer.put(imageBytes, imageWidth * imageHeight * 5 / 4, imageWidth * imageHeight / 4)
textureLuminance(vBuffer, imageWidth / 2, imageHeight / 2, vTextureId)

在渲染的时候,激活三个纹理单元并将纹理传递给着色器即可

在片元着色器中是如何从纹理中拿到Y,U,V分量的数据并且转化为R,G,B的呢?

从纹理中提取Y,U,V分量

代码语言:javascript
复制
// We had put the Y values of each pixel to the R, G, B components by GL_LUMINANCE,
// that's why we're pulling it from the R component, we could also use G or B
y = texture2D(yTexture, vTexCoord).r;

// u, v, same as above
u = texture2D(uTexture, vTexCoord).r;
v = texture2D(vTexture, vTexCoord).r;

YUV与RGB的互转公式

根据公式在片元着色器中进行YUV to RGB的转化

代码语言:javascript
复制
y = 1.164 * (y - 16.0 / 255.0);
u = u - 128.0 / 255.0;
v = v - 128.0 / 255.0;

r = y + 1.596 * v;
g = y - 0.391 * u - 0.813 * v;
b = y + 2.018 * u;

gl_FragColor = vec4(r, g, b, 1.0);

渲染nv21

在使用GL渲染nv21格式的YUV数据时,只需要使用两个2D纹理,Y分量纹理的颜色组件采用GL_LUMINANCE,UV分量纹理的颜色组件采用GL_LUMINANCE_ALPHA

代码语言:javascript
复制
private fun textureLuminanceAlpha(imageData: ByteBuffer, width: Int, height: Int, textureId: Int) {
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexImage2D(
            GLES20.GL_TEXTURE_2D, 0,
            GLES20.GL_LUMINANCE_ALPHA, width, height, 0,
            GLES20.GL_LUMINANCE_ALPHA,
            GLES20.GL_UNSIGNED_BYTE, imageData
        )
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
}

从文件中读取nv21数据,创建纹理和buffer,填充数据到buffer的流程和渲染i420的步骤是类似的,此处就不再赘述了

和渲染i420的片元着色器中唯一不同的就是获取U分量是从a通道获取

代码语言:javascript
复制
// We had put the U and V values of each pixel to the A and R, G, B components of the
// texture respectively using GL_LUMINANCE_ALPHA. Since U, V bytes are interspread
// in the texture
u = texture2D(uvTexture, vTexCoord).a;
v = texture2D(uvTexture, vTexCoord).r;

DEMO

代码传送门

https://github.com/sifutang/opengl.git

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 雪月清的随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档