不同的 GL 着色器来自不同的 platform/hw(YUV 到 RGB)
Different GL shader results from different platform/hw(YUV to RGB)
我写了一个 WebGL 着色器来渲染通过 3 个独立纹理单元提供的 yuv420p 图片(因此它也可以处理平面 422、444 ...)。您可以 运行 从您的浏览器 here. And 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 的软件。我想这是最准确的转换方式。在我的程序中究竟是什么原因造成的?我做了我的研究,但我无法得出明确的解释。浮点精度?
问题进一步扩大。不同设备上的颜色不同。例如,这个女人在我的 android phone 和我的 PC 上看起来略有不同。同样的事情发生在 Linux 和 Windows 浏览器上。当我测量右下角黑框的颜色时,我得到 "different black" 横跨 platform/hw.
- Chrome 在 Linux 上:#060106
- Linux 上的 Firefox:#010001
- Chrome 在 Android 上:#060006
- Chrome/Firefox/Oprea 在 Windows 上:#060106
有什么办法可以缓解这种情况?为什么我不能得到 "true" black(#000000)?
额外位
我非常担心这一点,因为有些人确实发现了这种差异。有一天,当我在办公室工作时,一位客户坚持认为我的视频播放器在颜色上与另一家公司的程序不同。没有人知道为什么(没有其他程序的源代码)。但似乎其他程序在 RGB 表面上渲染,而我的程序在 YUV DirectX 表面上渲染。因此,如果是这种情况,他们必须从 CPU 进行转换。它看起来不同的原因可能有很多。可能是一些吉米使用的颜色范围、不良系数……这个列表可以继续。
您是在假设您的色度示例站点所在的位置;它并不总是 UV 纹素的中间。
您正在对 YUV 样本进行线性插值,但仍处于非线性伽玛中。
YUV 的色域大于 RGB 的色域(您将获得 0 到 1 RGB 范围之外的值),因此您需要裁剪或重新映射颜色值。
并非所有图形堆栈都能正确处理 RGB 输出伽玛,因此您可能会发现 GPU 发出的伽玛与显示器对其的解释方式不匹配。
另外,您确定您收到的是 BT.709 输入吗?其他格式的色度常数不同,即使内存中的像素打包格式相同(在恢复 RGB 和使用窄或宽色域方面)
我写了一个 WebGL 着色器来渲染通过 3 个独立纹理单元提供的 yuv420p 图片(因此它也可以处理平面 422、444 ...)。您可以 运行 从您的浏览器 here. And 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 的软件。我想这是最准确的转换方式。在我的程序中究竟是什么原因造成的?我做了我的研究,但我无法得出明确的解释。浮点精度?
问题进一步扩大。不同设备上的颜色不同。例如,这个女人在我的 android phone 和我的 PC 上看起来略有不同。同样的事情发生在 Linux 和 Windows 浏览器上。当我测量右下角黑框的颜色时,我得到 "different black" 横跨 platform/hw.
- Chrome 在 Linux 上:#060106
- Linux 上的 Firefox:#010001
- Chrome 在 Android 上:#060006
- Chrome/Firefox/Oprea 在 Windows 上:#060106
有什么办法可以缓解这种情况?为什么我不能得到 "true" black(#000000)?
额外位
我非常担心这一点,因为有些人确实发现了这种差异。有一天,当我在办公室工作时,一位客户坚持认为我的视频播放器在颜色上与另一家公司的程序不同。没有人知道为什么(没有其他程序的源代码)。但似乎其他程序在 RGB 表面上渲染,而我的程序在 YUV DirectX 表面上渲染。因此,如果是这种情况,他们必须从 CPU 进行转换。它看起来不同的原因可能有很多。可能是一些吉米使用的颜色范围、不良系数……这个列表可以继续。
您是在假设您的色度示例站点所在的位置;它并不总是 UV 纹素的中间。
您正在对 YUV 样本进行线性插值,但仍处于非线性伽玛中。
YUV 的色域大于 RGB 的色域(您将获得 0 到 1 RGB 范围之外的值),因此您需要裁剪或重新映射颜色值。
并非所有图形堆栈都能正确处理 RGB 输出伽玛,因此您可能会发现 GPU 发出的伽玛与显示器对其的解释方式不匹配。
另外,您确定您收到的是 BT.709 输入吗?其他格式的色度常数不同,即使内存中的像素打包格式相同(在恢复 RGB 和使用窄或宽色域方面)