threejs案例开发-中国3D国旗动画
用three编写一个随风飘扬的红旗效果
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>中国国旗3D动画</title><script type="importmap">{"imports": {"three": "https://threejs.org/build/three.module.min.js","three/examples/jsm/": "https://threejs.org/examples/jsm/"}}</script><style>body {margin: 0;padding: 0;box-sizing: border-box;background-color: #0a0a0a;display: flex;flex-direction: column;width: 100vw;height: 100vh;overflow: hidden;color: white;font-family: Arial, sans-serif;}#container {width: 100%;height: 100%;position: relative;}.controls {position: absolute;top: 10px;left: 10px;background-color: rgba(0, 0, 0, 0.5);padding: 10px;border-radius: 5px;z-index: 100;}.controls button {margin: 5px;padding: 5px 10px;background-color: #c00;color: white;border: none;border-radius: 3px;cursor: pointer;}.controls button:hover {background-color: #e00;}</style></head><body><div id="container"></div><div class="controls"><button id="pauseBtn">暂停/继续</button><button id="resetBtn">重置</button><button id="zoomInBtn">放大</button><button id="zoomOutBtn">缩小</button></div><script type="module">import * as THREE from "three";import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";const container = document.getElementById("container");// 创建场景const scene = new THREE.Scene();// 创建相机const camera = new THREE.PerspectiveCamera(75,container.clientWidth / container.clientHeight,0.1,1000);camera.position.set(0, 0, 5);// 创建渲染器const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(container.clientWidth, container.clientHeight);renderer.setPixelRatio(window.devicePixelRatio);renderer.setClearColor(0x0a0a0a);container.appendChild(renderer.domElement);// 添加轨道控制器const controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.1;controls.enablePan = false;controls.maxDistance = 10;controls.minDistance = 2;controls.target.set(0, 0, 0);// 窗口大小调整事件window.onresize = () => {renderer.setSize(container.clientWidth, container.clientHeight);camera.aspect = container.clientWidth / container.clientHeight;camera.updateProjectionMatrix();};// 国旗材质const flagTexture = new THREE.TextureLoader().load("./chinaFlag.jpg");flagTexture.anisotropy = renderer.capabilities.getMaxAnisotropy();// 国旗波浪效果材质// 国旗材质 - 使用RawShaderMaterial实现高级自定义着色器效果const flagMaterial = new THREE.RawShaderMaterial({vertexShader: `// 全局变换矩阵 - 用于将顶点从模型空间转换到裁剪空间uniform mat4 projectionMatrix; // 投影矩阵,负责透视投影uniform mat4 modelMatrix; // 模型矩阵,负责模型的位置、旋转和缩放uniform mat4 viewMatrix; // 视图矩阵,代表相机的位置和方向// 波浪动画控制参数uniform vec2 uFrequency; // 波浪频率 (x和y方向的波浪密度)uniform float uTime; // 时间变量,控制波浪动画的进度uniform float uStrength; // 波浪强度,控制波浪的高度// 顶点属性 - 每个顶点都有这些属性attribute vec3 position; // 顶点的位置坐标attribute vec2 uv; // 顶点的纹理坐标,用于采样纹理// 传递给片段着色器的变量varying float vDark; // 暗部强度,用于模拟波浪的阴影varying vec2 vUv; // 纹理坐标,传递给片段着色器void main() {// 将顶点位置从模型空间转换到世界空间vec4 modelPosition = modelMatrix * vec4(position, 1.0);// 计算波浪衰减因子 - 从旗杆(左侧)到旗尾(右侧)波浪逐渐增强float xFactor = clamp((modelPosition.x + 1.25) / 2.0, 0.0, 2.0);// 计算波浪效果 - 使用正弦函数创建波浪运动float vWave = sin(modelPosition.x * uFrequency.x - uTime ) * xFactor * uStrength;// 添加次要波浪,使效果更自然vWave += sin(modelPosition.y * uFrequency.y - uTime) * xFactor * uStrength * 0.5;// 添加细微的垂直波动,模拟真实旗帜飘动modelPosition.y += sin(modelPosition.x * 2.0 + uTime * 0.5) * 0.05 * xFactor;// 应用波浪效果到Z轴(旗帜向前/向后飘动)modelPosition.z += vWave;// 完成顶点的坐标变换 - 从世界空间到视图空间再到裁剪空间vec4 viewPosition = viewMatrix * modelPosition;vec4 projectedPosition = projectionMatrix * viewPosition;gl_Position = projectedPosition;// 传递纹理坐标和波浪强度到片段着色器vUv = uv;vDark = vWave; // 波浪强度用于后续计算阴影效果}`,fragmentShader: `// 设置浮点数精度precision mediump float;// 从顶点着色器接收的插值变量varying float vDark; // 波浪强度(暗部信息)varying vec2 vUv; // 纹理坐标// 纹理采样器uniform sampler2D uTexture; // 国旗纹理void main() {// 从纹理中采样颜色vec4 textColor = texture2D(uTexture, vUv);// 应用波浪产生的暗部效果 - vDark值越大,颜色越暗// 0.85是基础亮度,确保即使在波谷处也不会完全黑暗textColor.rgb *= vDark + 0.85;// 设置最终的像素颜色gl_FragColor = textColor;}`,// 其他材质属性side: THREE.DoubleSide, // 双面渲染,确保从背面也能看到旗帜uniforms: {uFrequency: { value: new THREE.Vector2(3, 3) }, // 波浪频率uTime: { value: 0 }, // 初始时间uTexture: { value: flagTexture }, // 国旗纹理uStrength: { value: 0.2 }, // 波浪强度},});// 创建国旗几何体const flagGeometry = new THREE.PlaneGeometry(3, 2, 64, 64);// 创建国旗网格const flagMesh = new THREE.Mesh(flagGeometry, flagMaterial);flagMesh.position.set(0, 0, 0);scene.add(flagMesh);// 添加旗杆const poleGeometry = new THREE.CylinderGeometry(0.02, 0.02, 2.5, 32);const poleMaterial = new THREE.MeshStandardMaterial({ color: 0x8b4513 });const poleMesh = new THREE.Mesh(poleGeometry, poleMaterial);poleMesh.position.set(-1.52, -0.25, 0);scene.add(poleMesh);// 添加顶部装饰const topGeometry = new THREE.ConeGeometry(0.05, 0.15, 32);const topMaterial = new THREE.MeshStandardMaterial({ color: 0xffd700 });const topMesh = new THREE.Mesh(topGeometry, topMaterial);topMesh.position.set(-1.52, 1.25, 0);topMesh.rotation.x = Math.PI;scene.add(topMesh);// 添加光源const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(-1, 1, 1);scene.add(directionalLight);// 动画控制let isAnimating = true;let animationSpeed = 1.0;// 按钮事件document.getElementById("pauseBtn").addEventListener("click", () => {isAnimating = !isAnimating;});document.getElementById("resetBtn").addEventListener("click", () => {flagMaterial.uniforms.uTime.value = 0;camera.position.set(0, 0, 5);controls.target.set(0, 0, 0);});document.getElementById("zoomInBtn").addEventListener("click", () => {camera.position.z = Math.max(2, camera.position.z - 0.5);});document.getElementById("zoomOutBtn").addEventListener("click", () => {camera.position.z = Math.min(10, camera.position.z + 0.5);});// 动画循环function animate() {if (isAnimating) {flagMaterial.uniforms.uTime.value += 0.06 * animationSpeed;}controls.update();renderer.render(scene, camera);requestAnimationFrame(animate);}animate();</script></body>
</html>
更多案例:https://threelab.cn/openthree/#/example