-
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathindex.html
More file actions
3097 lines (2547 loc) · 155 KB
/
index.html
File metadata and controls
3097 lines (2547 loc) · 155 KB
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
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Title translated for international context -->
<title>Universal Planetary Volumetric Cloud & Atmosphere Engine</title>
<style>
body { margin: 0; overflow: hidden; background-color: #000; font-family: 'Courier New', Courier, monospace; }
canvas { display: block; width: 100vw; height: 100vh; }
#header-top {
position: absolute;
top: 20px;
left: 0;
width: 100%;
text-align: center;
color: rgba(255, 255, 255, 0.8);
font-size: 12px;
letter-spacing: 1px;
pointer-events: none; /* Ensures UI doesn't block mouse clicks on the canvas */
z-index: 10;
}
#header-top a {
color: #00ffcc;
text-decoration: none;
pointer-events: auto; /* Re-enables clicking specifically for the link */
font-weight: bold;
border-bottom: 1px solid transparent;
transition: border-bottom 0.3s ease;
}
#header-top a:hover {
border-bottom: 1px solid #00ffcc;
}
#ui-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
#instructions {
position: absolute;
/* Aligned to bottom HUD */
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: white;
text-align: center;
font-size: 14px;
background: rgba(0, 0, 0, 0.7);
padding: 20px;
border: 1px solid #555;
border-radius: 8px;
cursor: pointer;
pointer-events: auto; /* Allows clicking to lock pointer */
max-width: 450px;
z-index: 5;
}
#instructions h1 { font-size: 18px; margin-top: 0; color: #ffffff; }
#instructions p { font-size: 13px; line-height: 1.5; }
#hud-container-bottom-left {
position: absolute;
bottom: 20px;
left: 20px;
display: flex;
align-items: flex-end;
gap: 15px;
}
#hud-bottom-left {
position: relative;
color: #00ffcc;
font-size: 16px;
text-shadow: 1px 1px 2px black;
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-left: 3px solid #00ffcc;
}
.hud-label { opacity: 0.7; font-size: 12px; color: #aaa; }
.hud-value { font-weight: bold; font-size: 18px; }
</style>
</head>
<body>
<div id="ui-layer">
<div id="header-top">
Three.js HTML Atmospheric Engine - Vibecoded by
<a href="https://github.com/leoawen" target="_blank">"leoawen"</a>
</div>
<div id="instructions">
<h1>ENVIRONMENT READY</h1>
<p style="color: #00ffcc;">Click to Start Flight</p>
<p><strong>W, A, S, D</strong> - Move | <strong>SPACE</strong> - Up | <strong>SHIFT</strong> - Down</p>
<p><strong>Mouse</strong> - Look (ESC to pause)</p>
</div>
<div id="hud-container-bottom-left">
<div id="hud-bottom-left">
<div><span class="hud-label">ALTITUDE (Y)</span><br><span id="altimeter" class="hud-value">0 m</span></div>
<div style="margin-top: 10px;"><span class="hud-label">SPEED</span><br><span id="speedometer" class="hud-value">0 m/s</span></div>
<div style="margin-top: 10px;"><span class="hud-label">POSITION (X, Z)</span><br><span id="coords" class="hud-value">0, 0</span></div>
</div>
</div>
</div>
<!-- Importmap THREE.JS r165 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.165.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.165.0/examples/jsm/",
"lil-gui": "https://unpkg.com/lil-gui@0.19.1/dist/lil-gui.esm.min.js"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import GUI from 'lil-gui';
import Stats from 'three/addons/libs/stats.module.js';
// =========================================================================================
// 1. WORLD CONFIGURATION (SINGLE SOURCE OF TRUTH)
// =========================================================================================
// All initial "Softcode" configurations must be declared here.
const WORLD_CONFIG = {
flight: {
speed: 1000,
maxSpeed: 1000000
},
// Physical Dimensions of the Cloud Layer
dimensions: {
height: 20000, // Atmosphere thickness
baseY: 3000 // Cloud altitude (Above Planet Radius)
},
generation: {
resolution: 256, // Base 3D Texture Resolution (Low Freq Bake Quality)
// --- BASE LAYER 1 (STRUCTURAL BASE) ---
layer1: {
repeat: 2, // Number of repetitions in the cube (Integer = Seamless)
scale: 1.0, // Noise scale within the repetition
seed: 0.0 // Random variation
},
// --- BASE LAYER 2 (VARIATION) ---
layer2: {
repeat: 8, // Different repetition to create complexity
scale: 1.0, // Different scale for details
seed: 0.1 // Different seed to avoid overlap
},
// --- Detail Texture Configuration (Worley/Popcorn) ---
detail: {
resolution: 32, // 32x32x32 is sufficient for repetitive detail (optimization)
scaleR: 2.0, // Low Frequency (Large bubbles)
scaleG: 4.0, // Medium Frequency
scaleB: 8.0, // High Frequency (Fine crunchiness)
seed: 0.2 // Random variation
},
mixStrength: 0.3, // Blend strength (0.0 = Layer 1 only, 1.0 = Strong Layer 2)
// --- GENERAL FILTERS ---
coverage: 1.0,
erosion: 0.3,
contrast: 1.8,
// --- VERTICAL PROFILE ---
fadeCoverageBottom: 0.0,
fadeCoverageStart: 0.06,
fadeCoverageEnd: 1.0,
shapeBase: 0.2,
shapeBaseCurve: 0.1, // Controls funnel shape (1.0 = linear, > 1.0 = pointy)
shapeTop: 1.0,
opacityBase: 0.12, // Base Fade
opacityTop: 0.9, // Top Fade
},
// --- WEATHER MAP CONFIGURATION ----------
weather: {
// General Map Settings
enable: true, // Enable/Disable weather influence
resolution: 64, // Resolution (128 is enough for macro)
repeat: 19.0, // How many times weather repeats around the planet
poleTilt: 60.0, // Axis rotation in degrees (0 to 90)
poleMask: 0.1, // Size of the cleared area at the pole (0.0 to 1.0)
// --- DYNAMIC HEIGHT PARAMETERS ---
heightMax: 30000.0, // Max height clouds reach where weather is white
heightCurve: 0.01, // Curve: 1.0 linear, > 1.0 peaks taper, < 1.0 spreads height
// Channel R Config (Red) -> COVERAGE
channelR: {
seed: 0.3,
scale: 11.0,
persistence: 0.0,
offset: 0.0
},
// Channel G Config (Green) -> CLOUD TYPE/HEIGHT
channelG: {
enable: false,
seed: 0.0,
scale: 2.0,
persistence: 0.5,
offset: 10.0
}
},
// ---------------------------------------------------------
rendering: { // VOLUMETRIC RENDERING
densityScale: 1.0,
shadowOffset: 500.0, // Shadow check distance offset
threshold: 0.0, // "Noise Floor" threshold
cloudScale: 1.1,
globalScaleFactor: 0.00001,
// --- BASE LOD CONFIGURATION (TEXTURE LOD - MIPMAPPING) ---
baseLodMinDist: 0.0, // Max resolution up to here
baseLodMaxDist: 1000000.0, // Min resolution from here
baseLodMaxLevel: 4.0, // Mipmap Level (0 = Full, 1 = Half ... 4 = 1/16)
// Key Parameter: Controls edge gradient width
// 0.0 = Hard Edge (Blocky), 1.0 = Very diffuse smoke
edgeSoftness: 0.2,
detailScale: 10.0, // Detail texture tiling repetition
erosionStrength: 0.3, // Edge erosion strength (0.0 = smooth, 1.0 = eroded)
lodDistance: 1000000.0, // Distance where detail starts fading (Optimization)
popcornMode: true, // Toggles between "Dig Holes" (False) and "Round Popcorn" (True)
// --- RAYMARCHING OPTIMIZATION PARAMETERS ---
steps: 64, // Sample limit for "hits" (Visual quality inside cloud)
maxIterations: 800, // Hard loop limit (GPU Safety)
initialStep: 500.0, // First step size in meters (Close resolution)
stepGrowth: 1.001, // Geometric growth factor (1.5% larger each step)
spaceSkipRatio: 1.0, // Skip multiplier when no cloud found (Empty space acceleration)
maxDistance: 1000000.0, // Max rendering distance (Far clip)
// --- VISUAL PARAMETERS ---
absorp: 20.0, // Beer's Law - (uLightAbsorption) Increased for deeper shadows
skylightAbsorption: 0.0, // Softer absorption for skylight (blue shadows)
powderScale: 1.0, // "Sugar/Powder" effect scale on edges
powderIntensity: 1.0, // Edge brightness strength
phaseIntensity: 3.0, // Brightness multiplier when looking at sun
phaseG: 0.6, // Anisotropy (Light focus towards sun direction)
opacityClamp: 0.99, // Solidity Cutoff: if passed, becomes solid (blocks sun)
taa: {
resolution: 0.6, // Cloud resolution scale factor (0.5 = half res)
blend: 0.3, // Blend with previous frame (0.0 = no history, 0.9 = heavy trail)
ghosting: 0.8, // Ghost suppression (sensitivity to change)
distanceCutoff: 100.0 // Distance where noise method switches (Static vs Sliding)
},
},
// --- ANIMATION CONFIGURATION ---
animation: {
// 3D Texture Movement
wind3D: {
speedX: 0.002, // X axis speed
speedY: -0.002, // Y axis speed (vertical flow)
speedZ: 0.001, // Z axis speed
active: true
},
// Weather system rotation around planet
weatherRotation: {
speedU: -0.0001, // Longitudinal Rotation
speedV: 0.0, // Latitudinal Rotation
active: true
}
},
environment: {
sunElevation: 20.0, // Sun Elevation in degrees (-90 to 90)
sunAzimuth: 215.0, // Sun Rotation on horizon (0 to 360)
sunIntensity: 20.0, // Sun intrinsic brightness
exposure: 0.6, // Final camera exposure (tone mapping)
},
// --- GOD RAYS ---
godRays: {
intensity: 3.0, // Final composition intensity
density: 1.0, // Length / Spread
decay: 1.0, // Light decay
weight: 0.1, // Base brightness
exposure: 0.1, // Blur exposure
cloudThreshold: 0.9 // Cloud transparency hardness in rays (0.1 = hard, 1.0 = soft)
},
// Planet Visual Configuration (Ocean)
planet: {
color: 0x001e36, // Base ocean color (Deep Blue)
roughness: 0.5, // Roughness (Water shininess)
metalness: 0.1 // Metalness (Reflection)
},
physical: {
planetRadius: 6371000.0, // Physical planet radius in meters
atmosphereThickness: 100000.0, // Air layer thickness in meters
// Scattering coefficients (Physically based Rayleigh/Mie)
rayleighCoeff: new THREE.Vector3(5.802e-6, 13.558e-6, 33.100e-6),
mieCoeff: new THREE.Vector3(3.996e-6, 3.996e-6, 3.996e-6),
ozoneCoeff: new THREE.Vector3(0.650e-6, 1.881e-6, 0.085e-6),
// Intensity controls for artistic adjustments
rayleighScale: 1.0,
mieScale: 1.0,
ozoneScale: 1.0,
// Height where particle density halves
rayleighScaleHeight: 8000.0,
mieScaleHeight: 1200.0
},
debug: {
showAtmosphere: false, // Atmosphere wireframe
showPlanet: true // Planet mesh
},
};
// =========================================================================================
// 2. SHADERS
// =========================================================================================
// =========================================================================================
// CloudGeneratorShader - Perlin + Worley Mix
// =========================================================================================
const CloudGeneratorShader = {
vertexShader: /* glsl */ `
#include <common>
#include <logdepthbuf_pars_vertex>
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
#include <logdepthbuf_vertex>
}
`,
fragmentShader: /* glsl */ `
precision highp float;
#include <common>
#include <logdepthbuf_pars_fragment>
varying vec2 vUv;
uniform float uSliceZ; // Current Bake Slice (0 to 1)
// --- LAYER 1 PARAMETERS (PERLIN - GENERAL SHAPE) ---
uniform float uRepeat1;
uniform float uScale1;
uniform float uSeed1;
// --- LAYER 2 PARAMETERS (WORLEY - "POPCORN" STRUCTURE) ---
uniform float uRepeat2;
uniform float uScale2;
uniform float uSeed2;
uniform float uMixStrength; // Strength of "popcorn" over base
uniform float uContrast;
uniform float uErosion;
// --- Noise Functions ---
// Random Hash 3D
vec3 hash33(vec3 p) {
p = vec3( dot(p,vec3(127.1,311.7, 74.7)),
dot(p,vec3(269.5,183.3,246.1)),
dot(p,vec3(113.5,271.9,124.6)));
return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}
// Hash for Worley (0..1)
vec3 hash33_worley(vec3 p) {
p = vec3( dot(p,vec3(127.1,311.7, 74.7)),
dot(p,vec3(269.5,183.3,246.1)),
dot(p,vec3(113.5,271.9,124.6)));
return fract(sin(p)*43758.5453123);
}
// Periodic Perlin Noise (Smooth, smoke-like)
float periodicNoise(vec3 p, float period) {
vec3 pi = floor(p);
vec3 pf = fract(p);
vec3 w = pf * pf * (3.0 - 2.0 * pf);
vec3 p0 = mod(pi, period);
vec3 p1 = mod(pi + vec3(1.0), period);
float n000 = dot(hash33(vec3(p0.x, p0.y, p0.z)), pf - vec3(0.0, 0.0, 0.0));
float n100 = dot(hash33(vec3(p1.x, p0.y, p0.z)), pf - vec3(1.0, 0.0, 0.0));
float n010 = dot(hash33(vec3(p0.x, p1.y, p0.z)), pf - vec3(0.0, 1.0, 0.0));
float n110 = dot(hash33(vec3(p1.x, p1.y, p0.z)), pf - vec3(1.0, 1.0, 0.0));
float n001 = dot(hash33(vec3(p0.x, p0.y, p1.z)), pf - vec3(0.0, 0.0, 1.0));
float n101 = dot(hash33(vec3(p1.x, p0.y, p1.z)), pf - vec3(1.0, 0.0, 1.0));
float n011 = dot(hash33(vec3(p0.x, p1.y, p1.z)), pf - vec3(0.0, 1.0, 1.0));
float n111 = dot(hash33(vec3(p1.x, p1.y, p1.z)), pf - vec3(1.0, 1.0, 1.0));
return mix(mix(mix(n000, n100, w.x), mix(n010, n110, w.x), w.y),
mix(mix(n001, n101, w.x), mix(n011, n111, w.x), w.y), w.z);
}
// FBM Perlin (Accumulates octaves)
float fbmPerlin(vec3 p, float repeat, float scale) {
float f = 0.0;
float amp = 0.5;
float currentPeriod = repeat * scale;
vec3 currentPos = p * scale;
for(int i = 0; i < 4; i++) {
f += amp * periodicNoise(currentPos, currentPeriod);
currentPos *= 2.0;
currentPeriod *= 2.0;
amp *= 0.5;
}
return f + 0.5; // Normalize 0..1
}
// Periodic Worley Noise (Cellular/Bubbles)
// Returns 1.0 at cell center, 0.0 at edge
float worleyNoise(vec3 p, float period) {
vec3 id = floor(p);
vec3 f = fract(p);
float minDist = 1.0;
for(int x = -1; x <= 1; x++) {
for(int y = -1; y <= 1; y++) {
for(int z = -1; z <= 1; z++) {
vec3 neighbor = vec3(float(x), float(y), float(z));
vec3 tileId = mod(id + neighbor, period); // Perfect Tiling
vec3 point = hash33_worley(tileId + vec3(uSeed2 * 10.0)); // Layer 2 Seed
vec3 diff = neighbor + point - f;
float dist = length(diff);
minDist = min(minDist, dist);
}
}
}
// Invert to get "Bubbles" (Billowy)
return 1.0 - clamp(minDist, 0.0, 1.0);
}
// FBM Worley (Accumulates bubbles of different sizes)
float fbmWorley(vec3 p, float repeat, float scale) {
float f = 0.0;
float amp = 0.5; // Weight of larger bubble
float currentPeriod = repeat * scale;
vec3 currentPos = p * scale;
// 3 Octaves of Worley are enough for structure
for(int i = 0; i < 3; i++) {
f += amp * worleyNoise(currentPos, currentPeriod);
currentPos *= 2.0;
currentPeriod *= 2.0;
amp *= 0.5;
}
// Worley accumulates high values, normalize slightly
return f * 0.8;
}
// Remap Function (Essential for mixing Perlin and Worley)
// Remaps value 'v' from range 'minOld-maxOld' to 'minNew-maxNew'
float remap(float v, float minOld, float maxOld, float minNew, float maxNew) {
return minNew + (v - minOld) * (maxNew - minNew) / (maxOld - minOld);
}
void main() {
#include <logdepthbuf_fragment>
vec2 uv = vUv;
// --- GENERATION LAYER 1 (BASE PERLIN) ---
// Defines general "smoke" shape
vec3 pos1 = vec3(uv.x, uSliceZ, uv.y) * uRepeat1;
float baseCloud = fbmPerlin(pos1 + vec3(uSeed1*10.0), uRepeat1, uScale1);
// --- GENERATION LAYER 2 (WORLEY STRUCTURE) ---
// Defines "blobs" inside cloud
vec3 pos2 = vec3(uv.x, uSliceZ, uv.y) * uRepeat2;
float structure = fbmWorley(pos2, uRepeat2, uScale2);
// --- MIX ---
// Base defines WHERE cloud is. Worley sculpts the shape.
// If uMixStrength is high, clouds are very "bubbly".
// If low, they are smoother.
float finalNoise = mix(baseCloud, structure, uMixStrength);
// --- EROSION AND CONTRAST ---
float f = mix(finalNoise, finalNoise * finalNoise, uErosion);
// Apply Contrast (Sponge)
float cloudDensity = (uContrast * (f - 0.5)) + 0.5;
float visible = clamp(cloudDensity, 0.0, 1.0);
gl_FragColor = vec4(visible, visible, visible, 1.0);
}
`
};
// =========================================================================================
// DetailGeneratorShader - Generates Worley Noise (Cells) for Detail Erosion (Popcorn)
// =========================================================================================
// This shader creates a texture where:
// Channel R: Large Cells (Low Freq)
// Channel G: Medium Cells
// Channel B: Small Cells (High Freq)
// =========================================================================================
const DetailGeneratorShader = {
vertexShader: /* glsl */ `
#include <common>
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
precision highp float;
varying vec2 vUv;
// Current slice of 3D volume (0.0 to 1.0)
uniform float uSliceZ;
// Repetition settings for each channel (LOD)
uniform float uScaleR; // Base scale (e.g., 4.0)
uniform float uScaleG; // Medium scale (e.g., 8.0)
uniform float uScaleB; // Fine scale (e.g., 16.0)
uniform float uSeed;
// --- Hash Function for Randomness (vec3 -> vec3) ---
vec3 hash33(vec3 p) {
p = vec3( dot(p,vec3(127.1,311.7, 74.7)),
dot(p,vec3(269.5,183.3,246.1)),
dot(p,vec3(113.5,271.9,124.6)));
return fract(sin(p)*43758.5453123);
}
// --- Periodic Worley Noise (Tileable) ---
// Calculates distance to nearest random point in grid
// Returns: 1.0 - distance (to create convex spheres/popcorn)
float worleyNoise(vec3 uv, float freq) {
vec3 id = floor(uv);
vec3 f = fract(uv);
float minDist = 1.0;
// Search neighbors (3x3x3)
for(int x = -1; x <= 1; x++) {
for(int y = -1; y <= 1; y++) {
for(int z = -1; z <= 1; z++) {
vec3 neighbor = vec3(float(x), float(y), float(z));
// Tiling secret (Seamless repetition):
// Use 'mod' to connect right edge to left edge
vec3 tileId = mod(id + neighbor, freq);
vec3 point = hash33(tileId + vec3(uSeed)); // Random point in cell
// Optional animation (zero if static bake desired)
// point = 0.5 + 0.5 * sin(uSeed + 6.2831 * point);
vec3 diff = neighbor + point - f;
float dist = length(diff);
minDist = min(minDist, dist);
}
}
}
// Invert (1.0 - dist) to have white centers (convex)
// Crucial for "Popcorn/Billowy" effect
return 1.0 - clamp(minDist, 0.0, 1.0);
}
// Simplified FBM for Worley (direct sampling per channel for control)
void main() {
// 3D Coordinate based on UV and Z Slice
vec3 pos = vec3(vUv.x, uSliceZ, vUv.y);
// --- CHANNEL R (Coarse Detail) ---
// Multiply pos by scale to define frequency
float r = worleyNoise(pos * uScaleR, uScaleR);
// --- CHANNEL G (Medium Detail) ---
float g = worleyNoise(pos * uScaleG, uScaleG);
// --- CHANNEL B (Fine Detail) ---
float b = worleyNoise(pos * uScaleB, uScaleB);
// Write 3 detail levels to RGB channels
gl_FragColor = vec4(r, g, b, 1.0);
}
`
};
// =========================================================================================
// WeatherGeneratorShader - Generates 2D Weather Map (R: Coverage, G: Type)
// =========================================================================================
const WeatherGeneratorShader = {
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
precision highp float;
varying vec2 vUv;
// Channel R Uniforms (Coverage)
uniform float uScaleR;
uniform float uSeedR;
uniform float uPersistR;
uniform vec2 uOffsetR;
// Channel G Uniforms (Type)
uniform float uScaleG;
uniform float uSeedG;
uniform float uPersistG;
uniform vec2 uOffsetG;
// --- Noise Functions ---
vec3 hash33(vec3 p) {
p = vec3( dot(p,vec3(127.1,311.7, 74.7)),
dot(p,vec3(269.5,183.3,246.1)),
dot(p,vec3(113.5,271.9,124.6)));
return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}
float periodicNoise(vec3 p, float period) {
vec3 pi = floor(p);
vec3 pf = fract(p);
vec3 w = pf * pf * (3.0 - 2.0 * pf);
// Modulo ensures tiling (seamless repetition)
vec3 p0 = mod(pi, period);
vec3 p1 = mod(pi + vec3(1.0), period);
float n000 = dot(hash33(vec3(p0.x, p0.y, p0.z)), pf - vec3(0.0, 0.0, 0.0));
float n100 = dot(hash33(vec3(p1.x, p0.y, p0.z)), pf - vec3(1.0, 0.0, 0.0));
float n010 = dot(hash33(vec3(p0.x, p1.y, p0.z)), pf - vec3(0.0, 1.0, 0.0));
float n110 = dot(hash33(vec3(p1.x, p1.y, p0.z)), pf - vec3(1.0, 1.0, 0.0));
float n001 = dot(hash33(vec3(p0.x, p0.y, p1.z)), pf - vec3(0.0, 0.0, 1.0));
float n101 = dot(hash33(vec3(p1.x, p0.y, p1.z)), pf - vec3(1.0, 0.0, 1.0));
float n011 = dot(hash33(vec3(p0.x, p1.y, p1.z)), pf - vec3(0.0, 1.0, 1.0));
float n111 = dot(hash33(vec3(p1.x, p1.y, p1.z)), pf - vec3(1.0, 1.0, 1.0));
return mix(mix(mix(n000, n100, w.x), mix(n010, n110, w.x), w.y),
mix(mix(n001, n101, w.x), mix(n011, n111, w.x), w.y), w.z);
}
// Custom 2D FBM (using Z as seed offset)
float fbm(vec2 uv, float scale, float seed, float persistence, vec2 offset) {
float f = 0.0;
float amp = 0.5;
float currentScale = scale;
vec3 pos = vec3(uv, seed); // Use Z as "Temporal Seed"
// Accumulate noise (Octaves)
for(int i = 0; i < 4; i++) {
// Offset animates noise if needed
vec3 samplePos = (pos + vec3(offset, 0.0)) * currentScale;
// Call 3D periodic noise, varying only X and Y
f += amp * periodicNoise(samplePos, currentScale);
samplePos *= 2.0;
currentScale *= 2.0;
amp *= persistence; // Controls roughness
}
// Normalize -1..1 to 0..1
return f + 0.5;
}
void main() {
vec2 uv = vUv;
// --- CHANNEL R (COVERAGE) ---
float cov = fbm(uv, uScaleR, uSeedR, uPersistR, uOffsetR);
// Increase contrast to better define areas
cov = smoothstep(0.2, 0.8, cov);
// --- CHANNEL G (TYPE/HEIGHT) ---
float type = fbm(uv, uScaleG, uSeedG, uPersistG, uOffsetG);
gl_FragColor = vec4(cov, type, 0.0, 1.0);
}
`
};
// =========================================================================================
// CloudVolumeShader - PHYSICALLY BASED GLSL 3.0 WITH MULTIPLE OUTPUTS (WORLEY DETAIL EROSION)
// =========================================================================================
const CloudVolumeShader = {
vertexShader: /* glsl */ `
out vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */ `
precision highp float;
precision highp sampler3D;
layout(location = 0) out vec4 pc_FragColor;
layout(location = 1) out vec4 pc_FragDepth;
varying vec2 vUv;
// --- GENERAL UNIFORMS ---
uniform mat4 uInverseViewMatrix;
uniform mat4 uInverseProjectionMatrix;
uniform vec3 uCameraPos;
uniform sampler2D tDepth;
uniform float uLogDepthBufFC;
// --- TEXTURES ---
uniform sampler2D tBlueNoise;
uniform vec2 uBlueNoiseOffset;
uniform sampler3D uTexture;
uniform sampler3D uDetailTexture;
uniform sampler2D tWeatherMap;
uniform vec3 uWindOffset;
// --- ENVIRONMENT ---
uniform vec3 uSunDirection;
uniform float uSunIntensity;
uniform vec3 uPlanetCenter;
uniform float uPlanetRadius;
uniform float uAtmosphereRadius;
uniform vec3 uRayleighCoeff;
uniform vec3 uMieCoeff;
uniform float uRayleighScaleHeight;
// --- CLOUD CONTROLS ---
uniform float uCloudBaseOffset;
uniform float uCloudHeight;
uniform float uDensityScale;
uniform float uAbsorp;
uniform float uThreshold;
// Uniform for Shadow Offset
uniform float uShadowOffset;
// Edge Softness Envelope
uniform float uEdgeSoftness;
uniform float uCloudScale;
uniform float uGlobalScaleFactor;
// EROSION
uniform float uDetailScale;
uniform float uErosionStrength;
uniform float uLodDistance;
uniform bool uPopcornMode;
// --- BASE LOD SYSTEM UNIFORMS ---
uniform float uBaseLodMinDist;
uniform float uBaseLodMaxDist;
uniform float uBaseLodMaxLevel;
// VISUALS
uniform float uSkylightAbsorption;
uniform float uPowderScale;
uniform float uPowderIntensity;
uniform float uPhaseIntensity;
uniform float uPhaseG;
uniform float uOpacityClamp;
uniform float uCoverage;
uniform float uCovBottomShape;
uniform float uCovBottomCurve;
uniform float uCovTopShape;
uniform float uCloudBottom;
uniform float uCloudTop;
uniform float uFadeCovBottom;
uniform float uFadeCovStart;
uniform float uFadeCovEnd;
uniform bool uWeatherEnabled;
uniform vec2 uWeatherOffset;
uniform float uWeatherRepeat;
uniform float uWeatherPoleTilt;
uniform float uWeatherPoleMask;
uniform bool uWeatherGEnabled;
uniform float uWeatherHeightMax;
uniform float uWeatherHeightCurve;
// OPTIMIZATION
uniform float uSteps;
uniform float uMaxIterations;
uniform float uInitialStep;
uniform float uStepGrowth;
uniform float uSpaceSkipRatio;
uniform float uMaxDistance;
uniform sampler2D tPreviousCloud;
uniform mat4 uPreviousViewProjectionMatrix;
uniform float uBlendFactor;
uniform float uGhostingSuppression;
uniform float uGhostingDistanceCutoff;
uniform float uFrame;
// --- HELPER FUNCTIONS ---
float remap(float v, float minOld, float maxOld, float minNew, float maxNew) {
return minNew + (v - minOld) * (maxNew - minNew) / (maxOld - minOld);
}
vec3 rotateX(vec3 v, float angle) {
float s = sin(angle); float c = cos(angle);
return vec3(v.x, v.y * c - v.z * s, v.y * s + v.z * c);
}
float getLinearDepth(float fragCoordZ) {
float viewZ = -1.0 * (exp2(fragCoordZ / (uLogDepthBufFC * 0.5)) - 1.0);
return viewZ;
}
// Ray-Sphere Intersection
vec2 hitSphere(vec3 orig, vec3 dir, vec3 center, float radius) {
vec3 oc = orig - center;
float b = dot(oc, dir);
float c = dot(oc, oc) - radius * radius;
float h = b * b - c;
if (h < 0.0) return vec2(-1.0);
h = sqrt(h);
return vec2(-b - h, -b + h);
}
// Henyey-Greenstein Phase Function
float hg(float g, float cosTheta) {
float g2 = g * g;
return (1.0 - g2) / (4.0 * 3.14159 * pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5));
}
// Dual Phase Function (Forward + Backward scattering)
float phaseFunction(float cosTheta) {
return mix(hg(uPhaseG, cosTheta), hg(-0.2, cosTheta), 0.5);
}
// Calculates Sunlight Color reaching a point inside the atmosphere
vec3 getSunColorAtPoint(vec3 pointPos, vec3 sunDir) {
vec2 planetHit = hitSphere(pointPos, sunDir, uPlanetCenter, uPlanetRadius * 0.995);
if (planetHit.x > 0.0) return vec3(0.0); // Blocked by planet
float altitude = length(pointPos - uPlanetCenter) - uPlanetRadius;
vec3 up = normalize(pointPos - uPlanetCenter);
float rawCos = dot(up, sunDir);
float cosTheta = clamp(rawCos, -1.0, 1.0);
if (cosTheta < -0.1) return vec3(0.0);
// Air Mass approximation
float airMass = 1.0 / (max(cosTheta, 0.05) + 0.15 * pow(93.885 - acos(max(cosTheta, 0.0)) * 57.29, -1.253));
float density = exp(-altitude / uRayleighScaleHeight);
vec3 scattering = (uRayleighCoeff * 1.5 + uMieCoeff * 0.5) * airMass * density;
vec3 transmittance = exp(-scattering * 15000.0);
return transmittance * uSunIntensity;
}
// --- [CRITICAL] CLOUD SAMPLING FUNCTION WITH SOFT EDGE ---
// Returns density (0.0 to 1.0)
float sampleCloud(vec3 p) {
float distToCenter = length(p - uPlanetCenter);
float altitude = distToCenter - uPlanetRadius;
if(altitude < uCloudBaseOffset || altitude > (uCloudBaseOffset + uCloudHeight)) return 0.0;
// 1. WEATHER DATA
vec4 weatherData = vec4(1.0, 0.0, 0.0, 0.0);
float polarMask = 1.0;
if (uWeatherEnabled) {
vec3 dir = (p - uPlanetCenter) / distToCenter;
vec3 dirRotated = rotateX(dir, uWeatherPoleTilt);
float u = (atan(dirRotated.z, dirRotated.x) / (2.0 * 3.14159)) + 0.5;
float v = (asin(dirRotated.y) / 3.14159) + 0.5;
vec2 uv = vec2(u, v);
vec2 finalUV = (uv * uWeatherRepeat) + uWeatherOffset;
weatherData = texture2D(tWeatherMap, finalUV);
float poleProximity = abs(dirRotated.y);
polarMask = 1.0 - smoothstep(1.0 - uWeatherPoleMask, 1.0, poleProximity);
}
float mapCoverage = smoothstep(0.2, 0.8, weatherData.r) * polarMask;
if (mapCoverage <= 0.01) return 0.0;
// Dynamic Height Calculation
float heightModulator = pow(mapCoverage, uWeatherHeightCurve);
float localCloudHeight = uWeatherHeightMax * heightModulator;
float heightPercent = (altitude - uCloudBaseOffset) / localCloudHeight;
if(heightPercent > 1.0) return 0.0;
// 2. BASE SHAPE
vec3 noiseCoord = ((p - uPlanetCenter) * (uGlobalScaleFactor * uCloudScale)) + uWindOffset;
// --- [MANUAL LOD IMPLEMENTATION START] ---
// 1. Calculate real distance from point (p) to camera
float distToCam = distance(p, uCameraPos);
// 2. Calculate interpolation factor (0.0 = close, 1.0 = far)
// If distToCam < Min, lodFactor is 0. If > Max, it is 1.
float lodFactor = clamp((distToCam - uBaseLodMinDist) / (uBaseLodMaxDist - uBaseLodMinDist), 0.0, 1.0);
// 3. Define target Mipmap level
// Level 0 = Original Res (256), Level 4 = Low Res (16)
float targetLod = mix(0.0, uBaseLodMaxLevel, lodFactor);
// 4. Sample texture forcing specific LOD
// NOTE: textureLod only works if texture has mipmaps generated
float baseDensity = textureLod(uTexture, noiseCoord, targetLod).r;
// --- [MANUAL LOD IMPLEMENTATION END] ---
// 3. VERTICAL SHAPING AND COVERAGE
float mapType = weatherData.g;
float localShapeBase = uCovBottomShape;
float localShapeTop = uCovTopShape;
if (uWeatherGEnabled) {
localShapeTop = mix(uCovTopShape, min(1.0, uCovTopShape + (mapType * 0.8)), mapType);
localShapeBase = mix(uCovBottomShape, uCovBottomShape + (mapType * 0.1), mapType);
}
float shapeBottom = pow(smoothstep(0.0, localShapeBase, heightPercent), uCovBottomCurve);
float shapeTop = 1.0 - smoothstep(localShapeTop, 1.0, heightPercent);
// --- NEW DUAL FADE LOGIC (BASE AND TOP) ---
// 1. Fade In (Base): Increase coverage from 0 to 1
float fadeIn = smoothstep(0.0, uFadeCovBottom, heightPercent);
// 2. Fade Out (Top): Decrease coverage from 1 to 0
float fadeOut = 1.0 - smoothstep(uFadeCovStart, uFadeCovEnd, heightPercent);
// Combine: Cloud only exists where BOTH allow
float verticalFadeCov = fadeIn * fadeOut;
float dynamicCoverage = (mapCoverage * uCoverage) * verticalFadeCov * shapeBottom * shapeTop;
// --- SMART REMAP ---
// Instead of hard cutting, we remap the noise so coverage edge is 0.
float density = remap(baseDensity, 1.0 - dynamicCoverage, 1.0, 0.0, 1.0);
// Optimization: If negative (outside cloud), exit early.
if (density <= 0.0) return 0.0;
// 4. DETAIL EROSION
// Apply erosion BEFORE final edge smoothing
if (density > 0.0) {
vec3 detailCoord = noiseCoord * uDetailScale;
float distCamera = distance(p, uCameraPos);
float lodFactor = clamp(distCamera / uLodDistance, 0.0, 1.0);
if (lodFactor < 1.0) {
vec3 detailNoise = texture(uDetailTexture, detailCoord).rgb;
float highFreqFBM = detailNoise.r * 0.625 + detailNoise.g * 0.25 + detailNoise.b * 0.125;
float erosionPattern = uPopcornMode ? (1.0 - highFreqFBM) : highFreqFBM;
float currentStrength = uErosionStrength * (1.0 - lodFactor);
// Apply erosion remapping current density
density = remap(density, erosionPattern * currentStrength, 1.0, 0.0, 1.0);
}
}
// Final Vertical Fades (Hard clamp)
float bottomFade = smoothstep(0.0, uCloudBottom, heightPercent);
float topFade = 1.0 - smoothstep(uCloudTop, 1.0, heightPercent);
density *= bottomFade * topFade;
return density; // Returns raw density (can be > 0 but < threshold)
}
void main() {
vec4 ndc = vec4(vUv * 2.0 - 1.0, 1.0, 1.0);
vec4 clip = uInverseProjectionMatrix * ndc;
vec4 view = uInverseViewMatrix * (clip / clip.w);
vec3 worldDir = normalize(view.xyz - uCameraPos);
float depthLog = texture(tDepth, vUv).r;
float geometryViewZ = abs(getLinearDepth(depthLog));
float geoDist = geometryViewZ / dot(worldDir, (uInverseViewMatrix * vec4(0.0, 0.0, -1.0, 0.0)).xyz);
if (depthLog >= 1.0) geoDist = 1e20;
vec3 ro = uCameraPos;