1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
|
#define NON_LINEAR_PARAMETERIZATION 1
#define SafetyHeightMargin 16.0
#define HeightPower 0.5
#define ViewZenithPower 0.2
#define SunViewPower 1.5
// Returns the cosine of the horizon angle for the given altitude
// (height above the sphere surface) and sphere radius
//
// |
// _ _ _ _ _| Horizon angle
// A |'.
// fAltitude | | '.
// _V_ _ _ _| '.
// . ' ' ' .'.
// .' '.
// ' '
float GetCosHorizonAngle(float fAltitude, float fSphereRadius)
{
fAltitude = max(fAltitude, 0.0);
return -sqrt(fAltitude * (2.0 * fSphereRadius + fAltitude)) / (fSphereRadius + fAltitude);
}
// Returns the lookup table zenith angle coordinate
//
// | Zenith .'
// | angle .'
// | .'
// | .'
// | .'
// _ _ _ _ _|.'
// A |
// fAltitude | |
// _V_ _ _ _|
// . ' ' ' .
// .' '.
// ' '
float ZenithAngle2TexCoord(float fCosZenithAngle, // Cosine of the zenith angle
float fAltitude, // Altitude (height above the sea level)
float fEarthRadius, // Earth radius at sea level
float fAtmBottomAltitude, // Altitude of the bottom atmosphere boundary (wrt sea level)
float fTexDim, // Look-up texture dimension
float power, // Non-linear transform power
float fPrevTexCoord // Previous look-up texture coordinate
)
{
float fCosHorizonAngle = GetCosHorizonAngle(fAltitude - fAtmBottomAltitude, fEarthRadius + fAtmBottomAltitude);
// When performing look-ups into the scattering texture, it is very important that all the look-ups are consistent
// wrt to the horizon. This means that if the first look-up is above (below) horizon, then the second look-up
// should also be above (below) horizon.
// We use previous texture coordinate, if it is provided, to find out if previous look-up was above or below
// horizon. If texture coordinate is negative, then this is the first look-up
bool bIsAboveHorizon = fPrevTexCoord >= 0.5;
bool bIsBelowHorizon = 0.0 <= fPrevTexCoord && fPrevTexCoord < 0.5;
float fTexCoord;
if ( bIsAboveHorizon ||
!bIsBelowHorizon && (fCosZenithAngle > fCosHorizonAngle))
{
// Scale to [0,1]
fTexCoord = saturate( (fCosZenithAngle - fCosHorizonAngle) / (1.0 - fCosHorizonAngle) );
fTexCoord = pow(fTexCoord, power);
// Now remap texture coordinate to the upper half of the texture.
// To avoid filtering across discontinuity at 0.5, we must map
// the texture coordinate to [0.5 + 0.5/fTexDim, 1 - 0.5/fTexDim]
//
// 0.5 1.5 D/2+0.5 D-0.5 texture coordinate x dimension
// | | | |
// | X | X | .... | X || X | .... | X |
// 0 1 D/2-1 D/2 D-1 texel index
//
fTexCoord = 0.5 + 0.5 / fTexDim + fTexCoord * (fTexDim/2.0 - 1.0) / fTexDim;
}
else
{
fTexCoord = saturate( (fCosHorizonAngle - fCosZenithAngle) / (fCosHorizonAngle - (-1.0)) );
fTexCoord = pow(fTexCoord, power);
// Now remap texture coordinate to the lower half of the texture.
// To avoid filtering across discontinuity at 0.5, we must map
// the texture coordinate to [0.5/fTexDim, 0.5 - 0.5/fTexDim]
//
// 0.5 1.5 D/2-0.5 texture coordinate x dimension
// | | |
// | X | X | .... | X || X | ....
// 0 1 D/2-1 D/2 texel index
//
fTexCoord = 0.5f / fTexDim + fTexCoord * (fTexDim/2.0 - 1.0) / fTexDim;
}
return fTexCoord;
}
// Transforms zenith angle look-up coordinate into the cosine of the zenith angle
float TexCoord2ZenithAngle(float fTexCoord, // Texture coordinate
float fAltitde, // Altitude (height above the sea level)
float fEarthRadius, // Earth radius at sea level
float fAtmBottomAltitude, // Altitude of the bottom atmosphere boundary (wrt sea level)
float fTexDim, // Look-up texture dimension
float power // Non-linear transform power
)
{
float fCosZenithAngle;
float fCosHorzAngle = GetCosHorizonAngle(fAltitde - fAtmBottomAltitude, fEarthRadius + fAtmBottomAltitude);
if( fTexCoord > 0.5 )
{
// Remap to [0,1] from the upper half of the texture [0.5 + 0.5/fTexDim, 1 - 0.5/fTexDim]
fTexCoord = saturate( (fTexCoord - (0.5 + 0.5 / fTexDim)) * fTexDim / (fTexDim/2.0 - 1.0) );
fTexCoord = pow(fTexCoord, 1.0/power);
// Assure that the ray does NOT hit Earth
fCosZenithAngle = max( (fCosHorzAngle + fTexCoord * (1.0 - fCosHorzAngle)), fCosHorzAngle + 1e-4);
}
else
{
// Remap to [0,1] from the lower half of the texture [0.5/fTexDim, 0.5 - 0.5/fTexDim]
fTexCoord = saturate((fTexCoord - 0.5 / fTexDim) * fTexDim / (fTexDim/2.0 - 1.0));
fTexCoord = pow(fTexCoord, 1.0/power);
// Assure that the ray DOES hit Earth
fCosZenithAngle = min( (fCosHorzAngle - fTexCoord * (fCosHorzAngle - (-1.0))), fCosHorzAngle - 1e-4);
}
return fCosZenithAngle;
}
// Transforms inscattering look-up table coordinates into world parameters
void InsctrLUTCoords2WorldParams(in float4 f4UVWQ,
in float fEarthRadius,
in float fAtmBottomAltitude,
in float fAtmTopAltitude,
out float fAltitude,
out float fCosViewZenithAngle,
out float fCosSunZenithAngle,
out float fCosSunViewAngle)
{
#if NON_LINEAR_PARAMETERIZATION
// Rescale to exactly 0,1 range
f4UVWQ.xzw = saturate(( f4UVWQ * PRECOMPUTED_SCTR_LUT_DIM - float4(0.5,0.5,0.5,0.5) ) / ( PRECOMPUTED_SCTR_LUT_DIM - float4(1.0,1.0,1.0,1.0) )).xzw;
f4UVWQ.x = pow( f4UVWQ.x, 1.0/HeightPower );
// Allowable altitude range is limited to [fAtmBottomAltitude + SafetyHeightMargin, fAtmTopAltitude - SafetyHeightMargin] to
// avoid numeric issues at the atmosphere boundaries
fAltitude = f4UVWQ.x * ((fAtmTopAltitude - fAtmBottomAltitude) - 2.0 * SafetyHeightMargin) + (fAtmBottomAltitude + SafetyHeightMargin);
fCosViewZenithAngle = TexCoord2ZenithAngle(f4UVWQ.y, fAltitude, fEarthRadius, fAtmBottomAltitude, PRECOMPUTED_SCTR_LUT_DIM.y, ViewZenithPower);
// Use Eric Bruneton's formula for cosine of the sun-zenith angle
fCosSunZenithAngle = tan((2.0 * f4UVWQ.z - 1.0 + 0.26) * 1.1) / tan(1.26 * 1.1);
f4UVWQ.w = sign(f4UVWQ.w - 0.5) * pow( abs((f4UVWQ.w - 0.5)*2.0), 1.0/SunViewPower)/2.0 + 0.5;
fCosSunViewAngle = cos(f4UVWQ.w*PI);
#else
// Rescale to exactly 0,1 range
f4UVWQ = (f4UVWQ * PRECOMPUTED_SCTR_LUT_DIM - float4(0.5,0.5,0.5,0.5)) / (PRECOMPUTED_SCTR_LUT_DIM-float4(1.0,1.0,1.0,1.0));
// Allowable altitude range is limited to [fAtmBottomAltitude + SafetyHeightMargin, fAtmTopAltitude - SafetyHeightMargin] to
// avoid numeric issues at the atmosphere boundaries
fAltitude = f4UVWQ.x * ((fAtmTopAltitude - fAtmBottomAltitude) - 2.0 * SafetyHeightMargin) + (fAtmBottomAltitude + SafetyHeightMargin);
fCosViewZenithAngle = f4UVWQ.y * 2.0 - 1.0;
fCosSunZenithAngle = f4UVWQ.z * 2.0 - 1.0;
fCosSunViewAngle = f4UVWQ.w * 2.0 - 1.0;
#endif
fCosViewZenithAngle = clamp(fCosViewZenithAngle, -1.0, +1.0);
fCosSunZenithAngle = clamp(fCosSunZenithAngle, -1.0, +1.0);
// Compute allowable range for the cosine of the sun view angle for the given
// view zenith and sun zenith angles
float D = (1.0 - fCosViewZenithAngle * fCosViewZenithAngle) * (1.0 - fCosSunZenithAngle * fCosSunZenithAngle);
// !!!! IMPORTANT NOTE regarding NVIDIA hardware !!!!
// There is a very weird issue on NVIDIA hardware with clamp(), saturate() and min()/max()
// functions. No matter what function is used, fCosViewZenithAngle and fCosSunZenithAngle
// can slightly fall outside [-1,+1] range causing D to be negative
// Using saturate(D), max(D, 0) and even D>0?D:0 does not work!
// The only way to avoid taking the square root of negative value and obtaining NaN is
// to use max() with small positive value:
D = sqrt( max(D, 1e-20) );
// The issue was reproduceable on NV GTX 680, driver version 9.18.13.2723 (9/12/2013).
// The problem does not arise on Intel hardware
float2 f2MinMaxCosSunViewAngle = fCosViewZenithAngle*fCosSunZenithAngle + float2(-D, +D);
// Clamp to allowable range
fCosSunViewAngle = clamp(fCosSunViewAngle, f2MinMaxCosSunViewAngle.x, f2MinMaxCosSunViewAngle.y);
}
float4 WorldParams2InsctrLUTCoords(float fAltitude,
float fCosViewZenithAngle,
float fCosSunZenithAngle,
float fCosSunViewAngle,
float fEarthRadius,
float fAtmBottomAltitude,
float fAtmTopAltitude,
float4 f4RefUVWQ)
{
float4 f4UVWQ;
// Limit allowable altitude range to [fAtmBottomAltitude + SafetyHeightMargin, AtmTopAltitude - SafetyHeightMargin] to
// avoid numeric issues at the atmosphere boundaries.
// (ray/sphere intersection tests are unstable when fAltitude == fAtmBottomAltitude and fAltitude == AtmTopAltitude)
fAltitude = clamp(fAltitude, fAtmBottomAltitude + SafetyHeightMargin, fAtmTopAltitude - SafetyHeightMargin);
f4UVWQ.x = saturate( (fAltitude - (fAtmBottomAltitude + SafetyHeightMargin)) / ((fAtmTopAltitude - fAtmBottomAltitude) - 2.0*SafetyHeightMargin) );
#if NON_LINEAR_PARAMETERIZATION
f4UVWQ.x = pow(f4UVWQ.x, HeightPower);
f4UVWQ.y = ZenithAngle2TexCoord(fCosViewZenithAngle, fAltitude, fEarthRadius, fAtmBottomAltitude, PRECOMPUTED_SCTR_LUT_DIM.y, ViewZenithPower, f4RefUVWQ.y);
// Use Eric Bruneton's formula for cosine of the sun-zenith angle
f4UVWQ.z = (atan(max(fCosSunZenithAngle, -0.1975) * tan(1.26 * 1.1)) / 1.1 + (1.0 - 0.26)) * 0.5;
fCosSunViewAngle = clamp(fCosSunViewAngle, -1.0, +1.0);
f4UVWQ.w = acos(fCosSunViewAngle) / PI;
f4UVWQ.w = sign(f4UVWQ.w - 0.5) * pow( abs((f4UVWQ.w - 0.5)/0.5), SunViewPower)/2.0 + 0.5;
f4UVWQ.xzw = ((f4UVWQ * (PRECOMPUTED_SCTR_LUT_DIM - float4(1.0, 1.0, 1.0, 1.0)) + float4(0.5, 0.5, 0.5, 0.5)) / PRECOMPUTED_SCTR_LUT_DIM).xzw;
#else
f4UVWQ.y = (fCosViewZenithAngle+1.f) / 2.f;
f4UVWQ.z = (fCosSunZenithAngle +1.f) / 2.f;
f4UVWQ.w = (fCosSunViewAngle +1.f) / 2.f;
f4UVWQ = (f4UVWQ * (PRECOMPUTED_SCTR_LUT_DIM - float4(1.0,1.0,1.0,1.0)) + float4(0.5,0.5,0.5,0.5)) / PRECOMPUTED_SCTR_LUT_DIM;
#endif
return f4UVWQ;
}
float3 ComputeViewDir(in float fCosViewZenithAngle)
{
return float3(sqrt(saturate(1.0 - fCosViewZenithAngle*fCosViewZenithAngle)), fCosViewZenithAngle, 0.0);
}
float3 ComputeLightDir(in float3 f3ViewDir, in float fCosSunZenithAngle, in float fCosSunViewAngle)
{
float3 f3DirOnLight;
f3DirOnLight.x = (f3ViewDir.x > 0.0) ? (fCosSunViewAngle - fCosSunZenithAngle * f3ViewDir.y) / f3ViewDir.x : 0.0;
f3DirOnLight.y = fCosSunZenithAngle;
f3DirOnLight.z = sqrt( saturate(1.0 - dot(f3DirOnLight.xy, f3DirOnLight.xy)) );
// Do not normalize f3DirOnLight! Even if its length is not exactly 1 (which can
// happen because of fp precision issues), all the dot products will still be as
// specified, which is essentially important. If we normalize the vector, all the
// dot products will deviate, resulting in wrong pre-computation.
// Since fCosSunViewAngle is clamped to allowable range, f3DirOnLight should always
// be normalized. However, due to some issues on NVidia hardware sometimes
// it may not be as that (see IMPORTANT NOTE regarding NVIDIA hardware)
//f3DirOnLight = normalize(f3DirOnLight);
return f3DirOnLight;
}
float3 LookUpPrecomputedScattering(in float3 f3StartPoint,
in float3 f3ViewDir,
in float3 f3EarthCentre,
in float fEarthRadius,
in float3 f3DirOnLight,
in float fAtmBottomAltitude,
in float fAtmTopAltitude,
in Texture3D<float3> tex3DScatteringLUT,
in SamplerState tex3DScatteringLUT_sampler,
inout float4 f4UVWQ)
{
float3 f3EarthCentreToPointDir = f3StartPoint - f3EarthCentre;
float fDistToEarthCentre = length(f3EarthCentreToPointDir);
f3EarthCentreToPointDir /= fDistToEarthCentre;
float fAltitude = fDistToEarthCentre - fEarthRadius; // Height above sea level
float fCosViewZenithAngle = dot( f3EarthCentreToPointDir, f3ViewDir );
float fCosSunZenithAngle = dot( f3EarthCentreToPointDir, f3DirOnLight );
float fCosSunViewAngle = dot( f3ViewDir, f3DirOnLight );
// Provide previous look-up coordinates
f4UVWQ = WorldParams2InsctrLUTCoords(fAltitude,
fCosViewZenithAngle,
fCosSunZenithAngle,
fCosSunViewAngle,
fEarthRadius,
fAtmBottomAltitude,
fAtmTopAltitude,
f4UVWQ);
float3 f3UVW0;
f3UVW0.xy = f4UVWQ.xy;
float fQ0Slice = floor(f4UVWQ.w * PRECOMPUTED_SCTR_LUT_DIM.w - 0.5);
fQ0Slice = clamp(fQ0Slice, 0.0, PRECOMPUTED_SCTR_LUT_DIM.w-1.0);
float fQWeight = (f4UVWQ.w * PRECOMPUTED_SCTR_LUT_DIM.w - 0.5) - fQ0Slice;
fQWeight = max(fQWeight, 0.0);
float2 f2SliceMinMaxZ = float2(fQ0Slice, fQ0Slice+1.0)/PRECOMPUTED_SCTR_LUT_DIM.w + float2(0.5,-0.5) / (PRECOMPUTED_SCTR_LUT_DIM.z*PRECOMPUTED_SCTR_LUT_DIM.w);
f3UVW0.z = (fQ0Slice + f4UVWQ.z) / PRECOMPUTED_SCTR_LUT_DIM.w;
f3UVW0.z = clamp(f3UVW0.z, f2SliceMinMaxZ.x, f2SliceMinMaxZ.y);
float fQ1Slice = min(fQ0Slice+1.0, PRECOMPUTED_SCTR_LUT_DIM.w-1.0);
float fNextSliceOffset = (fQ1Slice - fQ0Slice) / PRECOMPUTED_SCTR_LUT_DIM.w;
float3 f3UVW1 = f3UVW0 + float3(0.0, 0.0, fNextSliceOffset);
float3 f3Insctr0 = tex3DScatteringLUT.SampleLevel(tex3DScatteringLUT_sampler/*LinearClamp*/, f3UVW0, 0.0);
float3 f3Insctr1 = tex3DScatteringLUT.SampleLevel(tex3DScatteringLUT_sampler/*LinearClamp*/, f3UVW1, 0.0);
float3 f3Inscattering = lerp(f3Insctr0, f3Insctr1, fQWeight);
return f3Inscattering;
}
|