这是之前的文章:3D动态圣诞树代码
优化:
1、增加圣诞树顶端五角星⭐️;
2、增加“树木”亮度。
<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>3D圣诞树</title><style>*{box-sizing:border-box;}body{margin:0;height:100vh;overflow:hidden;display:flex;align-items:center;justify-content:center;background:#161616;color:#c5a880;font-family:sans-serif;}</style></head><body><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/build/three.min.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/EffectComposer.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/RenderPass.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/ShaderPass.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/shaders/CopyShader.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/shaders/LuminosityHighPassShader.js"></script><scriptsrc="https://cdn.jsdelivr.net/npm/three@0.115.0/examples/js/postprocessing/UnrealBloomPass.js"></script><script>const{PI,sin,cos}=Math;constTAU=2*PI;// 工具函数constmap=(value,sMin,sMax,dMin,dMax)=>{returndMin+((value-sMin)/(sMax-sMin))*(dMax-dMin);};constrange=(n,m=0)=>Array(n).fill(m).map((i,j)=>i+j);constrand=(max,min=0)=>min+Math.random()*(max-min);constrandInt=(max,min=0)=>Math.floor(min+Math.random()*(max-min));constrandChoise=(arr)=>arr[randInt(arr.length)];constpolar=(ang,r=1)=>[r*cos(ang),r*sin(ang)];// 全局变量letscene,camera,renderer;letstep=0;constuniforms={time:{type:"f",value:0.0},step:{type:"f",value:0.0},};constparams={exposure:0.6,bloomStrength:0.4,bloomThreshold:0.2,bloomRadius:0.2,};letcomposer;consttotalPoints=4000;// 页面加载完成后直接初始化window.addEventListener('DOMContentLoaded',()=>{init();});// 初始化函数functioninit(){// 创建场景scene=newTHREE.Scene();scene.fog=newTHREE.Fog(0x0a0a0a,20,100);// 创建渲染器renderer=newTHREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth,window.innerHeight);renderer.setClearColor(0x0a0a0a);document.body.appendChild(renderer.domElement);// 创建相机camera=newTHREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,1,1000);camera.position.set(0,-2,25);camera.rotation.set(0.1,0,0);// 稍微向下看,能更好地看到树顶// 移除音频相关代码,只保留基本功能// 添加场景元素addGround(scene,uniforms);addSnow(scene,uniforms);addChristmasTree(scene,uniforms,totalPoints,[0,0,0]);// 后期处理constrenderScene=newTHREE.RenderPass(scene,camera);constbloomPass=newTHREE.UnrealBloomPass(newTHREE.Vector2(window.innerWidth,window.innerHeight),1.5,0.4,0.85);bloomPass.threshold=params.bloomThreshold;bloomPass.strength=params.bloomStrength;bloomPass.radius=params.bloomRadius;composer=newTHREE.EffectComposer(renderer);composer.addPass(renderScene);composer.addPass(bloomPass);// 添加事件监听器addEventListeners();animate();}// 动画循环functionanimate(time){step=(step+1)%1000;uniforms.time.value=time;uniforms.step.value=step;// 旋转主圣诞树consttree=scene.children.find(child=>child.name==='mainTree');if(tree){tree.rotation.y+=0.005;}// 为SVG五角星添加动态效果conststar=scene.children.find(child=>child.name==='treeStar');if(star){// 缓慢旋转star.rotation.z+=0.008;// 闪烁效果star.material.opacity=0.85+0.15*Math.sin(time*0.005);}composer.render();requestAnimationFrame(animate);}// 添加圣诞树functionaddChristmasTree(scene,uniforms,totalPoints,treePosition,scale=1){// 顶点着色器constvertexShader=`attribute float mIndex; varying vec3 vColor; varying float opacity; float norm(float value, float min, float max ){ return (value - min) / (max - min); } float lerp(float norm, float min, float max){ return (max - min) * norm + min; } float map(float value, float sourceMin, float sourceMax, float destMin, float destMax){ return lerp(norm(value, sourceMin, sourceMax), destMin, destMax); } void main() { vColor = color; vec3 p = position; // 移除音频可视化效果,使用固定点大小 vec4 mvPosition = modelViewMatrix * vec4(p, 1.0); float sizeMapped = 4.0; // 进一步减小点大小以降低亮度 // 根据距离设置透明度 opacity = map(mvPosition.z, -200.0, 15.0, 0.0, 1.0); // 设置点大小 gl_PointSize = sizeMapped * (100.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; }`;// 片段着色器constfragmentShader=`varying vec3 vColor; varying float opacity; uniform sampler2D pointTexture; void main() { gl_FragColor = vec4(vColor, opacity); gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord); }`;// 创建材质constshaderMaterial=newTHREE.ShaderMaterial({uniforms:{...uniforms,pointTexture:{value:newTHREE.TextureLoader().load(`https://assets.codepen.io/3685267/spark1.png`),},},vertexShader,fragmentShader,blending:THREE.AdditiveBlending,depthTest:false,transparent:true,vertexColors:true,});// 创建几何体constgeometry=newTHREE.BufferGeometry();constpositions=[];constcolors=[];constsizes=[];constphases=[];constmIndexs=[];constcolor=newTHREE.Color();// 生成圣诞树形状的粒子for(leti=0;i<totalPoints;i++){constt=Math.random();consty=map(t,0,1,-8,10)*scale;constang=map(t,0,1,0,6*TAU)+(TAU/2)*(i%2);constradius=map(t,0,1,5,0)*scale;const[z,x]=polar(ang,radius);constmodifier=map(t,0,1,1,0);positions.push(x+rand(-0.3*modifier,0.3*modifier)*scale);positions.push(y+rand(-0.3*modifier,0.3*modifier)*scale);positions.push(z+rand(-0.3*modifier,0.3*modifier)*scale);// 圣诞树颜色:大部分为绿色系,随机添加红色小光球点缀lethue,saturation,lightness;if(Math.random()<0.05){// 5%的概率生成红色小光球hue=0.0;// 红色saturation=1.0;// 高饱和度lightness=0.3;// 适中亮度}else{// 其余95%保持绿色系hue=map(t,0,1,0.2,0.35);// 绿色到深绿色saturation=0.7;lightness=0.1;// 进一步降低饱和度和亮度}color.setHSL(hue,saturation,lightness);colors.push(color.r,color.g,color.b);phases.push(rand(1000));sizes.push(1);constmIndex=map(i,0,totalPoints,1.0,0.0);mIndexs.push(mIndex);}// 设置几何体属性geometry.setAttribute("position",newTHREE.Float32BufferAttribute(positions,3).setUsage(THREE.DynamicDrawUsage));geometry.setAttribute("color",newTHREE.Float32BufferAttribute(colors,3));geometry.setAttribute("size",newTHREE.Float32BufferAttribute(sizes,1));geometry.setAttribute("phase",newTHREE.Float32BufferAttribute(phases,1));geometry.setAttribute("mIndex",newTHREE.Float32BufferAttribute(mIndexs,1));// 创建粒子系统consttree=newTHREE.Points(geometry,shaderMaterial);tree.name=treePosition[0]===0&&treePosition[2]===0?'mainTree':'tree';// 设置位置const[px,py,pz]=treePosition;tree.position.set(px,py,pz);// 添加到场景scene.add(tree);// 在圣诞树顶端添加SVG五角星addSvgStar(scene,treePosition,scale);}// 添加Canvas绘制的五角星functionaddSvgStar(scene,position,scale=1){// 使用Canvas绘制五角星constcreateStarCanvas=()=>{constcanvas=document.createElement('canvas');canvas.width=128;canvas.height=128;constctx=canvas.getContext('2d');// 绘制五角星ctx.fillStyle='#FFD700';// 金色填充ctx.strokeStyle='#FFA500';// 橙色边框ctx.lineWidth=2;// 计算五角星顶点constcenterX=canvas.width/2;constcenterY=canvas.height/2;constradius1=50;// 外半径constradius2=25;// 内半径ctx.beginPath();for(leti=0;i<5;i++){constangle1=(i*Math.PI*2/5)-Math.PI/2;constx1=centerX+Math.cos(angle1)*radius1;consty1=centerY+Math.sin(angle1)*radius1;constangle2=((i+0.5)*Math.PI*2/5)-Math.PI/2;constx2=centerX+Math.cos(angle2)*radius2;consty2=centerY+Math.sin(angle2)*radius2;if(i===0){ctx.moveTo(x1,y1);}else{ctx.lineTo(x1,y1);}ctx.lineTo(x2,y2);}ctx.closePath();ctx.fill();ctx.stroke();returncanvas;};// 从Canvas创建纹理conststarCanvas=createStarCanvas();conststarTexture=newTHREE.CanvasTexture(starCanvas);// 创建精灵材质conststarMaterial=newTHREE.SpriteMaterial({map:starTexture,transparent:true,opacity:1.0,blending:THREE.AdditiveBlending,// 增加发光效果depthTest:false,// 禁用深度测试,确保五角星始终可见depthWrite:false// 不写入深度缓冲区});// 创建精灵conststar=newTHREE.Sprite(starMaterial);// 设置大小star.scale.set(2.5*scale,2.5*scale,1);// 增大五角星的大小,使其更明显// 设置位置在树顶const[x,y,z]=position;star.position.set(x,y+10.7*scale,z);star.name='treeStar';// 添加到场景scene.add(star);}// 添加雪花效果functionaddSnow(scene,uniforms){// 顶点着色器constvertexShader=`attribute float size; attribute float phase; attribute float phaseSecondary; varying vec3 vColor; varying float opacity; uniform float time; uniform float step; float norm(float value, float min, float max ){ return (value - min) / (max - min); } float lerp(float norm, float min, float max){ return (max - min) * norm + min; } float map(float value, float sourceMin, float sourceMax, float destMin, float destMax){ return lerp(norm(value, sourceMin, sourceMax), destMin, destMax); } void main() { float t = time * 0.0006; vColor = color; vec3 p = position; // 雪花飘落动画 p.y = map(mod(phase + step, 1000.0), 0.0, 1000.0, 25.0, -8.0); p.x += sin(t + phase) * 0.1; p.z += sin(t + phaseSecondary) * 0.1; // 根据距离设置透明度 opacity = map(p.z, -150.0, 15.0, 0.0, 1.0); vec4 mvPosition = modelViewMatrix * vec4(p, 1.0); gl_PointSize = size * (100.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; }`;// 片段着色器constfragmentShader=`uniform sampler2D pointTexture; varying vec3 vColor; varying float opacity; void main() { gl_FragColor = vec4(vColor, opacity); gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord); }`;// 创建雪花粒子集functioncreateSnowSet(sprite){consttotalPoints=300;constshaderMaterial=newTHREE.ShaderMaterial({uniforms:{...uniforms,pointTexture:{value:newTHREE.TextureLoader().load(sprite),},},vertexShader,fragmentShader,blending:THREE.AdditiveBlending,depthTest:false,transparent:true,vertexColors:true,});constgeometry=newTHREE.BufferGeometry();constpositions=[];constcolors=[];constsizes=[];constphases=[];constphaseSecondaries=[];constcolor=newTHREE.Color();// 生成雪花粒子for(leti=0;i<totalPoints;i++){const[x,y,z]=[rand(25,-25),0,rand(15,-150)];positions.push(x);positions.push(y);positions.push(z);// 雪花颜色color.set(randChoise(["#ffffff","#f0f8ff","#e6f3ff","#d9ecff"]));colors.push(color.r,color.g,color.b);phases.push(rand(1000));phaseSecondaries.push(rand(1000));sizes.push(rand(4,2));}// 设置几何体属性geometry.setAttribute("position",newTHREE.Float32BufferAttribute(positions,3));geometry.setAttribute("color",newTHREE.Float32BufferAttribute(colors,3));geometry.setAttribute("size",newTHREE.Float32BufferAttribute(sizes,1));geometry.setAttribute("phase",newTHREE.Float32BufferAttribute(phases,1));geometry.setAttribute("phaseSecondary",newTHREE.Float32BufferAttribute(phaseSecondaries,1));// 创建粒子系统constmesh=newTHREE.Points(geometry,shaderMaterial);scene.add(mesh);}// 不同形状的雪花纹理constsprites=["https://assets.codepen.io/3685267/snowflake1.png","https://assets.codepen.io/3685267/snowflake2.png","https://assets.codepen.io/3685267/snowflake3.png","https://assets.codepen.io/3685267/snowflake4.png","https://assets.codepen.io/3685267/snowflake5.png",];// 创建多个雪花粒子集sprites.forEach((sprite)=>{createSnowSet(sprite);});}// 添加地面functionaddGround(scene,uniforms){// 顶点着色器constvertexShader=`attribute float size; attribute vec3 customColor; varying vec3 vColor; void main() { vColor = customColor; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); gl_PointSize = size * (300.0 / -mvPosition.z); gl_Position = projectionMatrix * mvPosition; }`;// 片段着色器constfragmentShader=`uniform sampler2D pointTexture; varying vec3 vColor; void main() { gl_FragColor = vec4(vColor, 1.0); gl_FragColor = gl_FragColor * texture2D(pointTexture, gl_PointCoord); }`;// 创建材质constshaderMaterial=newTHREE.ShaderMaterial({uniforms:{...uniforms,pointTexture:{value:newTHREE.TextureLoader().load(`https://assets.codepen.io/3685267/spark1.png`),},},vertexShader,fragmentShader,blending:THREE.AdditiveBlending,depthTest:false,transparent:true,vertexColors:true,});// 创建几何体constgeometry=newTHREE.BufferGeometry();constpositions=[];constcolors=[];constsizes=[];constcolor=newTHREE.Color();// 生成地面粒子for(leti=0;i<3000;i++){const[x,y,z]=[rand(-25,25),0,rand(-150,15)];positions.push(x);positions.push(y-8);// 地面位置positions.push(z);// 地面颜色color.set(randChoise(["#2d5016","#3a621a","#4a7c1f","#5b9925"]));colors.push(color.r,color.g,color.b);sizes.push(1);}geometry.setAttribute("position",newTHREE.Float32BufferAttribute(positions,3).setUsage(THREE.DynamicDrawUsage));geometry.setAttribute("customColor",newTHREE.Float32BufferAttribute(colors,3));geometry.setAttribute("size",newTHREE.Float32BufferAttribute(sizes,1));// 创建粒子系统constplane=newTHREE.Points(geometry,shaderMaterial);scene.add(plane);}// 添加事件监听器functionaddEventListeners(){document.addEventListener("keydown",(e)=>{const{x,y,z}=camera.position;console.log(`camera.position.set(${x},${y},${z})`);const{x:a,y:b,z:c}=camera.rotation;console.log(`camera.rotation.set(${a},${b},${c})`);});// 窗口大小调整window.addEventListener("resize",()=>{constwidth=window.innerWidth;constheight=window.innerHeight;camera.aspect=width/height;camera.updateProjectionMatrix();renderer.setSize(width,height);composer.setSize(width,height);},false);}</script></body></html>