import * as THREE from "three"; import { OrbitControls } from "three/addons/controls/OrbitControls.js"; const config = { imageUrl: "globe.png", globeRadius: 1.95, pointCount: 90000, pointSize: 0.015, pointColor: 0xc1c1c1, brightnessThreshold: 128 }; const canvas = document.getElementById("globe"); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, // 투명 배경을 위해 추가 canvas: canvas }); renderer.setSize(window.innerWidth, window.innerHeight); const scene = new THREE.Scene(); scene.background = null; // 배경 투명하게 설정 const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 100); camera.position.z = 3.5; const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.autoRotate = true; controls.autoRotateSpeed = 0.6; const group = new THREE.Group(); group.rotation.y = THREE.MathUtils.degToRad(30); scene.add(group); const gradientGeometry = new THREE.SphereGeometry(config.globeRadius * 0.99, 64, 64); const gradientMaterial = new THREE.MeshBasicMaterial({ color: 0xdcdcdc, transparent: true, opacity: 0.12, depthWrite: false }); const gradientSphere = new THREE.Mesh(gradientGeometry, gradientMaterial); group.add(gradientSphere); const glowGeometry = new THREE.SphereGeometry(config.globeRadius * 1.02, 64, 64); const glowMaterial = new THREE.ShaderMaterial({ uniforms: { intensity: { value: 1.0 } }, vertexShader: ` varying vec3 vNormal; void main() { vNormal = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` varying vec3 vNormal; uniform float intensity; void main() { float glow = pow(1.0 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0); vec3 white = vec3(1.0); vec3 gray = vec3(0.69); vec3 color = mix(gray, white, 1.0 - glow); gl_FragColor = vec4(color, (1.0 - glow) * intensity); } `, side: THREE.BackSide, transparent: true, depthWrite: false }); const glowSphere = new THREE.Mesh(glowGeometry, glowMaterial); group.add(glowSphere); const textureLoader = new THREE.TextureLoader(); textureLoader.load( config.imageUrl, (texture) => { const image = texture.image; const hiddenCanvas = document.createElement("canvas"); const hiddenCtx = hiddenCanvas.getContext("2d"); hiddenCanvas.width = image.width; hiddenCanvas.height = image.height; hiddenCtx.drawImage(image, 0, 0); const imageData = hiddenCtx.getImageData(0, 0, image.width, image.height).data; const pointsGeometry = new THREE.BufferGeometry(); const positions = []; for (let i = 0; i < config.pointCount; i++) { const phi = Math.acos(-1 + (2 * i) / config.pointCount); const theta = Math.sqrt(config.pointCount * Math.PI) * phi; const x = config.globeRadius * Math.cos(theta) * Math.sin(phi); const y = config.globeRadius * Math.sin(theta) * Math.sin(phi); const z = config.globeRadius * Math.cos(phi); const u = Math.atan2(x, z) / (2 * Math.PI) + 0.5; const v = Math.asin(y / config.globeRadius) / Math.PI + 0.5; const imgX = Math.floor(u * image.width); const imgY = Math.floor((1 - v) * image.height); const pixelIndex = (imgY * image.width + imgX) * 4; const brightness = imageData[pixelIndex]; if (brightness > config.brightnessThreshold) { positions.push(x, y, z); } } pointsGeometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3)); const pointsMaterial = new THREE.PointsMaterial({ color: config.pointColor, size: config.pointSize, sizeAttenuation: true }); const globePoints = new THREE.Points(pointsGeometry, pointsMaterial); group.add(globePoints); }, undefined, (err) => { console.error("An error happened.", err); } ); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate(); window.addEventListener("resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });