Three.js 全息影像效果(源码)
|
admin
2025年4月19日 21:45
本文热度 274
|
“使用 Three.js 的 WebGL 小实验。Three.js 全息影像效果,动画化模型进行过渡效果。”
HTML:
*, *::after, *::before { margin: 0; padding: 0; }
body { box-sizing: border-box; }
#app { position: fixed; top: 0; left: 0; z-index: -1; width: 100%; height: 100vh; }
JAVASCRIPT:
import * as THREE from "https://esm.sh/three"; import { OrbitControls } from "https://esm.sh/three/addons/controls/OrbitControls.js"; import gsap from "https://esm.sh/gsap"; import GUI from "https://esm.sh/lil-gui";
const vertexShader = ` uniform float uTime; uniform float uProgress; uniform float uMinY; uniform float uMaxY;
varying vec3 vPosition; varying vec3 vNormal;
float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123); }
void main() { vec4 modelPosition = modelMatrix * vec4(position, 1.0);
float glitchTime = uTime - modelPosition.y; float glitchstrength = sin(glitchTime) * sin(glitchTime * 3.45) + sin(glitchTime * 8.76); glitchstrength /= 3.0; glitchstrength = smoothstep(0.5, 1.0, glitchstrength); glitchstrength *= 2.0; modelPosition.x += (random(modelPosition.xz + uTime) - 0.5) * glitchstrength; modelPosition.z += (random(modelPosition.xz + uTime) - 0.5) * glitchstrength;
float normalizedY = (modelPosition.y - uMinY) / (uMaxY - uMinY); float diff = abs(normalizedY - uProgress); float glitchStrength = smoothstep(0.02, 0.0, diff); glitchStrength *= 0.3;
modelPosition.x += (random(modelPosition.xz + uTime) - 0.5) * glitchStrength; modelPosition.z += (random(modelPosition.xz + uTime) - 0.5) * glitchStrength;
gl_Position = projectionMatrix * viewMatrix * modelPosition;
vPosition = modelPosition.xyz;
vec4 newNormal = modelMatrix * vec4(normal, 0.0); vNormal = newNormal.xyz; }`;
const fragmentShader = ` uniform float uTime; uniform float uIndex; uniform float uCurrentIndex; uniform float uNextIndex; uniform float uProgress; uniform vec3 uColor; uniform float uMinY; uniform float uMaxY;
varying vec3 vPosition; varying vec3 vNormal;
void main() { if(uIndex != uCurrentIndex && uIndex != uNextIndex) { discard; }
float lines = 20.0; float offset = vPosition.y - uTime * 0.2; float density = mod(offset * lines, 1.0); density = pow(density, 3.0);
vec3 viewDirection = normalize(vPosition - cameraPosition); float fresnel = 1.0 - abs(dot(normalize(vNormal), viewDirection)); fresnel = pow(fresnel, 2.0);
float falloff = smoothstep(0.8, 0.0, fresnel);
float holographic = density * fresnel; holographic += fresnel * 1.25; holographic *= falloff;
float normalizedY = (vPosition.y - uMinY) / (uMaxY - uMinY); if(uIndex == uCurrentIndex && normalizedY < uProgress) { discard; } if(uIndex == uNextIndex && normalizedY > uProgress) { discard; }
gl_FragColor = vec4(uColor, holographic);
}`;
const params = { color: "#00d5ff", stageColor: "#d4d4d4", ambientLight: "#ffffff", directionalLight: "#a4d5f4", currentIndex: 0, nextIndex: 1, };
const uniforms = { uColor: new THREE.Uniform(new THREE.Color(params.color)), uTime: new THREE.Uniform(0), uProgress: new THREE.Uniform(0), uIndex: new THREE.Uniform(0), uCurrentIndex: new THREE.Uniform(params.currentIndex), uNextIndex: new THREE.Uniform(params.nextIndex), uMinY: new THREE.Uniform(0), uMaxY: new THREE.Uniform(0), };
const initThree = (app) => { const size = { width: window.innerWidth, height: window.innerHeight, dpr: window.devicePixelRatio, };
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 35, size.width / size.height, 0.1, 100 ); camera.position.set(0, 4, -10); scene.add(camera); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.shadowMap.enabled = true; renderer.shadowMap.type = THREE.PCFSoftShadowMap; renderer.setSize(size.width, size.height); renderer.setPixelRatio(size.dpr); app.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement);
const ambientLight = new THREE.AmbientLight( new THREE.Color(params.ambientLight), 0.5 ); scene.add(ambientLight);
const dLight = new THREE.DirectionalLight( new THREE.Color(params.directionalLight), 1.0 ); dLight.position.set(0, 3, 1); scene.add(dLight);
const pLight = new THREE.PointLight(new THREE.Color(params.color), 1, 10); pLight.position.set(0, -1.3, 0); scene.add(pLight);
const baseMaterial = new THREE.ShaderMaterial({ vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms, transparent: true, blending: THREE.AdditiveBlending, }); baseMaterial.depthWrite = false;
const cylinder = new THREE.Mesh( new THREE.CylinderGeometry(2, 2, 0.5, 128), new THREE.MeshStandardMaterial({ color: new THREE.Color(params.stageColor), }) ); cylinder.position.set(0, -2, 0); scene.add(cylinder);
const torusknotGeometry = new THREE.TorusKnotGeometry(1, 0.5, 128, 32); torusknotGeometry.computeBoundingBox(); const icosahedronGeometry = new THREE.IcosahedronGeometry(2, 24); icosahedronGeometry.computeBoundingBox(); const torusGeometry = new THREE.TorusGeometry(1.4, 0.5, 128, 32); torusGeometry.computeBoundingBox();
const minY = Math.min( torusknotGeometry.boundingBox.min.y, icosahedronGeometry.boundingBox.min.y, torusGeometry.boundingBox.min.y ); const maxY = Math.max( torusknotGeometry.boundingBox.max.y, icosahedronGeometry.boundingBox.max.y, torusGeometry.boundingBox.max.y ); const margin = 0.1; const posY = 0.5; uniforms.uMinY.value = minY + posY - margin; uniforms.uMaxY.value = maxY + posY + margin;
const torusKnotMaterial = baseMaterial.clone(); torusKnotMaterial.uniforms.uIndex.value = 0; const torusKnot = new THREE.Mesh(torusknotGeometry, torusKnotMaterial); torusKnot.position.y = posY; scene.add(torusKnot);
const icosahedronMaterial = baseMaterial.clone(); icosahedronMaterial.uniforms.uIndex.value = 1; const icosahedron = new THREE.Mesh(icosahedronGeometry, icosahedronMaterial); icosahedron.position.y = posY; scene.add(icosahedron);
const torusMaterial = baseMaterial.clone(); torusMaterial.uniforms.uIndex.value = 2; const torus = new THREE.Mesh(torusGeometry, torusMaterial); torus.position.y = posY; scene.add(torus);
const materials = [torusKnotMaterial, icosahedronMaterial, torusMaterial];
const clock = new THREE.Clock(); const animate = () => { requestAnimationFrame(animate); const delta = clock.getDelta(); const elapsedTime = clock.getElapsedTime();
const newIndex = Math.floor((elapsedTime * 0.25) % 3); if (newIndex !== params.currentIndex) { params.currentIndex = newIndex; params.nextIndex = newIndex === 2 ? 0 : newIndex + 1; materials.forEach((material) => { material.uniforms.uCurrentIndex.value = params.currentIndex; material.uniforms.uNextIndex.value = params.nextIndex; gsap.fromTo( material.uniforms.uProgress, { value: 0 }, { value: 1, duration: 1.5, ease: "linear" } ); }); }
torusKnot.rotation.y += delta * 0.5; torusKnot.rotation.x += delta * 0.5; torus.rotation.y += delta * 0.5; torus.rotation.x += delta * 0.5; torusMaterial.uniforms.uTime.value = elapsedTime; icosahedronMaterial.uniforms.uTime.value = elapsedTime;
renderer.render(scene, camera); }; animate(); materials.forEach((material) => { material.uniforms.uCurrentIndex.value = params.currentIndex; material.uniforms.uNextIndex.value = params.nextIndex; gsap.fromTo( material.uniforms.uProgress, { value: 0 }, { value: 1, duration: 1.5, ease: "linear" } ); });
window.addEventListener("resize", () => { size.width = window.innerWidth; size.height = window.innerHeight; size.dpr = window.devicePixelRatio; camera.aspect = size.width / size.height; camera.updateProjectionMatrix(); renderer.setSize(size.width, size.height); renderer.setPixelRatio(size.dpr); });
const gui = new GUI(); gui .addColor(params, "color") .name("hologram color") .onChange((val) => { materials.forEach((material) => { material.uniforms.uColor.value = new THREE.Color(val); }); pLight.color = new THREE.Color(val); }); gui .addColor(params, "stageColor") .name("stage color") .onChange((val) => (cylinder.material.color = new THREE.Color(val))); gui .addColor(params, "ambientLight") .name("ambient light") .onChange((val) => (ambientLight.color = new THREE.Color(val))); gui .addColor(params, "directionalLight") .name("directional light") .onChange((val) => (dLight.color = new THREE.Color(val))); };
const init = () => { const app = document.querySelector("#app"); if (app) { initThree(app); } };
document.addEventListener("DOMContentLoaded", init);
源码:
https://codepen.io/kyoko-nr/pen/bNGJrQe
体验:
https://codepen.io/kyoko-nr/full/bNGJrQe
阅读原文:原文链接
该文章在 2025/4/21 10:31:37 编辑过
|
|