片段着色器 - 平均亮度

7

有没有人知道如何在片段着色器中查找纹理的平均亮度?我可以访问RGB和YUV纹理,其中YUV中的Y分量是一个数组,我想从这个数组中获取一个平均数。

3个回答

7
我最近需要为OpenGL ES纹理输入图像和视频帧进行此操作。由于我使用的是非2次幂纹理,而在iOS上的OpenGL ES 2.0中无法为NPOT纹理生成mipmap,因此我没有选择为这些纹理生成mipmap。
相反,我进行了类似于mipmap生成的多级缩减,但进行了一些微调。每个向下的步骤都将图像的宽度和高度分别缩小四倍,而不是用于mipmap的正常因子二。我通过从四个纹理位置进行采样来实现这一点,这些位置位于更高级别图像中由四个像素组成的四个正方形的中心。这利用了硬件纹理插值来平均四组四个像素,然后我只需平均这四个像素即可在单个步骤中将像素减少16倍。
我在第一个阶段将图像转换为亮度,使用RGB值与(0.2125, 0.7154, 0.0721)的vec3进行点积。这使我可以仅在每个后续的缩减阶段中读取红色通道,这对于iOS硬件非常有帮助。请注意,如果您从Y通道亮度纹理开始,则不需要此操作,但我正在处理RGB图像。
一旦图像被缩小到足够小的尺寸,我就从中读取像素并将其返回到CPU上,在剩下的几个像素上进行最后的快速迭代,以得出最终的亮度值。
对于一个640x480的视频帧,在iPhone 4上,这个过程可以在大约6毫秒内产生一个亮度值。我认为通过一些调整,我可以将处理时间再缩短1-2毫秒。根据我的经验,在这个大小的2次幂图像上,iOS设备通常生成mipmaps所需的时间比这要长,但我没有确切的数据来支持这一点。
如果您想看到它的实际效果,请查看我开源GPUImage框架中的GPUImageLuminosity类代码(以及GPUImageAverageColor超类)。FilterShowcase示例演示了这种亮度提取器的操作。

后续问题:为什么选择一次四个样本的缩减?有八个可用变量,感觉在进行依赖纹理读取之前,您可以每步采样一个8x4区域进行缩减。 - Tommy
谢谢Brad的回复,它很完整,看起来你以前也经历过我的情况。我想我之前看过GPUImage,会再次寻找这个解决方案。干得好! - Rhm Akbari
@Tommy - 很好的问题。四个采样的方法是Core Image工程师用于他们的颜色平均滤波器的一种方法(在这篇GPU Gems文章中有描述: http://http.developer.nvidia.com/GPUGems3/gpugems3_ch26.html),所以我想先复制这个方法。不过你说得对,如果我真的想优化这个,我应该尝试使用所有8个相对便宜的纹理读取。我可能会尝试下一个。 - Brad Larson
@BradLarson 在阅读了您的帖子后,我也拿了示例项目并插入了一个 640 x 480 的 PNG。我记不清确切的数字,但它们都在 8 毫秒的范围内。然后我将 PNG 替换为相同的 JPEG 图像,得到了 12 毫秒对比 0.5 毫秒!经过一些思考,我得出结论,CGImage 必须保留对压缩数据的引用,并且在 JPEG 的情况下即使没有触及所有源像素也进行了绘制。在 JPEG 图像中,图像 IO 似乎能够仅解压所需的低分辨率下的 DCT 的较低频率因子。 - Nikolai Ruhe
@NikolaiRuhe - 很有趣。是的,我在我的640x480图像测试中使用了PNG,以便所有内容都对齐。现在我一定要在不同的尺寸下进行基准测试,以及将其与mipmap生成进行比较,并尝试Tommy的建议,增加每次采样的样本大小。 - Brad Larson
显示剩余4条评论

3
我将为您提供一份关于RGB纹理的解决方案,因为我不确定使用YUV纹理是否能生成mip map。
首先需要创建纹理的 Mipmaps (如果还没有的话):
glGenerateMipmapOES(GL_TEXTURE_2D);

现在我们可以通过采样器函数texture2D的可选第三个参数"偏移量"来从片元着色器访问最小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);

点积只是一种计算加权和的高级(而快速)方法。

谢谢您的回复,Nikolia。我相信这对于非iOS设备非常有帮助,但是由于我使用的是iOS设备,所以应该采用其他方法,不过这个想法非常有趣。 - Rhm Akbari
1
@robcyr - 我应该指出,Nikolai和peachykeen的答案在iOS上都是完全可行的。我的答案只是另一种替代方法。通过将NPOT纹理拉伸到下一个二次幂大小,然后从中生成mipmap,您会非常接近我建议的效果。我需要对比一下mipmap生成来看看我的方法是否有任何性能优势。 - Brad Larson
1
如果您将纹理缩小到下一个2的幂次方,您可以获得一个优势并跳过一个mip级别。我很想看看基准测试结果,因为生成mips通常是可以接受的快速的。 - ssube

3
通常不仅使用着色器来完成此操作。其中一种更常见的方法是创建一个带有完整mip-maps的缓冲纹理(向下到1x1,这很重要)。当您想要查找亮度时,将backbuffer复制到此缓冲区,然后使用最近邻算法重新生成mipmaps。底部像素将具有整个表面的平均颜色,并可用于通过类似于(c.r * 0.6)+(c.g * 0.3)+(c.b * 0.1)的方式找到平均亮度(编辑:如果您拥有YUV,则执行类似操作并使用Y;诀窍只是将纹理平均缩减为单个值,这就是mipmaps所做的)。这不是精确的技术,但是速度相对较快,特别是在可以在内部生成mipmaps的硬件上。

谢谢你的回复,Peachykeen。你的解决方案很棒。我已经点赞了。 - Rhm Akbari

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接