Three.js 虫洞特效

作者:初识Threejs

原文:mp.weixin.qq.com/s/yqCS5t_kJ...

" 使用 Three.js 的 WebGL 小实验。虫洞特效。 "

实现代码

HTML:

xml 复制代码
<canvas class="experience"></canvas><div class="scrollTarget"></div><div class="vignette-radial"></div>

CSS:

css 复制代码
body {  margin: 0;}
.experience {  position: fixed;  top: 0;  left: 0;  width: 100%;  height: 100vh;  z-index: 2;}
.scrollTarget {  position: absolute;  height: 1000vh;  width: 100px;  top: 0;  z-index: 0;}
.vignette-radial {  position: fixed;  z-index: 1;  top: 0;  left: 0;  height: 100vh;  width: 100%;  pointer-events: none;}
.vignette-radial:after {  pointer-events: none;  content: " ";  position: absolute;  top: 0;  left: 0;  bottom: 0;  right: 0;}

JAVASCRIPT:

ini 复制代码
//Wormhole by Kurt Grüng
const MathUtils = {  normalize: (value, min, max) => (value - min) / (max - min),  interpolate: (normValue, min, max) => min + (max - min) * normValue,  map: (value, min1, max1, min2, max2) => {    value = Math.min(Math.max(value, min1), max1);    return MathUtils.interpolate(      MathUtils.normalize(value, min1, max1),      min2,      max2    );  }};
let w = window.innerWidth;let h = window.innerHeight;
const renderer = new THREE.WebGLRenderer({  canvas: document.querySelector("canvas"),  antialias: true,  shadowMapEnabled: true,  shadowMapType: THREE.PCFSoftShadowMap});renderer.setSize(w, h);
const scene = new THREE.Scene();scene.background = new THREE.Color(0x000000);
const camera = new THREE.PerspectiveCamera(45, w / h, 0.001, 200);let cameraRotationProxyX = Math.PI;let cameraRotationProxyY = 0;camera.rotation.y = cameraRotationProxyX;camera.rotation.z = cameraRotationProxyY;
const cameraGroup = new THREE.Group();cameraGroup.position.z = 400;cameraGroup.add(camera);scene.add(cameraGroup);
const generatePathPoints = (count = 10, spacing = 25) => {  const points = [];  for (let i = 0; i < count; i++) {    const x = i * spacing;    const y = Math.sin(i * 0.5 + Math.random()) * 100 + 50;    const z = Math.cos(i * 0.3 + Math.random()) * 100 + 50;    points.push(new THREE.Vector3(x, z, y));  }  return points;};
const points = generatePathPoints(10);const path = new THREE.CatmullRomCurve3(points);path.closed = true;path.tension = 1;
const ringCount = 600;const ringRadius = 3;const ringSegments = 32;
const geometry = new THREE.TubeGeometry(  path,  ringCount,  ringRadius,  ringSegments,  true);const wireframe = new THREE.LineSegments(  new THREE.EdgesGeometry(geometry),  new THREE.LineBasicMaterial({ linewidth: 0.1, opacity: 0.1 }));scene.add(wireframe);
const ringMaterial = new THREE.LineBasicMaterial({ color: 0xffffff });
const ringMaterial1 = new THREE.LineBasicMaterial({  color: 0xffffff,  transparent: true,  opacity: 0.8,  depthWrite: false});
const frenetFrames = path.computeFrenetFrames(ringCount, true);
for (let i = 0; i <= ringCount; i++) {  const t = i / ringCount;  const pos = path.getPointAt(t);  const normal = frenetFrames.normals[i];  const binormal = frenetFrames.binormals[i];
  const ringPoints = [];  for (let j = 0; j <= ringSegments; j++) {    const theta = (j / ringSegments) * Math.PI * 2;    const x = Math.cos(theta) * ringRadius;    const y = Math.sin(theta) * ringRadius;
    const point = new THREE.Vector3().addVectors(      pos,      new THREE.Vector3()        .addScaledVector(normal, x)        .addScaledVector(binormal, y)    );
    ringPoints.push(point);  }
  const ringGeometry = new THREE.BufferGeometry().setFromPoints(ringPoints);  const ringMesh = new THREE.LineLoop(ringGeometry, ringMaterial);  scene.add(ringMesh);}
const light = new THREE.PointLight(0xffffff, 0.1, 4, 0);light.castShadow = true;scene.add(light);
const renderScene = new THREE.RenderPass(scene, camera);const bloomPass = new THREE.UnrealBloomPass(  new THREE.Vector2(w, h),  1.5,  0.4,  0.5);bloomPass.renderToScreen = true;
const composer = new THREE.EffectComposer(renderer);composer.setSize(w, h);composer.addPass(renderScene);composer.addPass(bloomPass);
let cameraTargetPercentage = 0;let currentCameraPercentage = 0;
function updateCameraPercentage(percentage) {  const p1 = path.getPointAt(percentage % 1);  const p2 = path.getPointAt((percentage + 0.01) % 1);
  cameraGroup.position.set(p1.x, p1.y, p1.z);  cameraGroup.lookAt(p2);  light.position.set(p2.x, p2.y, p2.z);}
const tubePerc = { percent: 0 };
function render(time) {  cameraTargetPercentage = (cameraTargetPercentage + 0.001) % 1;  updateCameraPercentage(cameraTargetPercentage);  composer.render();
  camera.rotation.y += (cameraRotationProxyX - camera.rotation.y) / 15;  camera.rotation.x += (cameraRotationProxyY - camera.rotation.x) / 15;
  requestAnimationFrame(render);}
function render() {  cameraTargetPercentage = (cameraTargetPercentage + 0.001) % 1;
  camera.rotation.y += (cameraRotationProxyX - camera.rotation.y) / 15;  camera.rotation.x += (cameraRotationProxyY - camera.rotation.x) / 15;  updateCameraPercentage(cameraTargetPercentage);  composer.render();  requestAnimationFrame(render);  console.log(cameraTargetPercentage);}
requestAnimationFrame(render);
window.addEventListener("resize", () => {  w = window.innerWidth;  h = window.innerHeight;  camera.aspect = w / h;  camera.updateProjectionMatrix();  renderer.setSize(w, h);  composer.setSize(w, h);});

源码:

codepen.io/kurtgrung/p...

体验:

codepen.io/kurtgrung/f...

相关推荐
寅时码38 分钟前
我开源了一款 Canvas “瑞士军刀”,十几种“特效与工具”开箱即用
前端·开源·canvas
CF14年老兵40 分钟前
🚀 React 面试 20 题精选:基础 + 实战 + 代码解析
前端·react.js·redux
CF14年老兵41 分钟前
2025 年每个开发人员都应该知道的 6 个 VS Code AI 工具
前端·后端·trae
十五_在努力44 分钟前
参透 JavaScript —— 彻底理解 new 操作符及手写实现
前端·javascript
拾光拾趣录1 小时前
🔥99%人答不全的安全链!第5问必翻车?💥
前端·面试
IH_LZH1 小时前
kotlin小记(1)
android·java·前端·kotlin
lwlcode1 小时前
前端大数据渲染性能优化 - 分时函数的封装
前端·javascript
Java技术小馆1 小时前
MCP是怎么和大模型交互
前端·面试·架构
玲小珑1 小时前
Next.js 教程系列(二十二)代码分割与打包优化
前端·next.js
coding随想1 小时前
HTML5插入标记的秘密:如何高效操控DOM而不踩坑?
前端·html