Skip to content

Instantly share code, notes, and snippets.

@SakuraRinDev
Created January 11, 2026 08:10
Show Gist options
  • Select an option

  • Save SakuraRinDev/602b9eeeb6c7e46276ea04a09baa0369 to your computer and use it in GitHub Desktop.

Select an option

Save SakuraRinDev/602b9eeeb6c7e46276ea04a09baa0369 to your computer and use it in GitHub Desktop.
Celestial Ascension
<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>
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);
});
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