You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用片段着色器将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 23:50:33