three.js制作全景图_“飞行员”的制作:使用Three.js动画制作基本的3D场景
three.js制作全景图
Today, we are going to create a simple 3D flying plane using Three.js, a 3D library that makes WebGL simpler. WebGL is a pretty unknown world for many developers because of the complexity and syntax of GLSL. But With Three.js, 3D in the browser becomes very easy to implement.
今天,我们将使用Three.js(一个使WebGL更简单的3D库)创建一个简单的3D飞行飞机。 由于GLSL的复杂性和语法,对于许多开发人员而言,WebGL是一个鲜为人知的世界。 但是使用Three.js,浏览器中的3D变得非常容易实现。
In this tutorial we’ll create a simple 3D scene with a few interactions in two major parts. In the first part we will explain the basics of Three.js and how to set up a very simple scene. The second part will go into some details on how to refine the shapes, how to add some atmosphere and better movements to the different elements of the scene.
在本教程中,我们将创建一个简单的3D场景,在两个主要部分进行一些交互。 在第一部分中,我们将解释Three.js的基础知识以及如何设置一个非常简单的场景。 第二部分将详细介绍如何优化形状,如何为场景的不同元素添加气氛和更好的运动。
Beyond the scope of this tutorial is the entire game, but you can download it and check out the code; it contains many interesting additional parts like the collisions, grabbing coins and increasing a score.
整个游戏都超出了本教程的范围,但是您可以下载它并查看代码。 它包含许多有趣的附加部分,例如碰撞,抓取硬币和增加得分。
In this tutorial we will focus on some basic concepts that will get you started in the world of WebGL with Three.js!
在本教程中,我们将重点介绍一些基本概念,这些概念将使您通过Three.js入门WebGL!
Let’s get started right away!
让我们马上开始!
HTML和CSS (The HTML & CSS)
This tutorial uses mainly the Three.js library, which makes WebGL easy to use. Check out the website and GitHub repo to get some more info on it.
本教程主要使用Three.js库,该库使WebGL易于使用。 查看网站和GitHub存储库以获取更多信息。
The first thing to do is to import the library in your HTML header:
首先要做的是在您HTML标头中导入库:
<script type="text/javascript" src="js/three.js"></script>
Then you need to add a container element in the HTML to hold the rendered scene:
然后,您需要在HTML中添加一个容器元素来保存渲染的场景:
<div id="world"></div>
You can simply style it like the following to make it fill the entire viewport:
您可以像下面这样简单地设置样式,以使其充满整个视口:
#world {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
background: linear-gradient(#e4e0ba, #f7d9aa);
}
As you can see, the background has a subtle gradient that will resemble the sky.
如您所见,背景具有类似于天空的微妙渐变。
And that’s it for the markup and style!
这就是标记和样式!
JavaScript (The JavaScript)
Three.js is very easy to use if you have some basic knowledge of JavaScript. Let’s have a look at the different parts of the code we are going to implement.
如果您具有JavaScript的一些基本知识,Three.js将非常易于使用。 让我们看一下我们将要实现的代码的不同部分。
调色板 (The Color Palette)
Before starting to code the scene, I always find it very useful to define a color palette that will be used consistently throughout the project. For this project we choose the following colors:
在开始编写场景代码之前,我总是发现定义一个在整个项目中一致使用的调色板非常有用。 对于此项目,我们选择以下颜色:
var Colors = {
red:0xf25346,
white:0xd8d0d1,
brown:0x59332e,
pink:0xF5986E,
brownDark:0x23190f,
blue:0x68c3c0,
};
代码的结构 (The Structure of the Code)
Although the JavaScript code is pretty verbose, its structure is quite simple. All the main functions we need to create are put into the init function:
尽管JavaScript代码非常冗长,但是其结构却非常简单。 我们需要创建的所有主要功能都放在init函数中:
window.addEventListener('load', init, false);
function init() {
// set up the scene, the camera and the renderer
createScene();
// add the lights
createLights();
// add the objects
createPlane();
createSea();
createSky();
// start a loop that will update the objects' positions
// and render the scene on each frame
loop();
}
设置场景 (Setting up the Scene)
To create a Three.js project, we’ll need at least the following:
要创建Three.js项目,我们至少需要满足以下条件:
-
A scene: consider this as the stage where every object needs to be added in order to be rendered
场景:将其视为需要添加每个对象才能进行渲染的阶段
-
A camera: in this case we will use a perspective camera, but it could also be an orthographic camera.
相机:在这种情况下,我们将使用透视相机,但也可以是正交相机。
-
A renderer that will display all the scene using WebGL.
一个渲染器,它将使用WebGL显示所有场景。
-
One or more objects to render, in our case, we will create a plane, a sea and a sky (a few clouds)
要渲染一个或多个对象,在我们的示例中,我们将创建一个平面,一个海洋和一个天空(几朵云)
-
One or more lights: there is also different types of lights available. In this project we will mainly use a hemisphere light for the atmosphere and a directional light for the shadows.
一个或多个灯:也有不同类型的灯可用。 在这个项目中,我们将主要使用半球灯作为大气,使用定向光作为阴影。
The scene, the camera, and the renderer are created in the createScene function:
场景,相机和渲染器是在createScene函数中创建的:
var scene,
camera, fieldOfView, aspectRatio, nearPlane, farPlane, HEIGHT, WIDTH,
renderer, container;
function createScene() {
// Get the width and the height of the screen,
// use them to set up the aspect ratio of the camera
// and the size of the renderer.
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
// Create the scene
scene = new THREE.Scene();
// Add a fog effect to the scene; same color as the
// background color used in the style sheet
scene.fog = new THREE.Fog(0xf7d9aa, 100, 950);
// Create the camera
aspectRatio = WIDTH / HEIGHT;
fieldOfView = 60;
nearPlane = 1;
farPlane = 10000;
camera = new THREE.PerspectiveCamera(
fieldOfView,
aspectRatio,
nearPlane,
farPlane
);
// Set the position of the camera
camera.position.x = 0;
camera.position.z = 200;
camera.position.y = 100;
// Create the renderer
renderer = new THREE.WebGLRenderer({
// Allow transparency to show the gradient background
// we defined in the CSS
alpha: true,
// Activate the anti-aliasing; this is less performant,
// but, as our project is low-poly based, it should be fine :)
antialias: true
});
// Define the size of the renderer; in this case,
// it will fill the entire screen
renderer.setSize(WIDTH, HEIGHT);
// Enable shadow rendering
renderer.shadowMap.enabled = true;
// Add the DOM element of the renderer to the
// container we created in the HTML
container = document.getElementById('world');
container.appendChild(renderer.domElement);
// Listen to the screen: if the user resizes it
// we have to update the camera and the renderer size
window.addEventListener('resize', handleWindowResize, false);
}
As the screen size can change, we need to update the renderer size and the camera aspect ratio:
随着屏幕尺寸的变化,我们需要更新渲染器尺寸和相机宽高比:
function handleWindowResize() {
// update height and width of the renderer and the camera
HEIGHT = window.innerHeight;
WIDTH = window.innerWidth;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
}
灯光 (The Lights)
Lightning is certainly one of the trickiest parts when it comes to setting up a scene. The lights will set the mood of the whole scene and must be determined carefully. At this step of the project, we will just try to make the lightning good enough to make the objects visible.
在设置场景时,闪电无疑是最棘手的部分之一。 灯光会影响整个场景的气氛,必须仔细确定。 在项目的这一步,我们将尝试使闪电足够好以使对象可见。
var hemisphereLight, shadowLight;
function createLights() {
// A hemisphere light is a gradient colored light;
// the first parameter is the sky color, the second parameter is the ground color,
// the third parameter is the intensity of the light
hemisphereLight = new THREE.HemisphereLight(0xaaaaaa,0x000000, .9)
// A directional light shines from a specific direction.
// It acts like the sun, that means that all the rays produced are parallel.
shadowLight = new THREE.DirectionalLight(0xffffff, .9);
// Set the direction of the light
shadowLight.position.set(150, 350, 350);
// Allow shadow casting
shadowLight.castShadow = true;
// define the visible area of the projected shadow
shadowLight.shadow.camera.left = -400;
shadowLight.shadow.camera.right = 400;
shadowLight.shadow.camera.top = 400;
shadowLight.shadow.camera.bottom = -400;
shadowLight.shadow.camera.near = 1;
shadowLight.shadow.camera.far = 1000;
// define the resolution of the shadow; the higher the better,
// but also the more expensive and less performant
shadowLight.shadow.mapSize.width = 2048;
shadowLight.shadow.mapSize.height = 2048;
// to activate the lights, just add them to the scene
scene.add(hemisphereLight);
scene.add(shadowLight);
}
As you can see here, a lot of parameters are used to create the lights. Do not hesitate to experiment with the colors, intensities and number of lights; you’ll discover interesting moods and ambiances for your scene and get a feel for how to tune them for your needs.
如您在这里看到的,许多参数用于创建灯光。 不要犹豫,尝试一下颜色,强度和数量。 您会发现场景中有趣的心情和氛围,并能感觉到如何根据需要进行调整。
使用Three.js创建对象 (Creating an Object with Three.js)
Three.js has already a great number of ready-to-use primitives like a cube, a sphere, a torus, a cylinder and a plane.
Three.js已经有大量现成的原语,例如立方体,球体,圆环,圆柱体和平面。
For our project, all the objects we will create are simply a combination of these primitives. That’s perfectly fitting for a low-poly style, and it will spare us from having to model the objects in a 3D software.
对于我们的项目,我们将创建的所有对象只是这些原语的组合。 这非常适合低多边形样式,这使我们不必在3D软件中为对象建模。
一个简单的海上缸 (A Simple Cylinder for the Sea)
Let’s start with creating the sea as it is the easiest object we have to deal with. To keep things simple for now, we will illustrate the sea as a simple blue cylinder placed at the bottom of the screen. Later on we will dive into some details on how to refine this shape.
让我们从创建海洋开始,因为它是我们必须处理的最简单的对象。 为了使事情现在变得简单,我们将海洋说明为放置在屏幕底部的简单蓝色圆柱体。 稍后,我们将深入探讨如何改进此形状的一些细节。
Next, let’s make the sea look a bit more attractive and the waves more realistic:
接下来,让我们使大海看起来更具吸引力,海浪更加逼真:
// First let's define a Sea object :
Sea = function(){
// create the geometry (shape) of the cylinder;
// the parameters are:
// radius top, radius bottom, height, number of segments on the radius, number of segments vertically
var geom = new THREE.CylinderGeometry(600,600,800,40,10);
// rotate the geometry on the x axis
geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2));
// create the material
var mat = new THREE.MeshPhongMaterial({
color:Colors.blue,
transparent:true,
opacity:.6,
shading:THREE.FlatShading,
});
// To create an object in Three.js, we have to create a mesh
// which is a combination of a geometry and some material
this.mesh = new THREE.Mesh(geom, mat);
// Allow the sea to receive shadows
this.mesh.receiveShadow = true;
}
// Instantiate the sea and add it to the scene:
var sea;
function createSea(){
sea = new Sea();
// push it a little bit at the bottom of the scene
sea.mesh.position.y = -600;
// add the mesh of the sea to the scene
scene.add(sea.mesh);
}
Let’s summarize what we need in order to create an object. We need to
让我们总结一下创建对象所需的条件。 我们要
- create a geometry创建几何
- create a material创建材料
- pass them into a mesh将它们传递给网格
- add the mesh to our scene将网格添加到我们的场景
With these basic steps, we can create many different kinds of primitive objects. Now, if we combine them, we can create much more complex shapes.
通过这些基本步骤,我们可以创建许多不同种类的原始对象。 现在,如果我们将它们组合起来,就可以创建更加复杂的形状。
In the following steps we will learn how to do that precisely.
在以下步骤中,我们将学习如何精确地做到这一点。
组合简单的立方体以创建复杂的形状 (Combining Simple Cubes to Create a Complex Shape)
The clouds are a little bit more complex, as they are a number of cubes assembled randomly to form one shape.
云要复杂一些,因为它们是许多随机组装成一个形状的立方体。
Cloud = function(){
// Create an empty container that will hold the different parts of the cloud
this.mesh = new THREE.Object3D();
// create a cube geometry;
// this shape will be duplicated to create the cloud
var geom = new THREE.BoxGeometry(20,20,20);
// create a material; a simple white material will do the trick
var mat = new THREE.MeshPhongMaterial({
color:Colors.white,
});
// duplicate the geometry a random number of times
var nBlocs = 3+Math.floor(Math.random()*3);
for (var i=0; i<nBlocs; i++ ){
// create the mesh by cloning the geometry
var m = new THREE.Mesh(geom, mat);
// set the position and the rotation of each cube randomly
m.position.x = i*15;
m.position.y = Math.random()*10;
m.position.z = Math.random()*10;
m.rotation.z = Math.random()*Math.PI*2;
m.rotation.y = Math.random()*Math.PI*2;
// set the size of the cube randomly
var s = .1 + Math.random()*.9;
m.scale.set(s,s,s);
// allow each cube to cast and to receive shadows
m.castShadow = true;
m.receiveShadow = true;
// add the cube to the container we first created
this.mesh.add(m);
}
}
Now that we have a cloud we will use it to create an entire sky by duplicating it, and placing it at random positions around the z-axis:
现在我们有了云,我们将通过复制它并将其放置在围绕z轴的随机位置来使用它来创建整个天空:
// Define a Sky Object
Sky = function(){
// Create an empty container
this.mesh = new THREE.Object3D();
// choose a number of clouds to be scattered in the sky
this.nClouds = 20;
// To distribute the clouds consistently,
// we need to place them according to a uniform angle
var stepAngle = Math.PI*2 / this.nClouds;
// create the clouds
for(var i=0; i<this.nClouds; i++){
var c = new Cloud();
// set the rotation and the position of each cloud;
// for that we use a bit of trigonometry
var a = stepAngle*i; // this is the final angle of the cloud
var h = 750 + Math.random()*200; // this is the distance between the center of the axis and the cloud itself
// Trigonometry!!! I hope you remember what you've learned in Math :)
// in case you don't:
// we are simply converting polar coordinates (angle, distance) into Cartesian coordinates (x, y)
c.mesh.position.y = Math.sin(a)*h;
c.mesh.position.x = Math.cos(a)*h;
// rotate the cloud according to its position
c.mesh.rotation.z = a + Math.PI/2;
// for a better result, we position the clouds
// at random depths inside of the scene
c.mesh.position.z = -400-Math.random()*400;
// we also set a random scale for each cloud
var s = 1+Math.random()*2;
c.mesh.scale.set(s,s,s);
// do not forget to add the mesh of each cloud in the scene
this.mesh.add(c.mesh);
}
}
// Now we instantiate the sky and push its center a bit
// towards the bottom of the screen
var sky;
function createSky(){
sky = new Sky();
sky.mesh.position.y = -600;
scene.add(sky.mesh);
}
更复杂:制造飞机 (Even More Complex: Creating The Airplane)
The bad news is that the code for creating the airplane is a bit more lengthy and complex. But the good news is that we already learned everything we need to know in order to do it! It’s all about combining and encapsulating shapes.
坏消息是,用于创建飞机的代码更加冗长和复杂。 但是好消息是,我们已经学到了要做这件事所需的一切! 所有这些都是关于组合和封装形状的。
var AirPlane = function() {
this.mesh = new THREE.Object3D();
// Create the cabin
var geomCockpit = new THREE.BoxGeometry(60,50,50,1,1,1);
var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
var cockpit = new THREE.Mesh(geomCockpit, matCockpit);
cockpit.castShadow = true;
cockpit.receiveShadow = true;
this.mesh.add(cockpit);
// Create the engine
var geomEngine = new THREE.BoxGeometry(20,50,50,1,1,1);
var matEngine = new THREE.MeshPhongMaterial({color:Colors.white, shading:THREE.FlatShading});
var engine = new THREE.Mesh(geomEngine, matEngine);
engine.position.x = 40;
engine.castShadow = true;
engine.receiveShadow = true;
this.mesh.add(engine);
// Create the tail
var geomTailPlane = new THREE.BoxGeometry(15,20,5,1,1,1);
var matTailPlane = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
var tailPlane = new THREE.Mesh(geomTailPlane, matTailPlane);
tailPlane.position.set(-35,25,0);
tailPlane.castShadow = true;
tailPlane.receiveShadow = true;
this.mesh.add(tailPlane);
// Create the wing
var geomSideWing = new THREE.BoxGeometry(40,8,150,1,1,1);
var matSideWing = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
var sideWing = new THREE.Mesh(geomSideWing, matSideWing);
sideWing.castShadow = true;
sideWing.receiveShadow = true;
this.mesh.add(sideWing);
// propeller
var geomPropeller = new THREE.BoxGeometry(20,10,10,1,1,1);
var matPropeller = new THREE.MeshPhongMaterial({color:Colors.brown, shading:THREE.FlatShading});
this.propeller = new THREE.Mesh(geomPropeller, matPropeller);
this.propeller.castShadow = true;
this.propeller.receiveShadow = true;
// blades
var geomBlade = new THREE.BoxGeometry(1,100,20,1,1,1);
var matBlade = new THREE.MeshPhongMaterial({color:Colors.brownDark, shading:THREE.FlatShading});
var blade = new THREE.Mesh(geomBlade, matBlade);
blade.position.set(8,0,0);
blade.castShadow = true;
blade.receiveShadow = true;
this.propeller.add(blade);
this.propeller.position.set(50,0,0);
this.mesh.add(this.propeller);
};
Don’t worry, later on we will see how to refine the shapes of the airplane to make it look much better!
不用担心,稍后我们将看到如何优化飞机的形状以使其看起来更好!
Now, we can instantiate the airplane and add it to our scene:
现在,我们可以实例化飞机并将其添加到场景中:
var airplane;
function createPlane(){
airplane = new AirPlane();
airplane.mesh.scale.set(.25,.25,.25);
airplane.mesh.position.y = 100;
scene.add(airplane.mesh);
}
渲染图 (Rendering)
We have created a couple of objects and added them to our scene. But if you try to run the game, you won’t be able to see anything! That’s because we still have to render the scene. We can simply do that by adding this line of code:
我们创建了两个对象,并将它们添加到场景中。 但是,如果您尝试运行游戏,将看不到任何东西! 那是因为我们仍然必须渲染场景。 我们可以简单地通过添加以下代码行来做到这一点:
renderer.render(scene, camera);
动画 (Animation)
Let’s bring some life to our scene by making the airplane’s propeller spin and by rotating the sea and the clouds.
通过使飞机的螺旋桨旋转并旋转海洋和云层,让我们的生活更加生动有趣。
For this we will need an infinite loop:
为此,我们将需要一个无限循环:
function loop(){
// Rotate the propeller, the sea and the sky
airplane.propeller.rotation.x += 0.3;
sea.mesh.rotation.z += .005;
sky.mesh.rotation.z += .01;
// render the scene
renderer.render(scene, camera);
// call the loop function again
requestAnimationFrame(loop);
}
As you can see, we have moved the call to the render method to the loop function. That’s because each change we make to an object needs to be rendered again.
如您所见,我们已经将对render方法的调用移到了循环函数中。 这是因为我们对对象所做的每次更改都需要重新呈现。
跟随鼠标:添加交互 (Follow the Mouse: Adding Interaction)
At this moment, we can see our airplane placed in the center of the scene. What we want to achieve next, is to make it follow the mouse movements.
这时,我们可以看到飞机被放置在场景的中心。 接下来我们要实现的就是使其跟随鼠标的移动。
Once the document is loaded, we need to add a listener to the document, to check if the mouse is moving. For that, we’ll modify the init function as follows:
加载文档后,我们需要在文档中添加一个侦听器,以检查鼠标是否在移动。 为此,我们将修改init函数,如下所示:
function init(event){
createScene();
createLights();
createPlane();
createSea();
createSky();
//add the listener
document.addEventListener('mousemove', handleMouseMove, false);
loop();
}
Additionally, we’ll create a new function to handle the mousemove event:
此外,我们将创建一个新函数来处理mousemove事件:
var mousePos={x:0, y:0};
// now handle the mousemove event
function handleMouseMove(event) {
// here we are converting the mouse position value received
// to a normalized value varying between -1 and 1;
// this is the formula for the horizontal axis:
var tx = -1 + (event.clientX / WIDTH)*2;
// for the vertical axis, we need to inverse the formula
// because the 2D y-axis goes the opposite direction of the 3D y-axis
var ty = 1 - (event.clientY / HEIGHT)*2;
mousePos = {x:tx, y:ty};
}
Now that we have a normalized x and y position of the mouse, we can move the airplane properly.
现在,我们已经将鼠标的x和y位置标准化了,我们可以正确地移动飞机了。
We need to modify the loop and add a new function to update the airplane:
我们需要修改循环并添加新功能以更新飞机:
function loop(){
sea.mesh.rotation.z += .005;
sky.mesh.rotation.z += .01;
// update the plane on each frame
updatePlane();
renderer.render(scene, camera);
requestAnimationFrame(loop);
}
function updatePlane(){
// let's move the airplane between -100 and 100 on the horizontal axis,
// and between 25 and 175 on the vertical axis,
// depending on the mouse position which ranges between -1 and 1 on both axes;
// to achieve that we use a normalize function (see below)
var targetX = normalize(mousePos.x, -1, 1, -100, 100);
var targetY = normalize(mousePos.y, -1, 1, 25, 175);
// update the airplane's position
airplane.mesh.position.y = targetY;
airplane.mesh.position.x = targetX;
airplane.propeller.rotation.x += 0.3;
}
function normalize(v,vmin,vmax,tmin, tmax){
var nv = Math.max(Math.min(v,vmax), vmin);
var dv = vmax-vmin;
var pc = (nv-vmin)/dv;
var dt = tmax-tmin;
var tv = tmin + (pc*dt);
return tv;
}
Congratulations, with this, you’ve made the airplane follow your mouse movements! Have a look at what we have achieved so far: Demo of part 1
恭喜,您已使飞机跟随鼠标的移动! 看看我们到目前为止所取得的成就:第一部分的演示
(几乎)完成!((Almost) Done!)
As you can see, Three.js helps tremendously with creating WebGL content. You don’t need to know a lot to set up a scene and render a few custom objects. Until now you’ve learned a couple of basic concepts and with this you can already start getting the hang of it by tweaking a few parameters like the light intensity, the fog color and the size of the objects. Maybe you are even comfortable with creating some new objects by now?
如您所见,Three.js在创建WebGL内容方面有很大帮助。 您无需了解太多内容即可设置场景并渲染一些自定义对象。 到现在为止,您已经学习了两个基本概念,并且通过调整一些参数(例如光强度,雾色和对象的大小),已经可以掌握这些基本概念。 也许您现在对创建一些新对象感到满意?
If you would like to learn some more in-depth techniques, continue reading as you are about to learn how to refine the 3D scene, make the airplane move much more smoothly, and simulate a low-poly wave effect on the sea.
如果您想学习一些更深入的技术,请继续阅读,以学习如何完善3D场景,使飞机更平稳地移动以及模拟海上的低多边形波浪效应。
更酷的飞机! (A Cooler Airplane!)
Well, the airplane we have created previously is very basic. We know now how to create objects and combine them but we still need to learn how to modify a primitive to make it fit to our needs better.
好吧,我们之前制造的飞机非常基础。 现在我们知道如何创建对象并将它们组合在一起,但是我们仍然需要学习如何修改基元以使其更好地适应我们的需求。
A cube, for example, can be modified by moving its vertices. In our case we want to make it look more like a cockpit.
例如,可以通过移动立方体的顶点来修改它。 在我们的情况下,我们希望使其看起来更像座舱。
Let’s take a look at the cockpit part of the airplane and see how we can make it narrower in the back:
让我们看一下飞机的座舱部分,看看如何使它的后部变窄:
// Cockpit
var geomCockpit = new THREE.BoxGeometry(80,50,50,1,1,1);
var matCockpit = new THREE.MeshPhongMaterial({color:Colors.red, shading:THREE.FlatShading});
// we can access a specific vertex of a shape through
// the vertices array, and then move its x, y and z property:
geomCockpit.vertices[4].y-=10;
geomCockpit.vertices[4].z+=20;
geomCockpit.vertices[5].y-=10;
geomCockpit.vertices[5].z-=20;
geomCockpit.vertices[6].y+=30;
geomCockpit.vertices[6].z+=20;
geomCockpit.vertices[7].y+=30;
geomCockpit.vertices[7].z-=20;
var cockpit = new THREE.Mesh(geomCockpit, matCockpit);
cockpit.castShadow = true;
cockpit.receiveShadow = true;
this.mesh.add(cockpit);
This is an example of how to manipulate a shape to adjust it for our needs.
这是一个如何操纵形状以适应我们需要的示例。
If you look at the complete code of the airplane, you will see a couple of more objects like a window and a better looking propeller. Nothing complicated. Try adjusting the values to get a feel for it and make your own version of the plane.
如果您查看飞机的完整代码,将会看到更多的其他物体,例如窗户和外观更好的螺旋桨。 没什么复杂的。 尝试调整值以感受它并制作自己的飞机版本。
但是谁在飞飞机? (But Who is Flying the Plane?)
Adding a pilot to our airplane is just as easy as adding a couple of boxes.
向飞机上添加飞行员就像添加几个盒子一样容易。
But we don’t just want any pilot, we want a cool pilot with windblown, animated hair! It seems like a complicated endeavor, but since we are working on a low-poly scene it becomes a much easier task. Trying to be creative to simulate fluttering hair with only a few boxes will also give a unique touch to your scene.
但是,我们不仅需要任何飞行员,还希望有一个风吹,动画头发的酷飞行员! 这似乎是一项复杂的工作,但是由于我们正在低聚场景中工作,因此这变得容易得多。 尝试发挥创意,仅用几个盒子模拟头发的飘动,也可以给场景带来独特的感觉。
Let’s see how it’s coded:
让我们看看它的编码方式:
var Pilot = function(){
this.mesh = new THREE.Object3D();
this.mesh.name = "pilot";
// angleHairs is a property used to animate the hair later
this.angleHairs=0;
// Body of the pilot
var bodyGeom = new THREE.BoxGeometry(15,15,15);
var bodyMat = new THREE.MeshPhongMaterial({color:Colors.brown, shading:THREE.FlatShading});
var body = new THREE.Mesh(bodyGeom, bodyMat);
body.position.set(2,-12,0);
this.mesh.add(body);
// Face of the pilot
var faceGeom = new THREE.BoxGeometry(10,10,10);
var faceMat = new THREE.MeshLambertMaterial({color:Colors.pink});
var face = new THREE.Mesh(faceGeom, faceMat);
this.mesh.add(face);
// Hair element
var hairGeom = new THREE.BoxGeometry(4,4,4);
var hairMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var hair = new THREE.Mesh(hairGeom, hairMat);
// Align the shape of the hair to its bottom boundary, that will make it easier to scale.
hair.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(0,2,0));
// create a container for the hair
var hairs = new THREE.Object3D();
// create a container for the hairs at the top
// of the head (the ones that will be animated)
this.hairsTop = new THREE.Object3D();
// create the hairs at the top of the head
// and position them on a 3 x 4 grid
for (var i=0; i<12; i++){
var h = hair.clone();
var col = i%3;
var row = Math.floor(i/3);
var startPosZ = -4;
var startPosX = -4;
h.position.set(startPosX + row*4, 0, startPosZ + col*4);
this.hairsTop.add(h);
}
hairs.add(this.hairsTop);
// create the hairs at the side of the face
var hairSideGeom = new THREE.BoxGeometry(12,4,2);
hairSideGeom.applyMatrix(new THREE.Matrix4().makeTranslation(-6,0,0));
var hairSideR = new THREE.Mesh(hairSideGeom, hairMat);
var hairSideL = hairSideR.clone();
hairSideR.position.set(8,-2,6);
hairSideL.position.set(8,-2,-6);
hairs.add(hairSideR);
hairs.add(hairSideL);
// create the hairs at the back of the head
var hairBackGeom = new THREE.BoxGeometry(2,8,10);
var hairBack = new THREE.Mesh(hairBackGeom, hairMat);
hairBack.position.set(-1,-4,0)
hairs.add(hairBack);
hairs.position.set(-5,5,0);
this.mesh.add(hairs);
var glassGeom = new THREE.BoxGeometry(5,5,5);
var glassMat = new THREE.MeshLambertMaterial({color:Colors.brown});
var glassR = new THREE.Mesh(glassGeom,glassMat);
glassR.position.set(6,0,3);
var glassL = glassR.clone();
glassL.position.z = -glassR.position.z
var glassAGeom = new THREE.BoxGeometry(11,1,11);
var glassA = new THREE.Mesh(glassAGeom, glassMat);
this.mesh.add(glassR);
this.mesh.add(glassL);
this.mesh.add(glassA);
var earGeom = new THREE.BoxGeometry(2,3,2);
var earL = new THREE.Mesh(earGeom,faceMat);
earL.position.set(0,0,-6);
var earR = earL.clone();
earR.position.set(0,0,6);
this.mesh.add(earL);
this.mesh.add(earR);
}
// move the hair
Pilot.prototype.updateHairs = function(){
// get the hair
var hairs = this.hairsTop.children;
// update them according to the angle angleHairs
var l = hairs.length;
for (var i=0; i<l; i++){
var h = hairs[i];
// each hair element will scale on cyclical basis between 75% and 100% of its original size
h.scale.y = .75 + Math.cos(this.angleHairs+i/3)*.25;
}
// increment the angle for the next frame
this.angleHairs += 0.16;
}
Now to make the hair move, just add this line to the loop function:
现在要使头发移动,只需将以下行添加到循环函数中:
airplane.pilot.updateHairs();
造浪 (Making Waves)
You have probably noticed that the sea doesn’t really look like a sea, but more like a surface that was flattened by a steamroller.
您可能已经注意到,海洋实际上并不像大海,而更像是被压路机弄平的表面。
It needs some waves. This can be done by combining two techniques we have used earlier:
它需要一些波浪。 这可以通过结合我们先前使用的两种技术来完成:
- Manipulating the vertices of a geometry like we did with the cockpit of the plane. 像处理飞机的驾驶舱一样,操纵几何图形的顶点。
- Applying a cyclic movement to each vertex like we did to move the hair of the pilot. 像我们移动飞行员头发一样,对每个顶点进行循环运动。
-
-
To make waves we will rotate each vertex of the cylinder around its initial position, by giving it a random speed rotation, and a random distance (radius of the rotation). Sorry, but you’ll also need to use some trigonometry here!
为了产生波浪,我们将使圆柱的每个顶点绕其初始位置旋转,方法是使其具有随机速度旋转和随机距离(旋转半径)。 抱歉,但是您还需要在这里使用一些三角函数!
Let’s make a modification to the sea:
让我们对大海进行修改:
Sea = function(){ var geom = new THREE.CylinderGeometry(600,600,800,40,10); geom.applyMatrix(new THREE.Matrix4().makeRotationX(-Math.PI/2)); // important: by merging vertices we ensure the continuity of the waves geom.mergeVertices(); // get the vertices var l = geom.vertices.length; // create an array to store new data associated to each vertex this.waves = []; for (var i=0; i<l; i++){ // get each vertex var v = geom.vertices[i]; // store some data associated to it this.waves.push({y:v.y, x:v.x, z:v.z, // a random angle ang:Math.random()*Math.PI*2, // a random distance amp:5 + Math.random()*15, // a random speed between 0.016 and 0.048 radians / frame speed:0.016 + Math.random()*0.032 }); }; var mat = new THREE.MeshPhongMaterial({ color:Colors.blue, transparent:true, opacity:.8, shading:THREE.FlatShading, }); this.mesh = new THREE.Mesh(geom, mat); this.mesh.receiveShadow = true; } // now we create the function that will be called in each frame // to update the position of the vertices to simulate the waves Sea.prototype.moveWaves = function (){ // get the vertices var verts = this.mesh.geometry.vertices; var l = verts.length; for (var i=0; i<l; i++){ var v = verts[i]; // get the data associated to it var vprops = this.waves[i]; // update the position of the vertex v.x = vprops.x + Math.cos(vprops.ang)*vprops.amp; v.y = vprops.y + Math.sin(vprops.ang)*vprops.amp; // increment the angle for the next frame vprops.ang += vprops.speed; } // Tell the renderer that the geometry of the sea has changed. // In fact, in order to maintain the best level of performance, // three.js caches the geometries and ignores any changes // unless we add this line this.mesh.geometry.verticesNeedUpdate=true; sea.mesh.rotation.z += .005; }
Like we did for the hair of the pilot, we add this line in the loop function:
就像我们为飞行员所做的一样,我们在循环函数中添加以下行:
sea.moveWaves();
Now, enjoy the waves!
现在,享受海浪!
改善场景的照明 (Refining the Lighting of the Scene)
In the first part of this tutorial, we have already set up some lighting. But we would like to add a better mood to the scene, and make the shadows softer. To achieve that we are going to use an ambient light.
在本教程的第一部分中,我们已经设置了一些照明。 但是我们想给场景增加更好的心情,并使阴影更柔和。 为此,我们将使用环境光。
In the createLights function we add the following lines:
在createLights函数中,添加以下行:
// an ambient light modifies the global color of a scene and makes the shadows softer ambientLight = new THREE.AmbientLight(0xdc8874, .5); scene.add(ambientLight);
Do not hesitate to play with the color and intensity of the ambient light; it will add a unique touch to your scene.
不要犹豫,以适应环境光的颜色和强度。 它将为您的场景增添独特的触感。
顺畅的飞行 (A Smoother Flight)
Our little plane already follows the mouse movements. But it doesn’t really feel like real flying. When the plane changes its altitude it would be nice if it changed its position and orientation more smoothly. In this final bit of the tutorial we will implement exactly that.
我们的小飞机已经跟随鼠标移动。 但这并没有真正的感觉。 当飞机改变高度时,如果更平稳地改变其位置和方向,那将是很好的选择。 在本教程的最后一部分中,我们将完全实现该目标。
An easy way to do that would be to make it move to a target by adding a fraction of the distance that separates it from this target in every frame.
一种简单的方法是通过在每一帧中添加将其与目标分开的距离的一小部分,使其移动到目标。
Basically, the code would look like this (this is a general formula, don’t add it to your code right away):
基本上,代码看起来像这样(这是一个通用公式,请不要立即将其添加到您的代码中):
currentPosition += (finalPosition - currentPosition)*fraction;
To be more realistic, the rotation of the plane could also change according to the direction of the movement. If the plane goes up very fast, it should quickly rotate counterclockwise. If the planes moves down slowly, it should rotate slowly in a clockwise direction. To achieve exactly that, we can simply assign a proportional rotation value to the remaining distance between the target and the position of the plane.
为了更真实,平面的旋转也可以根据运动的方向而改变。 如果飞机上升得非常快,则应Swift逆时针旋转。 如果飞机缓慢向下移动,则它应沿顺时针方向缓慢旋转。 为此,我们只需将比例旋转值分配给目标与平面位置之间的剩余距离即可。
In our code, the updatePlane function needs to look as follows:
在我们的代码中, updatePlane函数需要如下所示:
function updatePlane(){ var targetY = normalize(mousePos.y,-.75,.75,25, 175); var targetX = normalize(mousePos.x,-.75,.75,-100, 100); // Move the plane at each frame by adding a fraction of the remaining distance airplane.mesh.position.y += (targetY-airplane.mesh.position.y)*0.1; // Rotate the plane proportionally to the remaining distance airplane.mesh.rotation.z = (targetY-airplane.mesh.position.y)*0.0128; airplane.mesh.rotation.x = (airplane.mesh.position.y-targetY)*0.0064; airplane.propeller.rotation.x += 0.3; }
Now the plane movement looks much more elegant and realistic. By changing the fraction values, you can make the plane respond faster or slower to the mouse movement. Have a try and see how it changes.
现在,飞机运动看起来更加优雅和逼真。 通过更改分数值,可以使平面对鼠标移动的响应更快或更慢。 尝试看看它如何变化。
Have a look at this final stage of our scene: Demo of part 2
看看我们场景的最后阶段:第2部分的演示
Well done!
做得好!
然后去哪儿? (Where to Go From Here?)
If you have followed until here, you’ve learned some basic, yet versatile techniques in Three.js that will enable you to create your first scenes. You know now how to create objects from primitives, how to animate them and how to set the lighting of a scene. You’ve also seen how to refine the look and movement of your objects and how to tweak ambient light.
如果您一直遵循到这里,您已经在Three.js中学习了一些基本但通用的技术,这些技术使您可以创建第一个场景。 您现在知道了如何从基元创建对象,如何对其进行动画处理以及如何设置场景的照明。 您还已经了解了如何改善对象的外观和运动以及如何调整环境光。
The next step, which is out of the scope of this tutorial as it involves some more complex techniques, would be to implement a game where concepts like collisions, point collection and level control take place. Download the code and have a look at the implementation; you will see all the concepts you’ve learned so far and also some advanced ones that you can explore and play with. Please note that the game is optimized for desktop use.
下一步(超出本教程的范围,因为它涉及到一些更复杂的技术)将是实现一个游戏,其中发生碰撞,点收集和级别控制等概念。 下载代码并查看实现; 您将看到到目前为止所学的所有概念,以及可以探索和使用的一些高级概念。 请注意,该游戏针对桌面使用进行了优化。
Hopefully, this tutorial helped you get familiar with Three.js and motivated you to implement something on your own. Let me know about your creations, I would love to see what you do!
希望本教程可以帮助您熟悉Three.js,并激发您自己实现一些功能。 让我知道您的创作,很想看看您的作品!
翻译自: https://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/
three.js制作全景图
-