前言

今年过年因为疫情又没回家,就抽空看了看WebGL编程指南Three.js开发指南,为了练手,就简略实现了一下冬奥开幕式上的迎客松烟花成果,纯小白,这篇文章次要是记录这次实际过程,波及的也都是threejs最根底的内容,那咱们就开始吧...

成果

效果图如下,也能够点击链接预览 https://awebgl.vercel.app/ 先是大门关上的一个动作,接着机器人是threejs官网的一个例子,最初就是烟花飞上天空爆炸成迎客松的一个成果。

环境

用的vite+vue3,步骤如下,threejs的api比拟长,目前我是记不住(捂脸),npm装置了@types/three,vscode就有代码提醒性能了,还装置了一个动画库tween.js。

npm create vite@latest three3d --template vuecd three3dnpm install @tweenjs/tween.js @types/three three -S

实现

个人感觉做3d,齐全能够把本人当成一个导演(偷笑),相机放在哪个地位、灯光在哪儿等等,不然就容易遇到满屏黑,上面是我这个例子的简图,大家依照本人的习惯来构建就好。

场景和相机

首先须要创立一个场景,有了场景能力增加光照、图元。
而后这个例子应用的是透视摄像机PerspectiveCamera(fov,aspect,near,far), 它能够提供一个近大远小的3D视觉效果,aspect通常设为画布的长宽比,这里我选取的是整个页面,所以就是window.innerWidth/window.innerHeight,最初把相机指向核心(0,0,0)。

//App.vueconst createScene = ()=>{    scene = new THREE.Scene(); //创立场景}const createCamera = ()=>{    const scale = window.innerWidth/window.innerHeight;    camera = new THREE.PerspectiveCamera(60,scale,0.1,1000);    camera.position.set(0,0,20)    camera.lookAt(new THREE.Vector3(0,0,0))}

光照

首先增加的是环境光,AmbientLight只是简略地将材质的色彩与光照色彩进行叠加,再乘以光照强度,能够参考上面代码的正文局部,因为是夜晚,我选取了较暗的色彩。同样因为是夜晚,没有选用太阳光照那样的平行光,而是在大门的左右增加了一个相似路灯成果的聚光灯光源。

//App.vueconst createLight = ()=>{  // 环境光    // 这里的色彩计算是 RBG 通道上的值别离对应相乘    // 例: rgb(0.64,0.64,0.64) = rgb(0.8,0.8,0.8) * rgb(0.8,0.8,0.8) * 1    // color = materialColor * light.color * light.intensity;    const ambientLight = new THREE.AmbientLight(0x1c1c1c);    ambientLight.intensity = 1;    scene.add(ambientLight);    // 聚光灯    const spotLight1 = new THREE.SpotLight(0xffffff, 1);    spotLight1.position.set(-1.5,1.5,10);     spotLight1.target.position.set(-1.5, -2, 8);    spotLight1.castShadow = true;    spotLight1.shadow.mapSize.width = 2048;    spotLight1.shadow.mapSize.height = 2048;    spotLight1.shadow.camera.near = 1;    spotLight1.shadow.camera.far = 100;    spotLight1.shadow.camera.fov = 30;    spotLight1.penumbra = 1;    scene.add(spotLight1);    scene.add(spotLight1.target);    //同理增加右侧灯光...}

图元模型

相机光照舞台都搭好了,接着就该是咱们的配角们出场了。

小树枝:可见上面的示意图,选取圆的一部分,移到原点,而后弧线绕z轴旋转,左右各生成4条,旋转后点的计算公式,能够间接套用数学公式,这里须要提到的一点,threejs应用的是右手坐标系,z轴垂直屏幕指向外,所以示意图是绕z轴逆时针旋转了',左边四条弧线我传的也是正数。最初把这个小树枝的scale设置为0,而后放大到1,就简略实现了一个爆炸成果。

迎客松整个树枝:首先把下面的小树枝克隆60个,而后在一个16 4 2的立方体区域里,随机选取其中60个地位设置给小树枝,最初再分3批顺次绽开。

迎客松树干:斜着5条和竖着10条直线,实现成果和下面相似,大家能够间接看源码,我就不再介绍了。

//App.vue  const pointsMaterial = new THREE.PointsMaterial({    size: 0.15,    sizeAttenuation: true,    transparent: true,    opacity: 0.8,    color: 0xffffff,    depthWrite: false,    blending: THREE.AdditiveBlending,    vertexColors: true  });  const circleNum = 8; //8条弧线  const circlePointNum = circlePoint*3*circleNum;  const circleColors = getColors(circlePointNum);  let circleArr = [];  //右4条弧线左4条弧线  for(let i=0;i<4;i++){    circleArr = circleArr.concat(getRightPosition(-1*i/12)).concat(getLeftPosition(i/12));  }  const circleGeometry = new THREE.BufferGeometry();  circleGeometry.setAttribute("color",new THREE.BufferAttribute(circleColors,3))  circleGeometry.setAttribute("position",new THREE.BufferAttribute(new Float32Array(circleArr),3))  circleGeometry.attributes.position.needsUpdate = true;  const circlepoints = new THREE.Points(circleGeometry,pointsMaterial);  const circlegroup = new THREE.Group()  circlegroup.add(circlepoints);  circlegroup.visible = false  circlegroup.scale.set(0,0,0)  const flowerGroup = new THREE.Group();  //60个随机小烟花  for(let i=0;i<60;i++){    let tgroup = circlegroup.clone();    tgroup.position.set(16*Math.random()-8,4*Math.random()+5,-2*Math.random())    flowerGroup.add(tgroup);  }  flowerGroup.position.set(0,0,-2)  scene.add(flowerGroup);

