File: planet.frag

package info (click to toggle)
stellarium 25.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,307,224 kB
  • sloc: ansic: 317,377; cpp: 214,435; xml: 48,592; javascript: 26,073; python: 2,113; perl: 734; sh: 247; makefile: 192; pascal: 169
file content (449 lines) | stat: -rw-r--r-- 18,311 bytes parent folder | download
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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/*
 * Stellarium
 * Copyright (C) 2002-2016 Fabien Chereau and Stellarium contributors
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
 */

/*
  This is the fragment shader for solar system object rendering
 */

VARYING mediump vec2 texc; //texture coord
VARYING highp vec3 P; //original vertex pos in model space

const highp float PI = 3.14159265;

uniform sampler2D tex;
uniform mediump vec2 poleLat; //latitudes of pole caps, in terms of texture coordinate. x>0...north, y<1...south. 
uniform mediump vec3 ambientLight; // Must be in linear sRGB, without OETF application
uniform mediump vec3 diffuseLight; // Must be in linear sRGB, without OETF application
uniform highp vec4 sunInfo;
uniform mediump float skyBrightness;
uniform bool hasAtmosphere;
uniform bool hasNormalMap;
uniform bool hasHorizonMap;

uniform int shadowCount;
uniform highp mat4 shadowData;

//x = scaling, y = exponential falloff
uniform mediump vec2 outgasParameters;
//eye direction in model space, pre-normalized
uniform highp vec3 eyeDirection;

#ifdef RINGS_SUPPORT
uniform bool ring;
uniform highp float outerRadius;
uniform highp float innerRadius;
uniform sampler2D ringS;
uniform bool isRing;
#endif

#ifdef SHADOWMAP
uniform highp sampler2D shadowTex;
VARYING highp vec4 shadowCoord;
#endif

//light direction in model space, pre-normalized
uniform highp vec3 lightDirection;
#if defined(IS_OBJ) || defined(IS_MOON)
    #define OREN_NAYAR 1
    //x = A, y = B, z = scaling factor (rho/pi * E0), w roughness
    uniform mediump vec4 orenNayarParameters;
#endif
#ifdef IS_MOON
    uniform sampler2D earthShadow;
    uniform mediump float eclipsePush;
    uniform sampler2D normalMap;
    uniform sampler2D horizonMap;

    VARYING highp vec3 normalX;
    VARYING highp vec3 normalY;
    VARYING highp vec3 normalZ;
#else
    VARYING mediump vec3 normalVS; //pre-calculated normals or spherical normals in model space
#endif

const highp float M_PI=3.1415926535897932384626433832795;

#ifdef SHADOWMAP
uniform highp vec2 poissonDisk[64];

lowp float offset_lookup(in highp sampler2D sTex, in highp vec4 loc, in highp vec2 offset, in highp float zbias)
{
    //the macro SM_SIZE is set to the shadowmap size
    const mediump vec2 texmapscale=vec2(1.0/float(SM_SIZE));
    //"simulates" textureProjOffset for use in GLSL < 130
    highp vec4 coords = vec4(loc.xy + (offset * texmapscale * loc.w), loc.z, loc.w);

    //for some reason, when not adding a LOD bias, the result is wrong in my VM (Ubuntu 16.04)
    //even if the texture has NO mipmaps and the lookup filter is GL_NEAREST???
    //I'm 99% certain this is some bug in the GL driver, 
    //because adding ANY bias here makes it work correctly
    //It should have no effect on platforms which don't have this bug
    highp float texVal = texture2DProj_3(sTex, coords, -1000.0).r;
    //perform shadow comparison
    return texVal > (loc.z-zbias)/loc.w ? 1.0 : 0.0;
}

//basic pseudo-random number generator
mediump float random(in mediump vec4 seed4)
{
    highp float dot_product = dot(seed4, vec4(12.9898,78.233,45.164,94.673));
    return fract(sin(dot_product) * 43758.5453);
}

