有谁知道如何在片段着色器中找到纹理的平均亮度?我可以访问 RGB 和 YUV 纹理 YUV 中的 Y 分量是一个数组,我想从这个数组中获得一个平均数。
3 回答
我最近不得不自己为作为 OpenGL ES 纹理的输入图像和视频帧执行此操作。我没有为这些生成 mipmap,因为我正在使用非二次幂纹理,并且您无法在 iOS 上的 OpenGL ES 2.0 中为 NPOT 纹理生成 mipmap。
相反,我做了一个类似于 mipmap 生成的多阶段缩减,但有一些细微的调整。每降低一步,图像的宽度和高度都会减少四倍,而不是用于 mipmap 的正常两倍。我通过从四个纹理位置进行采样来做到这一点,这些位置位于四个像素的四个正方形中间,每个正方形构成更高级别图像中的一个 4x4 区域。这利用了硬件纹理插值来平均四组四个像素,然后我只需对这四个像素进行平均即可在一个步骤中产生 16 倍的像素减少。
我在第一阶段使用 RGB 值的点积将图像转换为亮度,vec3 为 (0.2125, 0.7154, 0.0721)。这让我可以只读取每个后续缩减阶段的红色通道,这对 iOS 硬件确实有帮助。请注意,如果您已经从 Y 通道亮度纹理开始,则不需要这个,但我正在处理 RGB 图像。
一旦图像被缩小到足够小的尺寸,我就将其中的像素读回 CPU,并对剩余的几个像素进行最后一次快速迭代,以获得最终的亮度值。
对于一个 640x480 的视频帧,这个过程在 iPhone 4 上产生了大约 6 毫秒的亮度值,我想我可以通过稍微调整来减少 1-2 毫秒的处理时间。以我的经验,这似乎比 iOS 设备通常为该大小的二次幂图像生成 mipmap 更快,但我没有可靠的数字来支持这一点。
如果您希望看到这一点,请查看我的开源GPUImage框架(以及 GPUImageAverageColor 超类)中 GPUImageLuminosity 类的代码。FilterShowcase 示例演示了此亮度提取器的实际应用。
您通常不会仅使用着色器来执行此操作。
一种更常见的方法是使用完整的 mip-maps 创建缓冲区纹理(低至 1x1,这很重要)。当你想找到亮度时,你将后台缓冲区复制到这个缓冲区,然后使用最近邻算法重新生成 mips。然后底部像素将具有整个表面的平均颜色,并可用于通过类似(c.r * 0.6) + (c.g * 0.3) + (c.b * 0.1)
(编辑:如果您有 YUV,然后做类似的事情并使用 Y;技巧只是将纹理平均到单个值,这就是 mips 所做的)。
这不是一种精确的技术,但相当快,尤其是在可以在内部生成 mipmap 的硬件上。
我在这里介绍 RGB 纹理的解决方案,因为我不确定 mip 贴图生成是否适用于 YUV 纹理。
第一步是为纹理创建 mipmap(如果尚未存在):
glGenerateMipmapOES(GL_TEXTURE_2D);
现在我们可以通过使用采样器函数的可选第三个参数texture2D
“bias”从片段着色器访问最小 mipmap 级别的 RGB 值:
vec4 color = texture2D(sampler, vec2(0.5, 0.5), 8.0);
这会将 mipmap 级别向上移动 8 个级别,从而导致采样的级别要小得多。
如果你有一个 256x256 的纹理并以 1 的比例渲染它,8.0 的偏差将有效地将拾取的 mipmap 减少到最小的 1x1 级别(256 / 2^8 == 1)。当然,您必须根据您的条件调整偏差以采样最小级别。
好的,现在我们有了整个图像的平均 RGB 值。第三步是将RGB降低为亮度:
float lum = dot(vec3(0.30, 0.59, 0.11), color.xyz);
点积只是计算加权和的一种奇特(且快速)的方法。