Harnessing the Elements - Sun, Noise, and Bloom in Layered Mesh Design

Rick
September 11th, 2024 · 1 min read

I have always wanted to create a sun like mesh and this is a small showcase of it.

The design is spheres scaled inside each other.

Avoiding artifacts which doublesided spheres have.

And creating a sun like object in 3D space.

Giph of sun

Here is the code:

1import { ImprovedNoise } from 'three/examples/jsm/math/ImprovedNoise';
2import React, { useState, useEffect, useMemo } from 'react';
3import { Canvas, useFrame } from '@react-three/fiber';
4import { OrbitControls } from '@react-three/drei';
5import * as THREE from 'three';
6import { EffectComposer, Bloom } from '@react-three/postprocessing';
7import { KernelSize } from 'postprocessing'
8
9const generate3DNoiseTexture = (width, height, depth) => {
10 const size = width * height * depth;
11 const data = new Float32Array(size);
12 const noise = new ImprovedNoise();
13 const scale = 10;
14
15 for (let z = 0; z < depth; z++) {
16 for (let y = 0; y < height; y++) {
17 for (let x = 0; x < width; x++) {
18 const nx = x / width;
19 const ny = y / height;
20 const nz = z / depth;
21 data[x + width * (y + height * z)] = (noise.noise(nx * scale, ny * scale, nz * scale) + 1) * 0.5;
22 }
23 }
24 }
25
26 const texture = new THREE.Data3DTexture(data, width, height, depth);
27 texture.minFilter = THREE.NearestMipMapLinearFilter;
28 texture.magFilter = THREE.NearestMipMapLinearFilter;
29 texture.format = THREE.RedFormat;
30 texture.type = THREE.FloatType;
31 texture.needsUpdate = true;
32 texture.wrapR = texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
33
34 return texture;
35};
36
37const NoiseMaterial = ({ noiseTexture, front, alpha }) => {
38 const [time, setTime] = useState(0);
39 useFrame(({ clock }) => setTime(clock.getElapsedTime()));
40
41 const vertexShader = `
42 varying vec3 vPosition;
43 void main() {
44 vPosition = position;
45 vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
46 gl_Position = projectionMatrix * mvPosition;
47 }
48 `;
49
50 const fragmentShader = `
51 varying vec3 vPosition;
52 uniform sampler3D noiseTexture;
53 uniform float alpha;
54 uniform float time;
55
56 void main() {
57 vec3 vPos = vPosition;
58 vPos.xyz += time * 0.3;
59 vPos.xyz = (vPos.xyz + time * 0.3 + 1.0) * (0.5 + time * 0.8);
60 vPos = vec3(vPos);
61 vec3 noise = texture(noiseTexture, vPos ).rgb;
62 float stepper = step(alpha / 0.3, noise.r);
63 gl_FragColor = vec4(0.0, 0.0, mix(stepper * 6.0 * 2.0 - alpha, 0.0, alpha), mix(0.0, stepper, noise.r / 0.7));
64 }
65 `;
66
67 return (
68 <shaderMaterial
69 attach="material"
70 transparent={true}
71 alphaToCoverage
72 sizeAttenuation={false}
73 depthTest={true}
74 depthWrite={true}
75 side={front ? THREE.FrontSide : THREE.BackSide}
76 args={[{
77 vertexShader,
78 fragmentShader,
79 uniforms: {
80 noiseTexture: { value: noiseTexture },
81 alpha: { value: alpha },
82 time: { value: time }
83 } }]}
84 uniforms-noiseTexture-value={noiseTexture}
85 uniforms-alpha-value={alpha / 10 - 0.1}
86 uniforms-time-value={time * 1.0 / alpha / 50.0}
87 />
88 );
89};
90
91const LayeredSpheres = ({ radius, noiseTexture, layers }) => {
92 const layerOffset = 0.01;
93
94 return (
95 <>
96 {[...Array(layers)].map((_, i) => {
97 const currentRadius = radius - i / layers * layerOffset;
98 if (currentRadius <= 0) return null; // Prevent spheres with non-positive radius
99
100 return (
101 <React.Fragment key={i}>
102 <mesh>
103 <sphereGeometry args={[currentRadius, 64, 64]} />
104 <NoiseMaterial noiseTexture={noiseTexture} front={false} alpha={i} /> {/* Outer Sphere */}
105 </mesh>
106 <mesh>
107 <sphereGeometry args={[currentRadius - layerOffset / 2, 64, 64]} />
108 <NoiseMaterial noiseTexture={noiseTexture} front={true} alpha={i} /> {/* Inner Sphere */}
109 </mesh>
110 </React.Fragment>
111 );
112 })}
113 </>
114 );
115};
116
117const LightingAndControls = () => (
118 <>
119 <ambientLight />
120 <pointLight position={[10, 10, 10]} />
121 <OrbitControls />
122 </>
123);
124
125const BloomEffect = () => (
126 <EffectComposer multisampling={8}>
127 <Bloom kernelSize={1} luminanceThreshold={0} luminanceSmoothing={0.9} intensity={0.6} />
128 <Bloom kernelSize={KernelSize.HUGE} luminanceThreshold={0} luminanceSmoothing={0} intensity={.05} />
129 </EffectComposer>
130);
131
132const StarField = ({ count = 5000, radius = 100 }) => {
133 const starsGeometry = useMemo(() => {
134 const positions = new Float32Array(count * 3);
135 for (let i = 0; i < count; i++) {
136 const theta = THREE.MathUtils.randFloatSpread(360);
137 const phi = THREE.MathUtils.randFloatSpread(360);
138
139 const distance = THREE.MathUtils.randFloatSpread(radius);
140
141 const x = distance * Math.cos(theta) * Math.sin(phi);
142 const y = distance * Math.sin(theta) * Math.sin(phi);
143 const z = distance * Math.cos(phi);
144
145 positions.set([x, y, z], i * 3);
146 }
147
148 const geometry = new THREE.BufferGeometry();
149 geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
150
151 return geometry;
152 }, [count, radius]);
153
154 return (
155 <points geometry={starsGeometry}>
156 <pointsMaterial size={0.001} color="white" />
157 </points>
158 );
159};
160
161const AppCanvas = ({ children }) => (
162 <Canvas style={{ height: '100vh', width: '100vw' }}>
163 <color attach="background" args={['black']} />
164 {children}
165 </Canvas>
166);
167
168const App = () => {
169 const [noiseTexture, setNoiseTexture] = useState(null);
170
171 useEffect(() => {
172 setNoiseTexture(generate3DNoiseTexture(256, 256, 256));
173 }, []);
174
175 if (!noiseTexture) return null;
176
177 return (
178 <AppCanvas>
179 <LayeredSpheres radius={1} noiseTexture={noiseTexture} layers={60} />
180 <LightingAndControls />
181 <BloomEffect />
182 <StarField />
183 <mesh>
184 <sphereGeometry args={[0.99, 64, 64]} />
185 <meshBasicMaterial color={'#FAF9F6'} emissionIntensity={1.0}/>
186 </mesh>
187 </AppCanvas>
188 );
189};
190
191export default App;

More articles from theFrontDev