├── .gitignore ├── src ├── index.ts ├── GalaxyGeometry.ts └── GalaxyShader.ts ├── tsconfig.json ├── tsconfig.commonjs.json ├── .github └── workflows │ └── npm_publish.yaml ├── tsconfig.base.json ├── LICENSE ├── package.json ├── README.md └── examples └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { GalaxyShader } from "./GalaxyShader"; 2 | export { GalaxyGeometry } from "./GalaxyGeometry"; 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "target": "esnext", 6 | "outDir": "dist/esm", 7 | }, 8 | } -------------------------------------------------------------------------------- /tsconfig.commonjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es2015", 6 | "outDir": "dist/common", 7 | } 8 | } -------------------------------------------------------------------------------- /.github/workflows/npm_publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish NPM Package 2 | on: 3 | push: 4 | branches: 5 | - main 6 | release: 7 | types: [created] 8 | jobs: 9 | publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: '18' 17 | registry-url: 'https://registry.npmjs.org/' 18 | - run: npm install 19 | - run: npm run build 20 | - run: npm publish 21 | env: 22 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} -------------------------------------------------------------------------------- /src/GalaxyGeometry.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | export class GalaxyGeometry extends THREE.BufferGeometry { 3 | constructor(totalPoints: number) { 4 | super(); 5 | this.setAttribute( 6 | "position", 7 | new THREE.BufferAttribute(new Float32Array(totalPoints * 3), 3) 8 | ); 9 | this.setAttribute( 10 | "a_index", 11 | new THREE.BufferAttribute(new Float32Array(totalPoints), 1) 12 | ); 13 | const positions = this.getAttribute("position"); 14 | const indices = this.getAttribute("a_index"); 15 | for (let i = 0; i < totalPoints; i++) { 16 | positions.setXYZ(i, 0, 0, 0); 17 | indices.setX(i, i); 18 | } 19 | this.computeBoundingSphere(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // "module": "ESNext", 4 | // "target": "esnext", 5 | // "outDir": "dist/esm", 6 | "types": [], 7 | "sourceMap": true, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "rootDir": "src/", 11 | "noUncheckedIndexedAccess": true, 12 | "exactOptionalPropertyTypes": true, 13 | "strict": true, 14 | "jsx": "react-jsx", 15 | "isolatedModules": true, 16 | "noUncheckedSideEffectImports": true, 17 | "moduleDetection": "force", 18 | "skipLibCheck": true, 19 | }, 20 | "watchOptions": { 21 | "watchFile": "useFsEvents", 22 | "watchDirectory": "useFsEvents", 23 | "fallbackPolling": "dynamicPriority", 24 | "synchronousWatchDirectory": true, 25 | "excludeDirectories": [ 26 | "**/node_modules", 27 | "dist" 28 | ] 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2025 AMIT DIGGA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threejs-galaxy-shader", 3 | "version": "1.0.2", 4 | "description": "A performant Three.js galaxy shader with customizable colors, spiral patterns, and black hole effects", 5 | "main": "dist/common/index.js", 6 | "module": "dist/esm/index.js", 7 | "types": "dist/esm/index.d.ts", 8 | "exports": { 9 | ".": { 10 | "import": "./dist/esm/index.js", 11 | "require": "./dist/common/index.js" 12 | } 13 | }, 14 | "scripts": { 15 | "build": "tsc --build && tsc --build -f tsconfig.commonjs.json", 16 | "build-esm:watch": "tsc --build --watch", 17 | "build-commonjs:watch": "tsc --build -f tsconfig.commonjs.json --watch", 18 | "prepublishOnly": "npm run build" 19 | }, 20 | "files": [ 21 | "dist/", 22 | "README.md", 23 | "LICENSE" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/AmitDigga/threejs-galaxy-shader" 28 | }, 29 | "keywords": [ 30 | "threejs", 31 | "galaxy", 32 | "shader", 33 | "webgl", 34 | "particles", 35 | "space", 36 | "astronomy", 37 | "visualization" 38 | ], 39 | "peerDependencies": { 40 | "three": ">=0.136.0 <1.0.0" 41 | }, 42 | "author": "Amit Digga", 43 | "license": "MIT", 44 | "devDependencies": { 45 | "@types/three": "^0.179.0", 46 | "typescript": "^5.9.2" 47 | } 48 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Galaxy Shader 2 | 3 | A Three.js custom shader for rendering spiral galaxy point clouds with black hole effects. 4 | 5 | ## Features 6 | 7 | - Spiral galaxy geometry 8 | - Black hole distortion 9 | - Customizable Parameters 10 | - Colors 11 | - Spiral Args, Twists 12 | - Start Count 13 | - Some more, not documented yet 14 | 15 | ## Getting Started 16 | 17 | ### React | Node 18 | 19 | #### Step 1. Install library with 20 | 21 | ```bash 22 | npm install --save threejs-galaxy-shader 23 | ``` 24 | 25 | Note: Make sure you have `threejs` installed 26 | 27 | ```bash 28 | npm install --save three 29 | ``` 30 | 31 | #### Step 2. Code Sample 32 | 33 | ```tsx 34 | import { useEffect, useRef } from "react"; 35 | import * as THREE from "three"; 36 | import { GalaxyGeometry, GalaxyShader } from "threejs-galaxy-shader"; 37 | let isFirst = true; 38 | const ThreeScene = () => { 39 | const mountRef = useRef(null); 40 | const sceneRef = useRef(null); 41 | const rendererRef = useRef(null); 42 | const frameRef = useRef(null); 43 | 44 | useEffect(() => { 45 | if (isFirst) { 46 | isFirst = false; 47 | return; 48 | } 49 | if (!mountRef.current) return; 50 | 51 | // Scene setup 52 | const scene = new THREE.Scene(); 53 | scene.background = new THREE.Color(0x000011); 54 | 55 | // Camera setup 56 | const camera = new THREE.PerspectiveCamera( 57 | 75, 58 | window.innerWidth / window.innerHeight, 59 | 0.1, 60 | 1000 61 | ); 62 | camera.position.z = 3; // Closer view for galaxy 63 | 64 | // Renderer setup 65 | const renderer = new THREE.WebGLRenderer({ antialias: true }); 66 | renderer.setSize(window.innerWidth, window.innerHeight); 67 | renderer.setPixelRatio(window.devicePixelRatio); 68 | mountRef.current.appendChild(renderer.domElement); 69 | 70 | // Create a simple galaxy using the GalaxyShaderLibrary 71 | const galaxyConfig = { 72 | spiralCount: 3, 73 | turnsPerSpiral: 1.0, 74 | totalStars: 15000, 75 | pointSize: 2.0, 76 | blackHoleRadius: 0.1, 77 | colorMode: 2, // Single color mode 78 | color: new THREE.Color(0x00ff88), // Fixed green color 79 | colorIntensity: 1.0, 80 | }; 81 | 82 | // Create galaxy geometry and shader material 83 | const geometry = new GalaxyGeometry(galaxyConfig.totalStars); 84 | const material = new GalaxyShader({ 85 | resolution: new THREE.Vector2(window.innerWidth, window.innerHeight), 86 | color: galaxyConfig.color, 87 | pointSize: galaxyConfig.pointSize, 88 | totalStars: galaxyConfig.totalStars, 89 | time: 0, 90 | blackHoleRadius: galaxyConfig.blackHoleRadius, 91 | blackHolePosition: new THREE.Vector3(0, 0, 0), 92 | spiralCount: galaxyConfig.spiralCount, 93 | turnsPerSpiral: galaxyConfig.turnsPerSpiral, 94 | colorMode: galaxyConfig.colorMode, 95 | colorIntensity: galaxyConfig.colorIntensity, 96 | fadeNear: 5.0, 97 | fadeFar: 100.0, 98 | }); 99 | 100 | const points = new THREE.Points(geometry, material); 101 | 102 | // Add a simple black hole visualization 103 | const blackHoleGeometry = new THREE.SphereGeometry( 104 | galaxyConfig.blackHoleRadius, 105 | 16, 106 | 16 107 | ); 108 | const blackHoleMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); 109 | const blackHole = new THREE.Mesh(blackHoleGeometry, blackHoleMaterial); 110 | 111 | scene.add(points); 112 | scene.add(blackHole); 113 | 114 | // Store references 115 | sceneRef.current = scene; 116 | rendererRef.current = renderer; 117 | 118 | // Animation loop 119 | const animate = () => { 120 | frameRef.current = requestAnimationFrame(animate); 121 | 122 | // Update time uniform for galaxy animation 123 | const time = performance.now() / 10000 / 5; 124 | material.uniforms.u_time.value = time; 125 | renderer.render(scene, camera); 126 | // console.log(material.uniforms.u_time.value); 127 | }; 128 | 129 | animate(); 130 | 131 | // Handle window resize 132 | const handleResize = () => { 133 | camera.aspect = window.innerWidth / window.innerHeight; 134 | camera.updateProjectionMatrix(); 135 | renderer.setSize(window.innerWidth, window.innerHeight); 136 | 137 | // Update resolution uniform 138 | material.uniforms.u_resolution.value.set( 139 | window.innerWidth, 140 | window.innerHeight 141 | ); 142 | }; 143 | 144 | window.addEventListener("resize", handleResize); 145 | 146 | // Cleanup 147 | return () => { 148 | window.removeEventListener("resize", handleResize); 149 | if (frameRef.current) { 150 | cancelAnimationFrame(frameRef.current); 151 | } 152 | if (mountRef.current && renderer.domElement) { 153 | mountRef.current.removeChild(renderer.domElement); 154 | } 155 | renderer.dispose(); 156 | geometry.dispose(); 157 | material.dispose(); 158 | }; 159 | }, []); 160 | 161 | return
; 162 | }; 163 | 164 | export default ThreeScene; 165 | ``` 166 | 167 | ### CommonJs 168 | 169 | Example is under `exmaples/index.html`. 170 | 171 | Please modify the import paths based on your setup. 172 | 173 | ## License 174 | 175 | MIT License 176 | 177 | Copyright 2025 AMIT DIGGA 178 | 179 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 180 | 181 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 182 | 183 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 184 | -------------------------------------------------------------------------------- /src/GalaxyShader.ts: -------------------------------------------------------------------------------- 1 | import * as THREE from "three"; 2 | 3 | const VERTEX_SHADER = ` 4 | uniform vec2 u_resolution; 5 | uniform float u_pointSize; 6 | uniform float u_totalPoints; 7 | uniform float u_time; 8 | attribute float a_index; 9 | varying float v_index; 10 | varying float vDistanceFromCamera; 11 | varying float radius; 12 | 13 | uniform float u_blackHoleRadius; // Radius of the black hole 14 | uniform vec3 u_blackHolePosition; // Position of the black hole 15 | 16 | float randM1To1(vec2 co){ 17 | return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453) * 2.0 - 1.0; // Center noise around 0 18 | } 19 | 20 | vec3 getNoiseM1To1(float index) { 21 | return vec3(randM1To1(vec2(index)), randM1To1(vec2(index + 1.0)), randM1To1(vec2(index + 2.0))); 22 | } 23 | float getRand01(float index) { 24 | return randM1To1(vec2(index,index+5.0)) / 2.0 + 0.5; // Center noise around 0.5 25 | } 26 | 27 | uniform float u_spiralCount; 28 | uniform float u_turnsPerSpiral; 29 | uniform float u_fadeNear; 30 | uniform float u_fadeFar; 31 | 32 | vec3 getSpiralCoordinate(float originalIndex) { 33 | v_index = originalIndex; 34 | float totalSpirals = u_spiralCount; 35 | float totalTurns = u_turnsPerSpiral; 36 | float pointsPerSpiral = floor(u_totalPoints / totalSpirals); 37 | float spiralIndex = floor(originalIndex / pointsPerSpiral); 38 | float angleOffset = float(spiralIndex) / totalSpirals * 3.14159 * 2.0; 39 | float index = mod(originalIndex, pointsPerSpiral); 40 | 41 | // Add time 42 | float timeOffset = mod(u_time , 1.0); 43 | index = mod(index - timeOffset * pointsPerSpiral ,pointsPerSpiral); 44 | 45 | // Range from 0 to 1 for the spiral 46 | float radiusInSpiral = index / pointsPerSpiral; 47 | radius = radiusInSpiral; 48 | float angleInSpiral = index / pointsPerSpiral * 3.14159 * 2.0 * totalTurns + angleOffset; 49 | 50 | // Convert polar to Cartesian coordinates 51 | 52 | vec3 noise = getNoiseM1To1(originalIndex) * .4; 53 | radius *= (1.0 + noise.x / 2.0); 54 | angleInSpiral += noise.y * (10.0) * 3.14159 / 180.0; // Add some noise to the angle 55 | float planeAngle = noise.z * 5.0 * 3.14159 / 180.0; // Angle of the spiral plane 56 | 57 | 58 | float x = cos(angleInSpiral) * radius; 59 | float y = sin(angleInSpiral) * radius; 60 | float z = sin(planeAngle) * radius; // Height based on radius 61 | // float randomZ = (getRand01(originalIndex) - 0.5) * 0.02; 62 | vec3 fullRandom = getNoiseM1To1(originalIndex + 22.2) * .02; 63 | return vec3(x + fullRandom.x, y + fullRandom.y, z + fullRandom.z); 64 | } 65 | 66 | vec3 getCoordinateFromBlackHole(vec3 position){ 67 | vec3 vecFromCenter = position - u_blackHolePosition; 68 | float distance = length(vecFromCenter); 69 | if( distance > u_blackHoleRadius || distance < 0.001) { 70 | return position; // Outside black hole radius, return original position 71 | } 72 | // scale position towards the black hole 73 | float scale = u_blackHoleRadius / distance; 74 | return u_blackHolePosition + vecFromCenter * scale; 75 | } 76 | 77 | 78 | 79 | void main() { 80 | vec3 pos = getSpiralCoordinate(a_index); 81 | pos = getCoordinateFromBlackHole(pos); 82 | vec4 viewPosition = modelViewMatrix * vec4(pos, 1.0); 83 | gl_Position = projectionMatrix * viewPosition; 84 | vDistanceFromCamera = -viewPosition.z; 85 | 86 | // Scale point size with screen height for consistent density 87 | float pointScale= 4.0 * pow(getRand01(a_index + 7.0)+ 0.1, 3.0) * pow(getRand01(a_index + 9.0)+ 0.1, 3.0); 88 | gl_PointSize = u_pointSize * pointScale * (u_resolution.y /1200.0) * (1.0 / vDistanceFromCamera); 89 | } 90 | `; 91 | 92 | const FRAGMENT_SHADER = ` 93 | uniform vec3 u_color; 94 | uniform float u_fadeNear; 95 | uniform float u_fadeFar; 96 | uniform int u_colorMode; // 0: fixed array, 1: gradient by radius, 2: single color 97 | uniform vec3 u_colorPalette[8]; // Up to 8 custom colors 98 | uniform int u_paletteSize; // Number of colors in palette (1-8) 99 | uniform float u_colorIntensity; // Overall color intensity multiplier 100 | varying float v_index; 101 | varying float vDistanceFromCamera; 102 | varying float radius; 103 | uniform float u_blackHoleRadius; 104 | uniform vec3 u_blackHolePosition; 105 | 106 | // Default color palettes for different galaxy types 107 | vec3 getDefaultColor(int paletteType, int colorIndex) { 108 | if (paletteType == 0) { // Classic mixed stars 109 | vec3 colors[4] = vec3[4]( 110 | vec3(0.96, 0.87, 0.70), // Light golden 111 | vec3(0.68, 0.85, 0.90), // Light blue 112 | vec3(0.95, 0.75, 0.95), // Soft pink/magenta 113 | vec3(0.70, 0.95, 0.85) // Pale cyan/mint 114 | ); 115 | return colors[colorIndex % 4]; 116 | } else if (paletteType == 1) { // Hot blue-white stars 117 | vec3 colors[4] = vec3[4]( 118 | vec3(0.7, 0.8, 1.0), // Blue 119 | vec3(0.8, 0.9, 1.0), // Light blue 120 | vec3(0.9, 0.95, 1.0), // White-blue 121 | vec3(1.0, 1.0, 1.0) // Pure white 122 | ); 123 | return colors[colorIndex % 4]; 124 | } else if (paletteType == 2) { // Warm red-orange stars 125 | vec3 colors[4] = vec3[4]( 126 | vec3(1.0, 0.6, 0.4), // Orange-red 127 | vec3(1.0, 0.7, 0.5), // Orange 128 | vec3(1.0, 0.8, 0.6), // Light orange 129 | vec3(1.0, 0.9, 0.7) // Pale yellow 130 | ); 131 | return colors[colorIndex % 4]; 132 | } 133 | // Default fallback 134 | return vec3(1.0, 1.0, 1.0); 135 | } 136 | 137 | float randM1To1(vec2 co){ 138 | return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); 139 | } 140 | 141 | vec3 getColorByMode(float index, float radius) { 142 | if (u_colorMode == 0) { 143 | // Fixed array cycling - using custom palette if provided 144 | int colorIndex = int(floor(index)) % max(1, u_paletteSize); 145 | if (u_paletteSize > 0) { 146 | return u_colorPalette[colorIndex]; 147 | } else { 148 | return getDefaultColor(0, colorIndex); 149 | } 150 | } else if (u_colorMode == 1) { 151 | // Gradient by radius (center to edge) 152 | if (u_paletteSize >= 2) { 153 | float t = clamp(radius, 0.0, 1.0); 154 | int baseIndex = int(floor(t * float(u_paletteSize - 1))); 155 | int nextIndex = min(baseIndex + 1, u_paletteSize - 1); 156 | float blend = fract(t * float(u_paletteSize - 1)); 157 | return mix(u_colorPalette[baseIndex], u_colorPalette[nextIndex], blend); 158 | } else { 159 | // Default blue to red gradient 160 | float t = clamp(radius, 0.0, 1.0); 161 | return mix(vec3(0.2, 0.4, 1.0), vec3(1.0, 0.4, 0.2), t); 162 | } 163 | } else if (u_colorMode == 2) { 164 | // Single color 165 | return u_color.rgb; 166 | } 167 | 168 | // Fallback 169 | return vec3(1.0, 1.0, 1.0); 170 | } 171 | 172 | void main() { 173 | // Round points with soft edge 174 | vec2 p = gl_PointCoord * 2.0 - 1.0; 175 | float d = dot(p, p); 176 | if (d > 1.0) { discard; } 177 | 178 | float cameraFade = 1.0 - smoothstep(u_fadeNear, u_fadeFar, vDistanceFromCamera); 179 | float galaxyFade = 1.0; 180 | float fade = cameraFade * galaxyFade; 181 | 182 | vec3 finalColor = getColorByMode(v_index, radius) * u_colorIntensity; 183 | gl_FragColor = vec4(finalColor, fade); 184 | } 185 | `; 186 | 187 | type GalaxyShaderParams = { 188 | resolution?: THREE.Vector2; 189 | color?: THREE.Color; 190 | pointSize?: number; 191 | totalStars?: number; 192 | time?: number; 193 | blackHoleRadius?: number; 194 | blackHolePosition?: THREE.Vector3; 195 | spiralCount?: number; 196 | turnsPerSpiral?: number; 197 | fadeNear?: number; 198 | fadeFar?: number; 199 | // New color parameters 200 | colorMode?: number; // 0: fixed array, 1: gradient by radius, 2: single color 201 | colorPalette?: THREE.Color[]; // Array of colors for custom palettes 202 | colorIntensity?: number; // Overall color intensity multiplier 203 | }; 204 | 205 | export class GalaxyShader extends THREE.ShaderMaterial { 206 | constructor(params: GalaxyShaderParams = {}) { 207 | // Prepare color palette array - pad with default colors if needed 208 | const colorPalette = params.colorPalette || []; 209 | const paletteSize = Math.min(colorPalette.length, 8); // Max 8 colors 210 | const paddedPalette = new Array(8); 211 | 212 | // Fill with provided colors or defaults 213 | for (let i = 0; i < 8; i++) { 214 | const color = colorPalette[i]; 215 | if (i < colorPalette.length && color) { 216 | paddedPalette[i] = new THREE.Vector3(color.r, color.g, color.b); 217 | } else { 218 | // Default fallback colors 219 | paddedPalette[i] = new THREE.Vector3(1.0, 1.0, 1.0); 220 | } 221 | } 222 | 223 | super({ 224 | uniforms: { 225 | u_resolution: { 226 | value: params.resolution ?? new THREE.Vector2(1200, 1200), 227 | }, 228 | u_color: { value: params.color ?? new THREE.Color(0xffffff) }, 229 | u_pointSize: { value: params.pointSize ?? 2.0 }, 230 | u_totalPoints: { value: params.totalStars ?? 10000 }, 231 | u_time: { value: params.time ?? 0 }, 232 | u_blackHoleRadius: { value: params.blackHoleRadius ?? 0.2 }, 233 | u_blackHolePosition: { 234 | value: params.blackHolePosition ?? new THREE.Vector3(0, 0, 0), 235 | }, 236 | u_spiralCount: { value: params.spiralCount ?? 3 }, 237 | u_turnsPerSpiral: { value: params.turnsPerSpiral ?? 1 }, 238 | u_fadeNear: { value: params.fadeNear ?? 1.0 }, 239 | u_fadeFar: { value: params.fadeFar ?? 5.0 }, 240 | // New color uniforms 241 | u_colorMode: { value: params.colorMode ?? 0 }, 242 | u_colorPalette: { value: paddedPalette }, 243 | u_paletteSize: { value: paletteSize }, 244 | u_colorIntensity: { value: params.colorIntensity ?? 1.0 }, 245 | }, 246 | vertexShader: VERTEX_SHADER, 247 | fragmentShader: FRAGMENT_SHADER, 248 | transparent: true, 249 | depthWrite: false, 250 | blending: THREE.AdditiveBlending, 251 | }); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Galaxy Shader Examples - 3x3 Grid 8 | 55 | 56 | 57 | 58 |
59 | 60 |
61 |

Galaxy Grid View

62 |

9 different galaxies in a 3×3 arrangement

63 |

Top-down view showing spiral structures

64 |

Mouse to orbit • Scroll to zoom

65 |

Hover to highlight • Double-click to focus

66 |

Double-click empty space to reset view

67 |
68 |
69 | 70 | 80 | 81 | 82 | 83 | 668 | 669 | 670 | --------------------------------------------------------------------------------