OpenGL渲染YUV420P

一 、YUV420P数据格式

在这里插入图片描述

图片来源于谷歌

结合上图可以看出YUV420P的特点如下:
①无论在横向还是纵向上都是两个亮度(Y)共享一组色度(UV),所以UV的宽度和高度都是Y的1/2
②在内存中有三片数据,也就是三个数据指针分别指向Y、U、V

  ffmpeg中avframe保存yuv420p的数据时是直接申请一整个的 image_size, 亮度数据y的地址是起始地址,u 在 y 的基础上偏移, v 在 u 的基础上偏移
  //以HD的yuv420p 8 bit数据为例
  uint8_t* yuv420_data = new [1920*1080*1.5]();
  uint8_t* data_y =  yuv420_data;
  uint8_t* data_u =  yuv420_data + 1920*1080;
  uint8_t* data_v =  data_u + 1920*1080/4;
  delete[] yuv420_data;
  

二 、GLFW渲染YUV420P

2.1 定义顶点数据

float vertex_coord_data[] = {
		-1.f, -1.f, 0.f,   0.f, 1.f,
		-1.f,  1.f, 0.f,   0.f, 0.f,
		 1.f,  1.f, 0.f,   1.f, 0.f,
		 1.f, -1.f, 0.f,   1.f, 1.f,
	};

	uint32_t vertx_index_data[] = {
		0, 1, 2,
		2, 3, 0
	};

	uint32_t m_vertex_buffer, m_index_buffer;
	glGenBuffers(1, &m_vertex_buffer);
	glGenBuffers(1, &m_index_buffer);

	glGenVertexArrays(1, &m_vertex_array);
	glBindVertexArray(m_vertex_array);

	glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_coord_data), vertex_coord_data, GL_STATIC_DRAW);

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertx_index_data), vertx_index_data, GL_STATIC_DRAW);
	
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3*sizeof(float)));
	glEnableVertexAttribArray(1);

2.2 创建YUV三张纹理

   // 纹理 y
   glGenTextures(1, &m_tex_y);
   glBindTexture(GL_TEXTURE_2D, m_tex_y);
   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);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_tex_width, m_wnd_height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
   glGenerateMipmap(GL_TEXTURE_2D);

 	// 纹理 u
   glGenTextures(1, &m_tex_u);
   glBindTexture(GL_TEXTURE_2D, m_tex_u);
   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);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_tex_width/2, m_wnd_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
   glGenerateMipmap(GL_TEXTURE_2D);

    // 纹理 v
   glGenTextures(1, &m_tex_v);
   glBindTexture(GL_TEXTURE_2D, m_tex_v);
   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);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, m_tex_width/2, m_wnd_height/2, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
   glGenerateMipmap(GL_TEXTURE_2D);

2.3上行YUV420数据

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_tex_y);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width, m_tex_height, GL_RED, GL_UNSIGNED_BYTE, y);

	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, m_tex_u);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, u);
	
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, m_tex_v);
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, v);

2.4 渲染纹理

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_tex_y);
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, m_tex_u);
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, m_tex_v);
	
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

2.5 着色器

/顶点着色器///
#version 330 core
layout (location = 0) in vec3 vertex_pos;
layout (location = 1) in vec2 tex_pos;

out vec2 tex_uv;

void main()
{
	gl_Position = vec4(vertex_pos, 1.f);
	tex_uv = vec2(tex_pos);
}


/像素着色器///
#version 330 core

out vec4 frage_color;

in vec2 tex_uv;
uniform sampler2D tex_y;
uniform sampler2D tex_u;
uniform sampler2D tex_v;

void main()
{
	vec3 yuv = vec3(0.f);
    
    //按照BT709的协议来转换YUV至RGB
    yuv.x = texture2D(tex_y, tex_uv).r - 16.f/235.f;
    yuv.y = texture2D(tex_u, tex_uv).r - 128.f/240.f;
    yuv.z = texture2D(tex_v, tex_uv).r - 128.f/240.f;
    yuv = clamp(yuv, 0.f, 1.f);
	mat3 yuv_to_rgb = mat3(1.164f,  0.f,     1.793f,
	                       1.164f, -0.213f, -0.533f,
	                       1.164f,  2.112f,  0.f);
 

    vec3 rgb = yuv_to_rgb *yuv;
    frage_color = vec4(rgb.r, rgb.g, rgb.b, 1.f);
}

