片段着色器 - 平均亮度

时间:2012-08-28 21:55:30

标签: iphone opengl-es shader fragment-shader

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

3 个答案:

答案 0 :(得分:5)

我最近不得不自己做输入图像和视频帧,我作为OpenGL ES纹理。我没有为这些生成mipmap,因为我使用的是非2次幂纹理,而且你无法在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 ms的亮度值,我想我可以通过一点调整来缩短处理时间1-2 ms。根据我的经验,这似乎比iOS设备通常生成大约相同大小的二次幂图像的mipmap更快,但我没有可靠的数字来支持它。

如果您希望看到这一点,请查看我的开源GPUImage框架(以及GPUImageAverageColor超类)中GPUImageLuminosity类的代码。 FilterShowcase示例演示了这个光度提取器的实际效果。

答案 1 :(得分:3)

您通常不会使用着色器执行此操作。

一种比较常见的方法是使用完整的mip-map创建缓冲区纹理(低至1x1,这很重要)。如果要查找亮度,请将后备缓冲区复制到此缓冲区,然后使用最近邻居算法重新生成mips。然后底部像素将具有整个表面的平均颜色,并可用于通过(c.r * 0.6) + (c.g * 0.3) + (c.b * 0.1)之类的内容找到平均lum(编辑:如果您有YUV,则执行类似操作Y;诀窍就是将纹理平均化为单个值,这就是mips所做的。)

这不是一种精确的技术,但速度相当快,尤其是在内部可以生成mipmap的硬件上。

答案 2 :(得分:3)

我在这里提出了RGB纹理的解决方案,因为我不确定mip贴图生成是否适用于YUV纹理。

第一步是为纹理创建mipmap(如果尚未存在):

glGenerateMipmapOES(GL_TEXTURE_2D);

现在我们可以使用采样器函数texture2D的可选第三个参数,即“偏差”,从片段着色器访问最小mipmap级别的RGB值:

vec4 color = texture2D(sampler, vec2(0.5, 0.5), 8.0);

这会将mipmap级别提升八级,从而使采样范围小得多。

如果您有256x256纹理并使用1的比例渲染它,则偏差8.0将有效地将拾取的mipmap减少到最小的1x1级别(256/2 ^ 8 == 1)。当然,您必须根据条件调整偏差以采样最小的水平。

好的,现在我们有了整个图像的平均RGB值。第三步是将RGB降低到亮度:

float lum = dot(vec3(0.30, 0.59, 0.11), color.xyz);

点积只是计算加权和的一种奇特(和快速)方式。