lowp float sampleShadowMap(in highp sampler2D sTex, in highp vec4 coord, in highp float zbias)
{
    //uncomment for a single sample
    //return offset_lookup(sTex,coord, vec2(0.0),zbias);

    // for some reason > 5 samples do not seem to work on my Ubuntu VM 
    // (no matter if ES2 or GL 2.1)
    // everything gets shadowed, but no errors?!
    // so to be sure we just fix the sample count at 4 for now, 
    // even though 16 would look quite a lot better
    const int SAMPLE_COUNT = 4;
    mediump float sum = 0.0;
    for(int i=0;i<SAMPLE_COUNT;++i)
    {
        //choose "random" locations out of 64 using the screen-space coordinates
        //for ES2, we have to use mod(float, float), mod(int, int) is not defined
        int index = int(mod( 64.0*random(vec4(gl_FragCoord.xyy,i)), 64.0));
        sum += offset_lookup(sTex, coord, poissonDisk[index], zbias);
    }

    return clamp(sum / float(SAMPLE_COUNT),0.0,1.0);
}
#endif

#ifdef OREN_NAYAR
// Calculates the Oren-Nayar reflectance (https://en.wikipedia.org/wiki/Oren%E2%80%93Nayar_reflectance_model)
// the scale parameter is actually rho/pi * E_0 here
// A and B are precalculated on the CPU side
mediump float orenNayar(in mediump vec3 normal, in highp vec3 lightDir, in highp vec3 viewDir, in mediump float A, in mediump float B, in mediump float scale, in mediump float roughSq)
{
    mediump float cosAngleLightNormal = dot(normal, lightDir);  //cos theta_i
    mediump float cosAngleEyeNormal = dot(normal, viewDir); //cos theta_r
    if(cosAngleLightNormal < 0.) return 0.;
    if(cosAngleEyeNormal < 0.)
        cosAngleEyeNormal = 0.;
    //acos can be quite expensive, can we avoid it?
    mediump float angleLightNormal = acos(cosAngleLightNormal); //theta_i
    mediump float angleEyeNormal = acos(cosAngleEyeNormal); //theta_r
    mediump float alpha = max(angleEyeNormal, angleLightNormal); //alpha = max(theta_i, theta_r)
    mediump float beta = min(angleEyeNormal, angleLightNormal); //beta = min(theta_i, theta_r)
    mediump float gamma = dot(viewDir - normal * cosAngleEyeNormal, lightDir - normal * cosAngleLightNormal); // cos(phi_r-phi_i)
    mediump float C = sin(alpha) * tan(beta);
    mediump float ON = A + B * max(0.0, gamma) * C; // Qualitative model done.
    // Now add third term:
    mediump float C3_2 = (4.0*alpha*beta)/(M_PI*M_PI);
    mediump float C3=0.125*roughSq/(roughSq+0.09)*C3_2*C3_2;
    mediump float third=(1.0-abs(gamma))*C3*tan(0.5*(alpha+beta));
    ON += third;
    // Add the intereflection term:
    mediump float betaterm = 2.0*beta/M_PI;
    mediump float ONir = (1.0-gamma*betaterm*betaterm)*0.17*roughSq/(roughSq+0.13);
    ON += ONir;
    return ON * cosAngleLightNormal * scale;
}
#endif

// calculate pseudo-outgassing effect, inspired by MeshLab's "electronic microscope" shader
lowp float outgasFactor(in mediump vec3 normal, in highp vec3 lightDir, in mediump float falloff)
{
    mediump float opac = dot(normal,lightDir);
    opac = abs(opac);
    opac = 1.0 - pow(opac, falloff);
    return opac;
}

