When we implement the pipeline of Tone Mapping, we always need to calculate the average luminance of a rendering texture in which float format mostly. Generally, there are two algorithms to calculate the average luminance: geometric mean and arithmetic mean.
The arithmetic mean A is defined by the formula
We assume thedenote ith texel of texture, Apparently, the arithmetic mean could be implemented easily, how about the geometric mean? Yes, you know it, we employ the so-called log average to get the geometric mean value.
The process is like that:
Hence, we could calculate the geometric mean in the same way of calculation of arithmetic mean except to append a log and exp operations.
First we explore the calculation arithmetic mean of texture, the difference between geometric mean and arithmetic mean is just to add a log and exp operation in the shader.
1. Based on DX11
There are three methods to calculate the average luminance:
(1) D3D API: with the function of “GenerateMips”, you should first translate the texel(RGB) to luminance and call the function “GenerateMips”, then we can get the average luminance from the mip 1x1 level. It works based on the algorithm of generating mipmap with box filter, as long as now, it’ ok for DX.
(2) Computer shader: with the new shader stage of DX11, computer shader, we can write a computer shader to do GPGPU operation, for calculating the average luminance, it easy to implement just a bit of more works compared to use function “GenerateMips”.
(3) Rendering multi-pass: actually it’s primary adapt to DX9, we explore it in DX9.
2. Based on DX9
As far as I know, we only have one approach to do it, rendering multi-pass
(1) Rendering multi-pass: with DX9, we don’t have the “GenerateMips” or computer shader, so we need to calculate the average luminance by repeated rendering pass. We benefit from GPU’s property of linear sampling to do down sample pass by pass. Is a simple pixel shader is enough? Yes, but you should be cautious of sampling. Suppose we have a original rendering texture with size of 1024x1024, usually the queue of down-sample rendering pass would be 1024x1024->256x256->64x64->16x16->4x4->1x1, a quarter was choose because we can down to 1x1 quickly, the only extra works is that you should do pcf youself in the shader. For instance, 1024x1024->256x256, a texel of 256x256 just the average of 16 texels which illustrated as follow:
Figure 1: down sampling
Where the GPU’s linear sampling is like
Figure 2: linear sampling
Hence, in the shader we do some things as follow
float2 offset =
for(int i=0; i<4; ++i)
oColor += tex2D(baseTex, texCoord.xy + (offset[i] + 0.5f) * invTexSize.xy);
oColor *= 0.25f;
Note: we need half pixel offset for DX9 render texture, but on DX11 not.