A Pen by Daniel Muñoz on CodePen.
Created
January 11, 2026 08:10
-
-
Save SakuraRinDev/602b9eeeb6c7e46276ea04a09baa0369 to your computer and use it in GitHub Desktop.
Celestial Ascension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://cdn.jsdelivr.net/npm/three@0.161.0/build/three.module.js", | |
| "jsm/": "https://cdn.jsdelivr.net/npm/three@0.161.0/examples/jsm/" | |
| } | |
| } | |
| </script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as THREE from "three"; | |
| import { OrbitControls } from "jsm/controls/OrbitControls.js"; | |
| import { EffectComposer } from "jsm/postprocessing/EffectComposer.js"; | |
| import { RenderPass } from "jsm/postprocessing/RenderPass.js"; | |
| import { UnrealBloomPass } from "jsm/postprocessing/UnrealBloomPass.js"; | |
| import { OBJLoader } from "jsm/loaders/OBJLoader.js"; | |
| const scene = new THREE.Scene(); | |
| const camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| camera.position.x = 1.2; | |
| camera.position.z = -0.1; | |
| camera.position.y = -0.5; | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.toneMapping = THREE.ReinhardToneMapping; | |
| document.body.appendChild(renderer.domElement); | |
| const controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| const renderScene = new RenderPass(scene, camera); | |
| const bloomPass = new UnrealBloomPass( | |
| new THREE.Vector2(window.innerWidth, window.innerHeight), | |
| 0.2, | |
| 0.4, // radius | |
| 0.2 // threshold | |
| ); | |
| bloomPass.strength = 2.5; | |
| bloomPass.radius = 0.8; | |
| bloomPass.threshold = 0.2; | |
| const composer = new EffectComposer(renderer); | |
| composer.addPass(renderScene); | |
| composer.addPass(bloomPass); | |
| const R = 0.9; | |
| const r = 0.8; | |
| renderer.localClippingEnabled = true; | |
| const planeFront = new THREE.Plane(new THREE.Vector3(0, 0, 1), 1); | |
| const planeBack = new THREE.Plane(new THREE.Vector3(0, 0, -1), 1); | |
| const planeLeft = new THREE.Plane(new THREE.Vector3(1, 0, 0), 1); | |
| const planeRight = new THREE.Plane(new THREE.Vector3(-1, 0, 0), 1); | |
| const clippingPlanes = [planeFront, planeBack, planeLeft, planeRight]; | |
| const lineCount = 90; | |
| const segmentsPerLine = 2000; | |
| const spiralRevolutions = 0.4; | |
| const tornadoGroup = new THREE.Group(); | |
| scene.add(tornadoGroup); | |
| const linesData = []; | |
| function getTorusPoint(u, v, target, rOffset = 0) { | |
| const effectiveR = r + rOffset; | |
| const x = (R + effectiveR * Math.cos(v)) * Math.cos(u); | |
| const y = (R + effectiveR * Math.cos(v)) * Math.sin(u); | |
| const z = effectiveR * Math.sin(v); | |
| target.set(x, y, z); | |
| } | |
| const lineVertexShader = ` | |
| varying vec3 vPosition; | |
| varying float vDistanceFromCenter; | |
| void main() { | |
| vPosition = position; | |
| float distanceFromCenter = sqrt(position.x * position.x + position.z * position.z); | |
| vDistanceFromCenter = distanceFromCenter; | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `; | |
| const lineFragmentShader = ` | |
| uniform vec3 uColor; | |
| uniform float uFadeDistance; | |
| uniform float uFadeStart; | |
| varying vec3 vPosition; | |
| varying float vDistanceFromCenter; | |
| void main() { | |
| float maxDistance = .9; | |
| float fadeStart = uFadeStart; | |
| float fadeEnd = .9; | |
| float normalizedDistance = vDistanceFromCenter / maxDistance; | |
| float fade = 1.0; | |
| if (normalizedDistance > fadeStart) { | |
| fade = 1.0 - smoothstep(fadeStart, fadeEnd, normalizedDistance); | |
| } | |
| float zFade = 1.0; | |
| if (abs(vPosition.z) > 0.5) { | |
| zFade = 1.0 - smoothstep(0.5, 0.9, abs(vPosition.z)); | |
| } | |
| float xFade = 1.0; | |
| if (abs(vPosition.x) > 0.5) { | |
| xFade = 1.0 - smoothstep(0.5, 0.9, abs(vPosition.x)); | |
| } | |
| float finalAlpha = fade * zFade * xFade; | |
| gl_FragColor = vec4(uColor, finalAlpha); | |
| } | |
| `; | |
| for (let i = 0; i < lineCount; i++) { | |
| const geometry = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(segmentsPerLine * 3); | |
| geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3)); | |
| const phaseOffset = Math.random() * Math.PI * 2; | |
| const speed = 0.001 + Math.random() * 0.001; | |
| const length = Math.PI * 3 + Math.random() * Math.PI; | |
| const material = new THREE.ShaderMaterial({ | |
| vertexShader: lineVertexShader, | |
| fragmentShader: lineFragmentShader, | |
| uniforms: { | |
| uColor: { value: new THREE.Vector3(0.4, 0.8, 1.0) }, | |
| uFadeDistance: { value: 0.5 }, | |
| uFadeStart: { value: 0.6 }, | |
| }, | |
| transparent: true, | |
| blending: THREE.AdditiveBlending, | |
| clippingPlanes: clippingPlanes, | |
| depthWrite: false, | |
| }); | |
| const line = new THREE.Line(geometry, material); | |
| tornadoGroup.add(line); | |
| linesData.push({ | |
| line: line, | |
| positions: positions, | |
| phaseOffset: phaseOffset, | |
| speed: speed, | |
| length: length, | |
| uOffset: Math.random() * Math.PI * 2, | |
| rOffset: (Math.random() - 0.2) * 0.1, | |
| }); | |
| } | |
| const particleCount = 10000; | |
| const particlesGeometry = new THREE.BufferGeometry(); | |
| const particlePositions = new Float32Array(particleCount * 3); | |
| particlesGeometry.setAttribute( | |
| "position", | |
| new THREE.BufferAttribute(particlePositions, 3) | |
| ); | |
| const particlesMaterial = new THREE.PointsMaterial({ | |
| color: 0x66ccff, | |
| size: 0.002, | |
| transparent: true, | |
| opacity: 0.2, | |
| blending: THREE.AdditiveBlending, | |
| clippingPlanes: clippingPlanes, | |
| }); | |
| const particles = new THREE.Points(particlesGeometry, particlesMaterial); | |
| tornadoGroup.add(particles); | |
| const particlesData = []; | |
| for (let i = 0; i < particleCount; i++) { | |
| particlesData.push({ | |
| uOffset: Math.random() * Math.PI * 2, | |
| phaseOffset: Math.random() * Math.PI * 2, | |
| speed: 0.0001 - Math.random() * 0.001, | |
| radiusOffset: (Math.random() - 0.1) * 0.2, // More volume | |
| }); | |
| } | |
| tornadoGroup.rotation.x = Math.PI / 2; | |
| tornadoGroup.rotation.y = THREE.MathUtils.degToRad(18); | |
| scene.background = new THREE.Color(0x000000); | |
| const ambientLight = new THREE.AmbientLight(0x404040); | |
| scene.add(ambientLight); | |
| const dirLight = new THREE.DirectionalLight(0xffffff, 2); | |
| dirLight.position.set(0, 5, 5); | |
| scene.add(dirLight); | |
| let angelModel = null; | |
| const loader = new OBJLoader(); | |
| loader.load( | |
| "https://cdn.jsdelivr.net/gh/danielyl123/person/person.obj", | |
| function (object) { | |
| const box = new THREE.Box3().setFromObject(object); | |
| const center = box.getCenter(new THREE.Vector3()); | |
| object.position.sub(center); | |
| const size = box.getSize(new THREE.Vector3()); | |
| const maxDim = Math.max(size.x, size.y, size.z); | |
| const scale = 0.13 / maxDim; | |
| object.scale.set(scale, scale, scale); | |
| object.position.set(0.9, -0.6, 0); | |
| object.rotation.y = Math.PI / -2; | |
| object.traverse(function (child) { | |
| if (child.isMesh) { | |
| child.material = new THREE.MeshStandardMaterial({ | |
| color: 0xaaaaaa, // Silver/White | |
| roughness: 0.3, | |
| metalness: 0.8, | |
| side: THREE.DoubleSide, | |
| }); | |
| } | |
| }); | |
| scene.add(object); | |
| angelModel = object; | |
| console.log("Angel loaded at center"); | |
| }, | |
| function (xhr) { | |
| console.log((xhr.loaded / xhr.total) * 100 + "% loaded"); | |
| }, | |
| function (error) { | |
| console.error("Error loading angel:", error); | |
| } | |
| ); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const time = Date.now() * 0.001; | |
| linesData.forEach((data) => { | |
| data.uOffset += data.speed; | |
| const positions = data.positions; | |
| const tempVec = new THREE.Vector3(); | |
| for (let j = 0; j < segmentsPerLine; j++) { | |
| const t = j / (segmentsPerLine - 1); | |
| const currentU = data.uOffset - t * data.length; | |
| const currentV = | |
| currentU * spiralRevolutions + data.phaseOffset + time * 0.4; | |
| getTorusPoint(currentU, currentV, tempVec, data.rOffset); | |
| positions[j * 3] = tempVec.x; | |
| positions[j * 3 + 1] = tempVec.y; | |
| positions[j * 3 + 2] = tempVec.z; | |
| } | |
| data.line.geometry.attributes.position.needsUpdate = true; | |
| }); | |
| const pPositions = particlesGeometry.attributes.position.array; | |
| particlesData.forEach((data, i) => { | |
| data.uOffset += data.speed; | |
| const currentU = data.uOffset; | |
| const currentV = | |
| currentU * spiralRevolutions + data.phaseOffset + time * 0.2; | |
| const effectiveR = r + data.radiusOffset; | |
| const x = (R + effectiveR * Math.cos(currentV)) * Math.cos(currentU); | |
| const y = (R + effectiveR * Math.cos(currentV)) * Math.sin(currentU); | |
| const z = effectiveR * Math.sin(currentV); | |
| pPositions[i * 3] = x; | |
| pPositions[i * 3 + 1] = y; | |
| pPositions[i * 3 + 2] = z; | |
| }); | |
| particlesGeometry.attributes.position.needsUpdate = true; | |
| if (angelModel) { | |
| angelModel.position.y = -0.55 + Math.sin(time * 2) * 0.01; | |
| } | |
| controls.update(); | |
| renderer.render(scene, camera); | |
| composer.render(); | |
| } | |
| animate(); | |
| window.addEventListener("resize", () => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| background-color: black; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment