<div id="container" class="container"></div>
.container {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
}
import * as THREE from "https://cdn.skypack.dev/three@0.133.1";
import { OrbitControls } from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/controls/OrbitControls";
import { EffectComposer } from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/postprocessing/RenderPass";
import { ShaderPass } from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/postprocessing/ShaderPass";
import { UnrealBloomPass } from "https://cdn.skypack.dev/three@0.133.1/examples/jsm/postprocessing/UnrealBloomPass";

// 場景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // 背景色必須改為黑色

// 相機
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(100, 100, 100); // 相機位置
camera.lookAt(scene.position); // 相機焦點

// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('container').appendChild(renderer.domElement);

// 物體
// 球形體
const sphereGeometry = new THREE.IcosahedronGeometry(20, 1);
const sphereMaterial = new THREE.MeshPhongMaterial({
  color: 0xffffff,
  emissive: 0x000000,
  specular: 0xffffff,
  shininess: 100,
  flatShading: true
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
// 行星環(內)
const color = new THREE.Color().setRGB(Math.random(), Math.random(), Math.random());
const RingInsideGeometry = new THREE.RingGeometry(35, 28, 30, 30);
const RingMaterial = new THREE.MeshBasicMaterial({ color: color });
const ringInside = new THREE.Mesh(RingInsideGeometry, RingMaterial);
ringInside.layers.enable(1);
// 行星環(外)
const RingOutsideGeometry = new THREE.RingGeometry(42, 38, 30, 30);
const ringOutside = new THREE.Mesh(RingOutsideGeometry, RingMaterial);
ringOutside.layers.enable(1);
// 組合行星環
const ringGroup = new THREE.Object3D();
ringGroup.rotation.x = THREE.MathUtils.degToRad(90);
ringGroup.add(ringInside, ringOutside);
// 組合行星體
const planet = new THREE.Group();
planet.add(sphere, ringGroup);
planet.rotation.x = THREE.MathUtils.degToRad(-30);
planet.rotation.y = THREE.MathUtils.degToRad(-10);
scene.add(planet);
// 群星背景
const geometry = new THREE.BufferGeometry();
const particleAmount = 2000; // 群星數
const vertices = [...Array(particleAmount * 3)].map(() => 2000 * Math.random() - 1000);
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
const particleTexture = new THREE.TextureLoader().load('https://i.imgur.com/9TwBBHH.png');
const material = new THREE.PointsMaterial({ size: 5, sizeAttenuation: true, map: particleTexture, transparent: true, alphaTest: 0.5, color: 0xf3f3af });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
// 建立光源
// 環境光
const ambientLight = new THREE.AmbientLight(0x222222);
// 點光源
const pointLight = new THREE.PointLight(0x777777, 1, 0);
pointLight.position.set(100, 100, 0);
// 聚光燈
const spotLight = new THREE.SpotLight(0xffffff, 3, 150, Math.PI / 15, 1, 1);
      spotLight.position.set(50, 100, -80);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 10;
spotLight.shadow.camera.far = 200;
spotLight.shadow.focus = 1;
scene.add(ambientLight, pointLight, spotLight);

// 建立 UnrealBloomPass 物件
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight),
  3, // 強度
  0.5, // 射散
  0 // 縮放
);

// 建立 bloom 合成器
const bloomComposer = new EffectComposer(renderer);
bloomComposer.renderToScreen = false;

// 將 UnrealBloomPass 加入合成器
bloomComposer.addPass(new RenderPass(scene, camera));
bloomComposer.addPass(bloomPass);

// 建立 ShaderPass,並設定自定義著色器
const shaderMaterial = new THREE.ShaderMaterial({
  uniforms: {
    baseTexture: { value: null }, // base 紋理
    bloomTexture: { value: bloomComposer.renderTarget2.texture } // bloom 紋理
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    uniform sampler2D baseTexture;
    uniform sampler2D bloomTexture;
    varying vec2 vUv; // 紋理座標
    void main() {
      // 輸出最終顏色
      gl_FragColor = texture2D(baseTexture, vUv) + vec4(1.0) * texture2D(bloomTexture, vUv);
    }
  `
});
const finalPass = new ShaderPass(shaderMaterial, 'baseTexture');
// 建立 Shader 合成器
const finalComposer = new EffectComposer(renderer);
// 將 ShaderPass 加入合成器
finalComposer.addPass(new RenderPass(scene, camera));
finalComposer.addPass(finalPass);

// 建立軌道控制器
const control = new OrbitControls(camera, renderer.domElement);

// 執行渲染
const bloomLayer = new THREE.Layers();
bloomLayer.set(1);

const materials = [];

const darkenMaterial = obj => {
  if (!bloomLayer.test(obj.layers)) {
    materials[obj.uuid] = obj.material
    obj.material = new THREE.MeshBasicMaterial({ color: 0x000000 })
  }
};

const restoreMaterial = obj => {
  if (materials[obj.uuid]) {
    obj.material = materials[obj.uuid]
    delete materials[obj.uuid]
  }
};

// 執行渲染
const animate = () => {
  requestAnimationFrame(animate)
  sphere.rotation.y += 0.01
  scene.traverse(darkenMaterial)
  bloomComposer.render()
  scene.traverse(restoreMaterial)
  finalComposer.render()
  // renderer.render(scene, camera);
};
animate();

// 監聽螢幕寬高變化來做簡單 RWD 設定
window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight
  camera.updateProjectionMatrix()
  renderer.setSize(window.innerWidth, window.innerHeight)
  bloomComposer.setSize(window.innerWidth, window.innerHeight)
  finalComposer.setSize(window.innerWidth, window.innerHeight)
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.