张鑫旭-鑫空间-鑫生活
it's my whole life!备份内容浏览
初识Three.js
小龙
前言
小明是一名程序员,小红是小明的女朋友。一天晚上,他们在床上重温《海贼王》的“顶上战争”。
突然,小红说,我不想艾斯死。小明心头一震,“我也不想艾斯死!!!”。小明默默地把这件事情记在心里。为了完成他们两个人共同的愿望,小明苦思冥想,日夜奔波,时间也一点点地过去。
一天晚上,小明把小红叫到身边,然后打开了电脑,在浏览器地址栏上敲下下面的地址:
http://one-pieces.me/threejs-practice/one-piece-top-war/index.html
神奇的事情发生了!!!
雾草!艾斯出现!!!
滑一滑鼠标,还有七武海!!!
雾草!好叼!小红激动了,抓着小明的手,小明脸上洋溢着自豪和感动!!!
激动之余,小红问:“这是怎么做到的?”
这时小明略带诡异地笑了:“想知道吗?”
“想。”
“用 Three.js 啊。”小明自信地说道。
“ Three.js ?”身为程序员的女朋友,小红还是懂什么叫 js,但何为 Three.js(三 .js ?),她不清楚。
“想知道吗?有点复杂哦。”
“不怕,我想听。”小红说道。
“好,那我就简单点说。”小明作敲黑板状(灯光 ok,摄像 ok,音效 ok,请开始你的表演,小明!)。
Three.js, WebGL 与 OpenGL
提到 Three.js,就必须说一下 OpenGL 和 WebGL。OpenGL 大概许多人都有所听闻,它是最常用的跨平台图形处理开源库。
WebGL 就是基于 OpenGL 设计的面向 web 的 3D 图形标准,它提供了一系列 JavaScript API,通过这些 API 进行图形渲染,系统硬件会加速 3D 渲染,从而获得较高性能。
而 Three.js 是 JavaScript 编写的 WebGL 第三方库,通过对 WebGL 接口的封装与简化而形成的一个易用的图形库。
WebGL 与 Three.js 对比
通过上面的简介,我们知道 WebGL 和 Three.js 都可以进行 Web 端的 3D 图形开发。那问题来了,既然我们有了 WebGL,为什么还需要 Three.js?这是因为前端工程师想要短时间上手 WebGL 还是挺有难度的。
WebGL 门槛相对较高,计算机图形学需要相对较多的数学知识。一个前端程序员或许还熟悉解析几何,但是还熟悉线性代数的应该寥寥无几了(比如求个逆转置矩阵试试?),更何况使用中强调矩阵运算中的物理意义,这在教学中也是比较缺失。
于是,Three.js 对 WebGL 提供的接口进行了非常好的封装,简化了很多细节,大大降低了学习成本。并且,几乎没有损失 WebGL 的灵活性。
因此,从 Three.js 入手是值得推荐的,这可以让你在较短的学习后就能面对大部分需求场景。
Three.js 中的一些概念
想在屏幕上展示 3D 物体,大体上的思路是这样的:- 创建一个三维空间,Three.js 称之为场景( Scene )
- 确定一个观察点,并设置观察的方向和角度,Three.js 称之为相机( Camera )
- 在场景中添加供观察的物体,Three.js 中有很多种物体,如 Mesh、Group、Line 等,他们都继承自 Object3D 类。
- 最后我们需要把所有的东西渲染到屏幕上,这就是 Three.js 中的 Renderer 的作用。
下面来仔细看看这些概念吧。
Scene
放置所有物体的空间容器,对应现实的三维空间。创建一个场景也很简单,只需直接 new 一个 Scene 类即可。Camera
Camera 相机,这个很好理解。“所见即所得”。虽然我是一个唯物主义者,不过只有被看到才能被感知。相机就相当于我们的眼睛,为了观察这个世界,我们需要描述某个物体的位置。描述物体位置需要用到坐标系。常用的坐标系有左手坐标系和右手坐标系。
Three.js 采用的是右手坐标系。
Three.js 中一共有四种相机,分别为 CubeCamera、OrthographicCamera、PerspectiveCamera、StereoCamera,它们都继承自 Camera 类。我们常用的有两种,正投影相机 THREE.OrthographicCamera 和透视投影相机 THREE.PerspectiveCamera。
三维投影
正投影相机 THREE.OrthographicCamera 和透视投影相机 THREE.PerspectiveCamera,相信学过绘画的人一下子就能明白,它们对应三维投影中的正交投影和透视投影。
上面左图是正交投影,物体反射的光平行投射到屏幕上,其大小始终不变,所以远近的物体大小一样。在渲染一些 2D 效果和 UI 元素的时候会用到。右图是透视投影,符合我们平时看东西的感觉,近大远小,经常用在 3D 场景中。
视景体
视景体是一个比较重要的概念。它是指成像景物所在空间的集合。简单点说,视景体是一个几何体,只有在视景体内的物体才会被我们看到,视景体之外的物体将被裁剪掉(所见即所得)。这是为了去除不必要的计算。通过变换视景体,我们就得到不同的相机。正交投影相机 OrthographicCamera 的视景体是一个长方体,其构造函数为 OrthographicCamera( left, right, top, bottom, near, far )。把 Camera 看作一个点,left 则表示视景体左平面在左右方向上与 Camera 的距离,另外几个参数同理。于是六个参数分别定义了视景体六个面的位置。我们可以近似地认为,视景体里的物体平行投影到近平面上,然后近平面上的图像被渲染到屏幕上。
透视投影相机 PerspectiveCamera 的视景体是一个四棱台,其构造函数为 PerspectiveCamera( fov, aspect, near, far )。fov 即 field of view,即视野,对应着图中的视角,是上下两面的夹角。aspect 是近平面的宽高比。再加上近平面距离 near,远平面距离 far,就可以唯一确定这个视景体了。
Objects
Objects 就是三维空间里的物体。Three.js 中提供了很多类型的物体,它们都继承自 Object3D 类,这里我们只看 Mesh。Mesh
有时当你察觉不到时,它就不在。这一点在计算机图形学中得到充分地体现。在计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。当线段数量越多,长度就越短,当达到你无法察觉这是线段时,一条平滑的弧线就出现了。
计算机的三维模型也是类似的。只不过线段变成了平面,普遍用三角形组成的网格来描述。我们把这种模型称之为 Mesh 模型。
这就是在 3D 图形处理中,与图像处理领域的lena图齐名的斯坦福兔子。随着三角形数量的增加,它的表面将会越来越平滑。
在 Three.js 中,Mesh 的构造函数为 Mesh( geometry, material )。geometry 是它的形状,material 是它的材质。
Geometry
Three.js 中有很多种形状 geometry,立方体、平面、球体、圆形、圆柱、圆台等许多基本形状。Geometry 通过存储模型中的点集和点间关系(哪些点构成一个三角形)来描述物体形状。因此我们也可以通过自己定义每个点的位置来构造形状。我们还可以通过导入外部的模型文件来构造更加复杂的形状。
Material
这里的材质不仅仅指物体纹理,而是物体表面除了形状以外所有可视属性的集合,例如色彩、纹理、光滑度、透明度、反射率、折射率、发光度。
讲到材质( Material ),就还需要再讲一下贴图( Map )、纹理( Texture )。
材质上面已经提到了,它包括了贴图以及其它。
贴图其实是“贴”和“图”,它包括了图片和图片应当贴到什么位置。
纹理嘛,其实就是“图”了。
Three.js 提供了多种材质可供选择,能够自由地选择漫反射 / 镜面反射等材质。
Light
神说:要有光!
光影效果可以让画面更丰富。
Three.js 提供了包括环境光 AmbientLight、点光源 PointLight、聚光灯 SpotLight、方向光 DirectionalLight、半球光 HemisphereLight 等多种光源。
只要在场景中添加需要的光源就好了。
代码实现
好了,前面说了这么多,总该写代码了~~根据上面说的,我们首先需要创建一个场景( Scene )、相机( Camera )、渲染器( Renderer )。
function init() {
// 初始化场景,添加场景内物体
var scene = initScene();
// 初始化整个流程,创建相机、渲染器
view = initView(scene);
controls = new THREE.TrackballControls( view.camera );
// 渲染
animate();
}
initScene()
首先 new 一个 THREE.Scene。
// 创建场景Scene
var scene = new THREE.Scene();
// 添加环境光
scene.add(new THREE.AmbientLight(0x3f2806));
// 添加方向光
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 100, 100);
scene.add(light);
// 设置地面
// 地面纹理
var textureSquares = textureLoader.load( "./imgs/bright_squares256.png" );
设置地面的材质和形状,将其添加到场景中。
// 地面材质
var groundMaterial = new THREE.MeshPhongMaterial( { shininess: 80, color: 0xffffff, specular: 0xffffff, map: textureSquares} );
// 地面形状
var planeGeometry = new THREE.PlaneBufferGeometry( 100, 100 );
// 生成地面
var ground = new THREE.Mesh( planeGeometry, groundMaterial );
scene.add( ground );
然后我们将人物放置到场景中。
labeldata.forEach(function(item) {
textureLoader.load(item.imgurl, function(texture) {
// THREE.BoxBufferGeometry为立方体,其构造函数参数对应立方体在 X 轴的宽度,Y 轴的高度,Z 轴的长度
var geometry = new THREE.BoxBufferGeometry(item.size[0], item.size[1], item.size[2]);
// 兰伯特材质,这种材质对光照有反应,用于创建暗淡的不发光的物体
var material = new THREE.MeshLambertMaterial({ map: texture });
var dotmesh = new THREE.Mesh(geometry, material);
// 设置人物位置
dotmesh.position.set(item.position[0], item.position[1], item.position[2]);
scene.add(dotmesh);
});
});
注:labeldata 是一个包含人物物体数据的数组。
最后返回这个场景实例。
return scene;
initView( scene )
下面我们还需要相机( Camera )、渲染器( Renderer )。
创建相机( Camera )。
// 创建相机
var camera = new THREE.PerspectiveCamera(FOV, SCREEN_WIDTH / SCREEN_HEIGHT, NEAR, FAR);
// 设置相机位置
camera.position.set(0, 0, 300);
// 将相机添加到场景
scene.add(camera);
// 创建渲染器
var renderer = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: true});
// 设置渲染器参数
renderer.setClearColor(0xf0f0f0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(SCREEN_WIDTH/2, SCREEN_HEIGHT);
renderer.domElement.style.position = 'relative’;
renderer.domElement.id = 'renderer_logzbuf';
var framecontainer = document.getElementById('container_logzbuf’);
// 将渲染器添加到html容器中
framecontainer.appendChild(renderer.domElement);
return { container: framecontainer, renderer: renderer, scene: scene, camera: camera };
animate()
上面我们所做的,不过是静态地往场景里添加元素。如果我们想让元素移动,就需要实现动画。一个简单实现动画的原理是,不断更新屏幕画面,animate() 做的就是这个。 在 animate() 里调用 requireAnimateFrame( animate ),就能实现画面的循环更新。
这里使用了 requireAnimateFrame,它是浏览器的一个原生方法。使用这个方法可以告诉浏览器我们需要播放动画,需要在每帧页面刷新时,调用某个函数。如果在这个函数里去有规律地改变物体的位置,那么物体看起来就像在移动。
function animate() {
// 帧动画
requestAnimationFrame(animate);
// 更新控制器
controls.update();
// 更新屏幕画面
render();
}
我们在 render() 函数里进行改变物体属性的操作。
function render() {
updateRendererSizes();
view.renderer.render(view.scene, view.camera);
}
结束
至此,主要的代码结构已介绍完毕。完整的代码请看one-pieces/threejs-practice。最后让我们重温一下我们的例子吧!
One Piece - Top war
后语
经过小明的一顿巴拉巴拉,小红终于明白了 Three.js的作用以及强大。小红仰慕地看着小明,小明微笑地看着小红。说时迟那时快,突然“啪”地一声,床塌了。
小明一下惊醒,一身是汗。环顾四周,独身一人。床没塌,很安静。
“又做梦了......”小明哀叹道。
“程序员怎么可能有女朋友......”
未完待续。
引用
three.js / documentationhttps://threejs.org/docs/index.html#manual/introduction/Creating-a-sceneThree.js快速入门教程_javascript技巧_脚本之家相关推荐
http://www.cnblogs.com/wanbo/p/6754066.htmlPS: 备份内容仅显示纯文字。