foreword
This year, I didn’t go home due to the epidemic, so I took time to look at WebGL Programming Guide and Three.js Development Guide . In order to practice my hands, I simply realized the fireworks effect of the welcoming pine at the opening ceremony of the Winter Olympics. Bai, this article is mainly to record the practice process, and it also involves the most basic content of threejs, so let's start...
Effect
The rendering is as follows, you can also click the link to preview https://awebgl.vercel.app/ First is an action of opening the door, then the robot is an example of the official website of threejs, and finally the fireworks fly into the sky and explode into a welcome pine Effect.
environment
I use vite+vue3. The steps are as follows. The API of threejs is relatively long. At present, I can't remember it (covering my face), npm installed @types/three, vscode has a code prompt function, and also installed an animation library tween. js.
npm create vite@latest three3d --template vue
cd three3d
npm install @tweenjs/tween.js @types/three three -S
accomplish
Personally, I feel that when I do 3D, I can think of myself as a director (laughing), where is the camera, where is the light, etc. Otherwise, it is easy to encounter a black screen. The following is a sketch of my example. habit to build just fine.
scene and camera
First, you need to create a scene. With the scene, you can add lighting and primitives.
Then this example uses the perspective camera PerspectiveCamera(fov,aspect,near,far), which can provide a 3D visual effect of near and far small, aspect is usually set to the aspect ratio of the canvas, here I select the entire page, So it's window.innerWidth/window.innerHeight, and finally point the camera to the center (0,0,0).
//App.vue
const 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))
}
illumination
The first thing to add is the ambient light. AmbientLight simply superimposes the color of the material and the light color, and then multiplies the light intensity. You can refer to the comment section of the code below. Since it is night, I chose a darker color. Also because it is night, instead of using parallel light like the sun, a spot light source with a street light effect is added to the left and right of the gate.
//App.vue
const 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);
//同理添加右侧灯光...
}
Element Model
The camera lighting stage is set up, and then it's time for our protagonists to appear.
Small branches: You can see the diagram below, select a part of the circle, move it to the origin, then rotate the arc around the z-axis, and generate 4 lines on the left and right. The calculation formula of the point after the rotation can be directly applied with the mathematical formula, which needs to be mentioned here. , threejs uses a right-handed coordinate system, the z-axis is pointing outwards vertically on the screen, so the schematic diagram is rotated θ' counterclockwise around the z-axis, and the four arcs on the right are also negative numbers. Finally, set the scale of this twig to 0, and then zoom in to 1, which simply achieves an explosion effect.
The whole branch of welcoming pine: first clone 60 small branches above, then in a cube area of 16 4 2, randomly select 60 positions and set them to small branches, and finally bloom in 3 batches in turn.
Welcome pine trunk: 5 straight lines and 10 vertical lines, the effect is similar to the above, you can directly look at the source code, I will not introduce it.
//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);
Robot: This is an example from the official website of threejs, link https://threejs.org/examples/#webgl_animation_skinning_morph , and this is because it feels a bit monotonous to open the door and set off fireworks, so I used this robot, waved, Running and jumping. It should be mentioned that when loading the robot model, if the next operation is performed without waiting for the loading to be completed, a black screen will appear.
//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)
Door: What needs to be mentioned here is that the primitives are rotated around the center point. I used a combination group. The door on the left is offset by half the width of the door to the right. Similarly, the door on the right is moved to the left. Offset, and then translate the Group to the left and right by a full width of the door. Finally, the Group rotates around the y-axis to achieve the effect of opening the door. The front of the door is textured, and the other surfaces are red, which is not found on the official website. The corresponding texture order of the cube faces, I tried it should be [right, left, top, bottom, front, back].
//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);
//同理添加右侧大门...
Finally, let’s simply sort out the entire animation process. It is probably the door opening-”the robot waved-”running to the firecrackers-”the firecrackers took off-”the fireworks bloomed. The animation uses the tweenjs library, and the api is quite simple, so I won't say more.
render
First create the WebGLRenderer renderer, set the device pixel ratio, enable shadows and other operations, and finally add the domElement, which is a canvas, to the page.
Then create an update method, call requestAnimationFrame to update the animation, for the convenience of viewing, create OrbitControls orbit controller, which can make the camera orbit around the target (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
The above methods are all created, and finally it can be called in onMounted to display a 3D scene, which is basically the following steps.
//App.vue
onMounted(()=>{
createScene(); //创建场景
createCamera(); //创建相机
createLight(); //创建光照
createMesh(); //加载模型
createRender(); //创建渲染
createControl();
updateRender(); //更新渲染
window.addEventListener('resize', onWindowResize, false);
})
finally
Source code address: https://github.com/chencld/three3d
I haven't written an article for a long time. After dragging it for half a month, I finally finished it. It's not easy to code words. Please give more likes. You are also welcome to communicate in the discussion area, thank you~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。