void main()
{
#ifndef IS_MOON
    if(sunInfo.w==0.)
    {
        // We are drawing the Sun
        vec4 texColor = texture2D(tex, texc);
        texColor.rgb = srgbToLinear(texColor.rgb * sunInfo.rgb);
        // Reference: chapter 14.7 "Limb Darkening" in "Allen’s Astrophysical Quantities",
        //            A.N.Cox (ed.), 4th edition, New York: Springer-Verlag, 2002.
        //            DOI 10.1007/978-1-4612-1186-0
        // The values for u2 and v2 for wavelengths 400nm-800nm were taken, linearly
        // interpolated, and integrated against CIE 1931 color matching functions.
        // The results were transformed from XYZ to linear sRGB color space.
        // We call the results for u2 "a1", and for v2 "a2".
        const vec3 a2 = vec3(-0.226988526315793, -0.232934589453355, -0.153026433664999);
        const vec3 a1 = vec3(0.848380336865573, 0.937696820066542, 0.981762186155682);
        const vec3 a0 = vec3(1) - a1 - a2;
        float cosTheta = dot(eyeDirection, normalize(normalVS));
        cosTheta = max(0., cosTheta); // Rounding errors sometimes lead to negative value
        float cosTheta2 = cosTheta*cosTheta;
        vec3 limbDarkeningCoef = a0 + a1*cosTheta + a2*cosTheta2;
        vec3 color = texColor.rgb * limbDarkeningCoef;
        FRAG_COLOR = vec4(linearToSRGB(color), texColor.a);
        return;
    }
#endif
    mediump float final_illumination = 1.0;
#ifdef OREN_NAYAR
    mediump float lum = 1.;
#else
    //simple Lambert illumination
    mediump float c = dot(lightDirection, normalize(normalVS));
    mediump float lum = clamp(c, 0.0, 1.0);
#endif
#ifdef RINGS_SUPPORT
    if(isRing)
        lum=1.0;
#endif
    //shadow calculation
    if(lum > 0.0)
    {
        highp vec3 sunPosition = sunInfo.xyz;
#ifdef RINGS_SUPPORT
        if(ring && !isRing)
        {
            highp vec3 ray = normalize(sunPosition);
            highp float u = - P.z / ray.z;
            if(u > 0.0 && u < 1e10)
            {
                mediump float ring_radius = length(P + u * ray);
                mediump float s = (ring_radius - innerRadius) / (outerRadius - innerRadius);
                lowp float ringAlpha = texture2D(ringS, vec2(s, 0.5)).w;

                // FIXME: this compensates for too much transparency in the texture (see e.g. Saturn)
                ringAlpha = pow(ringAlpha, 1./2.2);

                if(ring_radius > innerRadius && ring_radius < outerRadius)
                    final_illumination = 1.0 - ringAlpha;
            }
        }
#endif

        highp float sunRadius = sunInfo.w;
        highp float L = length(sunPosition - P);
        highp float R = asin(sunRadius / L);

        for (int i = 0; i < 4; ++i)
        {
            if (shadowCount>i)
            {
                highp vec3 satellitePosition = shadowData[i].xyz;
                highp float satelliteRadius = shadowData[i].w;
                highp float l = length(satellitePosition - P);
                highp float r = asin(satelliteRadius / l);
                // The min(1.0, .) here is required to avoid spurious bright pixels close to the shadow center.
                highp float d = acos(min(1.0, dot(normalize(sunPosition - P), normalize(satellitePosition - P))));

                mediump float illumination = 1.0;
                if(d >= R + r) // distance too far
                {
                    // illumination = 1.0; // NOP
                }
                else if( d <= r - R ) // fully inside umbra
                {
#ifdef IS_MOON
                    illumination = (d / (r - R)) * 0.594; // prepare texture coordinate. 0.6=umbra edge. Smaller number->larger shadow.
#else
                    illumination = 0.0;
#endif
                }
                else if(d <= R - r)
                {
                    // penumbra completely inside (Annular, or a moon transit in front of the Sun.)
                    illumination = 1.0 - r * r / (R * R);
                }
                else // penumbra: partially inside
                {
#ifdef IS_MOON
                    //illumination = ((d - abs(R-r)) / (R + r - abs(R-r))) * 0.4 + 0.6;
                    illumination = ((d - r + R) / (2.0 * R )) * 0.406 + 0.594;
#else
                    mediump float x = (R * R + d * d - r * r) / (2.0 * d);
                    mediump float alpha = acos(x / R);
                    mediump float beta = acos((d - x) / r);
                    mediump float AR = R * R * (alpha - 0.5 * sin(2.0 * alpha));
                    mediump float Ar = r * r * (beta - 0.5 * sin(2.0 * beta));
                    mediump float AS = R * R * 2.0 * 1.57079633;
                    illumination = 1.0 - (AR + Ar) / AS;
#endif
                }
                final_illumination = min(illumination, final_illumination);
            }
        }
    }

#ifdef IS_MOON
    mediump vec3 normal;
    if(hasNormalMap)
        normal = texture2D(normalMap, texc).rgb-vec3(0.5);
    else
        normal = vec3(0,0,1);

    normal = normalize(normalX*normal.x+normalY*normal.y+normalZ*normal.z);
    // normal now contains the real surface normal taking normal map into account

    mediump float horizonShadowCoefficient = 1.;
    if(hasHorizonMap)
    {
        // Check whether the fragment is in the shadow of surrounding mountains or the horizon
        mediump vec3 lonDir = normalX;
        mediump vec3 northDir = normalY;
        mediump vec3 zenith = normalZ;
        mediump float sunAzimuth = atan(dot(lightDirection,lonDir), dot(lightDirection,northDir));
        mediump float sinSunElevation = dot(zenith, lightDirection);
        mediump vec4 horizonElevSample = (texture2D(horizonMap, texc) - 0.5) * 2.;
        mediump vec4 sinHorizElevs = sign(horizonElevSample) * horizonElevSample * horizonElevSample;
        mediump float sinHorizElevLeft, sinHorizElevRight;
        mediump float alpha;
        if(sunAzimuth >= PI/2.)
        {
            // Sun is between East and South
            sinHorizElevLeft = sinHorizElevs[1];
            sinHorizElevRight = sinHorizElevs[2];
            alpha = (sunAzimuth - PI/2.) / (PI/2.);
        }
        else if(sunAzimuth >= 0.)
        {
            // Sun is between North and East
            sinHorizElevLeft = sinHorizElevs[0];
            sinHorizElevRight = sinHorizElevs[1];
            alpha = sunAzimuth / (PI/2.);
        }
        else if(sunAzimuth <= -PI/2.)
        {
            // Sun is between South and West
            sinHorizElevLeft = sinHorizElevs[2];
            sinHorizElevRight = sinHorizElevs[3];
            alpha = (sunAzimuth + PI) / (PI/2.);
        }
        else
        {
            // Sun is between West and North
            sinHorizElevLeft = sinHorizElevs[3];
            sinHorizElevRight = sinHorizElevs[0];
            alpha = (sunAzimuth + PI/2.) / (PI/2.);
        }
        mediump float horizElevLeft = asin(sinHorizElevLeft);
        mediump float horizElevRight = asin(sinHorizElevRight);
        mediump float horizElev = horizElevLeft + (horizElevRight-horizElevLeft)*alpha;
        if(sinSunElevation < sin(horizElev))
            horizonShadowCoefficient = 0.;
    }
#else
    // important to normalize here again
    mediump vec3 normal = normalize(normalVS);
#endif
#ifdef OREN_NAYAR
    // Use an Oren-Nayar model for rough surfaces
    // Ref: http://content.gpwiki.org/index.php/D3DBook:(Lighting)_Oren-Nayar
    lum = orenNayar(normal, lightDirection, eyeDirection, orenNayarParameters.x, orenNayarParameters.y, orenNayarParameters.z, orenNayarParameters.w);
#endif
#ifdef IS_MOON
    lum *= horizonShadowCoefficient;
#endif
    //calculate pseudo-outgassing/rim-lighting effect
    lowp float outgas = 0.0;
    if(outgasParameters.x > 0.0)
    {
        outgas = outgasParameters.x * outgasFactor(normal, eyeDirection, outgasParameters.y);
    }
    //Reduce lum if sky is bright, to avoid burnt-out look in daylight sky.
    //lum *= (1.0-0.4*skyBrightness);
    lum *= clamp((0.9-0.05*log(skyBrightness)), 0.1, 0.9);
#ifdef SHADOWMAP
    //use shadowmapping
    //z-bias is modified using the angle between the surface and the light
    //gives less shadow acne
    highp float NdotL = clamp(dot(normal, lightDirection), 0.0, 1.0);
    highp float zbias = 0.01 * tan(acos(NdotL));
    zbias = clamp(zbias, 0.0, 0.03);
    lowp float shadow = sampleShadowMap(shadowTex, shadowCoord, zbias);
    lum*=shadow;
#endif

    if(hasAtmosphere)
    {
        // Planets with atmosphere don't have such a sharp terminator as we get with
        // Oren-Nayar model. The following is a hack to make the terminator smoother.
        // TODO: replace it with the correct BRDF (possibly different for different planets).
        lum *= lum;
    }
    //final lighting color
    mediump vec4 litColor = vec4(lum * final_illumination * diffuseLight + ambientLight, 1.0);

    //apply texture-colored rimlight
    //litColor.xyz = clamp( litColor.xyz + vec3(outgas), 0.0, 1.0);

    lowp vec4 texColor;
#ifdef RINGS_SUPPORT
    if(isRing)
    {
        float radius = length(texc);
        float s = (radius - innerRadius) / (outerRadius - innerRadius);
        vec2 texCoord = vec2(s, 0.5);
        texColor = texture2D(tex, texCoord);
        // Guard against poor quality mipmap filtering (e.g. Mesa with NPOT textures).
        if(radius > outerRadius)
            texColor = vec4(0);
    }
    else
#endif
    {
        texColor = texture2D(tex, texc);
    }

    texColor.rgb = srgbToLinear(texColor.rgb);

    mediump vec4 finalColor = texColor;
    // apply (currently only Martian) pole caps. texc.t=0 at south pole, 1 at north pole. 
    if (texc.t>poleLat.x-0.01+0.001*sin(texc.s*18.*M_PI)) {	// North pole near t=1
        mediump float mixfactor=1.;
        if (texc.t<poleLat.x+0.01+0.001*sin(texc.s*18.*M_PI))
            mixfactor=(texc.t-poleLat.x+0.01-0.001*sin(texc.s*18.*M_PI))/0.02;
        //finalColor.xyz=mix(vec3(1., 1., 1.), finalColor.xyz, 1.-mixfactor); 
        finalColor.xyz=mix(vec3(1., 1., 1.), finalColor.xyz, smoothstep(0., 1., 1.-mixfactor)); 
    }
    if (texc.t<poleLat.y+0.01+0.001*sin(texc.s*18.*M_PI)) {	// South pole near texc.t~0
        mediump float mixfactor=1.;
        if (texc.t>poleLat.y-0.01+0.001*sin(texc.s*18.*M_PI))
            mixfactor=(poleLat.y+0.01-texc.t-0.001*sin(texc.s*18.*M_PI))/0.02;
        //finalColor.xyz=mix(vec3(1., 1., 1.), finalColor.xyz, 1.-mixfactor); 
        finalColor.xyz=mix(vec3(1., 1., 1.), finalColor.xyz, smoothstep(0., 1., 1.-mixfactor)); 
    }
    finalColor *= litColor;
#ifdef IS_MOON
    if(final_illumination < 0.9999)
    {
        lowp vec4 shadowColor = texture2D(earthShadow, vec2(final_illumination, 0.5));
        // FIXME: this should be calculated properly in linear space as
        // extinction of sunlight, and with subsequent tone mapping.
        // Current implementation is a legacy from older times.
        lowp vec4 color = vec4(linearToSRGB(finalColor.rgb), finalColor.a);
        lowp float alpha = clamp(shadowColor.a, 0.0, 0.7); // clamp alpha to allow some maria detail
        finalColor = eclipsePush * (1.0-0.75*shadowColor.a) * mix(color, shadowColor, alpha);
        finalColor.rgb = srgbToLinear(finalColor.rgb);
    }
#endif

    //apply white rimlight
    finalColor.xyz = clamp( finalColor.xyz + vec3(outgas), 0.0, 1.0);

    FRAG_COLOR = vec4(linearToSRGB(finalColor.rgb), finalColor.a);
    //to debug texture issues, uncomment and reload shader
    //FRAG_COLOR = texColor;
}