来自不同平台/硬件的不同 GL 着色器结果(YUV 到 RGB)

Posted

技术标签:

【中文标题】来自不同平台/硬件的不同 GL 着色器结果(YUV 到 RGB)【英文标题】:Different GL shader results from different platform/hw(YUV to RGB) 【发布时间】:2017-09-15 16:53:01 【问题描述】:

我编写了一个 WebGL 着色器,它通过 3 个独立的纹理单元渲染 yuv420p 图片(因此它也可以处理平面 422、444 ...)。您可以从浏览器here 运行它。而here是我转换yuv文件的原图。

我创建了 YUV 文件:

ffmpeg -i color-test.jpg -pix_fmt yuvj420p -f rawvideo color-test.yuv

我明确使用yuvj420p 而不是yuv420p 来获取全范围值(0-255)。所以如果我要在生产中使用这段代码,我会添加一些制服来调整颜色范围。

顶点着色器

attribute vec3 a_pos;
attribute vec2 a_uv;

uniform mat4 u_mvp;

varying vec2 v_uv;


void main () 
  gl_Position = u_mvp * vec4(a_pos, 1.0);
  v_uv = a_uv;

片段着色器

precision mediump float;

uniform sampler2D u_texY;
uniform sampler2D u_texU;
uniform sampler2D u_texV;
uniform vec4 u_diffuse;

varying vec2 v_uv;

// YUV to RGB coefficient matrix.
const mat3 coeff = mat3(
  1.0, 1.0, 1.0,
  0.0, -0.21482, 2.12798,
  1.28033, -0.38059, 0.0
);


void main () 
  vec3 yuv = vec3(texture2D(u_texY, v_uv).a, texture2D(u_texU, v_uv).a, texture2D(u_texV, v_uv).a) * 2.0 - 1.0;
  vec3 colour = (coeff * yuv + 1.0) / 2.0;

  gl_FragColor = u_diffuse * vec4(colour, 1.0);

系数矩阵是BT.709 rec(this wiki page)的矩阵。 u_diffuse 通常是 vec4(1.0, 1.0, 1.0, 1.0),除非您想用某种颜色扩散图片。

纹理上传

纹理像这样上传(第 233 行):

  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, tex.y);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, rp.yuv.meta.width, rp.yuv.meta.height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, picture.y);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

CLAMP_TO_EDGE 用于处理图像的任何维度(即任何可被 2 整除的维度)。请注意,纹理会在每次绘制回调时更新。这是故意的,因为通常我们必须在每次从视频解码器移交图片时更新纹理。

问题

最明显的问题是渲染的图像与图像查看器应用程序(即网络浏览器)的呈现明显不同。这些程序可能使用 CPU 软件将图像从 YUV 缩放到 RGB。我猜这是最准确的转换方式。在我的程序中究竟是什么导致了这种情况?我已经完成了我的研究,但我无法得出一个明确的解释。浮点精度?

问题进一步扩大。不同设备上的颜色不同。例如,这个女人在我的安卓手机上和我的电脑上看起来略有不同。同样的事情也发生在 Linux 和 Windows 浏览器上。当我测量右下角黑框的颜色时,我在平台/硬件上得到了“不同的黑色”。

Linux 上的 Chrome:#060106 Linux 上的 Firefox:#010001 android 上的 Chrome:#060006 Windows 上的 Chrome/Firefox/Oprea:#060106

有什么办法可以缓解这种情况?为什么我不能得到“真正的”黑色(#000000)?

额外的位

我对此非常担心,因为有些人确实发现了这种差异。有一天,当我在办公室工作时,一位客户坚持认为我的视频播放器的颜色与另一家公司的程序不同。没有人知道为什么(没有其他程序的源代码)。但似乎其他程序在 RGB 表面上渲染,而我的程序在 YUV DirectX 表面上渲染。因此,如果是这种情况,他们必须从 CPU 进行转换。为什么它看起来不同可能有很多原因。可能是颜色范围,一些 Jimmy 使用的坏系数......这个列表可以继续。

【问题讨论】:

@gman msdn.microsoft.com/en-us/library/dn302429(v=vs.85).aspx 我没有使用Image()。走开。 “走开。” 这个基调是inappropriate on ***,对于在最小和放大滤镜上都使用LINEAR 的人来说,这似乎是一个奇怪的决定对我来说。 我还建议不要使用像 "some Jimmy" 这样模棱两可的俚语,因为我设法查找的几乎所有含义都违反了上述Be-Nice-Policy。跨度> @LJᛃ 我只是厌倦了从那些甚至没有阅读我的问题的人那里得到答案。这也太粗鲁了。所以,我做了一些回报。是的,使用“吉米”这个词是不恰当的。如果有人担心,会采取额外的一点。 我知道使用 GL_LINEAR 的后果。样本将被平均化。 【参考方案1】:

您正在假设您的色度示例站点位于何处;它并不总是 UV 纹素的中间。

您正在线性插值 YUV 样本,这些样本仍处于非线性伽马中。

YUV 的色域大于 RGB 的色域(您会得到 0 到 1 RGB 范围之外的值),因此您需要对颜色值进行裁剪或重新映射。

并非所有图形堆栈都能正确处理 RGB 输出 gamma,因此您可能会发现 GPU 发出的 gamma 与显示器的解释方式不匹配。

另外,您确定您获得的是 BT.709 输入吗?其他格式的色度常数不同,即使内存中的像素打包格式相同(无论是在恢复 RGB 方面,还是在使用窄色域或宽色域方面)

【讨论】:

对。不知道仅使用 -pix_fmt 时会输出什么色彩空间。也许我必须使用视频过滤器才能始终获得 bt709 空间。 ffmpeg 读取图像在 bt470bg 中,但我不知道这是否真的来自 jpeg 文件信息。在我阅读您的答案之前,我不知道伽马压缩。谢谢。 祝你好运 - 我讨厌使用 YUV - 格式变体太多,许多“参考实现”实际上并没有正确地做到这一点,还有很多实现(例如如何未定义将 YUV 色域重新映射到 RGB 色域) 仅供参考,但我在以下 SO 问题中有代码,可解决非线性采样问题和伽马问题的一部分:***.com/questions/53911662/…

以上是关于来自不同平台/硬件的不同 GL 着色器结果(YUV 到 RGB)的主要内容,如果未能解决你的问题,请参考以下文章

对一个着色器中的不同属性使用不同的 GL_ELEMENT_ARRAY_BUFFER?

为两个不同的 VAOS 使用两个不同的着色器

OpenGL播放yuv数据流(着色器SHADER)-android

使用 SpriteKit 和 Swift 的自定义着色器有一些不同

在 glTexImage2D 中我选择与在着色器中采样不同的内部格式时会怎样?

将数据传递到不同的着色器