在GLSL片段着色器中将YUV(yCbCr420p)转换为RGB吗?

时间:2019-05-01 00:04:53

标签: java opengl-es opengl-es-2.0 fragment-shader

我知道同一个问题已经出现了很多,我已经尝试了所有可以找到的解决方案,但是没有任何效果。

我每帧接收3个缓冲区(每个缓冲区分别用于Y,Cr,Cb)。我目前只是将它们绑定到红色频道。 Y缓冲区是1个字节/像素,Cr和Cb缓冲区是1/4大小,因此它与我所看到的示例非常吻合,以下示例对U和V纹理贴图使用width / 2和height / 2,如下所示; / p>

int glFormat = GL20.GL_RED;
int glType = GL20.GL_UNSIGNED_BYTE;
int glInternalFormat = GL20.GL_RED;

GL20.glActiveTexture(GL20.GL_TEXTURE0);
GL20.glBindTexture(glTarget, yTexHandle);
GL20.glTexImage2D(glTarget, 0, glInternalFormat, width, height, 0, glFormat, glType, buffer);

int uvWidth = videoWidth/2;
int uvHeight = videoHeight/2;

GL20.glActiveTexture(GL20.GL_TEXTURE0+2);
GL20.glBindTexture(glTarget, cbTexHandle);
GL20.glTexImage2D(glTarget, 0, glInternalFormat, uvWidth, uvHeigth, 0, glFormat, glType, cbBuffer);
GL20.glActiveTexture(GL20.GL_TEXTURE0+1);
GL20.glBindTexture(glTarget, crTexHandle);
GL20.glTexImage2D(glTarget, 0, glInternalFormat, uvWidth, uvHeight, 0, glFormat, glType, crBuffer);

这是我的碎片着色器;

uniform sampler2D u_texture;
uniform sampler2D u_texture_cr;
uniform sampler2D u_texture_cb;

void main() {
  float y = texture2D(u_texture, v_texCoords).r;
  float u = texture2D(u_texture_cr, v_texCoords).r - 0.5;
  float v = texture2D(u_texture_cb, v_texCoords).r - 0.5;
  float r = y + 1.402 * v;
  float g = y - 0.344 * u - 0.714 * v;
  float b = y + 1.772 * u;
  gl_FragColor = v_color * vec4(r, g, b, 1.0);
}

虽然它可能不是最佳的转换算法,但我尝试了所有可以找到的替代方法,它看起来总是大致相同,非常绿色和非常粉红色。

我认为问题出在缓冲区本身,或者缓冲区如何绑定到GL,而不是碎片着色器本身。我尝试过切换u和v,甚至尝试对u和v进行所有操作,并且结果始终相同,因此看来当u和v缓冲区到达着色器时,它们是不正确的。

我已打印出Cb和Cr缓冲区的各个部分,以了解它们的值,这是一个示例;

Cr: -124 Cb: 110
Cr: -126 Cb: 109
Cr: -127 Cb: 107
Cr: -128 Cb: 106
Cr: 127 Cb: 104
Cr: 127 Cb: 101
Cr: 127 Cb: 99

请注意,这是一个Java ByteBuffer。我试过使用glType作为GL_BYTE而不是GL_UNSIGNED_BYTE,它看起来更糟。我也尝试过使用带有GL_ALPHA或GL_LUMINANCE作为格式的alpha通道,GL_LUMINANCE看起来略有不同,但输出仍然大致相同。

从2个不同的视频截屏; enter image description here

此外,我从中获得这些帧的程序包具有转换为RGBA帧的能力,可以完美地工作,但是这是一个昂贵的过程(〜30ms相比〜2ms)。这也是一种本机方法,我找不到它的源,因此我不知道它在后台执行的操作,但是我想这证明了当我获得缓冲区时它们是正确的。

更新

我尝试按照MoDJ的建议,使用该答案中的this (BT709 conversions)在着色器中实现灰度系数和饱和度。尽管输出仍然几乎相同。着色器导致了这一点;

uniform sampler2D u_texture;
uniform sampler2D u_texture_cr;
uniform sampler2D u_texture_cb;

const float yScale = 255.0 / (235.0 - 16.0); //(BT709_YMax-BT709_YMin)
const float uvScale = 255.0 / (240.0 - 16.0); //(BT709_UVMax-BT709_UVMin)

float BT709_nonLinearNormToLinear(float normV) {
    if (normV < 0.081) {
        normV *= (1.0 / 4.5);
    } else {
        float a = 0.099;
        float gamma = 1.0 / 0.45;
        normV = (normV + a) * (1.0 / (1.0 + a));
        normV = pow(normV, gamma);
    }
    return normV;
}

void main() {
    float y = texture2D(u_texture, v_texCoords).r;
    float u = texture2D(u_texture_cr, v_texCoords).r - 0.5;
    float v = texture2D(u_texture_cb, v_texCoords).r - 0.5;

    y = y - 16.0/255.0;

    float r = y*yScale +                          v*uvScale*1.5748;
    float g = y*yScale - u*uvScale*1.8556*0.101 - v*uvScale*1.5748*0.2973;
    float b = y*yScale + u*uvScale*1.8556;

    r = clamp(r, 0.0, 1.0);
    g = clamp(g, 0.0, 1.0);
    b = clamp(b, 0.0, 1.0);

    r = BT709_nonLinearNormToLinear(r);
    g = BT709_nonLinearNormToLinear(g);
    b = BT709_nonLinearNormToLinear(b);

1 个答案:

答案 0 :(得分:0)

我刚刚解决了这个问题,设置着色器制服时出现了我的愚蠢错误!

这就是我在做什么;

GL20.glActiveTexture(GL20.GL_TEXTURE0+2);
GL20.glBindTexture(GL20.GL_TEXTURE_2D, cbTexHandle);
program.setUniformi("u_texture_cb", GL20.GL_TEXTURE0+2);

GL20.glActiveTexture(GL20.GL_TEXTURE0+1);
GL20.glBindTexture(GL20.GL_TEXTURE_2D, crTexHandle);
program.setUniformi("u_texture_cr", GL20.GL_TEXTURE0+1);

它应该是;

program.setUniformi("u_texture_cb", 2);
program.setUniformi("u_texture_cr", 1);

因此,如果最终从YUV着色器获得绿色和粉红色的显示,我相信这实际上是没有获得任何UV值的原因(它们可能全为零)