机器人:这个是threejs官网的一个例子,链接https://threejs.org/examples/... ,加上这个是因为开门放烟花感觉有点枯燥,就把这个机器人用上了,用了挥手、跑、跳三个动作。须要提到的是,在加载机器人模型时,如果不期待加载完,就进行下一步操作,会呈现黑屏,感觉跟vue框架也有关系,最初就用Promise对立封装了。

//App.vue  let people = await loadMesh(peopleModel);  people.scene.traverse((child)=>{    if (child.isMesh) {      child.castShadow = true;    }  })  people.scene.position.set(0.5, -5, 4);  people.scene.scale.set(0.6, 0.6, 0.6);  //加载动画  mixer = new THREE.AnimationMixer(people.scene);  let animations = people.animations;  let jumpClip = mixer.clipAction(animations[3])  let runClip = mixer.clipAction(animations[6])  let waveClip = mixer.clipAction(animations[12])  waveClip.play()  scene.add(people.scene)

大门:这块须要提到的是,图元在旋转的时候都是绕着中心点的,我用了组合Group,右边的门往右偏移半个门的宽度,同理左边的门往左偏移,而后再把Group别离向左、右各平移一整个门的宽度,最初Group绕y轴旋转就达到开门的成果了,大门的后面贴了纹理,其它面都是红色,在官网没找到立方体面的对应纹理程序,我试了一下应该是[右,左,上,下,前,后]。

//App.vue  const doorWidth = 3;  const doorHeight = 6;  const cubeGeometry = new THREE.BoxGeometry(doorWidth,doorHeight,0.5);  const leftdoorTexture = await loadTexture(leftDoorPic);  const rightdoorTexture = await loadTexture(rightDoorPic);  const cubeMaterial1 = new THREE.MeshPhongMaterial({color:0x5C0400});  const cubeMaterial2 = new THREE.MeshPhongMaterial({map:leftdoorTexture});  const cubeMaterial3 = new THREE.MeshPhongMaterial({map:rightdoorTexture});  const doorGroup1 = new THREE.Group()  const doorCube1 = new THREE.Mesh(cubeGeometry,[cubeMaterial1,cubeMaterial1,cubeMaterial1,cubeMaterial1,cubeMaterial2,cubeMaterial1]);  doorCube1.castShadow = true;  doorCube1.position.x = doorWidth/2;  doorGroup1.position.set(-doorWidth,doorHeight/2-5,8);  doorGroup1.add(doorCube1)  scene.add(doorGroup1);  //同理增加右侧大门...

最初简略整顿一下整个动画过程吧,大略就是大门关上-》机器人挥手-》跑向炮竹-》炮竹腾飞-》烟花绽放。动画用的是tweenjs库,api挺简略的,我就不多说了。

渲染

首先创立WebGLRenderer渲染器,设置设施像素比、开启暗影等操作,最初把domElement也就是一个canvas增加到页面里。
而后又创立了一个update办法,调用requestAnimationFrame来更新动画,为了不便查看,创立了OrbitControls轨道控制器,能够使相机围绕指标(0,0,0)进行轨道静止。

//App.vue  const createRender = ()=>{    renderer = new THREE.WebGLRenderer({ antialias: true });    renderer.setPixelRatio(window.devicePixelRatio);    renderer.setSize(window.innerWidth,window.innerHeight);    renderer.shadowMap.enabled = true;    container.value.appendChild(renderer.domElement);  }  const updateRender = ()=>{    requestAnimationFrame(updateRender)    renderer.render(scene, camera);    orbitControls&&orbitControls.update();    TWEEN.update();    let time = clock.getDelta()    mixer&&mixer.update(time)  }

onMounted

下面的办法都创立好了,最初在onMounted调用就能够了,展现一个三维场景,根本就是上面几步。

//App.vueonMounted(()=>{  createScene(); //创立场景  createCamera(); //创立相机  createLight(); //创立光照  createMesh(); //加载模型  createRender(); //创立渲染  createControl();   updateRender(); //更新渲染  window.addEventListener('resize', onWindowResize, false);})

最初

附源码地址:https://github.com/chencld/th...

好久没写文章了,拖拖拉拉了半个月,终于写完了,码字不易,还请大家多多点赞,也欢送讨论区交换,谢谢~