三、代码地址以及存在的问题

demo中使用封装好的 ffmpeg 来获取YUV420P数据,相关代码地址:https://github.com/pengguoqing/samples_code.git
使用demo中的代码渲染后的效果画面会变紫,我尝试了用其他YUV_RGB的转换矩阵,也是一样的会变紫,各位老师看过后能不能纠正一下哪里不正确

本demo的渲染效果如下:
在这里插入图片描述
正确渲染效果如下:
在这里插入图片描述

四 、解决存在的问题

  经过这两天的思考和排查,以及请教行业前辈终于知道原因了,具体如下:
①y、u、v 三张纹理被声明成了 uniform 类型, 所以每次更新纹理数后都需要更新一下

   glActiveTexture(GL_TEXTURE0);
   glBindTexture(GL_TEXTURE_2D, m_tex_y);
   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width, m_tex_height, GL_RED, GL_UNSIGNED_BYTE, y);
   m_shader_parse.setInt("tex_y", 0);

   glActiveTexture(GL_TEXTURE1);
   glBindTexture(GL_TEXTURE_2D, m_tex_u);
   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, u);
   m_shader_parse.setInt("tex_u", 1);

   glActiveTexture(GL_TEXTURE2);
   glBindTexture(GL_TEXTURE_2D, m_tex_v);
   glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_tex_width/2, m_tex_height/2, GL_RED, GL_UNSIGNED_BYTE, v);
   m_shader_parse.setInt("tex_v", 2);
   
   //或一次性通知更新
   //m_shader_parse.setInt("tex_y", 0);
   //m_shader_parse.setInt("tex_u", 1);
   //m_shader_parse.setInt("tex_v", 2);

② shader里面需要对左乘矩阵进行转置
  因为shader里面的向量是列向量,所以 yuv_rgb 的矩阵需要左乘 采样的 yuv纹理数据

    /*mat3 yuv_to_rgb = mat3(1.164f,  0.f,     1.793f,
	                       1.164f, -0.213f, -0.533f,
	                       1.164f,  2.112f,  0.f);*/
    //709_YUV_to_RGB
    mat3 yuv_to_rgb = mat3(1.164f, 1.164f,  1.164f,
                           0.f,    -0.213f, 2.112f,
                           1.793f, -0.533f, 0.f);

 修改后的渲染效果正确了
在这里插入图片描述

  • 2
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
OpenGL PBO是OpenGL中的一个扩展,它允许通过在图形处理单元(GPU)上创建一个像素缓冲对象(PBO),将数据从CPU传输到GPU,然后可以使用这些数据在纹理中显示图像。 在使用PBO和YUV420p格式的纹理显示图像之前,我们需要将YUV420p格式的图像数据转换为适用于OpenGL纹理的格式。YUV420p是一种常见的视频图像格式,它包含一个与图像分辨率相同的Y分量(明亮度)和两个与图像分辨率的四分之一相同的UV分量(色度)。Y分量与图像分辨率相同,而UV分量的分辨率被降低以节省存储和传输带宽。 首先,我们需要创建一个纹理,并将它与PBO关联。然后,我们可以将YUV420p数据传输到PBO中。将数据传输到PBO的过程涉及到将Y、U和V分量的数据按照特定的布局传输到PBO中。我们可以使用glBufferData函数将数据传输到PBO。 接下来,我们需要将PBO中的数据绑定到纹理,并对纹理进行设置以正确地显示图像。我们可以使用glBindTexture函数来绑定纹理,并使用glTexSubImage2D函数将PBO中的数据传输到纹理中。 最后,我们可以使用OpenGL渲染管线将纹理中的图像显示在屏幕上。我们可以使用一个简单的顶点着色器和一个片段着色器将纹理中的图像转换为可视化的图像。 总结起来,使用OpenGL PBO和YUV420p纹理可以更高效地显示图像。通过将图像数据传输到PBO中,并将PBO与纹理关联,可以在GPU上进行图像处理和渲染,从而提高了图像显示的效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值