1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
5  * Copyright (C) 2015-2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25  /**
26  * @file post_shader_ssao.cpp
27  * @brief Implement a post shader screen space ambient occlusion in software.
28  */
29 
30 #include "post_shader_ssao.h"
31 #include "../3d_fastmath.h"
32 
33 
POST_SHADER_SSAO(const CAMERA & aCamera)34 POST_SHADER_SSAO::POST_SHADER_SSAO( const CAMERA& aCamera ) :
35         POST_SHADER( aCamera ),
36         m_shadedBuffer( nullptr ),
37         m_isUsingShadows( false )
38 {
39 }
40 
41 // There are different sources for this shader on the web
42 //https://github.com/scanberg/hbao/blob/master/resources/shaders/ssao_frag.glsl
43 
44 //http://www.gamedev.net/topic/556187-the-best-ssao-ive-seen/
45 //http://www.gamedev.net/topic/556187-the-best-ssao-ive-seen/?view=findpost&p=4632208
46 
aoFF(const SFVEC2I & aShaderPos,const SFVEC3F & ddiff,const SFVEC3F & cnorm,const float aShadowAtSamplePos,const float aShadowAtCenterPos,int c1,int c2) const47 float POST_SHADER_SSAO::aoFF( const SFVEC2I& aShaderPos, const SFVEC3F& ddiff,
48                               const SFVEC3F& cnorm, const float aShadowAtSamplePos,
49                               const float aShadowAtCenterPos, int c1, int c2 ) const
50 {
51     const float shadowGain = 0.60f;
52     const float aoGain = 1.0f;
53 
54     const float shadow_factor_at_sample = ( 1.0f - aShadowAtSamplePos ) * shadowGain;
55     const float shadow_factor_at_center = ( 1.0f - aShadowAtCenterPos ) * shadowGain;
56 
57     float return_value = shadow_factor_at_center;
58 
59     const float rd = glm::length( ddiff );
60 
61     // This limits the zero of the function (see below)
62     if( rd < 2.0f )
63     {
64         if( rd > FLT_EPSILON )
65         {
66             const SFVEC3F vv = glm::normalize( ddiff );
67 
68             // Calculate an attenuation distance factor, this was get the best
69             // results by experimentation
70             // Changing this factor will change how much shadow in relation to the
71             // distance of the hit it will be in shadow
72 
73             // http://www.fooplot.com/#W3sidHlwZSI6MCwiZXEiOiIwLjgteCowLjYiLCJjb2xvciI6IiMwMDAwMDAifSx7InR5cGUiOjAsImVxIjoiMS8oeCp4KjAuNSsxKSIsImNvbG9yIjoiIzAwMDAwMCJ9LHsidHlwZSI6MTAwMCwid2luZG93IjpbIi0yLjU5Mjk0NTkyNTA5ODA0MSIsIjQuNTUzODc5NjU1NDQ1OTIzIiwiLTEuNzY3MDMwOTAzMjgxNjgxOCIsIjIuNjMxMDE1NjA3ODIyMjk3Il0sInNpemUiOls2NDksMzk5XX1d
74             const float attDistFactor = 1.0f / ( rd * rd * 8.0f + 1.0f );
75 
76             const SFVEC2I vr = aShaderPos + SFVEC2I( c1, c2 );
77 
78             float sampledNormalFactor = glm::max( glm::dot( GetNormalAt( vr ), cnorm ), 0.0f );
79 
80             sampledNormalFactor = glm::max( 1.0f - sampledNormalFactor *
81                                             sampledNormalFactor, 0.0f );
82 
83             const float shadowAttDistFactor = glm::max( glm::min( rd * 5.0f - 0.25f, 1.0f ), 0.0f );
84 
85             float shadowAttFactor = glm::min( sampledNormalFactor + shadowAttDistFactor, 1.0f );
86 
87             const float shadowFactor = glm::mix( shadow_factor_at_sample, shadow_factor_at_center,
88                                                  shadowAttFactor );
89 
90             // This is a dot product threshold factor.
91             // it defines after which angle we consider that the point starts to occlude.
92             // if the value is high, it will discard low angles point
93             const float aDotThreshold = 0.15f;
94 
95             // This is the dot product between the center pixel normal (the one that is being
96             // shaded) and the vector from the center to the sampled point
97             const float localNormalFactor = glm::dot( cnorm, vv );
98 
99             const float localNormalFactorWithThreshold =
100                     ( glm::max( localNormalFactor, aDotThreshold ) - aDotThreshold ) /
101                     ( 1.0f - aDotThreshold );
102 
103             const float aoFactor = localNormalFactorWithThreshold * aoGain * attDistFactor;
104 
105             return_value = glm::min( aoFactor + shadowFactor, 1.0f );
106         }
107     }
108 
109     return return_value;
110 }
111 
112 
giFF(const SFVEC2I & aShaderPos,const SFVEC3F & ddiff,const SFVEC3F & cnorm,const float aShadow,int c1,int c2) const113 float POST_SHADER_SSAO::giFF( const SFVEC2I& aShaderPos, const SFVEC3F& ddiff,
114                               const SFVEC3F& cnorm, const float aShadow, int c1, int c2 ) const
115 {
116     if( ( ddiff.x > FLT_EPSILON ) || ( ddiff.y > FLT_EPSILON ) || ( ddiff.z > FLT_EPSILON ) )
117     {
118         const SFVEC3F vv = glm::normalize( ddiff );
119         const float rd = glm::length( ddiff );
120         const SFVEC2I vr = aShaderPos + SFVEC2I( c1, c2 );
121 
122         const float attDistFactor = 1.0f / ( rd * rd + 1.0f );
123 
124         return ( glm::clamp( glm::dot( GetNormalAt( vr ), -vv), 0.0f, 1.0f ) *
125                  glm::clamp( glm::dot( cnorm, vv ), 0.0f, 1.0f ) * attDistFactor ) *
126                  ( 0.03f + aShadow ) * 3.0f;
127     }
128 
129     return 0.0f;
130 }
131 
132 
Shade(const SFVEC2I & aShaderPos) const133 SFVEC3F POST_SHADER_SSAO::Shade( const SFVEC2I& aShaderPos ) const
134 {
135     float cdepth = GetDepthAt( aShaderPos );
136 
137     if( cdepth > FLT_EPSILON )
138     {
139         cdepth = ( 30.0f / ( cdepth * 2.0f + 1.0f ) );
140 
141         // read current normal, position and color.
142         const SFVEC3F n = GetNormalAt( aShaderPos );
143         const SFVEC3F p = GetPositionAt( aShaderPos );
144 
145         const float shadowAt0 = GetShadowFactorAt( aShaderPos );
146 
147         // initialize variables:
148         float ao = 0.0f;
149         SFVEC3F gi = SFVEC3F( 0.0f );
150 
151 #define ROUNDS 3
152         for( unsigned int i = 0; i < ROUNDS; ++i )
153         {
154             static const int limit[ROUNDS] = { 0x01, 0x03, 0x03 };
155 
156             const int pw = Fast_rand() & limit[i];
157             const int ph = Fast_rand() & limit[i];
158 
159             const int npw = (int) ( ( pw + i ) * cdepth ) + ( i + 1 );
160             const int nph = (int) ( ( ph + i ) * cdepth ) + ( i + 1 );
161 
162             const SFVEC3F ddiff  = GetPositionAt( aShaderPos + SFVEC2I(  npw,  nph ) ) - p;
163             const SFVEC3F ddiff2 = GetPositionAt( aShaderPos + SFVEC2I(  npw, -nph ) ) - p;
164             const SFVEC3F ddiff3 = GetPositionAt( aShaderPos + SFVEC2I( -npw,  nph ) ) - p;
165             const SFVEC3F ddiff4 = GetPositionAt( aShaderPos + SFVEC2I( -npw, -nph ) ) - p;
166             const SFVEC3F ddiff5 = GetPositionAt( aShaderPos + SFVEC2I(  pw,   nph ) ) - p;
167             const SFVEC3F ddiff6 = GetPositionAt( aShaderPos + SFVEC2I(  pw,  -nph ) ) - p;
168             const SFVEC3F ddiff7 = GetPositionAt( aShaderPos + SFVEC2I( npw,    ph ) ) - p;
169             const SFVEC3F ddiff8 = GetPositionAt( aShaderPos + SFVEC2I(-npw,    ph ) ) - p;
170 
171             const float shadowAt1 = GetShadowFactorAt( aShaderPos + SFVEC2I( +npw,  nph ) );
172             const float shadowAt2 = GetShadowFactorAt( aShaderPos + SFVEC2I( +npw, -nph ) );
173             const float shadowAt3 = GetShadowFactorAt( aShaderPos + SFVEC2I( -npw,  nph ) );
174             const float shadowAt4 = GetShadowFactorAt( aShaderPos + SFVEC2I( -npw, -nph ) );
175             const float shadowAt5 = GetShadowFactorAt( aShaderPos + SFVEC2I(  +pw,  nph ) );
176             const float shadowAt6 = GetShadowFactorAt( aShaderPos + SFVEC2I(   pw, -nph ) );
177             const float shadowAt7 = GetShadowFactorAt( aShaderPos + SFVEC2I(  npw,   ph ) );
178             const float shadowAt8 = GetShadowFactorAt( aShaderPos + SFVEC2I( -npw,   ph ) );
179 
180             ao += aoFF( aShaderPos, ddiff , n, shadowAt1, shadowAt0,  npw,  nph );
181             ao += aoFF( aShaderPos, ddiff2, n, shadowAt2, shadowAt0,  npw, -nph );
182             ao += aoFF( aShaderPos, ddiff3, n, shadowAt3, shadowAt0, -npw,  nph );
183             ao += aoFF( aShaderPos, ddiff4, n, shadowAt4, shadowAt0, -npw, -nph );
184             ao += aoFF( aShaderPos, ddiff5, n, shadowAt5, shadowAt0,   pw,  nph );
185             ao += aoFF( aShaderPos, ddiff6, n, shadowAt6, shadowAt0,   pw, -nph );
186             ao += aoFF( aShaderPos, ddiff7, n, shadowAt7, shadowAt0,  npw,   ph );
187             ao += aoFF( aShaderPos, ddiff8, n, shadowAt8, shadowAt0, -npw,   ph );
188 
189             gi += giFF( aShaderPos, ddiff , n, shadowAt1, npw,  nph) *
190                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I(  npw, nph ) ) );
191             gi += giFF( aShaderPos, ddiff2, n, shadowAt2, npw, -nph) *
192                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I(  npw,-nph ) ) );
193             gi += giFF( aShaderPos, ddiff3, n, shadowAt3, -npw,  nph) *
194                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I( -npw, nph ) ) );
195             gi += giFF( aShaderPos, ddiff4, n, shadowAt4, -npw, -nph) *
196                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I( -npw,-nph ) ) );
197             gi += giFF( aShaderPos, ddiff5, n, shadowAt5 , pw, nph) *
198                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I(   pw, nph ) ) );
199             gi += giFF( aShaderPos, ddiff6, n, shadowAt6,  pw,-nph) *
200                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I(   pw,-nph ) ) );
201             gi += giFF( aShaderPos, ddiff7, n, shadowAt7,  npw, ph) *
202                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I(  npw,  ph ) ) );
203             gi += giFF( aShaderPos, ddiff8, n, shadowAt8, -npw, ph) *
204                     giColorCurve( GetColorAt( aShaderPos + SFVEC2I( -npw,  ph ) ) );
205         }
206 
207         // If it received direct light, it shouldn't consider much AO
208         // shadowAt0 1.0 when no shadow
209         const float reduceAOwhenNoShadow = m_isUsingShadows ? ( 1.0f - shadowAt0 * 0.3f ) : 1.0f;
210 
211         ao = reduceAOwhenNoShadow * ( ao / ( ROUNDS * 8.0f ) );
212 
213         ao = ( 1.0f - 1.0f / ( ao * ao * 5.0f + 1.0f ) ) * 1.2f;
214 
215         gi = ( gi / ( ROUNDS * 8.0f ) );
216 
217         float giL = glm::min( glm::length( gi ) * 4.0f, 1.0f );
218 
219         giL = ( 1.0f - 1.0f / ( giL * 4.0f + 1.0f ) ) * 1.5f;
220 
221         return glm::mix( SFVEC3F( ao ), -gi, giL );
222     }
223     else
224     {
225         return SFVEC3F( 0.0f );
226     }
227 }
228 
229 
ApplyShadeColor(const SFVEC2I & aShaderPos,const SFVEC3F & aInputColor,const SFVEC3F & aShadeColor) const230 SFVEC3F POST_SHADER_SSAO::ApplyShadeColor( const SFVEC2I& aShaderPos, const SFVEC3F& aInputColor,
231                                            const SFVEC3F& aShadeColor ) const
232 {
233     SFVEC3F outColor;
234 
235     const SFVEC3F subtracted = aInputColor - aShadeColor;
236     const SFVEC3F mixed = glm::mix( aInputColor, aInputColor * 0.50f - aShadeColor * 0.05f,
237                                     glm::min( aShadeColor, 1.0f ) );
238 
239     outColor.r = ( aShadeColor.r < 0.0f ) ? subtracted.r : mixed.r;
240     outColor.g = ( aShadeColor.g < 0.0f ) ? subtracted.g : mixed.g;
241     outColor.b = ( aShadeColor.b < 0.0f ) ? subtracted.b : mixed.b;
242 
243     return outColor;
244 }
245 
246 
giColorCurve(const SFVEC3F & aColor) const247 SFVEC3F POST_SHADER_SSAO::giColorCurve( const SFVEC3F& aColor ) const
248 {
249     const SFVEC3F vec1 = SFVEC3F( 1.0f );
250 
251     // This option actually apply a gamma since we are using linear color space
252     // and the result shader will be applied after convert back to sRGB
253 
254     // http://fooplot.com/#W3sidHlwZSI6MCwiZXEiOiIxLjAtKDEuMC8oeCo5LjArMS4wKSkreCowLjEiLCJjb2xvciI6IiMwMDAwMDAifSx7InR5cGUiOjEwMDAsIndpbmRvdyI6WyItMC4wNjIxODQ2MTUzODQ2MTU1MDUiLCIxLjE0Mjk4NDYxNTM4NDYxNDYiLCItMC4xMjcwOTk5OTk5OTk5OTk3NyIsIjEuMTMyNiJdfV0-
255     return vec1 - ( vec1 / (aColor * SFVEC3F(9.0f) + vec1) ) + aColor * SFVEC3F(0.10f);
256 }
257 
258 
Blur(const SFVEC2I & aShaderPos) const259 SFVEC3F POST_SHADER_SSAO::Blur( const SFVEC2I& aShaderPos ) const
260 {
261     const float dCenter = GetDepthAt( aShaderPos );
262 
263     SFVEC3F shadedOut = SFVEC3F( 0.0f );
264 
265     float totalWeight = 1.0f;
266 
267     for( int y = -3; y < 3; y++ )
268     {
269         for( int x = -3; x < 3; x++ )
270         {
271 
272             const unsigned int idx = GetIndex( SFVEC2I( aShaderPos.x + x, aShaderPos.y + y ) );
273 
274             const SFVEC3F s = m_shadedBuffer[idx];
275 
276             if( !( ( x == 0 ) && ( y == 0 ) ) )
277             {
278 
279                 const float d = GetDepthAt( SFVEC2I( aShaderPos.x + x, aShaderPos.y + y ) );
280 
281                 // Increasing the value will get more sharpness effect.
282                 const float depthAtt = ( dCenter - d ) * dCenter * 25.0f;
283 
284                 const float depthAttSqr = depthAtt * depthAtt;
285 
286                 float weight = ( 1.0f / ( depthAttSqr + 1.0f ) ) - 0.02f * depthAttSqr;
287 
288                 weight = glm::max( weight, 0.0f );
289 
290                 shadedOut += s * weight;
291                 totalWeight += weight;
292             }
293             else
294             {
295                 shadedOut += s;
296             }
297         }
298     }
299 
300     return shadedOut / totalWeight;
301 }
302