root/cafu/trunk/CaLight/Ward97.cpp

Revision 455, 9.3 KB (checked in by Carsten, 4 months ago)

Updated copyright banners (in C++ files).

Line 
1/*
2=================================================================================
3This file is part of Cafu, the open-source game engine and graphics engine
4for multiplayer, cross-platform, real-time 3D action.
5Copyright (C) 2002-2012 Carsten Fuchs Software.
6
7Cafu is free software: you can redistribute it and/or modify it under the terms
8of the GNU General Public License as published by the Free Software Foundation,
9either version 3 of the License, or (at your option) any later version.
10
11Cafu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
12without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13PURPOSE. See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with Cafu. If not, see <http://www.gnu.org/licenses/>.
17
18For support and more information about Cafu, visit us at <http://www.cafu.de>.
19=================================================================================
20*/
21
22// Ward 97 Tone Reproduction Operator
23// **********************************
24
25const double DisplayLuminanceMax=80.0;
26
27
28bool HistogramCeiling(ArrayT<unsigned long>& Bins, double DeltaBin)
29{
30    unsigned long Total=0;
31    unsigned long BinNr;
32
33    for (BinNr=0; BinNr<Bins.Size(); BinNr++) Total+=Bins[BinNr];
34
35    unsigned long Tolerance=(unsigned long)(0.025*Total);
36    unsigned long Trimmings;
37
38    do
39    {
40        Trimmings=0;
41        Total    =0;
42        for (BinNr=0; BinNr<Bins.Size(); BinNr++) Total+=Bins[BinNr];
43
44        if (Total<Tolerance) return false;
45
46        for (BinNr=0; BinNr<Bins.Size(); BinNr++)
47        {
48            unsigned long Ceiling=(unsigned long)(Total*DeltaBin/log(DisplayLuminanceMax));
49
50            if (Bins[BinNr]>Ceiling)
51            {
52                Trimmings+=Bins[BinNr]-Ceiling;
53                Bins[BinNr]=Ceiling;
54            }
55        }
56    } while (Trimmings>Tolerance);
57
58    return true;
59}
60
61
62/***************************************************************************************************************
63 *** TODO: Die Luminanz sollte aus RGB-Werten anhand der CIE XYZ luminous efficiency functions berechnet werden.
64 *** Vgl. dazu Paper "Computational Model of Lightness Perception in High Dynamic Range Imaging" unter
65 *** http://www.mpi-inf.mpg.de/resources/hdr/lightness/, Zitat aus Kapitel "4. Computational Model":
66 ***    The presented model takes an image with the relative luminance values as an input. Such values can be
67 *** computed from RGB channels of an HDR image according to CIE XYZ luminous efficiency functions.
68 ***************************************************************************************************************/
69
70
71// Diese Funktion findet einen Tone-Reproduction Operator (Funktion) nach Ward97,
72// anhand dessen die Energiewerte der Patches in RGB-Tripel umgewandelt werden.
73void ToneReproduction(const CaLightWorldT& CaLightWorld)
74{
75    printf("\n%-50s %s\n", "*** Tone Reproduction (Ward97) ***", GetTimeSinceProgramStart());
76
77    const unsigned long   NrOfBins=300;
78    ArrayT<unsigned long> Bins;
79    unsigned long         BinNr;
80
81    for (BinNr=0; BinNr<NrOfBins; BinNr++) Bins.PushBack(0);
82
83    double MinBrightness=log(0.0001);
84    double MaxBrightness=MinBrightness;
85
86    // Suche die MaxBrightness
87    for (unsigned long PatchMeshNr=0; PatchMeshNr<PatchMeshes.Size(); PatchMeshNr++)
88        for (unsigned long PatchNr=0; PatchNr<PatchMeshes[PatchMeshNr].Patches.Size(); PatchNr++)
89        {
90            const cf::PatchT& Patch=PatchMeshes[PatchMeshNr].Patches[PatchNr];
91
92            if (!Patch.InsideFace) continue;
93
94            double Luminance=Max3(Patch.TotalEnergy);
95
96            if (Luminance<0.0001) continue;
97            double Brightness=log(Luminance);
98
99            if (Brightness>MaxBrightness) MaxBrightness=Brightness;
100        }
101
102    printf("MinBrightness: %9.5f\n", MinBrightness);
103    printf("MaxBrightness: %9.5f\n", MaxBrightness);
104
105    // Bilde das Histogram
106    for (unsigned long PatchMeshNr=0; PatchMeshNr<PatchMeshes.Size(); PatchMeshNr++)
107        for (unsigned long PatchNr=0; PatchNr<PatchMeshes[PatchMeshNr].Patches.Size(); PatchNr++)
108        {
109            const cf::PatchT& Patch=PatchMeshes[PatchMeshNr].Patches[PatchNr];
110
111            if (!Patch.InsideFace) continue;
112
113            double Luminance=Max3(Patch.TotalEnergy);
114
115            if (Luminance<0.0001) Luminance=0.0001;
116            double Brightness=log(Luminance);
117
118            BinNr=(unsigned long)((Brightness-MinBrightness)/(MaxBrightness-MinBrightness)*NrOfBins);
119            if (BinNr>NrOfBins-1) BinNr=NrOfBins-1;
120            Bins[BinNr]++;
121        }
122
123    /***********************************************************************************************
124    printf("Writing histogram file...\n");
125    FILE* FilePtr=fopen("histogr.dat", "wb");
126
127    if (FilePtr!=NULL)
128    {
129        fwrite(&NrOfBins, sizeof(NrOfBins), 1, FilePtr);
130
131        for (BinNr=0; BinNr<Bins.Size(); BinNr++)
132            fwrite(&Bins[BinNr], sizeof(unsigned long), 1, FilePtr);
133
134        fclose(FilePtr);
135    }
136    else printf("      WARNING: Unable to write histogram data file!\n");
137    ***********************************************************************************************/
138
139    // Arbeite die Ceiling in das Histogram ein
140    if (HistogramCeiling(Bins, (MaxBrightness-MinBrightness)/double(NrOfBins)))
141    {
142        // Bilde das Integral über Bins[0..NrOfBins-1] als einfache Summe und normalisiere
143        ArrayT<double> BinsNormSum;
144        unsigned long  Sum=0;
145
146        for (BinNr=0; BinNr<Bins.Size(); BinNr++)
147        {
148            BinsNormSum.PushBack(Sum);
149            Sum+=Bins[BinNr];
150        }
151        for (BinNr=0; BinNr<Bins.Size(); BinNr++) BinsNormSum[BinNr]/=double(Sum);
152
153        // Ordne nun anhand der DisplayLuminanceMax^BinsNormSum[i] Funktion RGB-Werte zu
154        for (unsigned long PatchMeshNr=0; PatchMeshNr<PatchMeshes.Size(); PatchMeshNr++)
155            for (unsigned long PatchNr=0; PatchNr<PatchMeshes[PatchMeshNr].Patches.Size(); PatchNr++)
156            {
157                cf::PatchT& Patch=PatchMeshes[PatchMeshNr].Patches[PatchNr];
158
159                if (!Patch.InsideFace) continue;
160
161                double Luminance=Max3(Patch.TotalEnergy);
162
163                if (Luminance<0.0001) Luminance=0.0001;
164                double Brightness=log(Luminance);
165
166                BinNr=(unsigned long)((Brightness-MinBrightness)/(MaxBrightness-MinBrightness)*NrOfBins);
167                if (BinNr>NrOfBins-1) BinNr=NrOfBins-1;
168
169                double DisplayLuminance=pow(DisplayLuminanceMax, BinsNormSum[BinNr]);
170                Patch.TotalEnergy=scale(Patch.TotalEnergy, DisplayLuminance/Luminance);
171            }
172    }
173
174    // Skaliere die nun vorhandenen RGB-Werte der Patches linear in den gewünschten [0, 255] Bereich.
175    double Max=0;
176    for (unsigned long PatchMeshNr=0; PatchMeshNr<PatchMeshes.Size(); PatchMeshNr++)
177        for (unsigned long PatchNr=0; PatchNr<PatchMeshes[PatchMeshNr].Patches.Size(); PatchNr++)
178        {
179            const cf::PatchT& Patch=PatchMeshes[PatchMeshNr].Patches[PatchNr];
180
181            if (!Patch.InsideFace) continue;
182
183            const VectorT& RGB=Patch.TotalEnergy;
184
185            if (RGB.x>Max) Max=RGB.x;
186            if (RGB.y>Max) Max=RGB.y;
187            if (RGB.z>Max) Max=RGB.z;
188        }
189
190    if (Max==0) Max=1.0;
191    Max=1.0/Max;
192    for (unsigned long PatchMeshNr=0; PatchMeshNr<PatchMeshes.Size(); PatchMeshNr++)
193        for (unsigned long PatchNr=0; PatchNr<PatchMeshes[PatchMeshNr].Patches.Size(); PatchNr++)
194        {
195            cf::PatchT& Patch=PatchMeshes[PatchMeshNr].Patches[PatchNr];
196
197            Patch.TotalEnergy=scale(Patch.TotalEnergy, Max);
198
199            // This is the forced application of a gamma correction by 2.0 (the sqrt(x) is equivalent to pow(x, 1.0/2.0)).
200            //
201            // Q: Why here and not in the Cafu engine, at load time?
202            // A: 1. Applying gamma to all patches at load time is expensive at every map load, implying a sub-optimal experience for the user.
203            //    2. Full numeric precision is only available here. Later the patch values are rounded to and kept as unsigned chars,
204            //       limiting their precision to one of only 256 possible values.
205            //
206            // Q: Why choose a gamma value of 2.0, and not any other value?
207            // A: During rendering, lightmaps and texture images are combined by multiplication.
208            //    This has a tendency to darken the overall image, because e.g. a texel value of 0.5 multiplied with a lightmap value of 0.5
209            //    yields a pixel value of only 0.25. If we wanted a texel value of 0.5 and a lightmap value of 0.5 to yield a pixel value of
210            //    0.5, we had to apply a gamma correction of 2.0 to both the texture images and lightmaps. This is arbitrary though, and as it
211            //    is out of the question to modify the texture images anyway, we just implement a gamma correction of 2.0 for the ilghtmaps here.
212            Patch.TotalEnergy.x=sqrt(Patch.TotalEnergy.x);
213            Patch.TotalEnergy.y=sqrt(Patch.TotalEnergy.y);
214            Patch.TotalEnergy.z=sqrt(Patch.TotalEnergy.z);
215
216            Patch.TotalEnergy=scale(Patch.TotalEnergy, 255.0);
217        }
218}
Note: See TracBrowser for help on using the browser.