如何用片段着色器将RGB打包数据转换为YUV420P平面格式?
用OpenGL片段着色器实现RGB转YUV420P平面格式
核心思路
YUV420P是三个独立的平面数据:
- Y平面:和原RGB图像同分辨率,每个像素对应一个Y分量
- U/V平面:分辨率为原图像的1/2(宽高各减半),每4个RGB像素共享一组U/V分量
OpenGL片段着色器一次默认只能输出一个颜色目标,对新手来说,分三次渲染生成三个平面是最直观的实现方案。
具体实现步骤
1. 准备三个帧缓冲(Framebuffer)
创建三个帧缓冲对象(FBO),分别绑定用于存储Y、U、V平面的纹理:
- Y纹理:和原RGB纹理同尺寸,格式用
GL_RED(单通道存储Y值) - U/V纹理:尺寸为原RGB纹理的1/2(宽高均除以2),格式同样用
GL_RED
示例伪代码:
// 创建Y纹理与对应FBO GLuint yTex, yFbo; glGenTextures(1, &yTex); glBindTexture(GL_TEXTURE_2D, yTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); glGenFramebuffers(1, &yFbo); glBindFramebuffer(GL_FRAMEBUFFER, yFbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, yTex, 0); // U/V纹理与FBO同理,尺寸设为width/2、height/2
2. 编写三个片段着色器
分别对应生成Y、U、V平面:
生成Y平面的着色器
输入原RGB像素,计算Y分量:
#version 330 core in vec2 texCoord; uniform sampler2D rgbTex; out float yValue; void main() { vec3 rgb = texture(rgbTex, texCoord).rgb; // BT.601标准转换公式(标清视频常用),高清场景可换BT.709系数 yValue = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; }
生成U平面的着色器
对原RGB图像做2x2下采样,计算对应U分量:
#version 330 core in vec2 texCoord; uniform sampler2D rgbTex; uniform vec2 texSize; // 原RGB纹理的宽高 out float uValue; void main() { vec2 step = 1.0 / texSize; // 定位当前U像素对应的2x2 RGB像素块左上角坐标 vec2 topLeft = floor(texCoord * texSize / 2.0) * 2.0 * step; vec3 rgb1 = texture(rgbTex, topLeft).rgb; vec3 rgb2 = texture(rgbTex, topLeft + vec2(step.x, 0)).rgb; vec3 rgb3 = texture(rgbTex, topLeft + vec2(0, step.y)).rgb; vec3 rgb4 = texture(rgbTex, topLeft + step).rgb; // 取4个像素的RGB平均值计算U vec3 avgRgb = (rgb1 + rgb2 + rgb3 + rgb4) / 4.0; uValue = -0.14713 * avgRgb.r - 0.28886 * avgRgb.g + 0.436 * avgRgb.b; // 将带符号的U值转换到[0,1]范围 uValue = uValue * 0.5 + 0.5; }
生成V平面的着色器
逻辑与U平面一致,仅转换公式不同:
#version 330 core in vec2 texCoord; uniform sampler2D rgbTex; uniform vec2 texSize; out float vValue; void main() { vec2 step = 1.0 / texSize; vec2 topLeft = floor(texCoord * texSize / 2.0) * 2.0 * step; vec3 rgb1 = texture(rgbTex, topLeft).rgb; vec3 rgb2 = texture(rgbTex, topLeft + vec2(step.x, 0)).rgb; vec3 rgb3 = texture(rgbTex, topLeft + vec2(0, step.y)).rgb; vec3 rgb4 = texture(rgbTex, topLeft + step).rgb; vec3 avgRgb = (rgb1 + rgb2 + rgb3 + rgb4) / 4.0; vValue = 0.615 * avgRgb.r - 0.51499 * avgRgb.g - 0.10001 * avgRgb.b; vValue = vValue * 0.5 + 0.5; }
3. 分三次渲染生成平面数据
- 绑定Y的FBO,使用Y着色器渲染全屏四边形,传入原RGB纹理,生成Y平面
- 绑定U的FBO,使用U着色器渲染全屏四边形(输出纹理为1/2尺寸,需确保UV坐标对应)
- 绑定V的FBO,重复U平面的渲染流程生成V平面
4. 提取平面数据
渲染完成后,用glReadPixels从各FBO中读取数据:
// 读取Y平面 glBindFramebuffer(GL_FRAMEBUFFER, yFbo); unsigned char* yData = new unsigned char[width * height]; glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, yData); // 读取U平面 glBindFramebuffer(GL_FRAMEBUFFER, uFbo); unsigned char* uData = new unsigned char[(width/2) * (height/2)]; glReadPixels(0, 0, width/2, height/2, GL_RED, GL_UNSIGNED_BYTE, uData); // V平面读取逻辑与U一致
注意事项
- 转换公式需根据场景选择:BT.601用于标清,BT.709用于高清,二者系数不同
- 下采样时可通过设置纹理过滤模式(
GL_NEAREST或GL_LINEAR)控制采样效果 - 若使用OpenGL ES,需注意着色器版本和API参数的差异
内容的提问来源于stack exchange,提问作者Vikas Kumar
相关产品推荐
相关产品推荐

