Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);

Expand Down
109 changes: 109 additions & 0 deletions src/shaders/sun/flare.frag
Original file line number Diff line number Diff line change
@@ -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);
}
28 changes: 28 additions & 0 deletions src/shaders/sun/flare.vert
Original file line number Diff line number Diff line change
@@ -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);
}
36 changes: 27 additions & 9 deletions src/shaders/sun/glow.frag
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
57 changes: 55 additions & 2 deletions src/sun.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}