diff --git a/src/main.js b/src/main.js index 20194da..87e0548 100644 --- a/src/main.js +++ b/src/main.js @@ -16,7 +16,7 @@ import { inject } from '@vercel/analytics'; // import sun -import { sun } from './sun.js'; +import { sun, solarFlares } from './sun.js'; // Import planets and orbital groups import { planetData, orbitalGroups } from './planets.js'; @@ -405,6 +405,16 @@ const tick = () => // Update shader time uniform sun.material.uniforms.uTime.value = elapsedTime; + // Rotate sun slowly to show flares + sun.rotation.y = elapsedTime * 0.05; + + // Update solar flare animations + solarFlares.forEach((flare) => { + if (flare.material.uniforms.uTime) { + flare.material.uniforms.uTime.value = elapsedTime; + } + }); + // Update the renderer renderer.render(scene, camera); diff --git a/src/shaders/sun/flare.frag b/src/shaders/sun/flare.frag new file mode 100644 index 0000000..324e171 --- /dev/null +++ b/src/shaders/sun/flare.frag @@ -0,0 +1,109 @@ +/** +* @file flare.frag +* @brief Fragment shader for solar flares +* @details Creates animated solar flares (prominences) using procedural noise +* @author Mitch Campbell +* @copyright 2024 +*/ + +precision mediump float; + +uniform float uTime; +uniform float uFlareIntensity; +uniform vec3 uFlareColor; + +varying vec2 vUv; +varying vec3 vPosition; +varying vec3 vNormal; + +// Simple 2D noise function for performance +float hash(vec2 p) +{ + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float noise(vec2 p) +{ + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float a = hash(i); + float b = hash(i + vec2(1.0, 0.0)); + float c = hash(i + vec2(0.0, 1.0)); + float d = hash(i + vec2(1.0, 1.0)); + + return mix(mix(a, b, f.x), mix(c, d, f.x), f.y); +} + +// Fractal noise for more detail +float fbm(vec2 p) +{ + float value = 0.0; + float amplitude = 0.5; + float frequency = 1.0; + + for(int i = 0; i < 3; i++) + { + value += amplitude * noise(p * frequency); + frequency *= 2.0; + amplitude *= 0.5; + } + + return value; +} + +void main() +{ + // Create vertical flare shape using UV coordinates + vec2 uv = vUv; + + // Center the UV + uv = uv * 2.0 - 1.0; + + // Cache commonly used value for performance + float uvXAbs = abs(uv.x); + + // Create elongated flare shape with stronger base + float flareShape = 1.0 - uvXAbs; + flareShape = pow(flareShape, 1.5); + + // Add height falloff with visible base + float heightFalloff = smoothstep(1.2, -0.2, abs(uv.y)); + flareShape *= heightFalloff; + + // Animate with noise for turbulent appearance + vec2 noiseCoord = vec2(uv.x * 2.0, uv.y * 3.0 - uTime * 0.3); + float turbulence = fbm(noiseCoord * 2.0); + + // Add time-based animation for dynamic movement + float wave = sin(uv.y * 5.0 - uTime * 2.0) * 0.5 + 0.5; + turbulence = mix(turbulence, wave, 0.3); + + // Combine shape with turbulence + float flare = flareShape * (0.5 + turbulence * 0.5); + + // Add very bright core at base + float coreShape = pow(1.0 - uvXAbs, 3.0) * smoothstep(0.3, -0.3, uv.y); + float core = coreShape * 3.0; + flare = max(flare * 1.5, core); + + // Color gradient from bright yellow-white at base to orange-red at tips + vec3 baseColor = vec3(1.0, 0.95, 0.7); // Bright yellow-white + vec3 tipColor = vec3(1.0, 0.3, 0.0); // Orange-red + vec3 color = mix(tipColor, baseColor, pow(smoothstep(1.0, -0.5, uv.y), 1.5)); + + // Boost intensity for visibility + color *= 1.3; + + // Apply flare intensity + float alpha = flare * uFlareIntensity; + + // Ensure minimum visibility at base using the cached core calculation + alpha = max(alpha, coreShape * uFlareIntensity * 0.8); + + // Clamp alpha for performance + alpha = clamp(alpha, 0.0, 1.0); + + gl_FragColor = vec4(color * alpha, alpha); +} diff --git a/src/shaders/sun/flare.vert b/src/shaders/sun/flare.vert new file mode 100644 index 0000000..36eed31 --- /dev/null +++ b/src/shaders/sun/flare.vert @@ -0,0 +1,28 @@ +/** +* @file flare.vert +* @brief Vertex shader for solar flares +* @author Mitch Campbell +* @copyright 2024 +*/ + +precision mediump float; + +attribute vec2 uv; +attribute vec3 position; +attribute vec3 normal; + +varying vec2 vUv; +varying vec3 vPosition; +varying vec3 vNormal; + +uniform mat4 modelMatrix; +uniform mat4 viewMatrix; +uniform mat4 projectionMatrix; + +void main() +{ + vUv = uv; + vNormal = normal; + vPosition = (modelMatrix * vec4(position, 1.0)).xyz; + gl_Position = projectionMatrix * viewMatrix * vec4(vPosition, 1.0); +} diff --git a/src/shaders/sun/glow.frag b/src/shaders/sun/glow.frag index 3c22069..7eba562 100644 --- a/src/shaders/sun/glow.frag +++ b/src/shaders/sun/glow.frag @@ -25,15 +25,33 @@ void main() // Calculate the view direction vec3 viewDirection = normalize(uCameraPosition - vPosition); - // Calculate the Fresnel effect - float fresnel = pow(1.0 - dot(vNormal, viewDirection), 3.0); + // Calculate the Fresnel effect with softer falloff for corona + float fresnelPower = 2.0; + float fresnel = pow(1.0 - abs(dot(vNormal, viewDirection)), fresnelPower); - // // Calculate the glow effect based on distance - float dist = distance(gl_FragCoord.xy, uCenter.xy); - float glow = smoothstep(uInnerRadius, uOuterRadius, dist); - - // Blend the Fresnel effect with the glow effect - vec3 glowColor = mix(uSunColor * fresnel, vec3(0.0), glow); + // Enhanced glow effect with multiple layers for realism + // Inner bright corona + float innerGlow = pow(1.0 - abs(dot(vNormal, viewDirection)), 4.0); + + // Outer soft corona + float outerGlow = pow(1.0 - abs(dot(vNormal, viewDirection)), 1.5); + + // Combine glow layers + float combinedGlow = innerGlow * 0.6 + outerGlow * 0.4; + + // Add Fresnel rim lighting + float rim = fresnel * 0.8; + + // Final glow with intensity control + float finalGlow = (combinedGlow + rim) * uGlowIntensity; + + // Color gradient from yellow-orange to orange-red + vec3 innerColor = vec3(1.0, 0.9, 0.3); // Bright yellow + vec3 outerColor = vec3(1.0, 0.3, 0.0); // Orange-red + vec3 finalColor = mix(outerColor, innerColor, fresnel); + + // Apply intensity and add subtle pulsing effect + finalColor *= finalGlow; - gl_FragColor = vec4(uGlowColor * glow * uGlowIntensity, glow); + gl_FragColor = vec4(finalColor, finalGlow * 0.7); } diff --git a/src/sun.js b/src/sun.js index 4206302..6fddb53 100644 --- a/src/sun.js +++ b/src/sun.js @@ -11,6 +11,8 @@ import * as THREE from 'three'; import vertexShader from './shaders/sun/sun.vert'; import fragmentShader from './shaders/sun/sun.frag'; import glowFragmentShader from './shaders/sun/glow.frag' +import flareVertexShader from './shaders/sun/flare.vert'; +import flareFragmentShader from './shaders/sun/flare.frag'; // Sun Material @@ -45,15 +47,66 @@ export const glowMaterial = new THREE.RawShaderMaterial({ uInnerRadius: { value: sun.geometry.parameters.radius * 1.1 }, uOuterRadius: { value: sun.geometry.parameters.radius * 1.2 }, uGlowColor: { value: new THREE.Color('orange') }, - uGlowIntensity: { value: 1.0 }, + uGlowIntensity: { value: 1.5 }, uCameraPosition: { value: new THREE.Vector3() }, }, blending: THREE.AdditiveBlending, transparent: true, + depthWrite: false, }); const glowMesh = new THREE.Mesh( - new THREE.SphereGeometry(sun.geometry.parameters.radius * 1.01, 32, 32), + new THREE.SphereGeometry(sun.geometry.parameters.radius * 1.4, 32, 32), glowMaterial ); sun.add(glowMesh); + +// Solar Flares +// Create solar flares - multiple small prominences around the sun +export const solarFlares = []; +const flareCount = 6; // Keep count low for performance +const sunRadius = sun.geometry.parameters.radius; + +for (let i = 0; i < flareCount; i++) { + // Create flare material (each flare gets its own material for independent animation) + const flareMaterial = new THREE.RawShaderMaterial({ + vertexShader: flareVertexShader, + fragmentShader: flareFragmentShader, + uniforms: { + uTime: { value: Math.random() * 10.0 }, // Random start time for variety + uFlareIntensity: { value: 1.0 + Math.random() * 0.5 }, + uFlareColor: { value: new THREE.Color(1.0, 0.5, 0.1) }, + }, + blending: THREE.AdditiveBlending, + transparent: true, + side: THREE.DoubleSide, + depthWrite: false, + }); + + // Create flare geometry - simple plane for performance + const flareWidth = sunRadius * 0.6; + const flareHeight = sunRadius * (2.0 + Math.random() * 1.5); // Larger, more visible flares + const flareGeometry = new THREE.PlaneGeometry(flareWidth, flareHeight); + + const flareMesh = new THREE.Mesh(flareGeometry, flareMaterial); + + // Position flares around the equator and mid-latitudes for better visibility + const angle = (i / flareCount) * Math.PI * 2; + const latitude = (Math.random() - 0.5) * Math.PI * 0.5; // -45° to +45° + + flareMesh.position.set( + sunRadius * Math.cos(latitude) * Math.cos(angle), + sunRadius * Math.sin(latitude), + sunRadius * Math.cos(latitude) * Math.sin(angle) + ); + + // Orient flare to point outward from sun surface + const outwardPoint = flareMesh.position.clone().multiplyScalar(2); + flareMesh.lookAt(outwardPoint); + flareMesh.rotateX(Math.PI / 2); + + // Store reference for animation + solarFlares.push(flareMesh); + + sun.add(flareMesh); +}