import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: false });
renderer.setPixelRatio(Math.min(devicePixelRatio, 1.5));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const vertexShader = ['void main() { gl_Position = vec4(position, 1.0); }'].join('\n');
const fragmentShader = [
'precision highp float;',
'uniform vec2 uResolution;',
'uniform float uTime;',
'float sphere(vec3 p, vec3 c, float r) { return length(p - c) - r; }',
'float smin(float a, float b, float k) {',
' float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);',
' return mix(b, a, h) - k * h * (1.0 - h);',
'}',
'float map(vec3 p) {',
' float t = uTime;',
' vec3 c1 = vec3(sin(t * 0.8) * 1.1, cos(t * 0.6) * 0.6, 0.0);',
' vec3 c2 = vec3(cos(t * 0.7) * 1.0, sin(t * 0.9) * 0.7, 0.0);',
' vec3 c3 = vec3(sin(t * 0.5 + 2.0) * 0.9, cos(t * 0.4 + 1.0) * 0.5, 0.0);',
' float d1 = sphere(p, c1, 0.62);',
' float d2 = sphere(p, c2, 0.5);',
' float d3 = sphere(p, c3, 0.55);',
' return smin(smin(d1, d2, 0.4), d3, 0.4);',
'}',
'vec3 normalAt(vec3 p) {',
' vec2 e = vec2(0.001, 0.0);',
' return normalize(vec3(',
' map(p + e.xyy) - map(p - e.xyy),',
' map(p + e.yxy) - map(p - e.yxy),',
' map(p + e.yyx) - map(p - e.yyx)',
' ));',
'}',
'void main() {',
' vec2 uv = (gl_FragCoord.xy - 0.5 * uResolution) / uResolution.y;',
' vec3 ro = vec3(0.0, 0.0, 3.2);',
' vec3 rd = normalize(vec3(uv, -1.4));',
' float total = 0.0;',
' vec3 col = vec3(0.03, 0.02, 0.07);',
' for (int i = 0; i < 48; i++) {',
' vec3 p = ro + rd * total;',
' float d = map(p);',
' if (d < 0.001) {',
' vec3 n = normalAt(p);',
' vec3 lightDir = normalize(vec3(0.6, 0.7, 0.5));',
' float diff = max(dot(n, lightDir), 0.0);',
' float fresnel = pow(1.0 - max(dot(n, -rd), 0.0), 2.5);',
' vec3 base = mix(vec3(0.25, 0.35, 0.95), vec3(0.9, 0.35, 0.65), uv.x + 0.5);',
' col = base * (0.25 + diff * 0.85) + fresnel * vec3(0.6, 0.8, 1.0);',
' break;',
' }',
' if (total > 7.0) break;',
' total += d;',
' }',
' gl_FragColor = vec4(col, 1.0);',
'}',
].join('\n');
const mat = new THREE.ShaderMaterial({
uniforms: {
uResolution: { value: new THREE.Vector2(innerWidth, innerHeight) },
uTime: { value: 0 },
},
vertexShader,
fragmentShader,
});
const quad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), mat);
quad.frustumCulled = false;
scene.add(quad);
function resize() {
renderer.setSize(innerWidth, innerHeight);
mat.uniforms.uResolution.value.set(innerWidth, innerHeight);
}
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
mat.uniforms.uTime.value = t * 0.001;
renderer.render(scene, camera);
});