<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; WebGL</title>
	<atom:link href="https://blog.gmem.cc/tag/webgl/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Mon, 13 Apr 2026 08:03:10 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Three.js学习笔记</title>
		<link>https://blog.gmem.cc/three-js-study-note</link>
		<comments>https://blog.gmem.cc/three-js-study-note#comments</comments>
		<pubDate>Sun, 01 Jan 2017 04:16:22 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebGL]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=14391</guid>
		<description><![CDATA[<p>简介 关于WebGL Web图形库（Web Graphics Library）简称WebGL，是在浏览器环境下进行3D/2D图像渲染的技术。你不需要额外的插件，就可以在HTML5的Canvas上绘制复杂的、可交互的图形。 大部分现代浏览器支持WebGL技术，IE从11开始支持，老版本的IE可以通过第三方插件支持，例如IEWebGL。 WebGL基于OpenGL ES 2.0提供3D图形接口。后者是OpenGL的一个子集，主要针对手机、PDA之类的嵌入式设备。 关于Three.js 直接使用WebGL编程难度较高，需要了解WebGL的细节、学习复杂的着色器（Shader）语言。Three.js对WebGL的底层细节进行了封装，让你更加容易的、仅利用JavaScript语言创建3D图形，你可以： 创建简单/复杂的3D几何图形 在3D场景中动画、移动对象 给对象应用纹理、材质 从3D模型软件中加载对象 Three.js基本概念 术语 说明 场景Scene 存储并跟踪所有待渲染对象的容器。场景被渲染器渲染到一个HTML5画布中 镜头Camera 定义查看场景的视角，有多种实现： <a class="read-more" href="https://blog.gmem.cc/three-js-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/three-js-study-note">Three.js学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<div class="blog_h2"><span class="graybg">关于WebGL</span></div>
<p>Web图形库（Web Graphics Library）简称WebGL，是在浏览器环境下进行3D/2D图像渲染的技术。你不需要额外的插件，就可以在HTML5的Canvas上绘制复杂的、可交互的图形。</p>
<p>大部分现代浏览器支持WebGL技术，IE从11开始支持，老版本的IE可以通过第三方插件支持，例如IEWebGL。</p>
<p>WebGL基于OpenGL ES 2.0提供3D图形接口。后者是OpenGL的一个子集，主要针对手机、PDA之类的嵌入式设备。</p>
<div class="blog_h2"><span class="graybg">关于Three.js</span></div>
<p>直接使用WebGL编程难度较高，需要了解WebGL的细节、学习复杂的着色器（Shader）语言。Three.js对WebGL的底层细节进行了封装，让你更加容易的、仅利用JavaScript语言创建3D图形，你可以：</p>
<ol>
<li>创建简单/复杂的3D几何图形</li>
<li>在3D场景中动画、移动对象</li>
<li>给对象应用纹理、材质</li>
<li>从3D模型软件中加载对象</li>
</ol>
<div class="blog_h2"><span class="graybg">Three.js基本概念</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>场景<br />Scene</td>
<td>存储并跟踪所有待渲染对象的容器。场景被渲染器渲染到一个HTML5画布中</td>
</tr>
<tr>
<td>镜头<br />Camera</td>
<td>
<p>定义查看场景的视角，有多种实现：</p>
<ol>
<li>PerspectiveCamera，基于透视投影（perspective projection）的镜头。透视投影模拟人的视觉效果（近大远小），从某个投射中心（人眼）将物体投射到单一投影面（画面）之上。是最常使用的投影模式</li>
</ol>
</td>
</tr>
<tr>
<td>视截锥<a id="ViewFrustum"></a><br />View Frustum</td>
<td>
<p>在3D计算机图形学中，视截锥是指被建模世界空间的一个区域，该区域可以出现在屏幕中，视截锥定义了概念相机的视界（Field of view，FOV）</p>
<p>使用两个平行的平面，对视野金字塔（pyramid of vision）进行截断操作，即得到视截锥。视截锥的精确形状取决于期望模拟的相机棱镜的形状，典型情况下为六面体，其中远近平面为同长宽比的矩形，如下图：</p>
<p><img class="aligncenter size-full wp-image-14439" src="https://blog.gmem.cc/wp-content/uploads/2017/01/viewfrustum-02.png" alt="viewfrustum-02" width="100%" /> </p>
<p>所谓远、近平面，是指六面体中与视觉方向正交的那两个平面。近平面即上图中标为黄色的那一面。比近平面更近、远平面更远的区域中的对象，不会被绘制。某些情况下远平面被放置到无限远处</p>
<p>视截锥选择（View frustum culling）是指从渲染过程中移除完全位于视截锥之外的对象的处理步骤</p>
</td>
</tr>
<tr>
<td>视界<br />FOV</td>
<td>
<p>在第一人称游戏中，所谓视界（ field of view, field of vision）是指某一时刻游戏世界中显示在屏幕中的（矩形）范围（extent）。视界通常用角度（angle）来描述，但是此角度可能指FOV在垂直、水平、对角线（diagonal）方向的分量</p>
<p>在一定的分辨率下，FOV会依据屏幕纵横比（aspect ratio）而变化，通常FOV在宽屏上更大</p>
<p>我们常以FOV在水平/垂直方向的角度、结合纵横比来描述FOV。它们之间的换算公式如下：</p>
<p style="padding-left: 60px;"><em>r  = w / h = tan(H/2) / tan(V/2)</em></p>
<p>其中r为屏幕纵横比，w/h为屏幕宽高度，H/V为水平、垂直方向的FOV分量</p>
<p>在Three.js中，PerspectiveCamera的fov参数为FOV的垂直分量，这意味着取值从0 ~ 180之间时变化时，如果r保持不变，则视界越大，场景中的目标显得越小</p>
</td>
</tr>
<tr>
<td>渲染器<br />Renderer</td>
<td>负责计算在指定的Camera之下，Scene长得什么样子</td>
</tr>
<tr>
<td>多边形网格<br />Polygon mesh</td>
<td>多边形网格是一系列顶点（vertex）、边线（edge）、面（face）的几何。它在3D计算机图形学中定义了一个多面体的轮廓。网格中的面通常由三角形、四边形或者其它简单的凸面多边形（ convex polygons）构成，以简化渲染的计算量。下面是一个由三角形网格构成的海豚模型示例：<a href="https://blog.gmem.cc/wp-content/uploads/2017/01/Dolphin_triangle_mesh.png"><img class="aligncenter size-full wp-image-14413" src="https://blog.gmem.cc/wp-content/uploads/2017/01/Dolphin_triangle_mesh.png" alt="dolphin_triangle_mesh" width="95%" /></a> </td>
</tr>
<tr>
<td>混合模式<br />Blend mode / <br />Mixing mode</td>
<td>
<p>图像处理中的概念。用于确定两层图像如何叠加到一起。大部分应用中默认的叠加模式就是让顶层（top layer ）直接覆盖较低的层（lower layers ）。由于每个像素的色彩都是基于数字来表示的，因此基于数学运算的大量混合模式可用</p>
<p>大部分图像处理软件，例如Photoshop、GIMP，都支持用户修改混合模式。</p>
<p>参考：<a href="https://en.wikipedia.org/wiki/Blend_modes">https://en.wikipedia.org/wiki/Blend_modes</a></p>
</td>
</tr>
<tr>
<td>粒子，精灵<br />Particles, Sprite</td>
<td>
<p>指存在于3D场景中的二维图形或者动画</p>
</td>
</tr>
<tr>
<td>右手系<br />right-handed system</td>
<td>
<p>Three.js默认使用右手坐标系，因为这是OpenGL默认的坐标系</p>
<p>所谓右手系，是指：</p>
<ol>
<li>伸出右手，伸直拇指，让它与另外四指垂直</li>
<li>弯曲中、无名、小指，让它们与食指垂直</li>
<li>以拇指指向为X轴正向、食指指向为Y轴正向、其它手指指向为Z轴正向的坐标系，即右手系</li>
</ol>
<p>图示如下：</p>
<p><img class="aligncenter size-full wp-image-14527" src="https://blog.gmem.cc/wp-content/uploads/2017/01/right-handed-system.png" alt="right-handed-system" width="220" height="165" /></p>
<p>Blender等3D建模软件，使用Z轴向上（上图右手系沿X轴正向逆时针旋转90度）的右手系。主要原因是大部分CAD软件均使用这样的坐标系</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">第一个3D场景</span></div>
<div class="blog_h2"><span class="graybg">渲染并查看3D对象</span></div>
<p>在本节，我们创建以下几个对象：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">对象</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Plane</td>
<td>平面，二维的矩形，作为“地面”在场景的中央展示</td>
</tr>
<tr>
<td>Cube</td>
<td>三维盒子，展示为红色</td>
</tr>
<tr>
<td>Sphere</td>
<td>三维球体，展示为蓝色</td>
</tr>
<tr>
<td>Camera</td>
<td>镜头，决定你看到的场景是什么样子</td>
</tr>
<tr>
<td>Axes</td>
<td>X/Y/Z轴，辅助的调试工具，方便查看对象在哪里渲染</td>
</tr>
</tbody>
</table>
<p>代码及注释：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title&gt;Three.js Study&lt;/title&gt;
    &lt;script src="https://code.jquery.com/jquery-1.12.4.js"&gt;&lt;/script&gt;
    &lt;script src="three.js"&gt;&lt;/script&gt;
    &lt;style&gt;
        body {
            margin: 0;
            overflow: hidden;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="WebGL"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
    $( function () {
        // 场景
        var scene = new THREE.Scene();

        var aspect = window.innerWidth / window.innerHeight;
        /**
         * 定义一个透视镜头，参数：
         * fov FOV垂直分量，镜头到视截锥近平面上下边之间的夹角
         * aspect 视截锥的纵横比
         * near 近平面离镜头多远
         * far 远平面离镜头多远
         */
        var camera = new THREE.PerspectiveCamera( 45, aspect, 0.1, 1000 );

        // WebGL渲染器，使用显卡来渲染场景。尽管还存在其它渲染器实现，但是处于性能、特性的考虑，不推荐使用
        var renderer = new THREE.WebGLRenderer();
        // 设置背景色，第二参数为透明度
        renderer.setClearColor( 0xEEEEEE, 1 );
        // 设置场景的大小
        renderer.setSize( window.innerWidth, window.innerHeight );

        // 创建一个调试用途的坐标轴，X轴红色、Y轴绿色、Z轴蓝色。20表示轴线长度
        var axes = new THREE.AxisHelper( 20 );
        // 把对象添加到场景中
        scene.add( axes );

        // 创建一个平面几何图形，宽60高20，在宽、高方向上分段数为1（不切分）
        var planeGeometry = new THREE.PlaneGeometry( 60, 20, 1, 1 );
        // 材质，定义颜色、透明度、反光效果之类的属性
        // MeshBasicMaterial是一种简单材质，它不受光线影响，使用纯色或者网格（wireframe）渲染几何图形
        var planeMaterial = new THREE.MeshBasicMaterial( { color: 0xcccccc } );
        // Mesh表示一类基于三角形网格（triangular polygon mesh）的对象
        var plane = new THREE.Mesh( planeGeometry, planeMaterial );
        // 默认的，平面的对称中心位于原点，width与X轴平行，height与Y轴平行
        // 在X轴方向逆时针（从原点往X轴正向看）旋转90度
        plane.rotation.x = -0.5 * Math.PI; // 圆周长2PI，PI代表180度，
        // 在X轴方向偏移15
        plane.position.x = 15;
        plane.position.y = 0;
        plane.position.z = 0;
        scene.add( plane );

        // 类似的，创建一个立方体，类似的，其对称中心也是默认位于原点
        var cubeGeometry = new THREE.CubeGeometry( 4, 4, 4 );
        // wireframe表示绘制网格线
        var cubeMaterial = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
        var cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
        cube.position.x = -4;
        cube.position.y = 3;
        cube.position.z = 0;
        scene.add( cube );

        // 绘制一个球体
        var sphereGeometry = new THREE.SphereGeometry( 4, 20, 20 );
        var sphereMaterial = new THREE.MeshBasicMaterial( { color: 0x7777ff, wireframe: true } );
        var sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
        sphere.position.x = 20;
        sphere.position.y = 4;
        sphere.position.z = 2;
        scene.add( sphere );

        // 移动镜头
        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        // 将镜头指向场景的中心
        camera.lookAt( scene.position );
        
        // 渲染
        $( "#WebGL" ).append( renderer.domElement );
        renderer.render( scene, camera );
    } );
&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p>渲染效果：<img class="aligncenter size-full wp-image-14416" src="https://blog.gmem.cc/wp-content/uploads/2017/01/first-threejs-scene-01.png" alt="first-threejs-scene-01" width="100%" /></p>
<div class="blog_h2"><span class="graybg">添加材质/光影效果</span></div>
<p>本节我们改进一下上面的例子，修改材质，并添加光线、阴影效果。</p>
<p>首先，为场景添加一个光源：</p>
<pre class="crayon-plain-tag">// 聚光灯效果的白色光源
var spotLight = new THREE.SpotLight( 0xffffff );
// 设置聚光灯的位置
spotLight.position.set( -40, 60, -10 );
scene.add( spotLight );</pre>
<p>添加这段代码后，渲染效果不会有任何改变。原因我们已经在前面的代码注释中提到过， MeshBasicMaterial这种材质不会对光线作出反应。我们替换一下材质：</p>
<pre class="crayon-plain-tag">var planeMaterial = new THREE.MeshLambertMaterial( { color: 0xcccccc } );
// ...
var cubeMaterial = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
// ... 
var sphereMaterial = new THREE.MeshLambertMaterial( { color: 0x7777ff} );</pre>
<p>除了MeshLambertMaterial之外，MeshPhongMaterial也会对光源作出反应。</p>
<p>现在刷新一下页面，可以看到如下渲染效果：<img class="aligncenter size-full wp-image-14419" src="https://blog.gmem.cc/wp-content/uploads/2017/01/first-threejs-scene-02.png" alt="first-threejs-scene-02" width="100%" /></p>
<p>比上一幅截图好看多了，但是还有点不自然，因为没有阴影效果。</p>
<p>由于渲染阴影比较消耗资源，因此默认情况下Three.js关闭了阴影。要启用阴影其实很简单：</p>
<pre class="crayon-plain-tag">// 启用阴影效果
renderer.shadowMapEnabled = true;</pre>
<p>此外，你还需要定义什么对象产生（cast）阴影，什么对象接收阴影：</p>
<pre class="crayon-plain-tag">// 阴影由平面接收
plane.receiveShadow = true;
// ...
cube.castShadow = true;
// ...
sphere.castShadow = true;


// 设置产生阴影的光源
spotLight.castShadow = true;
// 提高阴影质量
spotLight.shadowMapWidth = spotLight.shadowMapHeight = 1024 * 4;</pre>
<div class="blog_h2"><span class="graybg">添加动画效果</span></div>
<p>要想为场景添加动画效果，我们需要找到定期重渲染场景的方法。setInterval()这种定时器是不适合的，因为它与渲染行为不是同步的，会导致严重性能问题。</p>
<p><pre class="crayon-plain-tag">requestAnimationFrame()</pre> 是现代浏览器支持的、避免两setInterval()缺点的函数。你可以为它提供一个回调，此回调会定期（间隔由浏览器定义）的被调用。在回调中你可以指定任何渲染逻辑，浏览器负责尽可能平滑、高效的绘制。示例代码：</p>
<pre class="crayon-plain-tag">function renderScene() {
    requestAnimationFrame( renderScene );
    renderer.render( scene, camera );
}</pre>
<p>上面的函数把自己传递给requestAnimationFrame，从而导致函数的逻辑被反复调用，从而可以产生动画效果。</p>
<div class="blog_h3"><span class="graybg">FPS统计</span></div>
<p>为了显示动画帧率信息，我们引入一个助手库<a href="https://github.com/mrdoob/stats.js/">stats.js</a>：</p>
<pre class="crayon-plain-tag">&lt;script src="stat.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript"&gt;
    var stats = new Stats();
    stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom
    document.body.appendChild( stats.dom );
    function renderScene() {
        // 开始统计
        stats.begin();
        // 这里编写被监控的代码
        stats.end();
        requestAnimationFrame( animate );
    }
    requestAnimationFrame( renderScene );
&lt;/script&gt;</pre>
<div class="blog_h3"><span class="graybg">添加动画</span></div>
<p>下面我们为立方体添加一个翻滚效果，为球体添加一个弹跳效果：</p>
<pre class="crayon-plain-tag">var step = 0;

function animate() {
    stats.begin();
    cube.rotation.x += 0.02;
    cube.rotation.y += 0.02;
    cube.rotation.z += 0.02;
    step += 0.04; // 定义弹跳速度
    sphere.position.x = 20 + ( 10 * (Math.cos( step )));
    sphere.position.y = 2 + ( 10 * Math.abs( Math.sin( step ) ));
    renderer.render( scene, camera );  // 反复渲染
    stats.end();
    requestAnimationFrame( animate );
}

requestAnimationFrame( animate );</pre>
<p>刷新浏览器，可以查看动画效果，注意左上角的帧率窗口。</p>
<div class="blog_h1"><span class="graybg">使用基本组件</span></div>
<p>上一章的学习中，我们创建了由几个对象构成的简单场景，并制作了简单的动画效果。现在我们来更深入的了解一下构成Three.js场景的组件。 </p>
<div class="blog_h2"><span class="graybg">场景的内容物</span></div>
<p>之前我们调用<pre class="crayon-plain-tag">new THREE.Scene()</pre> 创建了一个场景。场景是一个容器，它内部可以包含三类东西：</p>
<ol>
<li>镜头（Camera）：决定了查看场景的角度和方式。在渲染场景的时候镜头可以自动创建，但是你也可以手工指定其参数</li>
<li>光源（Lights）：影响材质的渲染效果、阴影</li>
<li>物体（Objects）：场景中渲染的主要东西。包括各种几何形状、导入的模型</li>
</ol>
<div class="blog_h2"><span class="graybg">场景的基本API</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>children</td>
<td>所有对象组成的数组</td>
</tr>
<tr>
<td>getChildByName(name)</td>
<td>根据名称来查找对象</td>
</tr>
<tr>
<td>remove(obj)</td>
<td>从场景中移除一个对象</td>
</tr>
<tr>
<td>traverse(callback)</td>
<td>指定一个回调，针对场景中所有对象调用之</td>
</tr>
<tr>
<td>fog</td>
<td>添加烟雾效果，这样越远的物体显示越模糊：<br />
<pre class="crayon-plain-tag">// 白色雾，从near=0.015开始出现，far=100表示雾变浓厚的速率
scene.fog = new THREE.Fog( 0xffffff, 0.015, 100 );
// 指定颜色、浓度
scene.fog = new THREE.FogExp2( 0xffffff, 0.01 );</pre>
</td>
</tr>
<tr>
<td>overrideMaterial</td>
<td>覆盖场景中所有物体的材质设置：<br />
<pre class="crayon-plain-tag">scene.overrideMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Geometry/Mesh的基本API</span></div>
<p>Three.js提供了大量开箱即用的Geometry， Geometry用于定义物体的形状，材质则定义其外观。</p>
<p>在大部分3D图形库中，Geometry基本上都是三维空间中一系列点、以及连接这些点的面的集合。以立方体为例：</p>
<ol>
<li>每个立方体包含8个角，这些角可以由三维空间中的一个点来确定。这些点称为顶点（vertices）</li>
<li>每个立方体包含6个面，这些面的每个角都对应一个顶点。这些面称为face</li>
</ol>
<p>当使用Three.js自带的Geometry时你不需要逐个定义所有点、面。例如对于Cube，你只需要定义长宽高即可，Three.js会利用你提供的这些信息创建所有必须的点、面。</p>
<p>Three.js允许自定义点、面，然后构成一个几何图形。下面是手工构建Cube的例子：</p>
<pre class="crayon-plain-tag">// 所有顶点
var vertices = [
    new THREE.Vector3( 1, 3, 1 ),
    new THREE.Vector3( 1, 3, -1 ),
    new THREE.Vector3( 1, -1, 1 ),
    new THREE.Vector3( 1, -1, -1 ),
    new THREE.Vector3( -1, 3, -1 ),
    new THREE.Vector3( -1, 3, 1 ),
    new THREE.Vector3( -1, -1, -1 ),
    new THREE.Vector3( -1, -1, 1 )
];
// 所有三角形的面，数字为从0开始的顶点序号
var faces = [
    new THREE.Face3( 0, 2, 1 ),
    new THREE.Face3( 2, 3, 1 ),
    new THREE.Face3( 4, 6, 5 ),
    new THREE.Face3( 6, 7, 5 ),
    new THREE.Face3( 4, 5, 1 ),
    new THREE.Face3( 5, 0, 1 ),
    new THREE.Face3( 7, 6, 2 ),
    new THREE.Face3( 6, 3, 2 ),
    new THREE.Face3( 5, 7, 0 ),
    new THREE.Face3( 7, 2, 0 ),
    new THREE.Face3( 1, 3, 4 ),
    new THREE.Face3( 3, 6, 4 ),
];
var geom = new THREE.Geometry();
geom.vertices = vertices;
geom.faces = faces;
// 在重新设置顶点数组后，提示需要更新。这是因为Three.js默认假设Mesh的Geometry的形状在生命周期内保持不变
geom.verticesNeedUpdate = true;
// 根据顶点重新计算面法线
geom.computeFaceNormals();</pre>
<p>在以前版本的Three.js中，允许使用四边形来定义面。四边形在建模时比较受欢迎，原因是很容易被增强、平滑。三角形在渲染、游戏引擎中比较受欢迎，原因是比较简单。 </p>
<p>有了Geometry后，加上材质就可以构成简单的3D物体——Mesh了：</p>
<pre class="crayon-plain-tag">// 材质数组
var materials = [
    new THREE.MeshLambertMaterial( { opacity: 0.6, color: 0x44ff44, transparent: true } ),
    new THREE.MeshBasicMaterial( { color: 0x666666, wireframe: true } )
];
// Mesh组
var mesh = THREE.SceneUtils.createMultiMaterialObject( geom, materials );
scene.add( mesh );</pre>
<p>Three.js允许给Geometry应用多个材质，上例中的Cube既有颜色填充，也显示了线条，这是两种材质的混合效果。从实现角度来说，<span style="background-color: #c0c0c0;">Three.js创建了两个THREE.Mesh实例</span>，每个材质对应一个实例，这<span style="background-color: #c0c0c0;">两个实例被放置到一个组里面</span>。添加组到场景的方式，与添加Mesh一致。</p>
<p>我们可以调用组的forEach，对其中所有Mesh进行操作：</p>
<pre class="crayon-plain-tag">mesh.children.forEach( function ( e ) {
    e.castShadow = true
} );</pre>
<div class="blog_h3"><span class="graybg">Geometry的基本API</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>vertices</td>
<td>构成此Geometry的顶点坐标数组</td>
</tr>
<tr>
<td>faces</td>
<td>构成Geometry的三角形面数组</td>
</tr>
<tr>
<td>verticesNeedUpdate</td>
<td>修改顶点数组后，提示Three.js需要更新顶点</td>
</tr>
<tr>
<td>computeFaceNormals()</td>
<td>重新根据顶点来计算面</td>
</tr>
<tr>
<td>clone()</td>
<td>克隆一个Geometry</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Mesh的基本API</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>position.x|y|z<br />position.set(x,y,z)</td>
<td>此物体相对于父对象的位置，大部分物体的父对象是THREE.Scene对象，对于组中的Mesh，其父对象是组。示例代码：<br />
<pre class="crayon-plain-tag">// 方法一：
cube.position.x=10;
cube.position.y=3;
cube.position.z=1;
// 方法二：
cube.position.set(10,3,1);
// 方法三：
cube.postion=new THREE.Vector3(10,3,1)</pre>
</td>
</tr>
<tr>
<td>rotation.x|y|z</td>
<td>让物体围绕自己的轴（而不是场景的）旋转一定角度。与position类似，具有三种设置方法</td>
</tr>
<tr>
<td>scale.x|y|z</td>
<td>让物体在其轴方向缩放。与position类似，具有三种设置方法</td>
</tr>
<tr>
<td>translateX(amount)</td>
<td rowspan="3">
<p>将物体沿着X/Y/Z轴方向移动</p>
<p>这些方法指定的是相对位移，而position指定的是绝对值</p>
</td>
</tr>
<tr>
<td>translateY(amount)</td>
</tr>
<tr>
<td>translateZ(amount)</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">使用镜头</span></div>
<p>Three.js支持两种类型的镜头：正交（orthographic）镜头、透视（perspective）镜头。到目前为止我们还没有使用过正交镜头。</p>
<p>正交镜头的特点是，物品的渲染尺寸与它距离镜头的远近无关。也就是说在场景中移动一个物体，其大小不会变化。正交镜头适合2D游戏。</p>
<p>透视镜头则是模拟人眼的视觉特点，距离远的物体显得更小。透视镜头通常更适合3D渲染。</p>
<div class="blog_h3"><span class="graybg">THREE.PerspectiveCamera的API</span></div>
<p>构造函数参数：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>fov</td>
<td>
<p>视界，从镜头可以看到的场景的部分。其值为镜头到近平面上下边的夹角</p>
<p>人眼的FOV接近180度，某些鸟类的FOV打到360度。但是计算机屏幕做不到覆盖视野，通常3D游戏的FOV取值在60-90度之间</p>
<p>较好的默认值为45</p>
</td>
</tr>
<tr>
<td>aspect</td>
<td>渲染区域的纵横比。较好的默认值为window.innerWidth/window.innerHeight</td>
</tr>
<tr>
<td>near</td>
<td>近平面离镜头的距离。较好的默认值为0.1</td>
</tr>
<tr>
<td>far</td>
<td>远平面离镜头的距离。较好的默认值为1000</td>
</tr>
</tbody>
</table>
<p>关于这些参数的形象化描述，请参考<a href="#ViewFrustum">术语视截锥</a>中的截图。</p>
<div class="blog_h3"><span class="graybg">THREE.OrthographicCamera的API</span></div>
<p>正交镜头不关心FOV、纵横比这些概念。其构造函数实际上是指定了一个Cube，落在其中的物体会被渲染：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>left</td>
<td>相机截锥左平面位置，如果你将其设置为-100，则位置在其更左边的物体将不可见</td>
</tr>
<tr>
<td>right</td>
<td>相机截锥右平面位置</td>
</tr>
<tr>
<td>top</td>
<td>相机截锥上平面位置</td>
</tr>
<tr>
<td>bottom</td>
<td>相机截锥下平面位置</td>
</tr>
<tr>
<td>near</td>
<td>近平面的位置</td>
</tr>
<tr>
<td>far</td>
<td>远平面的位置</td>
</tr>
</tbody>
</table>
<p>关于这些参数的形象化描述，参考下图：</p>
<div class="blog_h2"><img class="aligncenter size-full wp-image-14445" src="https://blog.gmem.cc/wp-content/uploads/2017/01/orthographic-camera.png" alt="orthographic-camera" width="100%" /><span class="graybg">镜头聚焦</span></div>
<p>创建镜头后，还需要将其移动、然后对准物体积聚的场景中心位置，才能确保物体的渲染。移动镜头，通过设置其position属性来实现：</p>
<pre class="crayon-plain-tag">camera.position.x = 120;
camera.position.y = 60;
camera.position.z = 180;</pre>
<p>聚焦，则是调用下面的方法实现：</p>
<pre class="crayon-plain-tag">camera.lookAt( new THREE.Vector3( x, 10, 0 ) );</pre>
<div class="blog_h2"><span class="graybg">HUD</span></div>
<p>所谓HUD（head-up display，平视显示），是指在屏幕（挡风玻璃）上显示一些辅助信息（例如飞机、汽车的仪表信息），避免驾驶员低头分散注意力。HUD的特点是其显示内容的大小、位置与镜头无关。</p>
<p>要实现HUD效果，可以同时渲染两套场景，其中HUD场景使用OrthographicCamera镜头：</p>
<pre class="crayon-plain-tag">var scene = new THREE.Scene();
var sceneOrtho = new THREE.Scene();

var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 250);
// 正交镜头的近平面的大小，和浏览器窗口大小一致
var cameraOrtho = new THREE.OrthographicCamera(0, window.innerWidth, window.innerHeight, 0, -10, 10);

var webGLRenderer = new THREE.WebGLRenderer();

webGLRenderer.render(scene, camera);
// 防止在下一次render时，自动清屏
webGLRenderer.autoClear = false;
webGLRenderer.render(sceneOrtho, cameraOrtho);</pre>
<div class="blog_h1"><span class="graybg">使用光源</span></div>
<p>在Three.js中光源很重要。不设置光源你就看不到被渲染的物体。Three.js内置了多种光源以满足特定场景的需要。</p>
<div class="blog_h2"><span class="graybg">光源分类</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">光源</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>AmbientLight</td>
<td>环境光，其颜色均匀的应用到场景及其所有对象上</td>
</tr>
<tr>
<td>PointLight</td>
<td>3D空间中的一个点光源，向所有方向发出光线</td>
</tr>
<tr>
<td>SpotLight</td>
<td>产生圆锥形光柱的聚光灯，台灯、天花板射灯通常都属于这类光源</td>
</tr>
<tr>
<td>DirectionalLight</td>
<td>也就无限光，光线是平行的。典型的例子是日光</td>
</tr>
<tr>
<td>HemisphereLight</td>
<td>特殊光源，用于创建户外自然的光线效果，此光源模拟物体表面反光效果、微弱发光的天空</td>
</tr>
<tr>
<td>AreaLight</td>
<td>面光源，指定一个发光的区域</td>
</tr>
<tr>
<td>LensFlare</td>
<td>不是光源，用于给光源添加镜头光晕效果</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基本光源</span></div>
<div class="blog_h3"><span class="graybg">AmbientLight</span></div>
<p>这种光源为场景添加全局的环境光。这种光没有特定的方向，不会产生阴影。通常不会把AmbientLight作为唯一的光源，而是和SpotLight、DirectionalLight等光源结合使用，从而达到<span style="background-color: #c0c0c0;">柔化阴影、添加全局色调</span>的效果。</p>
<p>指定颜色时要相对保守，例如#0c0c0c。设置太亮的颜色会导致整个画面过度饱和，什么都看不清：</p>
<pre class="crayon-plain-tag">var ambiColor = "#0c0c0c";
var ambientLight = new THREE.AmbientLight(ambiColor);
scene.add(ambientLight);</pre>
<div class="blog_h3"><span class="graybg">PointLight</span></div>
<p>该类模拟一个点光源，具有以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>光线的颜色</td>
</tr>
<tr>
<td>intensity</td>
<td>光线的强度，默认1，浮点数</td>
</tr>
<tr>
<td>distance</td>
<td>光线能照耀的距离</td>
</tr>
<tr>
<td>position</td>
<td>光源的位置</td>
</tr>
<tr>
<td>visible</td>
<td>设置为true则打开光源</td>
</tr>
</tbody>
</table>
<p>示例代码：</p>
<pre class="crayon-plain-tag">var pointColor = "#ccffcc";
var pointLight = new THREE.PointLight( pointColor );
pointLight.distance = 100;
scene.add( pointLight );
// 设置强度
pointLight.intensity = 2.4;</pre>
<div class="blog_h3"><span class="graybg">SpotLight</span></div>
<p>这种光源的使用场景最多，特别是在你需要阴影效果的时候。PointLight的所有属性对于SpotLight可用，前者还包括以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>castShadow</td>
<td>
<p>此光源是否可以导致物体产生阴影</p>
<p>注意：目标物体需要设置receiveShadow</p>
</td>
</tr>
<tr>
<td>shadowCameraNear</td>
<td>从距离光源多远的地方开始创建阴影</td>
</tr>
<tr>
<td>shadowCameraFar</td>
<td>到距离光源多远的地方不再创建阴影</td>
</tr>
<tr>
<td>shadowCameraFov</td>
<td>阴影的FOV</td>
</tr>
<tr>
<td>target</td>
<td>此光源指向的目标。光线从光源照向该目标：<br />
<pre class="crayon-plain-tag">var targetObject = new THREE.Object3D();
scene.add(targetObject);
// 聚光灯将跟踪三维空间中的一个点
light.target = targetObject;</pre>
</td>
</tr>
<tr>
<td>shadowBias</td>
<td>设置阴影的位置偏移</td>
</tr>
<tr>
<td>angle</td>
<td>光锥的夹角，默认Math.PI/3</td>
</tr>
<tr>
<td>exponent</td>
<td>衰减指数，即随着与光源距离的增加，光线衰减的速度</td>
</tr>
<tr>
<td>onlyShadow</td>
<td>如果设置为true，仅仅产生阴影，而不照亮物体</td>
</tr>
<tr>
<td>shadowCameraVisible</td>
<td>如果设置为true，你将看到光源如何、从何处产生阴影（显示截锥）。用于调试目的</td>
</tr>
<tr>
<td>shadowDarkness</td>
<td>阴影的暗度，默认0.5。一旦场景被创建此参数即不可修改</td>
</tr>
<tr>
<td>shadowMapWidth<br />shadowMapHeight</td>
<td>
<p>有多少像素用于创建阴影，如果阴影出现锯齿效果，可以增加此参数。一旦场景被创建此参数即不可修改</p>
<p>另一种减轻阴影锯齿的方法是，让阴影相机截锥尽可能小</p>
</td>
</tr>
</tbody>
</table>
<p>示例代码：</p>
<pre class="crayon-plain-tag">var spotLight = new THREE.SpotLight(pointColor);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
spotLight.shadowCameraNear = 2;
spotLight.shadowCameraFar = 200;
spotLight.shadowCameraFov = 30;
spotLight.target = plane;   // 跟踪目标
spotLight.distance = 0;
spotLight.angle = 0.4;

scene.add(spotLight);</pre>
<p>光锥的宽、高可以基于以下代码求出：</p>
<pre class="crayon-plain-tag">var coneLength = light.distance ? light.distance : 10000;
var coneWidth = coneLength * Math.tan( light.angle * 0.5 ) * 2;</pre>
<div class="blog_h3"><span class="graybg">DirectionalLight</span></div>
<p>用于模拟遥远的，类似太阳那样的光源。该光源与SpotLight的主要区别是，它不会随着距离而变暗，所有被照耀的地方获得相同的光照强度。</p>
<p>DirectionalLight具有大部分SpotLight的属性。示例代码：</p>
<pre class="crayon-plain-tag">var directionalLight = new THREE.DirectionalLight( pointColor );
directionalLight.position.set( -40, 60, -10 );
directionalLight.castShadow = true;
directionalLight.shadowCameraNear = 2;
directionalLight.shadowCameraFar = 200;
directionalLight.shadowCameraLeft = -50;
directionalLight.shadowCameraRight = 50;
directionalLight.shadowCameraTop = 50;
directionalLight.shadowCameraBottom = -50;

directionalLight.distance = 0;
directionalLight.intensity = 0.5;
directionalLight.shadowMapHeight = 1024;
directionalLight.shadowMapWidth = 1024;

scene.add( directionalLight );</pre>
<div class="blog_h2"><span class="graybg">高级光源</span></div>
<div class="blog_h3"><span class="graybg">HemisphereLight</span></div>
<p>模拟穹顶（半球）的微弱发光效果，让户外场景更加逼真。使用DirectionalLight + AmbientLight可以在某种程度上来模拟户外光线，但是不够真实，因为无法体现大气层的散射效果、地面或物体的反射效果。常用属性：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>天空散射的光线颜色</td>
</tr>
<tr>
<td>groundColor</td>
<td>地面散射的光线颜色</td>
</tr>
<tr>
<td>intensity</td>
<td>光线强度</td>
</tr>
</tbody>
</table>
<p>示例代码：</p>
<pre class="crayon-plain-tag">// 三个参数分别对应天空颜色、地面颜色、强度
var hemiLight = new THREE.HemisphereLight(0x0000ff, 0x00ff00, 0.6);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);</pre>
<div class="blog_h3"><span class="graybg">AreaLight</span></div>
<p>用于定义一个发光的矩形区域，该光源属于Three.js扩展。</p>
<p>THREE.WebGLRenderer这个渲染器不能和AreaLight一起使用，原因是THREE.AreaLight是一种复杂的光源，与WebGLRenderer一起使用会导致严重的性能问题。</p>
<p>渲染器THREE.WebGLDeferredRenderer使用不同的途径来渲染场景，它将渲染拆分为几个步骤。它能够处理复杂的光源或者数量众多的光源。</p>
<div class="blog_h1"><span class="graybg">使用材质</span></div>
<p>通过前面章节的学习，我们已经知道材质 + Geometry可以构成Mesh——可以添加到3D场景中的物体。</p>
<p>Geometry就好像是骨架，材质则类似于皮肤，它定义了Geometry的外观——是否有金属质感、是否透明、是否显示为线框（wireframe）。</p>
<div class="blog_h2"><span class="graybg">材质分类</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">材质</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>MeshBasicMaterial</td>
<td>基本的材质，显示为简单的颜色或者显示为线框。不考虑光线的影响</td>
</tr>
<tr>
<td>MeshDepthMaterial</td>
<td>使用简单的颜色，但是颜色深度和距离相机的远近有关</td>
</tr>
<tr>
<td>MeshNormalMaterial</td>
<td>基于面Geometry的法线（normals）数组来给面着色</td>
</tr>
<tr>
<td>MeshFacematerial</td>
<td>容器，允许为Geometry的每一个面指定一个材质</td>
</tr>
<tr>
<td>MeshLambertMaterial</td>
<td>考虑光线的影响，哑光材质</td>
</tr>
<tr>
<td>MeshPhongMaterial</td>
<td>考虑光线的影响，光泽材质</td>
</tr>
<tr>
<td>ShaderMaterial</td>
<td>允许使用自己的着色器来控制顶点如何被放置、像素如何被着色</td>
</tr>
<tr>
<td>LineBasicMaterial</td>
<td>用于THREE.Line对象，创建彩色线条</td>
</tr>
<tr>
<td>LineDashMaterial</td>
<td>用于THREE.Line对象，创建虚线条</td>
</tr>
<tr>
<td>RawShaderMaterial</td>
<td>仅和THREE.BufferedGeometry联用，优化静态Geometry（顶点、面不变）的渲染</td>
</tr>
<tr>
<td>SpriteCanvasMaterial</td>
<td rowspan="3">在针对单独的点进行渲染时用到</td>
</tr>
<tr>
<td>SpriteMaterial</td>
</tr>
<tr>
<td>PointCloudMaterial</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">公共属性</span></div>
<p>作为所有材质的基类，THREE.Material提供了以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"><em><strong>基本属性</strong></em><br />最常用的属性，用于控制对象的透明度、是否可见、如何被引用（基于ID还是名称）</td>
</tr>
<tr>
<td>id</td>
<td>当你创建一个材质的时候自动分配，作为材质实例的标识，从0开始自动计数</td>
</tr>
<tr>
<td>uuid</td>
<td>全局唯一标识，内部使用</td>
</tr>
<tr>
<td>name</td>
<td>给材质分配一个名称，调试用</td>
</tr>
<tr>
<td>opacity</td>
<td>
<p>设定材质的透明度，和transparent联用，范围0～1</p>
</td>
</tr>
<tr>
<td>transparent</td>
<td>
<p>如果设置为true，则Three.js考虑opacity的设置。对于具有Alpha通道的纹理，该属性也需要设置为true</p>
</td>
</tr>
<tr>
<td>overdraw</td>
<td>使用THREE.CanvasRenderer渲染器时多边形会比预期的绘制的大一些。如果使用该渲染器时你法线Gaps可设置为true </td>
</tr>
<tr>
<td>visible</td>
<td>材质是否可见，如果设置为false则物体看不见</td>
</tr>
<tr>
<td>side</td>
<td>
<p>材质应用到目标Geomotry的哪一面。默认THREE.Frontside表示应用在外面，可选值THREE.BackSide应用在里面、THREE.DoubleSide应用到两面</p>
<p>对于不封闭空间的Geomotry，例如平面，此属性重要</p>
</td>
</tr>
<tr>
<td>needsUpdate</td>
<td>改变材质的某些属性后，你可以设置该属性为true，这样Three.js就会丢弃缓存，重新渲染材质</td>
</tr>
<tr>
<td colspan="2"><em><strong>混合（Blending）属性</strong></em><br />定义对象如何与其背景混合，或者说我们渲染的颜色如何与其背后的颜色交互</td>
</tr>
<tr>
<td>blending </td>
<td>决定材质如何与背景混合，默认值THREE.NormalBlending，表示仅仅显示顶层颜色</td>
</tr>
<tr>
<td>blendsrc</td>
<td>定义物体（源）如何混合到背景（目标）中，默认THREE.SrcAlphaFactor表示基于物体的Alpha通道进行混合</td>
</tr>
<tr>
<td>blenddst</td>
<td>定义在混合时，背景（目标）如何渲染，默认THREE.OneMinusSrcAlphaFactor表示基于物体的Alpha通道进行混合</td>
</tr>
<tr>
<td> blendequation</td>
<td>定义blendsrc、blenddst如何使用，默认将它们相加（AddEquation）</td>
</tr>
<tr>
<td colspan="2"><strong><em>高级属性</em></strong><br />控制低级别的WebGL上下文如何渲染对象，大部分情况下不需要使用</td>
</tr>
<tr>
<td>depthTest</td>
<td>
<p>如果关闭depthTest，意味着同时关闭reading/testing/writing</p>
<p>到底什么是深度测试（depthTest）呢？假设由两个完全一样的形状，位于你的正前方。真实世界中，你仅能看到里你近的那一个。但是在3D渲染过程中：</p>
<ol>
<li>如果远的物体先被绘制，那么没有问题，效果和真实世界一致</li>
<li>如果近的物体先被绘制，远物体后被绘制，就会有问题，远物体可以被看见</li>
</ol>
<p>所谓深度测试，是现代GPU中内置的一个工具，能够让渲染输出总是符合预期，而不管对象的输出先后顺序。具体实现机制是：当绘制一个像素时，会查看此像素位置原先的depth（即离相机的远近）值，<span style="background-color: #c0c0c0;">如果新的像素depth值较小，则执行绘制</span>，否则保留原来的值</p>
<p>由于深度测试的实现机制，和透明度（混合）在一起工作时可能出现问题，有时候需要禁用</p>
</td>
</tr>
</tbody>
</table>
<p>本章跳过了所有和纹理（textures）、映射（maps）、动画 有关的属性。</p>
<div class="blog_h2"><span class="graybg">简单Mesh材质</span></div>
<p>你可以把属性组成一个对象，作为构造函数的入参：</p>
<pre class="crayon-plain-tag">var material = new THREE.MeshBasicMaterial( {
    color: 0xff0000,
    name: 'material-1',
    opacity: 0.5,
    transparency: true
} );</pre>
<p>或者逐个的设置属性：</p>
<pre class="crayon-plain-tag">var material = new THREE.MeshBasicMaterial();
material.color = new THREE.Color( 0xff0000 );  // 这种方式必须提供Color对象
material.name = 'material-1';
material.opacity = 0.5;
material.transparency = true;</pre>
<div class="blog_h3"><span class="graybg">THREE.MeshBasicMaterial</span></div>
<p>该材质不考虑场景中的光源，目标物体被渲染成简单的、扁平（Flat）的形状。可选的，你可以显示物体的线框（Wireframe），线框由所有面的边构成。</p>
<p>该材质具有以下额外属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>材质的颜色</td>
</tr>
<tr>
<td>wireframe</td>
<td>是否显示线框。显示线框对于调试有帮助</td>
</tr>
<tr>
<td>Wireframelinewidth</td>
<td>线框的线条宽度</td>
</tr>
<tr>
<td>shading</td>
<td>
<p>定义如何着色，可选值THREE.SmoothShading、THREE.NoShading、THREE.FlatShadin</p>
<p>默认值THREE.SmoothShading，导致渲染平滑的渲染——例如平滑过渡颜色</p>
</td>
</tr>
<tr>
<td>vertexColors</td>
<td>可以定义各个顶点的颜色，默认值THREE.NoColors。你可以设置为THREE.VertexColors，这样渲染器会考虑Geometry.colors属性</td>
</tr>
<tr>
<td>fog</td>
<td>该材质是否被全局迷雾效果影响</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.MeshDepthMaterial</span></div>
<p>使用这种材质，物体的外观会受到物体离镜头的距离的影响——随着距离增加而淡出。你可以联合使用其它材质，产生淡出效果。该材质具有以下额外属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>wireframe</td>
<td>是否显示线框</td>
</tr>
<tr>
<td>wireframeLineWidth</td>
<td>线框的宽度</td>
</tr>
</tbody>
</table>
<p>设置相机的near、far属性，可以决定使用此材质的物体的淡出速度 。如果far - near很大，则物体的淡出速度非常慢。</p>
<div class="blog_h3"><span class="graybg">THREE.MeshNormalMaterial</span></div>
<p>每个面显示特定的颜色，其颜色取决于该面的法线（垂直于面的向量）。当物体旋转时，其固定角度的颜色保持不变。</p>
<p>法线在Three.js中被大量使用，它被用来确定光线反射效果、帮助映射纹理到3D模型，并且为如何照亮、shade、染色（color）一个表面上的像素点。</p>
<p>为了查看法线的方向，我们可以使用THREE.ArrowHelper：</p>
<pre class="crayon-plain-tag">//遍历球体的所有面
for ( var f = 0, fl = sphere.geometry.faces.length; f &lt; fl; f++ ) {
    var face = sphere.geometry.faces[ f ];
    // 计算面的中心点：把面的3个顶点依次加到三维向量中，然后除以3
    var centroid = new THREE.Vector3( 0, 0, 0 );
    centroid.add( sphere.geometry.vertices[ face.a ] );
    centroid.add( sphere.geometry.vertices[ face.b ] );
    centroid.add( sphere.geometry.vertices[ face.c ] );
    centroid.divideScalar( 3 );
    // 创建一个箭头助手
    var arrow = new THREE.ArrowHelper(
        face.normal,  // 法线矢量（箭头方向）
        centroid,   // 中心点 （箭头起点）
        2, // 长度
        0x3333FF, // 颜色
        0.5,  //箭头长度
        0.5 //箭头宽度
    );
    sphere.add( arrow );
}</pre>
<p>该材质的额外属性包括：wireframe、wireframeLineWidth、shading。 使用FlatShading、SmoothShading的效果分别如下图：</p>
<p><img class="aligncenter size-full wp-image-14459" src="https://blog.gmem.cc/wp-content/uploads/2017/01/shading-diff.png" alt="shading-diff" width="582" height="297" /></p>
<div class="blog_h3"><span class="graybg">THREE.MeshFaceMaterial</span></div>
<p>这不是一个单独的材质，而是一个容器。使用它，你可以为每个面指定材质。例如，对于具有12个面（Three.js仅支持三角形面）的Cube，你可以指定具有12个元素的MeshFaceMaterial：</p>
<pre class="crayon-plain-tag">var mats = [];
mats.push(new THREE.MeshBasicMaterial({color: 0x009e60}));
mats.push(new THREE.MeshBasicMaterial({color: 0x009e60}));
mats.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
mats.push(new THREE.MeshBasicMaterial({color: 0x0051ba}));
mats.push(new THREE.MeshBasicMaterial({color: 0xffd500}));
mats.push(new THREE.MeshBasicMaterial({color: 0xffd500}));
mats.push(new THREE.MeshBasicMaterial({color: 0xff5800}));
mats.push(new THREE.MeshBasicMaterial({color: 0xff5800}));
mats.push(new THREE.MeshBasicMaterial({color: 0xC41E3A}));
mats.push(new THREE.MeshBasicMaterial({color: 0xC41E3A}));
mats.push(new THREE.MeshBasicMaterial({color: 0xffffff}));
mats.push(new THREE.MeshBasicMaterial({color: 0xffffff}));

var faceMaterial = new THREE.MeshFaceMaterial(mats);

var cubeGeom = new THREE.BoxGeometry( 2.9, 2.9, 2.9 );
var cube = new THREE.Mesh( cubeGeom, faceMaterial );</pre>
<p>你可以设置<pre class="crayon-plain-tag">geometry.faces[*].materialIndex</pre>  来指名某个面使用MeshFaceMaterial中的哪个元素来渲染。</p>
<div class="blog_h2"><span class="graybg">联合多个材质</span></div>
<p>像MeshDepthMaterial这样的材质，不能设置颜色或者纹理，基本不能单独使用。</p>
<p>Three.js允许联合使用多个材质，以产生新的特效。<span style="background-color: #c0c0c0;">材质联合也使混合（blending）有意义</span>。示例：</p>
<pre class="crayon-plain-tag">var cubeMaterial = new THREE.MeshDepthMaterial();
var colorMaterial = new THREE.MeshBasicMaterial( {
    // 绿色材质
    color: 0x00ff00,
    // 允许透明度
    transparent: true,
    // 决定如何与背景（即使用了MeshDepthMaterial的那个内部盒子）进行交互
    // MultiplyBlending将前景、背景色进行乘积运算（正片叠底）
    blending: THREE.MultiplyBlending
} );
// 创建两个物体构成的组
var cube = new THREE.SceneUtils.createMultiMaterialObject( cubeGeometry, [ colorMaterial, cubeMaterial ] );
// 避免两个相同大小的重叠物体产生闪烁
cube.children[ 1 ].scale.set( 0.99, 0.99, 0.99 );</pre>
<div class="blog_h2"><span class="graybg">高级材质</span></div>
<div class="blog_h3"><span class="graybg">THREE.MeshLambertMaterial </span></div>
<p>此材质用于创建哑光效果。提供额外属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ambient</td>
<td>材质的阴影色，与AmbientLight配合。AmbientLight的颜色与该颜色进行乘积混合（正片叠底），默认白色</td>
</tr>
<tr>
<td>emissive</td>
<td>材质发出的光线的颜色，注意这不会让材质成为光源，只是一个不会被其它光源影响的颜色而已，默认黑色 </td>
</tr>
<tr>
<td>wrapAround</td>
<td>
<p>设置为true则启用半环境光（half-lambert lighting）技术——光线的减弱行为更加微妙。如果你的Mesh具有尖锐、黑暗的区域，设置为true可以柔化阴影、更均匀的分散（distribute）光线</p>
</td>
</tr>
<tr>
<td>wrapRGB</td>
<td>当wrapAround设置为true时，使用一个THREE.Vector3来控制光线减弱（drop off）的速度，可以用来微调物体的色泽</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.MeshPhongMaterial</span></div>
<p>此材质用于创建高反光效果。提供额外属性： </p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ambient</td>
<td>材质的阴影色，与AmbientLight配合。AmbientLight的颜色与该颜色进行乘积混合（正片叠底），默认白色</td>
</tr>
<tr>
<td>emissive</td>
<td>材质发出的光线的颜色，注意这不会让材质成为光源，只是一个不会被其它光源影响的颜色而已，默认黑色</td>
</tr>
<tr>
<td>specular</td>
<td>
<p>材质的高光色，即反光的颜色。如果将其设置：</p>
<ol>
<li>和color属性相同，可以得到金属质感（metallic-looking）的材质</li>
<li>为灰色，可以得到塑料质感（plastic-looking）的材质</li>
</ol>
</td>
</tr>
<tr>
<td>shininess</td>
<td>高光色的亮度，默认30</td>
</tr>
<tr>
<td>metal</td>
<td>设置为true，则Three.js更改算法，让材质更加像金属</td>
</tr>
<tr>
<td>wrapAround</td>
<td>
<p>设置为true则启用半环境光（half-lambert lighting）技术——光线的减弱行为更加微妙。如果你的Mesh具有尖锐、黑暗的区域，设置为true可以柔化阴影、更均匀的分散（distribute）光线</p>
</td>
</tr>
<tr>
<td>wrapRGB</td>
<td>当wrapAround设置为true时，使用一个THREE.Vector3来控制光线减弱（drop off）的速度，可以用来微调物体的色泽</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">THREE.ShaderMaterial</span></div>
<p>基于这种材质，可以应用自己开发的着色器。通过定制着色器，你可以精确的定义物体如何被渲染，或者修改Threee.js的默认渲染行为。</p>
<p>ShaderMaterial支持wireframe、Wireframelinewidth、linewidth、shading、vertexColors、fog以及以下额外属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>fragmentShader</td>
<td>
<p>使用的片断着色器程序的名称</p>
<p>片断着色器，也叫像素着色器（pixel shader）。用于定义顶点之间每个点如何渲染</p>
</td>
</tr>
<tr>
<td>vertexShader</td>
<td>
<p>使用的顶点着色器程序的名称</p>
<p>顶点着色器，可以操控顶点的属性（例如改变顶点位置）</p>
<p>如果你像让多边形为全红色，可以基于此着色器，指定所有顶点为红色（此颜色信息会传递给片断着色器）。反之，如果你想在顶点之间产生渐变效果，则需要基于片断着色器</p>
<p>顶点着色器位于图形管线（graphic pipeline）的早期，在模型坐标转换、多边形修剪（clipping）之前，此时实际渲染工作并为开始</p>
</td>
</tr>
<tr>
<td>uniforms</td>
<td>用于向着色器程序发送信息，相同的信息被传递给每个vertex、fragment</td>
</tr>
<tr>
<td>defines </td>
<td>转换为#define代码片断，设置一些全局变量供着色器程序使用</td>
</tr>
<tr>
<td>attributes</td>
<td>用于传递位置性的、法线相关的信息。如果使用该属性，必须为每个顶点提供</td>
</tr>
<tr>
<td>lights</td>
<td>是否把光照数据传入着色器，默认false</td>
</tr>
</tbody>
</table>
<p>对于前面已经讨论过的其它材质，Three.js已经提供了它们的片断着色器、顶点着色器。</p>
<div class="blog_h3"><span class="graybg">GSGL</span></div>
<p>着色器不是基于JavaScript语言编写的，它的专用语言是GSGL，即OpenGL ES着色器语言的WebGL支持。这种语言的语法风格类似于C语言。</p>
<div class="blog_h3"><span class="graybg">示例一：动画材质</span></div>
<p>在本节，我们编写：</p>
<ol>
<li>一个简单顶点着色器。该着色器能够修改Cube顶点的坐标值</li>
<li>多个借用自<a href="http://glslsandbox.com/">glslsandbox</a>代码的片断着色器，创建具有动画效果的材质</li>
</ol>
<p>顶点着色器代码：</p>
<pre class="crayon-plain-tag">&lt;script id="vertex-shader" type="x-shader/x-vertex"&gt;
    // 外部传入的时间
    uniform float time;
    varying vec2 vUv;


    void main()
    {
        // 计算变换后的位置
        vec3 posChanged = position;
        posChanged.x = posChanged.x*(abs(sin(time*1.0)));
        posChanged.y = posChanged.y*(abs(cos(time*1.0)));
        posChanged.z = posChanged.z*(abs(sin(time*1.0)));
        gl_Position = projectionMatrix * modelViewMatrix * vec4(posChanged,1.0);
    }

&lt;/script&gt;</pre>
<p>为了JavaScript与着色器之间的通信，我们使用所谓uniforms。上面的例子中定义了一个uniform，传递外部的时间，根据此时间来变换顶点的位置。</p>
<p><pre class="crayon-plain-tag">gl_Position</pre> 是一个特殊变量，用于将顶点位置信息传回JavaScript。</p>
<p>其中一个片断着色器代码：</p>
<pre class="crayon-plain-tag">&lt;script id="fragment-shader-6" type="x-shader/x-fragment"&gt;


    uniform float time;
    uniform vec2 resolution;


    void main( void )
    {
        vec2 uPos = ( gl_FragCoord.xy / resolution.xy );

        uPos.x -= 1.0;
        uPos.y -= 0.5;

        vec3 color = vec3(0.0);
        float vertColor = 2.0;
        for( float i = 0.0; i &lt; 15.0; ++i )
        {
        float t = time * (0.9);

        uPos.y += sin( uPos.x*i + t+i/2.0 ) * 0.1;
        float fTemp = abs(1.0 / uPos.y / 100.0);
        vertColor += fTemp;
        color += vec3( fTemp*(10.0-i)/10.0, fTemp*i/10.0, pow(fTemp,1.5)*1.5 );
        }

        vec4 color_final = vec4(color, 1.0);
        // 把颜色传递回JavaScript
        gl_FragColor = color_final;
    }

&lt;/script&gt; </pre>
<p>材质的创建，可以基于以下助手函数：</p>
<pre class="crayon-plain-tag">function createMaterial( vertexShader, fragmentShader ) {
    // 从HTML标签中读取着色器源码vertexShader、fragmentShader为脚本标签的ID
    var vertShader = document.getElementById( vertexShader ).innerHTML;
    var fragShader = document.getElementById( fragmentShader ).innerHTML;

    var attributes = {};
    // 向着色器传递变量
    var uniforms = {
        time: { type: 'f', value: 0.2 },
        scale: { type: 'f', value: 0.2 },
        alpha: { type: 'f', value: 0.6 },
        resolution: { type: "v2", value: new THREE.Vector2() }
    };

    uniforms.resolution.value.x = window.innerWidth;
    uniforms.resolution.value.y = window.innerHeight;

    // 创建一个ShaderMaterial材质
    var meshMaterial = new THREE.ShaderMaterial( {
        uniforms: uniforms,
        attributes: attributes,
        vertexShader: vertShader,
        fragmentShader: fragShader,
        transparent: true

    } );

    return meshMaterial;
}</pre>
<p>渲染循环中，我们需要改变uniform，从而导致着色器绘制结果发生变化，进而产生动画效果：</p>
<pre class="crayon-plain-tag">function render() {
    // 递增time
    cube.material.materials.forEach( function ( e ) {
        e.uniforms.time.value += 0.01;
    } );

    requestAnimationFrame( render );
    renderer.render( scene, camera );
}</pre>
<div class="blog_h2"><span class="graybg">线形几何图形的材质</span></div>
<p>有两类仅仅支持用在线条（THREE.Line）的材质。线条这种特殊的Geometry仅仅具有顶点，而没有面。</p>
<div class="blog_h3"><span class="graybg">THREE.LineBasicMaterial</span></div>
<p>这种线条非常简单，可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>线条的颜色</td>
</tr>
<tr>
<td>linewidth</td>
<td>线条的宽度</td>
</tr>
<tr>
<td>vertexColors</td>
<td>设置各个顶点的颜色为THREE.VertexColors类型。覆盖color属性</td>
</tr>
<tr>
<td>fog</td>
<td>是否受到全局迷雾的影响</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.LineDashedMaterial</span></div>
<p>除了上面的四个属性以外，还具有以下额外属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>scale</td>
<td>虚线条、线条间隔的缩放比例</td>
</tr>
<tr>
<td>dashSize</td>
<td>虚线条的大小</td>
</tr>
<tr>
<td>gapSize</td>
<td>线条间隔的大小</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">使用几何图形</span></div>
<p>Three.js内置了大量的Geometry，可以开箱即用。 本章介绍其中的二维、三维Geometry，线条类不再介绍。</p>
<div class="blog_h2"><span class="graybg">二维几何图形</span></div>
<p>二维图形的初始摆放位置是X-Y平面，但是很多情况下需要需要将它们（特别是PlaneGeometry）放置到“地面”上，也就是X-Z平面上。此时可以让它绕着X轴逆时针旋转90度：</p>
<pre class="crayon-plain-tag">mesh.rotation.x =- Math.PI/2; </pre>
<div class="blog_h3"><span class="graybg">THREE.PlaneGeometry</span></div>
<p>外观上是一个矩形。示例：</p>
<pre class="crayon-plain-tag">new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);</pre>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td> width</td>
<td style="text-align: center;">Y</td>
<td>矩形的宽度</td>
</tr>
<tr>
<td> height</td>
<td style="text-align: center;">Y </td>
<td>矩形的高度 </td>
</tr>
<tr>
<td> widthSegments</td>
<td style="text-align: center;">N </td>
<td>宽方向上分段的数量，默认1 </td>
</tr>
<tr>
<td> heightSegments</td>
<td style="text-align: center;">N </td>
<td>高方向上分段的数量，默认1 </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.CircleGeometry</span></div>
<p>外观上是一个圆形或者扇形。示例：</p>
<pre class="crayon-plain-tag">// 半径为3的圆
new THREE.CircleGeometry(3, 12);
// 半径为3的半圆
new THREE.CircleGeometry(3, 12, 0, Math.PI);</pre>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>radius</td>
<td style="text-align: center;">N</td>
<td>圆的半径，默认50</td>
</tr>
<tr>
<td>segments</td>
<td style="text-align: center;">N</td>
<td>分段数，定义了构成圆的面的数量，最小值3，默认值8。更大的面数意味着更平滑的边缘</td>
</tr>
<tr>
<td>thetaStart</td>
<td style="text-align: center;">N</td>
<td>从什么角度绘制起始扇边，默认0，支持范围0 ～ 2 * PI</td>
</tr>
<tr>
<td>thetaLength</td>
<td style="text-align: center;">N</td>
<td>从什么角度绘制终止扇边，默认 2 * PI，支持范围0 ～ 2 * PI</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.RingGeometry</span></div>
<p>外观上是一个圆环或者扇环。示例：</p>
<pre class="crayon-plain-tag">Var ring = new THREE.RingGeometry();</pre>
<p>可用属性： </p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>innerRadius</td>
<td style="text-align: center;">N</td>
<td>内半径，默认0</td>
</tr>
<tr>
<td>outerRadius</td>
<td style="text-align: center;">N</td>
<td>外半径，默认50</td>
</tr>
<tr>
<td>thetaSegments</td>
<td style="text-align: center;">N</td>
<td>分段数，定义了构成环的面的数量。影响圆弧的平滑度</td>
</tr>
<tr>
<td>phiSegments</td>
<td style="text-align: center;">N</td>
<td>不影响圆环的平滑度，但是可以增加其构成面的数量</td>
</tr>
<tr>
<td>thetaStart</td>
<td style="text-align: center;">N</td>
<td>从什么角度绘制起始扇边，默认0，支持范围0 ～ 2 * PI</td>
</tr>
<tr>
<td>thetaLength</td>
<td style="text-align: center;">N</td>
<td>从什么角度绘制终止扇边，默认 2 * PI，支持范围0 ～ 2 * PI</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.ShapeGeometry</span></div>
<p>该形状允许你创建自定义的二维图形，其操作方式类似于SVG/Canvas中的画布。 示例：</p>
<pre class="crayon-plain-tag">var shape = new THREE.Shape();

// 移动画笔到指定的点
shape.moveTo( 10, 10 );

// 向Y方向画30像素的线段
shape.lineTo( 10, 40 );

// 绘制贝塞尔曲线
shape.bezierCurveTo( 15, 25, 25, 25, 30, 40 );

// 绘制拟合曲线
shape.splineThru( [
    new THREE.Vector2( 32, 30 ),
    new THREE.Vector2( 28, 20 ),
    new THREE.Vector2( 30, 10 ),
] );

// 绘制二次方曲线
shape.quadraticCurveTo( 20, 15, 10, 10 );

// 添加一个路径到形状中，挖洞
var hole1 = new THREE.Path();
hole1.absellipse( 16, 24, 2, 3, 0, Math.PI * 2, true );
shape.holes.push( hole1 );
// 再挖一个洞
var hole2 = new THREE.Path();
hole2.absellipse( 23, 24, 2, 3, 0, Math.PI * 2, true );
shape.holes.push( hole2 );
// 再一个挖洞
var hole3 = new THREE.Path();
hole3.absarc( 20, 16, 2, 0, Math.PI, true );
shape.holes.push( hole3 );

// 基于上述形状创建Geometry对象
new THREE.ShapeGeometry( shape );</pre>
<p>运行结果示意图：</p>
<p><img class="aligncenter size-full wp-image-14474" src="https://blog.gmem.cc/wp-content/uploads/2017/01/ShapeGeometry.png" alt="shapegeometry" width="406" height="431" />ShapeGeometry支持以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>shapes</td>
<td style="text-align: center;">Y</td>
<td>构成此Geometry的一个或者多个THREE.Shape对象，可以传入数组</td>
</tr>
<tr>
<td>options</td>
<td style="text-align: center;">N</td>
<td>
<p>应用到所有THREE.Shape的选项：</p>
<ol>
<li>curveSegments，决定了曲线的平滑程度，默认12</li>
<li>material，使用MeshFaceMaterial时，指定该形状使用的materialIndex</li>
<li>UVGenerator，当为材质指定纹理时，指定UV Mapping——决定纹理的哪个部分给哪个面使用。默认THREE.ExtrudeGeometry.WorldUVGenerator</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.Shape</span></div>
<p>该类型是THREE.ShapeGeometry的最重要的部分，允许你创建自定义的形状。它提供以下方法和属性：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td><strong>moveTo(x,y)  </strong>移动画笔到指定的位置</td>
</tr>
<tr>
<td><strong>lineTo(x,y)</strong>  从当前位置向(x,y)绘制直线</td>
</tr>
<tr>
<td>
<p><strong>quadraticCurveTo(aCPx, aCPy, x, y)</strong><br /><strong>bezierCurveTo(aCPx1, aCPy1, aCPx2, aCPy2, x, y)</strong></p>
<p>你可以使用两种方式绘制曲线：二次曲线、贝塞尔曲线。两种方式的不同之处在于如何指定曲线的曲率（curvature）。下图显示这两种曲线的差别：</p>
<p><img class="aligncenter size-full wp-image-14478" src="https://blog.gmem.cc/wp-content/uploads/2017/01/curve.png" alt="curve" width="430" height="154" /></p>
<p>除了曲线的两个端点以外，对于：</p>
<ol>
<li>二次曲线，你需要提供额外的一个点(aCPx, aCPy)，这个点决定了曲线的曲率</li>
<li>三次曲线（贝塞尔曲线），你需要提供额外的两个点(aCPx1, aCPy1, aCPx2, aCPy2)</li>
</ol>
<p>注意：起点都是画笔当前位置，不需要在参数中指定</p>
</td>
</tr>
<tr>
<td><strong>splineThru(pts)</strong><br />在一系列点之间绘制流线型（拟合）的曲线，参数必须是THREE.Vector2对象的数组</td>
</tr>
<tr>
<td><strong>arc(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise)</strong><br />绘制一个圆圈或者圆弧。(aX, aY)指定离开当前画笔位置的偏移量，aRadius表示半径，(aStartAngle, aEndAngle)表示起始、终止角度，aClockwise为布尔值，表示是否顺时针绘制</td>
</tr>
<tr>
<td><strong>absArc(aX, aY, aRadius, aStartAngle, aEndAngle,AClockwise)</strong><br />在绝对位置上绘制圆弧</td>
</tr>
<tr>
<td><strong>ellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise)</strong><br />绘制椭圆或者部分椭圆</td>
</tr>
<tr>
<td><strong>absellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise)</strong><br />在绝对位置上绘制椭圆或者部分椭圆</td>
</tr>
<tr>
<td><strong>fromPoints(vectors)</strong><br />根据THREE.Vector2或者THREE.Vector3数组绘制路径</td>
</tr>
<tr>
<td><strong>holes</strong><br />THREE.Shape对象的数组，表示在当前形状上挖去的洞</td>
</tr>
<tr>
<td><strong>makeGeometry(options)<br /></strong>基于此形状生成一个THREE.ShapeGeometry对象<strong><br /></strong></td>
</tr>
<tr>
<td><strong>createPointsGeometry(divisions)<br /></strong>把形状转换为一系列采样点的数组，divisions指定点的数量。你可以基于这些点生成一个线条对象：<br />
<pre class="crayon-plain-tag">new THREE.Line( 
    shape.createPointsGeometry(10), new
    THREE.LineBasicMaterial( { color: 0xff3333, linewidth: 2 } ) 
);</pre>
</td>
</tr>
<tr>
<td><strong>createSpacedPointsGeometry(divisions)</strong> <br />与上面类似，但是生成一个Path对象</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">三维几何图形</span></div>
<div class="blog_h3"><span class="graybg">THREE.BoxGeometry</span></div>
<p>这是一个非常简单的三维图形，具有长宽高的盒子：</p>
<pre class="crayon-plain-tag">new THREE.BoxGeometry(10,10,10);</pre>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>width</td>
<td style="text-align: center;">Y</td>
<td>宽度，沿着X轴</td>
</tr>
<tr>
<td>height</td>
<td style="text-align: center;">Y</td>
<td>高度，沿着Y轴</td>
</tr>
<tr>
<td>depth</td>
<td style="text-align: center;">Y</td>
<td>长度，沿着Z轴</td>
</tr>
<tr>
<td>widthSegments</td>
<td style="text-align: center;">N</td>
<td rowspan="3">在三个方向上的分段数</td>
</tr>
<tr>
<td>heightSegments</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>depthSegments</td>
<td style="text-align: center;">N</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.SphereGeometry</span></div>
<p>基于此类型，你可以绘制三维球体、不完整球体：</p>
<p><img class="aligncenter size-full wp-image-14482" src="https://blog.gmem.cc/wp-content/uploads/2017/01/sphere.png" alt="sphere" width="676" height="430" />可以看到，你可以截取球体经度、纬度方向的任意片断。</p>
<p>代码示例：</p>
<pre class="crayon-plain-tag">new THREE.SphereGeometry(radius,widthSegments,heightSegments,phiStart,phiLength,thetaStart,thetaLength) </pre>
<p>该类型提供以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>radius</td>
<td style="text-align: center;">N</td>
<td>球体的半径，默认50</td>
</tr>
<tr>
<td>widthSegments</td>
<td style="text-align: center;">N</td>
<td>垂直方向的分段数，默认8</td>
</tr>
<tr>
<td>heightSegments</td>
<td style="text-align: center;">N</td>
<td>水平方向的分段数，默认8</td>
</tr>
<tr>
<td>phiStart</td>
<td style="text-align: center;">N</td>
<td>在经度方向上，绘制球体的起点，向东绘制。范围0 ~ 2*PI</td>
</tr>
<tr>
<td>phiLength</td>
<td style="text-align: center;">N</td>
<td>在经度方向上，绘制的长度。范围0 ~ 2*PI</td>
</tr>
<tr>
<td>thetaStart</td>
<td style="text-align: center;">N</td>
<td>在纬度方向上，绘制球体的起点，向南绘制。范围0 ~ 2*PI</td>
</tr>
<tr>
<td>thetaLength</td>
<td style="text-align: center;">N</td>
<td>在纬度方向上，绘制的长度。范围0 ~ 2*PI</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.CylinderGeometry</span></div>
<p>可以绘制圆柱、圆筒、圆锥或者截锥。代码示例：</p>
<pre class="crayon-plain-tag">new THREE.CylinderGeometry(radiusTop,radiusBottom,height,radialSegments,heightSegments,openEnded)</pre>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>radiusTop</td>
<td style="text-align: center;">N</td>
<td>上半径</td>
</tr>
<tr>
<td>radiusBottom</td>
<td style="text-align: center;">N</td>
<td>下半截</td>
</tr>
<tr>
<td>height</td>
<td style="text-align: center;">N</td>
<td>高度</td>
</tr>
<tr>
<td>radialSegments</td>
<td style="text-align: center;">N</td>
<td>在上下底方向上的分段数，决定光滑度</td>
</tr>
<tr>
<td>heightSegments</td>
<td style="text-align: center;">N</td>
<td>在高度方向上的分段数</td>
</tr>
<tr>
<td>openEnded</td>
<td style="text-align: center;">N</td>
<td>是否上下底开放，默认false</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">THREE.TorusGeometry</span></div>
<p>类似于甜甜圈的圆环面。代码示例：</p>
<pre class="crayon-plain-tag">new THREE.TorusGeometry(radius, tube, radialSegments,tubularSegments,arc)</pre>
<p> 可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>radius</td>
<td style="text-align: center;">N</td>
<td>外半径</td>
</tr>
<tr>
<td>tube</td>
<td style="text-align: center;">N</td>
<td>甜甜圈管道的半径</td>
</tr>
<tr>
<td>radialSegments</td>
<td style="text-align: center;">N</td>
<td rowspan="2">分段数</td>
</tr>
<tr>
<td>tubularSegments</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>arc</td>
<td style="text-align: center;">N</td>
<td>弧度，决定是不是绘制完整的甜甜圈，最大值2 * PI</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">高级图形及二进制操作</span></div>
<div class="blog_h2"><span class="graybg">THREE.ConvexGeometry</span></div>
<p>创建基于若干点的最小化凸面体。该形状不是Three.js核心库的组成部分。</p>
<div class="blog_h2"><span class="graybg">THREE.LatheGeometry</span></div>
<p>允许你基于一个光滑曲线来创建形状。此曲线由一系列的点（Knots）指定，通常是拟合曲线。曲线围绕对象的中心Z轴转动，可以产生类似于花瓶、钟之类的形状。示例：</p>
<p><img class="aligncenter size-full wp-image-14484" src="https://blog.gmem.cc/wp-content/uploads/2017/01/lathe.png" alt="lathe" width="566" height="560" /></p>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>points</td>
<td style="text-align: center;">Y</td>
<td>绘制曲线的基准点</td>
</tr>
<tr>
<td>segments</td>
<td style="text-align: center;">N</td>
<td>分段数，数字越大则形状越光滑</td>
</tr>
<tr>
<td>phiStart</td>
<td style="text-align: center;">N</td>
<td>开始弧度</td>
</tr>
<tr>
<td>phiLength</td>
<td style="text-align: center;">N</td>
<td>绘制弧长</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">THREE.ExtrudeGeometry</span></div>
<p>可以把2D图形凸起、抬高为3D图形。比如我们可以把上面章节中的ShapeGeometry抬高：</p>
<pre class="crayon-plain-tag">var options = {
    amount: 10,
    bevelThickness: 2,
    bevelSize: 1,
    bevelSegments: 3,
    bevelEnabled: true,
    curveSegments: 12,
    steps: 1
};
new THREE.ExtrudeGeometry( drawShape(), options );</pre>
<p> 运行效果图如下：</p>
<p><img class="aligncenter size-full wp-image-14485" src="https://blog.gmem.cc/wp-content/uploads/2017/01/extrude-geometry.png" alt="extrude-geometry" width="95%" /></p>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>shapes</td>
<td style="text-align: center;">Y</td>
<td>基于其进行凸起、抬高的THREE.Shape或者THREE.Shape数组</td>
</tr>
<tr>
<td>amount</td>
<td style="text-align: center;">N</td>
<td>抬高的高度，默认100</td>
</tr>
<tr>
<td>bevelThickness</td>
<td style="text-align: center;">N</td>
<td>
<p>在形状前面、后面，以及抬起的哪个侧面之间，创建一个斜坡</p>
<p>此厚度，即为圆滑斜坡给侧面“增加”的厚度的1/2</p>
</td>
</tr>
<tr>
<td>bevelSize</td>
<td style="text-align: center;">N</td>
<td>斜坡的高度，此高度导致从前/后面看形状，其面积变大</td>
</tr>
<tr>
<td>bevelSegments</td>
<td style="text-align: center;">N</td>
<td>斜坡分段数，让斜坡光滑</td>
</tr>
<tr>
<td>bevelEnabled</td>
<td style="text-align: center;">N</td>
<td>是否启用斜坡，默认启用</td>
</tr>
<tr>
<td>curveSegments</td>
<td style="text-align: center;">N</td>
<td>让曲线光滑</td>
</tr>
<tr>
<td>steps</td>
<td style="text-align: center;">N</td>
<td>凸起生成的面的分段数，默认1</td>
</tr>
<tr>
<td>extrudePath</td>
<td style="text-align: center;">N</td>
<td>沿着什么路径执行凸起，默认沿着Z轴，可以指定任意的路径</td>
</tr>
<tr>
<td>material</td>
<td style="text-align: center;">N</td>
<td>用作前后面的材质的索引，如果希望前后面使用不同材质可以调用THREE.SceneUtils.createMultiMaterialObject()</td>
</tr>
<tr>
<td>extrudeMaterial</td>
<td style="text-align: center;">N</td>
<td>凸起面和斜坡使用的材质的索引</td>
</tr>
<tr>
<td>uvGenerator</td>
<td style="text-align: center;">N</td>
<td>UVGenerator，当为材质指定纹理时，指定UV Mapping——决定纹理的哪个部分给哪个面使用。默认THREE.ExtrudeGeometry.WorldUVGenerator</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">THREE.TubeGeometry</span></div>
<p>与ExtrudeGeometry类似，这个类也是用于“凸起”的，只是它凸起的目标是3D的拟合曲线，而非2D图形。可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>path</td>
<td style="text-align: center;">Y</td>
<td>凸起的目标，一个THREE.SplineCurve3对象</td>
</tr>
<tr>
<td>segments</td>
<td style="text-align: center;">N</td>
<td>分段数，路径越长，该值应该越大，默认64</td>
</tr>
<tr>
<td>radius</td>
<td style="text-align: center;">N</td>
<td>管道的半径，默认1</td>
</tr>
<tr>
<td>radiusSegments</td>
<td style="text-align: center;">N</td>
<td>管道截面分段数，默认8</td>
</tr>
<tr>
<td>closed</td>
<td style="text-align: center;">N</td>
<td>是否闭合管道，默认false</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">THREE.ParametricGeometry</span></div>
<p>基于一个函数来生成几何图形。此函数有两个入参u、v，其返回值是一个三维向量，此向量作为几何图形的顶点，本质上是二维平面到三维空间的映射。</p>
<p>可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>function</td>
<td style="text-align: center;">Y</td>
<td>生成器函数，其返回值作为结果Geometry的顶点</td>
</tr>
<tr>
<td>slices</td>
<td style="text-align: center;">Y</td>
<td>u值被划分为多少子值，u的取值范围是0～1</td>
</tr>
<tr>
<td>stacks</td>
<td style="text-align: center;">Y</td>
<td>v值被划分为多少子值，v的取值范围是0～1</td>
</tr>
</tbody>
</table>
<p>示例：</p>
<pre class="crayon-plain-tag">var radialWave = function ( u, v ) {
    var r = 50;

    var x = Math.sin( u ) * r;
    var z = Math.sin( v / 2 ) * 2 * r;
    var y = (Math.sin( u * 4 * Math.PI ) + Math.cos( v * 2 * Math.PI )) * 2.8;

    return new THREE.Vector3( x, y, z );
};

var mesh = createMesh( new THREE.ParametricGeometry( radialWave, 120, 120, false ) );</pre>
<p>运行效果图：</p>
<p><img class="aligncenter size-full wp-image-14498" src="https://blog.gmem.cc/wp-content/uploads/2017/01/radialWave.png" alt="radialwave" width="400" height="294" /> </p>
<div class="blog_h2"><span class="graybg">THREE.TextGeometry</span></div>
<p>该类型用于创建凸起、抬升的3D文本。可用属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="width: 28px; text-align: center;">必</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>size</td>
<td style="text-align: center;">N</td>
<td>文本的尺寸，默认100</td>
</tr>
<tr>
<td>height</td>
<td style="text-align: center;">N</td>
<td>凸起的高度</td>
</tr>
<tr>
<td>weight</td>
<td style="text-align: center;">N</td>
<td>粗体设置，可选值bold、normal</td>
</tr>
<tr>
<td>font</td>
<td style="text-align: center;">N</td>
<td>字体名称，默认helvetiker</td>
</tr>
<tr>
<td>style</td>
<td style="text-align: center;">N</td>
<td>字体样式，可选值normal、italic</td>
</tr>
<tr>
<td>bevelThickness</td>
<td style="text-align: center;">N</td>
<td rowspan="4">斜坡设置，默认不启用斜坡</td>
</tr>
<tr>
<td>bevelSize</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>bevelSegments</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>bevelEnabled</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>curveSegments</td>
<td style="text-align: center;">N</td>
<td>让曲线光滑</td>
</tr>
<tr>
<td>steps</td>
<td style="text-align: center;">N</td>
<td rowspan="5">参考ExtrudeGeometry</td>
</tr>
<tr>
<td>extrudePath</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>material</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>extrudeMaterial</td>
<td style="text-align: center;">N</td>
</tr>
<tr>
<td>uvGenerator</td>
<td style="text-align: center;">N</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">二进制操作</span></div>
<p>你可以把Three.js的标准几何图形联合起来，形成复杂的新图形，这种技术叫做CSG（Constructive Solid Geometry，构造实体几何）。</p>
<p>为了支持CSG，我们需要使用到Three.js扩展<a href="https://github.com/skalnik/ThreeBSP">ThreeBSP</a>。该库提供了以下函数：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>intersect</td>
<td>基于两个既有Geometry的空间交叉部分（intersection）来生成新的Geometry</td>
</tr>
<tr>
<td>union</td>
<td>联合两个既有Geometry的空间，生成新的Geometry </td>
</tr>
<tr>
<td>subtract</td>
<td>从一个Geometry中挖去与另外一个Geometry重叠的部分，形成新的Geometry</td>
</tr>
</tbody>
</table>
<p>注意：这三个函数都基于Mesh的绝对位置执行计算。因此，如果你对组（或者应用多重材质）进行操作，可能得到意外的结果。</p>
<p>下面的代码示例了如何对两个球体进行二进制操作：</p>
<pre class="crayon-plain-tag">// 创建BSP对象
var sphere1BSP = new ThreeBSP( sphere1 );
var sphere2BSP = new ThreeBSP( sphere2 );

var resultBSP;

switch ( controls.actionSphere ) {
    case "subtract":
        resultBSP = sphere1BSP.subtract( sphere2BSP );
        break;
    case "intersect":
        resultBSP = sphere1BSP.intersect( sphere2BSP );
        break;
    case "union":
        resultBSP = sphere1BSP.union( sphere2BSP );
        break;
    case "none": // noop;
}
// 转换为Mesh并添加到场景
result = resultBSP.toMesh();
result.geometry.computeFaceNormals();
result.geometry.computeVertexNormals();
scene.add(result);</pre>
<div class="blog_h1"><span class="graybg">粒子和点云</span></div>
<p>在前面的章节中，我们以及了解了Three.js的大部分重要组件：场景、镜头、灯光、图形、材质。本章主要研究一个重要的，但是迄今为止尚未提及的重要概念——粒子。</p>
<p>粒子（particles）某些时候也称为精灵（sprites），是场景中的小物体。这些物体很容易被大量的创建，以模拟雨、雪、烟以及其它多种有趣的特效。</p>
<p>需要注意，在较近版本的Three.js中，与粒子相关的物体的类型名称从THREE.ParticleSystem变为THREE.PointCloud。粒子本身的类型名称从THREE.Particle变为THREE.Sprite。</p>
<div class="blog_h2"><span class="graybg">理解粒子</span></div>
<p>粒子是<span style="background-color: #c0c0c0;">2D的平面，该平面总是正向面对镜头</span>。下面的代码创建了100个粒子：</p>
<pre class="crayon-plain-tag">// 粒子的材质
var material = new THREE.SpriteMaterial();
for ( var x = -5; x &lt; 5; x++ ) {
    for ( var y = -5; y &lt; 5; y++ ) {
        // 创建粒子
        var sprite = new THREE.Sprite( material );
        // 设置其位置
        sprite.position.set( x * 10, y * 10, 0 );
        // 添加到场景
        scene.add( sprite );
    }
}</pre>
<p>当你不指定任何属性的时候，粒子被渲染为白色二维小方块。 所以，上面的代码会在场景中展示10 x 10的小方块阵列。</p>
<p>粒子接受的材质类型只有：THREE.SpriteCanvasMaterial、THREE.SpriteMaterial。</p>
<p>与Three.Mesh类似，THREE.Sprite也继承自THREE.Object3D。这意味着THREE.Mesh的很多属性/方法对于粒子也是可用的，你可以使用scale属性对其缩放、使用position让其移动。</p>
<div class="blog_h2"><span class="graybg">理解点云</span></div>
<p>虽然创建并移动粒子很简单，但是如果操控的粒子数量很大，你很快就会遇到性能问题。为此，Three.js提供了THREE.PointCloud用来统一处理大量的粒子。基于PointCloud的、与上面等效的代码如下：</p>
<pre class="crayon-plain-tag">var geom = new THREE.Geometry();
// 点云材质
var material = new THREE.PointCloudMaterial( {
    size: 4,
    vertexColors: true, color: 0xffffff
} );
for ( var x = -5; x &lt; 5; x++ ) {
    for ( var y = -5; y &lt; 5; y++ ) {
        // 每个粒子是三维空间中的一个点
        var particle = new THREE.Vector3( x * 10, y * 10, 0 );
        geom.vertices.push( particle );
        geom.colors.push( new THREE.Color( Math.random() * 0x00ffff ) );
    }
}
// 点的集合，点云
var cloud = new THREE.PointCloud( geom, material );
scene.add( cloud );</pre>
<p>要创建点云，需要两个参数：</p>
<ol>
<li>材质，使用颜色或者纹理来装饰粒子</li>
<li>Geometry， 指定所有粒子的位置</li>
</ol>
<p>下面再举一个例子：创建15000个随机亮度的绿色粒子构成的点云：</p>
<pre class="crayon-plain-tag">var geom = new THREE.Geometry();
var material = new THREE.PointCloudMaterial( {
    size: size,
    transparent: transparent,
    opacity: opacity,
    vertexColors: vertexColors,
    sizeAttenuation: sizeAttenuation,
    color: color
} );


var range = 500;
for ( var i = 0; i &lt; 15000; i++ ) {
    // 位置随机的粒子
    var particle = new THREE.Vector3( 
        Math.random() * range - range / 2, 
        Math.random() * range - range / 2, 
        Math.random() * range - range / 2 
    );
    geom.vertices.push( particle );
    var color = new THREE.Color( 0x00ff00 );
    // 以色相、饱和度、亮度的方式设置颜色。随机亮度的绿色
    color.setHSL( color.getHSL().h, color.getHSL().s, Math.random() * color.getHSL().l );
    // 顶点颜色数组
    geom.colors.push( color );

}</pre>
<p>当自动旋转点云时，你可以看到粒子满天飞舞的效果。</p>
<div class="blog_h3"><span class="graybg">THREE.PointCloudMaterial</span></div>
<p>该材质的属性说明如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>点云（粒子系统）中所有粒子的颜色，如果vertexColors设置为true，并且设置了Geometry的colors属性，则该属性被覆盖</td>
</tr>
<tr>
<td>map</td>
<td>指定该属性，你可以为粒子设置纹理。使用该属性，你可以让粒子看起来更像真实世界中的粒子，例如雪花 </td>
</tr>
<tr>
<td>size</td>
<td>粒子的尺寸，默认1</td>
</tr>
<tr>
<td>sizeAnnutation</td>
<td>如果false，则所有粒子的大小一样。否则，其尺寸取决于粒子距离镜头的远近</td>
</tr>
<tr>
<td>vertexColors</td>
<td>默认情况下，点云中所有粒子的颜色一致，设置该属性为THREE.VertexColors则Geometry的colors属性被用来指定粒子的颜色。默认值THREE.NoColors </td>
</tr>
<tr>
<td>opacity</td>
<td>与transparent联用，设置粒子的透明度</td>
</tr>
<tr>
<td>transparent</td>
<td>默认false，如果设置为true，允许粒子具有透明度 </td>
</tr>
<tr>
<td>blending</td>
<td>渲染粒子时使用的混合模式 </td>
</tr>
<tr>
<td>fog </td>
<td>默认true，粒子是否被全局迷雾影响</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基于HTML5画布来装饰粒子</span></div>
<p>你可以使用三种方式来基于HTML画布装饰（Style）粒子：</p>
<ol>
<li>如果使用THREE.CanvasRenderer，你可以直接通过THREE.SpriteCanvasMaterial引用HTML5画布对象</li>
<li>如果使用THREE.WebGLRenderer，你需要一些额外的步骤来使用HTML5画布</li>
</ol>
<div class="blog_h3"><span class="graybg">使用THREE.CanvasRenderer</span></div>
<p>在使用该渲染器时，你可以使用THREE.SpriteCanvasMaterial，直接把画布的输出作为粒子的纹理使用。SpriteCanvasMaterial这个材质是专门为CanvasRenderer准备的，支持以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>color</td>
<td>粒子的颜色，依据混合模式的设置，该颜色会和画布中图片进行混合</td>
</tr>
<tr>
<td>program</td>
<td>一个函数，以画布上下文作为入参。在粒子渲染时该函数被调用，函数的输出被绘制为粒子</td>
</tr>
<tr>
<td>opacity</td>
<td>粒子的透明度</td>
</tr>
<tr>
<td>transparent</td>
<td>是否允许粒子透明</td>
</tr>
<tr>
<td>blending</td>
<td>使用的混合模式</td>
</tr>
<tr>
<td>rotation</td>
<td>用于旋转画布的内容</td>
</tr>
</tbody>
</table>
<p>示例：</p>
<pre class="crayon-plain-tag">var canvasRenderer = new THREE.CanvasRenderer();
// ...

// 抽取纹理的程序
var getTexture = function ( ctx ) {

    // the body
    ctx.translate( -81, -84 );

    ctx.fillStyle = "orange";
    ctx.beginPath();
    // ...
    ctx.fill();

};

// 粒子材质
var material = new THREE.SpriteCanvasMaterial( {
        program: getTexture,
        color: 0xffffff
    }
);
// 旋转
material.rotation = Math.PI;
// 创建粒子
var range = 500;
for ( var i = 0; i &lt; 1500; i++ ) {
    var sprite = new THREE.Sprite( material );
    sprite.position.set( /* random */ );
    sprite.scale.set( 0.1, 0.1, 0.1 );
    scene.add( sprite );
}</pre>
<div class="blog_h3"><span class="graybg">使用WebGLRenderer</span></div>
<p>使用该渲染器时， 你需要手工在内存中创建画布对象，完成2D图形绘制，并返回一个纹理对象：</p>
<pre class="crayon-plain-tag">var getTexture = function () {
    var canvas = document.createElement( 'canvas' );
    canvas.width = 32;
    canvas.height = 32;

    var ctx = canvas.getContext( '2d' );
    // ...

    // 返回一个纹理对象
    var texture = new THREE.Texture( canvas );
    texture.needsUpdate = true;
    return texture;
};

var geom = new THREE.Geometry();


var material = new THREE.PointCloudMaterial( {
    size: size,
    transparent: transparent,
    opacity: opacity,
    // 指定使用的THREE.Texture对象
    map: getTexture(),
    sizeAttenuation: sizeAttenuation,
    color: color
} );


var range = 500;
for ( var i = 0; i &lt; 5000; i++ ) {
    var particle = new THREE.Vector3( /* random */ );
    geom.vertices.push( particle );
}

cloud = new THREE.PointCloud( geom, material );</pre>
<div class="blog_h3"><span class="graybg">径向渐变的例子</span></div>
<p>下面的代码演示了如何使用Canvas绘制一个径向渐变的光球：</p>
<pre class="crayon-plain-tag">var canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;

var context = canvas.getContext('2d');
var gradient = context.createRadialGradient(
    canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2
);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
gradient.addColorStop(0.4, 'rgba(0,0,64,1)');
gradient.addColorStop(1, 'rgba(0,0,0,1)');

context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);

var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;</pre>
<p>可以使用此光球来装饰粒子，产生荧光那样的效果。 </p>
<div class="blog_h2"><span class="graybg">使用纹理装饰粒子</span></div>
<p>上一个粒子中，我们已经使用了纹理，纹理的图像从画布中抓取。</p>
<p>实际上，我们可以把任何图片作为纹理使用：</p>
<pre class="crayon-plain-tag">var texture = THREE.ImageUtils.loadTexture( "../assets/textures/particles/raindrop-3.png" );</pre>
<p>注意：作为纹理的图片，大小必须是2的N次方，必须是正方形。</p>
<div class="blog_h3"><span class="graybg">下雨的例子</span></div>
<p>下面使用该纹理模拟下雨效果：</p>
<pre class="crayon-plain-tag">var geom = new THREE.Geometry();

var material = new THREE.ParticleBasicMaterial( {
    size: size,
    transparent: transparent,
    opacity: opacity,
    map: texture,
    // 设置混合模式为相加，意味着雨滴图片中黑色背景部分（000000）不会被绘制——背景色 + 0 仍然是背景色
    // 使用透明背景的纹理是不支持的
    blending: THREE.AdditiveBlending,
    sizeAttenuation: sizeAttenuation,
    color: color
} );


var range = 40;
for ( var i = 0; i &lt; 1500; i++ ) {
    var particle = new THREE.Vector3(
        Math.random() * range - range / 2,
        Math.random() * range * 1.5,
        Math.random() * range - range / 2 
    );
    // 设置随机的速度属性，备用
    particle.velocityY = 0.1 + Math.random() / 5;
    particle.velocityX = (Math.random() - 0.5) / 3;
    geom.vertices.push( particle );
}

cloud = new THREE.ParticleSystem( geom, material );
cloud.sortParticles = true;

scene.add( cloud );


function render() {
    scene.children.forEach( function ( child ) {
        if ( child instanceof THREE.PointCloud ) {
            var vertices = child.geometry.vertices;
            // 在渲染循环中遍历处理所有粒子，根据速度设置其位置
            vertices.forEach( function ( v ) {
                v.y = v.y - (v.velocityY);
                v.x = v.x - (v.velocityX);
                // 如果粒子超出显示范围，则重置其位置
                if ( v.y &lt;= 0 ) v.y = 60;
                if ( v.x &lt;= -20 || v.x &gt;= 20 ) v.velocityX = v.velocityX * -1;
            } );
        }
    } );
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
}</pre>
<div class="blog_h3"><span class="graybg">下雪的例子</span></div>
<p>我们改进一下上面的例子，模拟更真实的下雪效果：</p>
<ol>
<li>建立多个点云，来模拟不同大小的雪花</li>
<li>在Z轴方向改变雪花的位置，模拟三维空间中雪花的飘舞</li>
</ol>
<p>代码如下：</p>
<pre class="crayon-plain-tag">function createPointCloud( name, texture, size, transparent, opacity, sizeAttenuation, color ) {
    var geom = new THREE.Geometry();

    var color = new THREE.Color( color );
    // 随机的改变亮度
    color.setHSL( color.getHSL().h, color.getHSL().s, (Math.random()) * color.getHSL().l );

    var material = new THREE.PointCloudMaterial( {
        size: size,
        transparent: transparent,
        opacity: opacity,
        map: texture,
        blending: THREE.AdditiveBlending,
        // 设置为false，表示对象不影响WebGL的depth buffer，这样不同的粒子系统就不会相互干扰
        depthWrite: false,
        sizeAttenuation: sizeAttenuation,
        color: color
    } );

    var range = 40;
    for ( var i = 0; i &lt; 50; i++ ) {
        var particle = new THREE.Vector3(
            Math.random() * range - range / 2,
            Math.random() * range * 1.5,
            Math.random() * range - range / 2 );
        // 雪花在三个轴的方向上都具有速度
        particle.velocityY = 0.1 + Math.random() / 5;
        particle.velocityX = (Math.random() - 0.5) / 3;
        particle.velocityZ = (Math.random() - 0.5) / 3;
        geom.vertices.push( particle );
    }

    var system = new THREE.PointCloud( geom, material );
    system.name = name;
    system.sortParticles = true;
    return system;
}
// 创建多个粒子系统
function createPointClouds( size, transparent, opacity, sizeAttenuation, color ) {

    var texture1 = THREE.ImageUtils.loadTexture( "../assets/textures/particles/snowflake1.png" );
    var texture2 = THREE.ImageUtils.loadTexture( "../assets/textures/particles/snowflake2.png" );
    var texture3 = THREE.ImageUtils.loadTexture( "../assets/textures/particles/snowflake3.png" );
    var texture4 = THREE.ImageUtils.loadTexture( "../assets/textures/particles/snowflake5.png" );

    scene.add( createPointCloud( "system1", texture1, size, transparent, opacity, sizeAttenuation, color ) );
    scene.add( createPointCloud( "system2", texture2, size, transparent, opacity, sizeAttenuation, color ) );
    scene.add( createPointCloud( "system3", texture3, size, transparent, opacity, sizeAttenuation, color ) );
    scene.add( createPointCloud( "system4", texture4, size, transparent, opacity, sizeAttenuation, color ) );
}

createPointClouds( controls.size, controls.transparent, controls.opacity, controls.sizeAttenuation, controls.color );

function render() {

    scene.children.forEach( function ( child ) {
        if ( child instanceof THREE.PointCloud ) {
            var vertices = child.geometry.vertices;
            vertices.forEach( function ( v ) {
                // 模拟三维飘落效果
                v.y = v.y - (v.velocityY);
                v.x = v.x - (v.velocityX);
                v.z = v.z - (v.velocityZ);

                if ( v.y &lt;= 0 ) v.y = 60;
                if ( v.x &lt;= -20 || v.x &gt;= 20 ) v.velocityX = v.velocityX * -1;
                if ( v.z &lt;= -20 || v.z &gt;= 20 ) v.velocityZ = v.velocityZ * -1;
            } );
        }
    } );

    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
}</pre>
<div class="blog_h2"><span class="graybg">Sprite Map</span></div>
<p>我们可以把多个Sprite放在一个图片中，然后通过偏移量来加载、使用。就像CSS Sprite那样：</p>
<pre class="crayon-plain-tag">var spriteMaterial = new THREE.SpriteMaterial({
        opacity: opacity,
        color: color,
        transparent: transparent,
        map: getTexture() // 这个纹理中包含五个横向排列的小图片，我们只需要其中一个
    }
);
// 纹理图片中，X轴（u）\Y轴（v）方向的偏移量
// 如果spriteNumber取2，表示需要第三个小图片，u-offset = 0.2 * 2 = 0.4，即u偏移为0.4
// 注意u、v的最大值都是1，表示整个图片的大小，因此每个图片的大小是0.2
spriteMaterial.map.offset = new THREE.Vector2(0.2 * spriteNumber, 0);
// 如果不设置repeat，那么3、4、5几个小图都称为Sprite的一部分。而我们只需要第三个图片
// 1/5表示在u方向仅需要1/5长度，恰好是一个小图的大小
spriteMaterial.map.repeat = new THREE.Vector2(1 / 5, 1);
spriteMaterial.depthTest = false;

spriteMaterial.blending = THREE.AdditiveBlending;

var sprite = new THREE.Sprite(spriteMaterial);</pre>
<div class="blog_h2"><span class="graybg">从高级形状创建点云</span></div>
<p>点云基于你所提供的Geometry的顶点来渲染粒子。这意味着我们可以向它传递前面所学过的任何几何图形。</p>
<div class="blog_h1"><span class="graybg">创建、加载高级Mesh和Geometry</span></div>
<p>前面的章节我们了解到，可以通过ThreeBSP这个插件来创建复合的Mesh。本章我们将学习另外两种创建高级Geometry/Mesh的机制：</p>
<ol>
<li>分组、合并：Three.js支持内置的分组/合并机制，允许基于现存的对象来创建Mesh/Geometry</li>
<li>加载模型：Three.js支持从多种外部格式来加载Mesh/Geometry</li>
</ol>
<div class="blog_h2"><span class="graybg">分组多个Mesh</span></div>
<p>这个机制我们已经使用过，当为Geometry应用多个材质时，实际上Three.js就创建了组。</p>
<p>创建组非常容易，任何Mesh都可以包含子元素，你可以通过<pre class="crayon-plain-tag">add()</pre> 方法随时添加子元素：</p>
<pre class="crayon-plain-tag">sphere = createMesh(new THREE.SphereGeometry(5, 10, 10));
cube = createMesh(new THREE.BoxGeometry(6, 6, 6));

// 任何3D对象可以作为组的容器，Object3D是Mesh、Scene的超类
group = new THREE.Object3D();   // 最近版本的Three.js引入THREE.Group，专门用作组容器
// 向容器中添加其它Mesh
group.add(sphere);
group.add(cube);

scene.add(group); </pre>
<p>当你针对组中的父对象进行移动、缩放、旋转等操作时，所有子对象将会被应用相同的操作。需要强调的是旋转操作，执行旋转的时候，是整个组围绕组的中心进行旋转，而不是每个元素绕着各自的中心旋转。</p>
<p>使用组时，你依然可以对单个元素进行移动、缩放、旋转等操作。但是需要注意，这些操作都是相对于父对象进行的。</p>
<div class="blog_h2"><span class="graybg">合并多个Geometry</span></div>
<p>大部分情况下，使用分组可以让你方便的操控大量的Mesh。但是性能问题也可能出现，因为使用分组时，每个对象依然需要被单独的处理、渲染。</p>
<p>使用<pre class="crayon-plain-tag">THREE.Geometry.merge()</pre> 你可以合并多个几何图形，然后创建单个Mesh：</p>
<pre class="crayon-plain-tag">var geometry = new THREE.Geometry();
for ( var i = 0; i &lt; controls.numberOfObjects; i++ ) {
    var cubeMesh = createCube();
    cubeMesh.updateMatrix();
    // 提供被合并geometry的转换矩阵，确保geometry被正确的置位、旋转
    geometry.merge( cubeMesh.geometry, cubeMesh.matrix );
}
scene.add( new THREE.Mesh( geometry, cubeMaterial ) );</pre>
<div class="blog_h2"><span class="graybg">加载外部模型</span></div>
<p>使用编程方式来模拟真实世界中复杂的形状是困难的，Three.js允许加载3D建模软件设计的Geometry/Mesh。</p>
<div class="blog_h3"><span class="graybg">加载器</span></div>
<p>加载外部模型，是通过Three.js加载器（Loader）实现的。加载器把文本/二进制的模型文件转化为Three.js对象结构。</p>
<p>每个加载器理解某种特定的文件格式。</p>
<div class="blog_h3"><span class="graybg">支持的格式</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">格式</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>JSON</td>
<td>
<p>Three.js自定义的、基于JSON的格式。可以声明式的定义一个Geometry或者Scene</p>
<p>利用该格式，你可以方便的重用复杂的Geometry或Scene</p>
</td>
</tr>
<tr>
<td>OBJ / MTL</td>
<td>
<p>OBJ是Wavefront开发的一种简单3D格式，此格式被广泛的支持，用于定义Geometry</p>
<p>MTL用于配合OBJ，它指定OBJ使用的材质</p>
<p>Three.js提供了OBJExporter.js，使用它可以把Three.js模型导出为OBJ格式</p>
</td>
</tr>
<tr>
<td>Collada</td>
<td>基于XML的格式，被大量3D应用程序、渲染引擎支持</td>
</tr>
<tr>
<td>STL</td>
<td>
<p>STereoLithography的简写，在快速原型领域被广泛使用。3D打印模型通常使用该格式定义</p>
<p>Three.js提供了STLExporter.js，使用它可以把Three.js模型导出为STL格式</p>
</td>
</tr>
<tr>
<td>CTM</td>
<td>openCTM定义的格式，以紧凑的格式存储基于三角形的Mesh</td>
</tr>
<tr>
<td>VTK</td>
<td>Visualization Toolkit定义的格式，用于声明顶点和面。此格式有二进制/ASCII两种变体，Three.js仅支持ASCII变体</td>
</tr>
<tr>
<td>AWD</td>
<td>3D场景的二进制格式，主要被away3d引擎使用，Three.js不支持AWD压缩格式</td>
</tr>
<tr>
<td>Assimp</td>
<td>开放资产导入库（Open asset import library）是导入多种3D模型的标准方式。使用该Loader你可以导入多种多样的3D模型格式</td>
</tr>
<tr>
<td>VRML</td>
<td>
<p>虚拟现实建模语言（Virtual Reality Modeling Language）是一种基于文本的格式，现已经被X3D格式取代</p>
<p>尽管Three.js不直接支持X3D，但是后者很容易被转换为其它格式</p>
</td>
</tr>
<tr>
<td>Babylon</td>
<td>
<p>游戏引擎Babylon的私有格式</p>
</td>
</tr>
<tr>
<td>PLY</td>
<td>
<p>常用于存储来自3D扫描仪的信息</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">保存/加载JSON格式</span></div>
<p>使用Three.js的JSON格式，你可以保存/加载一个Mesh或者整个场景。</p>
<div class="blog_h3"><span class="graybg">保存/加载Mesh</span></div>
<pre class="crayon-plain-tag">knot = createMesh(new THREE.TorusKnotGeometry());
scene.add(knot);

// 保存
var result = knot.toJSON();
localStorage.setItem("json", JSON.stringify(result));

// 加载
var json = localStorage.getItem("json");
if (json) {
    var loadedGeometry = JSON.parse(json);
    var loader = new THREE.ObjectLoader();
    // 将JSON解析为Mesh
    loadedMesh = loader.parse(loadedGeometry);
    loadedMesh.position.x -= 50;
    scene.add(loadedMesh);
}</pre>
<div class="blog_h3"><span class="graybg">保存/加载场景</span></div>
<p>首先引入必要的脚本：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="../libs/SceneLoader.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="../libs/SceneExporter.js"&gt;&lt;/script&gt;</pre>
<p>代码示例：</p>
<pre class="crayon-plain-tag">var exporter = new THREE.SceneExporter();
var sceneJson = JSON.stringify(exporter.parse(scene));
localStorage.setItem('scene', sceneJson);


var json = (localStorage.getItem('scene'));
var sceneLoader = new THREE.SceneLoader();
// 最后一个参数 . 定义了相对URL，加载纹理时需要
sceneLoader.parse(JSON.parse(json), function(e) {
    scene = e.scene;
}, '.');</pre>
<div class="blog_h2"><span class="graybg">与Blender一起使用</span></div>
<p>市场上有大量3D建模软件，用来设计复杂的Mesh。开源领域比较流行的是Blender。</p>
<p>Three.js提供了针对Blender、Maya、3D Studio Max等流行软件的Exporter，可以把基于这些软件设计的模型直接导出为Three.js的JSON格式。 </p>
<p>Exporter并非Three.js支持Blender的唯一途径，因为Three.js本身理解多种3D格式，而Blender也支持保存为这些格式。</p>
<div class="blog_h3"><span class="graybg">安装Blender加载项</span></div>
<ol>
<li>复制/home/alex/JavaScript/three.js/utils/exporters/blender/addons目录到/home/alex/Applications/blender/2.78/scripts/addons</li>
<li>打开Blender，点击菜单栏File ⇨ User Preferences，选择Addons选项卡，搜索Three.js，勾选以启用：<a href="https://blog.gmem.cc/wp-content/uploads/2017/01/Blender-User-Preferences_001.png"><img class="aligncenter size-full wp-image-14519" src="https://blog.gmem.cc/wp-content/uploads/2017/01/Blender-User-Preferences_001.png" alt="blender-user-preferences_001" width="95%" /></a></li>
<li>点击File ⇨ Export，在弹出的菜单中应该可以看到Three.js项</li>
</ol>
<div class="blog_h3"><span class="graybg">从Blender导出</span></div>
<p>点击File ⇨ Open，你可以打开既有模型文件并编辑。点击File ⇨ Export ⇨ Three.js(json)，选择目标路径即可导出。</p>
<p>在导出对话框中，可以修改设置：</p>
<p><img class="aligncenter size-full wp-image-14522" src="https://blog.gmem.cc/wp-content/uploads/2017/01/BlenderExportSettings.png" alt="blenderexportsettings" width="369" height="98" /></p>
<p>这样，导出的JSON会包含材质的声明，并且模型使用的纹理自动导出为图片。</p>
<div class="blog_h3"><span class="graybg">导入到Three.js场景</span></div>
<pre class="crayon-plain-tag">var loader = new THREE.JSONLoader();
// 设置纹理的加载路径，JSON中仅仅包含纹理图片文件的名称，不包括目录前缀
loader.setTexturePath( './assets/' ); // 注意结尾的 /
// 异步的加载操作，回调函数提供两个入参：加载的Geometry、加载到的材质的数组
loader.load( './assets/chair.json', function ( geometry, materials ) {
    // 回调参数是THREE.Geometry，THREE.Material[]
    var material = new THREE.MultiMaterial( materials );
    var mesh = new THREE.Mesh( geometry, material );
    // 放大以便看清
    mesh.scale.x = 15;
    mesh.scale.y = 15;
    mesh.scale.z = 15;
    scene.add( mesh );

} ); </pre>
<div class="blog_h2"><span class="graybg">加载OBJ/MTL格式</span></div>
<p>此格式被Blender原生支持、Three.js也提供了相应的加载器。</p>
<p>首先引入必要的脚本：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="../libs/OBJLoader.js"&gt;&lt;/script&gt;
&lt;!-- 下面两个用于加载MTL --&gt;
&lt;script type="text/javascript" src="../libs/MTLLoader.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="../libs/OBJMTLLoader.js"&gt;&lt;/script&gt;</pre>
<div class="blog_h3"><span class="graybg">仅加载OBJ</span></div>
<pre class="crayon-plain-tag">var loader = new THREE.OBJLoader();
loader.load( '../assets/models/pinecone.obj', function ( loadedMesh ) {
    // 回调参数是一个THREE.Object3D对象
    var material = new THREE.MeshLambertMaterial( { color: 0x5C3A21 } );
    loadedMesh.children.forEach( function ( child ) {
        child.material = material;
        child.geometry.computeFaceNormals();
        child.geometry.computeVertexNormals();
    } );

    scene.add( loadedMesh );
} );</pre>
<p>一个好的实践是，在回调中打印加载对象的结构。通常，加载的Geometry/Mesh表现为层次化的Group。理解此Group的结构，以便正确的应用材质，并执行额外的处理步骤。</p>
<p>此外，注意查看顶点的位置信息，然后估算是否需要进行缩放、如何放置镜头。</p>
<p>对Geometry调用computeFaceNormals、computeVertexNormals，以确保材质被正确的渲染。</p>
<div class="blog_h3"><span class="graybg">同时加载MTL</span></div>
<p>如果你需要通过OBJ/MTL来加载模型，首先检查MTL的内容，确保它以相对路径来引用纹理图片。</p>
<p>下面的例子加载一个蝴蝶模型，需要注意，某些时候需要对材质进行微调：</p>
<pre class="crayon-plain-tag">var loader = new THREE.OBJMTLLoader();

loader.load( '../assets/models/butterfly.obj', '../assets/models/butterfly.mtl', function ( object ) {
    // 回调参数是一个THREE.Group对象

    var wing2 = object.children[ 5 ].children[ 0 ];
    var wing1 = object.children[ 4 ].children[ 0 ];

    // 模型源文件中，蝴蝶翅膀的透明度设置有误导致看不见，这里手工调整一下材质
    wing1.material.opacity = 0.6;
    wing1.material.transparent = true;
    // 禁用深度测试，避免渲染错误（不指定下面的代码来运行示例，可以看到翅膀中部分像素不停抖动）
    wing1.material.depthTest = false;
    // 默认情况下，Three.js仅仅会渲染一个面
    wing1.material.side = THREE.DoubleSide;

    wing2.material.opacity = 0.6;
    wing2.material.depthTest = false;
    wing2.material.transparent = true;
    wing2.material.side = THREE.DoubleSide;

    object.scale.set( 140, 140, 140 );
    mesh = object;
    scene.add( mesh );

    object.rotation.x = 0.2;
    object.rotation.y = -1.3;
} );</pre>
<div class="blog_h2"><span class="graybg">加载Collada格式</span></div>
<p>此格式的默认扩展名为.dae，也被广泛的使用。此格式用来定义场景、模型，甚至是动画。一个Collada模型同时包含了Geometry、材质的定义。</p>
<p>不意外的，要加载Collada格式同样需要引入Loader脚本：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="../libs/ColladaLoader.js"&gt;&lt;/script&gt;</pre>
<p>下面的代码，从Collada模型中导入一个卡车模型：</p>
<pre class="crayon-plain-tag">var loader = new THREE.ColladaLoader();

var mesh;
loader.load( "../assets/models/dae/Truck_dae.dae", function ( result ) {
    // 从模型场景中克隆出一个对象
    mesh = result.scene.children[ 0 ].children[ 0 ].clone();
    mesh.scale.set( 4, 4, 4 );
    // 添加到当前场景中
    scene.add( mesh );
} );</pre>
<p>需要注意的是，Collada加载器回调参数是如下结构：</p>
<pre class="crayon-plain-tag">var result = {
    scene: scene,  // 场景对象，THREE.Scene，包括所有模型对象，都在其中
    morphs: morphs,
    skins: skins,
    animations: animData,
    dae: {}
};</pre>
<p>本章仅关心scene属性中的对象。需要注意，纹理可能基于WebGL不支持的格式（例如.tga），你可能需要将其转换为.png格式，并编辑Collada文件。</p>
<div class="blog_h1"><span class="graybg">动画和镜头控制</span></div>
<div class="blog_h2"><span class="graybg">基本动画</span></div>
<p>我们之前的动画，都是基于渲染循环来实现——通知Three.js尽快的重新渲染。实现代码都是如下的模式：</p>
<pre class="crayon-plain-tag">render();
function render() {
    // 在此，可以修改模型属性
    /* ... */
    // 执行渲染
    renderer.render( scene, camera );
    // 调度下依次渲染
    requestAnimationFrame( render );
}</pre>
<p>我们只需要手工触发一次render()调用，之后它就会被定期（通常是每秒60次）递归调用了。 </p>
<p>基于这种方式，我们可以修改模型的各种属性——otation,scale, position, material, vertices, faces从而产生简单的动画效果。</p>
<div class="blog_h2"><span class="graybg">选择对象</span></div>
<p>尽管用鼠标选择对象和动画没有直接关系，但是为了深入理解镜头和动画，我们需要用到这一功能。</p>
<p>Three.js没有直接提供“点击”功能，但是我们可以基于THREE.Projector、THREE.Raycaster来判断鼠标当前对应到哪个物体：</p>
<pre class="crayon-plain-tag">document.addEventListener( 'mousedown', onDocumentMouseDown, false );

var projector = new THREE.Projector();

function onDocumentMouseDown( event ) {
    // 基于鼠标当前位置，创建一个3D向量
    var x = ( event.clientX / window.innerWidth ) * 2 - 1;
    var y = -( event.clientY / window.innerHeight ) * 2 + 1;
    var z = 0.5;
    var vector = new THREE.Vector3( x, y, z );
    // 把鼠标当前位置转换为Three.js场景中的坐标 —— 把2D屏幕坐标unproject为3D世界坐标
    vector = vector.unproject( camera );
    // 从相机所在位置发出一条射线，射到鼠标位置
    var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    // 检查此射线穿过哪些物体
    var intersects = raycaster.intersectObjects( [ sphere, cylinder, cube ] );

    if ( intersects.length &gt; 0 ) {

        console.log( intersects[ 0 ] );

        intersects[ 0 ].object.material.transparent = true;
        intersects[ 0 ].object.material.opacity = 0.1;
    }
}</pre>
<div class="blog_h2"><span class="graybg">基于Tween.js的动画</span></div>
<p><a href="https://github.com/tweenjs/tween.js/blob/master/docs/user_guide.md">Tween.js</a>是一个简单的JS库，可以基于给定的初值、终值自动计算所有中间值。这个中间值计算过程一般叫做tweening。示例代码：</p>
<pre class="crayon-plain-tag">var coords = { x: 0, y: 0 };
var tween = new TWEEN.Tween( coords )
    .to( { x: 100, y: 100 }, 1000 ) // 在1秒内完成变换
    .onUpdate( function () {  // 每当值变化时，执行的回调
        console.log( this.x, this.y );
    } )
    .start();

requestAnimationFrame( animate );
// requestAnimationFrame会自动把一个高精度的、从DOM加载到当前流逝的时间传递给回调
function animate( time ) {
    requestAnimationFrame( animate );
    TWEEN.update( time );
}</pre>
<p>我们可以创建一个改变物体位置的循环动画：</p>
<pre class="crayon-plain-tag">var posSrc = { pos: 1 };
// 创建两个Tween并链接，形成循环动画
var tween = new TWEEN.Tween( posSrc ).to( { pos: 0 }, 5000 );
tween.easing( TWEEN.Easing.Sinusoidal.InOut );

var tweenBack = new TWEEN.Tween( posSrc ).to( { pos: 1 }, 5000 );
tweenBack.easing( TWEEN.Easing.Sinusoidal.InOut );

tween.chain( tweenBack );
tweenBack.chain( tween );
// 当值变化时，改变物体的顶点位置
var onUpdate = function () {
    var count = 0;
    var pos = this.pos;

    loadedGeometry.vertices.forEach( function ( e ) {
        var newY = ((e.y + 3.22544) * pos) - 3.22544;
        pointCloud.geometry.vertices[ count++ ].set( e.x, newY, e.z );
    } );

    pointCloud.sortParticles = true;
};

tween.onUpdate( onUpdate );
tweenBack.onUpdate( onUpdate );

function render() {
    TWEEN.update();
    // 以16.7次/秒的频率，更新Tween，然后重渲染场景
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
}</pre>
<div class="blog_h2"><span class="graybg">镜头控制</span></div>
<div class="blog_h3"><span class="graybg">TrackballControls</span></div>
<p>跟踪球控制，允许你使用鼠标进行镜头的平移（鼠标左键）、缩放（鼠标中键）、旋转操作（鼠标右键）。</p>
<p>使用该控制方式，需要引入：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="../libs/TrackballControls.js"&gt;&lt;/script&gt;</pre>
<p>然后，创建控制器对象：</p>
<pre class="crayon-plain-tag">// 关联到镜头
var trackballControls = new THREE.TrackballControls( camera );
// 设置速度
trackballControls.rotateSpeed = 1.0;
trackballControls.zoomSpeed = 1.0;
trackballControls.panSpeed = 1.0;


function render() {
    // 获取上一次调用getDelta到现在流逝的时间
    var delta = clock.getDelta();
    // 传递此时间增量，控制器会根据移动速度来计算距离
    trackballControls.update( delta );
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera )
}</pre>
<div class="blog_h3"><span class="graybg">FlyControls</span></div>
<p>飞行控制，好像你在驾驶一架飞机，在场景中穿梭。</p>
<p>使用该控制方式，需要引入：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="../libs/FlyControls.js"&gt;&lt;/script&gt;</pre>
<p>示例代码：</p>
<pre class="crayon-plain-tag">var flyControls = new THREE.FlyControls(camera);
flyControls.movementSpeed = 25;
// 需要指向渲染场景的DOM元素
flyControls.domElement = document.querySelector('#WebGL');
flyControls.rollSpeed = Math.PI / 24;
flyControls.autoForward = true;
flyControls.dragToLook = false;</pre>
<div class="blog_h3"><span class="graybg">FirstPersonControls</span></div>
<p>第一人称视角。示例代码：</p>
<pre class="crayon-plain-tag">var camControls = new THREE.FirstPersonControls(camera);
camControls.lookSpeed = 0.1;
camControls.movementSpeed = 20;
camControls.noFly = true;
camControls.lookVertical = true;
camControls.constrainVertical = true;
camControls.verticalMin = 1.0;
camControls.verticalMax = 2.0;
// 场景最初渲染时，镜头的位置
camControls.lon = -150;
camControls.lat = 120; </pre>
<div class="blog_h3"><span class="graybg">PointerLockControls</span></div>
<p>与上一个类似，但是提供鼠标锁定功能。避免镜头一直移动导致晃眼。具体查看<a href="https://threejs.org/examples/misc_controls_pointerlock.html">这个示例</a>。</p>
<div class="blog_h3"><span class="graybg">OrbitControl</span></div>
<p>这种方式可以很方便的旋转、平移、缩放位于场景中心位置的物体。例如太空场景中的星球。示例代码：</p>
<pre class="crayon-plain-tag">var orbitControls = new THREE.OrbitControls(camera);
orbitControls.autoRotate = true;
var clock = new THREE.Clock();
...
var delta = clock.getDelta();
orbitControls.update(delta);</pre>
<div class="blog_h2"><span class="graybg">变形与骨骼动画</span></div>
<p>当利用3D建模软件创建动画时，通常有两种机制—— 变形目标、骨骼动画。</p>
<div class="blog_h3"><span class="graybg">Morph targets</span></div>
<p>使用变形目标（Morph targets），你可以定义模型的变形（deformed）版本——Mesh的一个关键位置（key position）。对于此变形版本，所有顶点的位置被记录下来。根据原始版本、变形版本的顶点位置的变化，可以方便的创建变化。其本质就是移动顶点的位置。</p>
<p>变形目标是定义动画最直接的方式，其缺点是对于大的Mesh和大的动画，模型文件会边的庞大。</p>
<p>Three.js支持手工的从一个关键位置移动到另一个，但是手工控制比较麻烦，你需要跟踪当前位置、需要变形到的目标位置。THREE.MorphAnimMesh把这些细节封装起来，我们通常直接使用该类。下面的代码示例了如何加载内置了变形目标的模型：</p>
<pre class="crayon-plain-tag">var loader = new THREE.JSONLoader();
loader.load( '../assets/models/horse.js', function ( geometry, mat ) {

    var mat = new THREE.MeshLambertMaterial({
        morphTargets: true, // 一定要设置材质的morphTargets为true，否则不支持动画
        vertexColors: THREE.FaceColors
    } );

    geometry.computeVertexNormals();
    geometry.computeFaceNormals();
    // 在创建MorphAnimMesh之前，要调用computeMorphNormals确保变形目标的所有法向量被正确的计算
    // 此操作对于正确的灯光、阴影效果是必须的
    geometry.computeMorphNormals();
    if ( geometry.morphColors &amp;&amp; geometry.morphColors.length ) {
        // 你可以为某个特定的变形目标的面定制颜色
        var colorMap = geometry.morphColors[ 0 ];
        for ( var i = 0; i &lt; colorMap.colors.length; i++ ) {
            geometry.faces[ i ].color = colorMap.colors[ i ];
            geometry.faces[ i ].color.offsetHSL( 0, 0.3, 0 );
        }
    }
    meshAnim = new THREE.MorphAnimMesh( geometry, mat );
    meshAnim.duration = 1000;
    meshAnim.position.x = 200;
    meshAnim.position.z = 0;
    
    scene.add( meshAnim );
}, '../assets/models' );

function render() {
    var delta = clock.getDelta();
    webGLRenderer.clear();
    // 推进动画
    meshAnim.updateAnimation( delta * 1000 );
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
}</pre>
<p>Three.js的默认行为是一下子运行所有动画，如果为单个Geometry定义了多个动画，则可以通过<pre class="crayon-plain-tag">parseAnimations()</pre> 和<pre class="crayon-plain-tag">playAnimation(name,fps)</pre> 来运行其中一个动画。</p>
<div class="blog_h3"><span class="graybg">Skeletal animation</span></div>
<p>这种方式允许你为模型定义骨骼，并且把顶点附着在骨骼上。当你移动骨骼的时候，所有相连的骨骼也跟随移动，并导致顶点移动，产生变形。</p>
<p>变形动画比较简单，Three.js只需要转换顶点位置就可以了。骨骼动画则要复杂一些，当你移动骨骼时，Three.js需要知道如何计算附着其上的皮肤（Mesh顶点）的位置。</p>
<p>下面这个例子是手工执行骨骼动画的代码：</p>
<pre class="crayon-plain-tag">var clock = new THREE.Clock();

var loader = new THREE.JSONLoader();
loader.load( '../assets/models/hand-1.js', function ( geometry, mat ) {
    // 注意设置skinning为true，否则不会看到任何骨骼移动的效果
    var mat = new THREE.MeshLambertMaterial( { color: 0xF0C8C9, skinning: true } );
    // 专门针对骨骼/皮肤几何图形的Mesh
    mesh = new THREE.SkinnedMesh( geometry, mat );
    scene.add( mesh );
    // 开始动画
    tween.start();

}, '../assets/models' );


var onUpdate = function () {
    var pos = this.pos;
    // 转动手指
    mesh.skeleton.bones[ 5 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 6 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 10 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 11 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 15 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 16 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 20 ].rotation.set( 0, 0, pos );
    mesh.skeleton.bones[ 21 ].rotation.set( 0, 0, pos );
    // 转动手腕
    mesh.skeleton.bones[ 1 ].rotation.set( pos, 0, 0 );
};
var tween = new TWEEN.Tween( { pos: -1 } )
    .to( { pos: 0 }, 3000 )
    .easing( TWEEN.Easing.Cubic.InOut )
    .yoyo( true ) // 下一次反向执行
    .repeat( Infinity ) // 无限执行
    .onUpdate( onUpdate );

render();

function render() {
    TWEEN.update();
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
} </pre>
<div class="blog_h2"><span class="graybg">从外部模型创建动画</span></div>
<p>前面我们讨论过，Three.js支持多种外部模型格式。这些格式中的一部分，支持动画：</p>
<ol>
<li>对于JSON格式，可以使用Blender with the JSON exporter导出</li>
<li>Collada，该格式支持动画</li>
</ol>
<div class="blog_h3"><span class="graybg">导入Blender骨骼动画</span></div>
<p>基于Blender创建骨骼动画时，要注意以下几点：</p>
<ol>
<li>模型的所有顶点，至少分配到一个顶点组（vertex group）中</li>
<li>顶点组的名称必须和控制它的骨骼的名称一致，这样Three.js才知道，移动骨骼时，需要修改哪些顶点</li>
<li>注意仅仅第一个Action被导出，因此要确保你需要导出的动画时第一个</li>
<li>创建关键帧时，最好选取所有骨头，即使某些不变化</li>
<li>导出模型时，需要保证模型处于rest pose，否则动画可能变形严重</li>
<li>导出时，注意勾选：Vertices、Faces、Normals、Skinning、UVs、Colors、Materials、Flip YZ、Skeletal animation</li>
</ol>
<p>这样，骨骼的移动路径会被一同导出，在Three.js中可以简单的进行回放：</p>
<pre class="crayon-plain-tag">var loader = new THREE.JSONLoader();
loader.load( '../assets/models/hand-2.js', function ( model, mat ) {

    var mat = new THREE.MeshLambertMaterial( { color: 0xF0C8C9, skinning: true } );
    mesh = new THREE.SkinnedMesh( model, mat );

    var animation = new THREE.Animation( mesh, model.animation );

    mesh.rotation.x = 0.5 * Math.PI;
    mesh.rotation.z = 0.7 * Math.PI;
    scene.add( mesh );
    // 此助手用于查看骨骼如何变化
    helper = new THREE.SkeletonHelper( mesh );
    helper.material.linewidth = 2;
    helper.visible = false;
    scene.add( helper );

    // 开始播放动画
    animation.play();

}, '../assets/models' );

render();

function render() {
    var delta = clock.getDelta();
    if ( mesh ) {
        helper.update();
        THREE.AnimationHandler.update( delta );
    }
    requestAnimationFrame( render );
    webGLRenderer.render( scene, camera );
}</pre>
<div class="blog_h3"><span class="graybg">导入Collada动画</span></div>
<p>和从JSON格式导入动画的方式差不多，但是要注意Collada可以存储整个场景，包括镜头、灯光、动画，因此你需要找到需要使用的那个附带动画的Mesh：</p>
<pre class="crayon-plain-tag">var child = collada.skins[0];  // THREE.SkinnedMesh
scene.add(child);
var animation = new THREE.Animation(child, child.geometry.animation);
animation.play(); </pre>
<div class="blog_h1"><span class="graybg">使用纹理</span></div>
<p>纹理在Three.js中有多种不同的使用方式，你可以用纹理来定义Mesh的颜色，或者定义发光效果、凹凸（bump）、反射效果。</p>
<div class="blog_h2"><span class="graybg">在材质中使用纹理</span></div>
<p>最基本的用例是加载纹理，并将其作为材质上的一个map。当你基于此材质创建Mesh时，Mesh被纹理着色：</p>
<pre class="crayon-plain-tag">var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile)
var mat = new THREE.MeshPhongMaterial();
mat.map = texture;
var mesh = new THREE.Mesh(geom, mat);
return mesh;</pre>
<p>作为纹理的图片，可以是PNG、GIF或者JPG格式，且大小必须是2的N次方。 需要注意，纹理图片的加载是异步的，如果你希望纹理加载完毕之后再进行渲染，可以：</p>
<pre class="crayon-plain-tag">texture = THREE.ImageUtils.loadTexture('texture.png', {},function() { renderer.render(scene); });</pre>
<p>由于纹理图片的像素一般不能和面的像素一一对应，纹理需要放大或者缩小后使用。WebGL/Three.js提供了几个不同的选项。你可以设置纹理的magFilter、minFilter属性，来声明它将如何被缩放 。两个基本的取值为：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 35%;">THREE.NearestFilter</td>
<td>使用最临近的像素。当放大时，出现色块；当缩小时，丢失细节</td>
</tr>
<tr>
<td>THREE.LinearFilter</td>
<td>基于周围四个像素的值，来决定一个正确的颜色。缩小时仍然会丢失细节，但是放大时会更加平滑，不会出现色块</td>
</tr>
</tbody>
</table>
<p>除了这两个基本的取值之外，我们还可以使用mipmap——一系列纹理图片的集合，后者是前者的一半大小。mipmap可以在加载纹理时自动创建，结合以下filter取值使用：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 35%;">THREE.NearestMipMapNearestFilter</td>
<td>选取最匹配分辨率的mipmap，并应用NearestFilter规则。放大时仍然出现色块，但是看起来好很多</td>
</tr>
<tr>
<td>THREE.NearestMipMapLinearFilter</td>
<td>选取最接近的两个mipmap级别，在两个级别上分别应用NearestFilter规则，得到中间结果。这两个中间结果随之传递给LinearFilter获得最终结果</td>
</tr>
<tr>
<td>THREE.LinearMipMapNearestFilter</td>
<td> </td>
</tr>
<tr>
<td>THREE.LinearMipMapLinearFilter </td>
<td> </td>
</tr>
</tbody>
</table>
<p>如果不明确指定，magFilter默认取值THREE.LinearFilter，minFilter默认取值THREE.LinearMipMapLinearFilter。</p>
<p>纹理本身是方形的，但是Three.js可以确保不管对于什么形状，材质都能正确的覆盖（wrap around），此保证由UV mapping实现。</p>
<div class="blog_h2"><span class="graybg">创建凹凸效果</span></div>
<div class="blog_h3"><span class="graybg">基于bump map</span></div>
<p>所谓bump map，是一幅额外的纹理，用于在材质上添加更多的深度效果：</p>
<pre class="crayon-plain-tag">var texture = THREE.ImageUtils.loadTexture( "../assets/textures/general / " + imageFile)
var mat = new THREE.MeshPhongMaterial();
mat.map = texture;
var bump = THREE.ImageUtils.loadTexture("../assets/textures/general/" + bump )
mat.bumpMap = bump;
mat.bumpScale = 0.2;  // 设置凸起的高度（负值则表示凹下的深度）
var mesh = new THREE.Mesh( geom, mat );
return mesh;</pre>
<p>bump map通常都是灰度图，像素的密度代表了凸起的（相对）高度 。</p>
<div class="blog_h3"><span class="graybg">基于normal map</span></div>
<p>对于normal map来说，高度信息没有被保存，但是法线的方向被保存了。使用normal map你可以在仅使用很少点、面的情况下创建具有复杂细节的模型：</p>
<pre class="crayon-plain-tag">var t = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile);
var m = THREE.ImageUtils.loadTexture("../assets/textures/general/" + normal);
var mat2 = new THREE.MeshPhongMaterial();
mat2.map = t;
mat2.normalMap = m;
mat.normalScale.set(1,1); // 设置凸起的高度（负值则表示凹下的深度）
var mesh = new THREE.Mesh(geom, mat2);
return mesh;</pre>
<p>normal map的缺点是不容易创建，需要使用Blender/Photoshop之类的特殊工具。 </p>
<div class="blog_h2"><span class="graybg">创建假反射</span></div>
<p>计算环境反射效果是非常消耗资源的操作。在Three.js中你可以模拟这种效果。步骤如下：</p>
<ol>
<li>创建一个CubeMap对象，CubeMap是六个纹理的集合，可以被应用到Cube的六个面</li>
<li>使用CubeMap创建一个Box，此Box作为场景的环境，当你转动镜头时，看到的是此Box的内侧</li>
<li>把上述模拟环境的CubMap应用到需要反射效果的Mesh上面，Three.js可以确保其看起来就像是环境的反射</li>
</ol>
<div class="blog_h3"><span class="graybg">创建CubeMap</span></div>
<p>准备好<a href="http://www.humus.name/index.php?page=Textures">纹理图片</a>后，创建CubeMap非常容易。你需要的是能够组成完整环境的六幅图片：向前看时的图片（posz）、向后看时的图片（negz）、向上看的图片（posy）、向下看的图片（negy）、向右看的图片（posx）、向左看的图片（negx）。示例代码：</p>
<pre class="crayon-plain-tag">var path = "../assets/textures/cubemap/parliament/";
var format = '.jpg';
var urls = [
    path + 'posx' + format, path + 'negx' + format,
    path + 'posy' + format, path + 'negy' + format,
    path + 'posz' + format, path + 'negz' + format
];
var textureCube = THREE.ImageUtils.loadTextureCube( urls );</pre>
<p>如果你已经获得360度全景图片，可以利用<a href="//gonchar.me/%20panorama/">工具</a>将其切割为上面的六幅图。 或者直接让Three.js处理切割过程：</p>
<pre class="crayon-plain-tag">var textureCube = THREE.ImageUtils.loadTexture("360-degrees.png", new THREE.UVMapping());</pre>
<div class="blog_h3"><span class="graybg">创建Skybox</span></div>
<p>Three.js提供了一个特殊的着色器，用来基于CubeMap来创建Skybox（环境）：</p>
<pre class="crayon-plain-tag">var shader = THREE.ShaderLib[ "cube" ];
shader.uniforms[ "tCube" ].value = textureCube;

var material = new THREE.ShaderMaterial( {

    fragmentShader: shader.fragmentShader,
    vertexShader: shader.vertexShader,
    uniforms: shader.uniforms,
    depthWrite: false,
    side: THREE.DoubleSide

} );

var skybox = new THREE.Mesh( new THREE.BoxGeometry( 10000, 10000, 10000 ), material );
scene.add( skybox );</pre>
<div class="blog_h3"><span class="graybg">创建反射物体</span></div>
<pre class="crayon-plain-tag">// 此镜头看到的景象，将用于球体的动态反射效果
cubeCamera = new THREE.CubeCamera( 0.1, 20000, 256 );
scene.add( cubeCamera );

// 动态反射，不仅仅CubeMap出现在反射图像中，Mesh也是
// 两个动态反射的物体不支持相互反射
var dynamicEnvMaterial = new THREE.MeshBasicMaterial( { envMap: cubeCamera.renderTarget, side: THREE.DoubleSide } );
sphere = new THREE.Mesh( sphereGeometry, dynamicEnvMaterial );
scene.add( sphere );

// 静态反射
// 注意材质可以设置反射率
var envMaterial = new THREE.MeshBasicMaterial( { envMap: textureCube, side: THREE.DoubleSide， reflection: 1 } );
var cylinder = new THREE.Mesh( cylinderGeometry, envMaterial );
scene.add( cylinder );


function render() {
    // 此镜头看到的内容需要更新，否则动态反射物体漆黑一片
    cubeCamera.updateCubeMap( renderer, scene );
    requestAnimationFrame( render );
}</pre>
<p>材质的envMap属性可以设置为一个CubeMap对象，这样Mesh就可以反射CubeMap代表的环境。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/three-js-study-note">Three.js学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/three-js-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cesium学习笔记</title>
		<link>https://blog.gmem.cc/cesium-study-note</link>
		<comments>https://blog.gmem.cc/cesium-study-note#comments</comments>
		<pubDate>Tue, 17 Mar 2015 01:46:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[GIS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[WebGL]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=4739</guid>
		<description><![CDATA[<p>Cesium简介 Cesium是一个基于JavaScript的开源框架，可用于在浏览器中绘制3D的地球，并在其上绘制地图（支持多种格式的瓦片服务），该框架不需要任何插件支持，但是浏览器必须支持WebGL。 Cesium支持多种数据可视化方式，可以绘制各种几何图形、导入图片，甚至3D模型。同时，Cesium还支持基于时间轴的动态数据展示，例如，我们可以用它绘制卫星运行轨迹。 Cesium HelloWorld 下面的例子在浏览器中显示一个太空背景、具有地图覆盖的3D地球： [crayon-69de77a1253e2558972839/] [crayon-69de77a1253e6913676882/] 可以加快时间的运行，并且模拟日光照射效果： [crayon-69de77a1253e9722913131/] 通过以下代码，可以设置镜头位置与指向，Cesium的Camera对象提供了多种操控镜头的方法： [crayon-69de77a1253eb494318880/] 可以添加若干实体，实体可以用于组织多个可视化对象，下面的例子模拟了卫星波束的覆盖范围： [crayon-69de77a1253ee274157930/] 空间数据可视化 Cesium提供Entity API来绘制空间数据，例如点、标记、标签、线、3D模型、形状、立体形状（volume）。 Entity API简介 Cesium提供两类API： 面向图形开发人员的底层API，通常称为“Primitive API”。该API暴露最小限度的抽象，使用图形学术语，具有很大的灵活性，需要具有图形学编程的知识 <a class="read-more" href="https://blog.gmem.cc/cesium-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/cesium-study-note">Cesium学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Cesium简介</span></div>
<p>Cesium是一个基于JavaScript的开源框架，可用于在浏览器中绘制3D的地球，并在其上绘制地图（支持多种格式的瓦片服务），该框架不需要任何插件支持，但是浏览器必须支持WebGL。</p>
<p>Cesium支持多种数据可视化方式，可以绘制各种几何图形、导入图片，甚至3D模型。同时，Cesium还支持基于时间轴的动态数据展示，例如，我们可以用它绘制卫星运行轨迹。</p>
<div class="blog_h1"><span class="graybg">Cesium HelloWorld</span></div>
<p>下面的例子在浏览器中显示一个太空背景、具有地图覆盖的3D地球：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"&gt;
&lt;title&gt;Cesium Example&lt;/title&gt;
&lt;script src="Cesium-1.7.1/Build/CesiumUnminified/Cesium.js"&gt;&lt;/script&gt;
&lt;link rel="stylesheet" type="text/css" href="Cesium-1.7.1/Build/CesiumUnminified/Widgets/widgets.css"&gt;&lt;/link&gt;
&lt;style&gt;
html,body,#cesiumContainer {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id="cesiumContainer"&gt;&lt;/div&gt;
    &lt;script type="text/javascript" src="index.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><br />
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer', {
    animation : false,//是否创建动画小器件，左下角仪表
    baseLayerPicker : false,//是否显示图层选择器
    fullscreenButton : false,//是否显示全屏按钮
    geocoder : false,//是否显示geocoder小器件，右上角查询按钮
    homeButton : false,//是否显示Home按钮
    infoBox : false,//是否显示信息框
    sceneModePicker : false,//是否显示3D/2D选择器
    selectionIndicator : false,//是否显示选取指示器组件
    timeline : false,//是否显示时间轴
    navigationHelpButton : false,//是否显示右上角的帮助按钮
    scene3DOnly : true,//如果设置为true，则所有几何图形以3D模式绘制以节约GPU资源
    clock : new Cesium.Clock(),//用于控制当前时间的时钟对象
    selectedImageryProviderViewModel : undefined,//当前图像图层的显示模型，仅baseLayerPicker设为true有意义
    imageryProviderViewModels : Cesium.createDefaultImageryProviderViewModels(),//可供BaseLayerPicker选择的图像图层ProviderViewModel数组
    selectedTerrainProviderViewModel : undefined,//当前地形图层的显示模型，仅baseLayerPicker设为true有意义
    terrainProviderViewModels : Cesium.createDefaultTerrainProviderViewModels(),//可供BaseLayerPicker选择的地形图层ProviderViewModel数组
    imageryProvider : new Cesium.OpenStreetMapImageryProvider( {
        credit :'',
        url : '//192.168.0.89:5539/planet-satellite/'
    } ),//图像图层提供者，仅baseLayerPicker设为false有意义
    terrainProvider : new Cesium.EllipsoidTerrainProvider(),//地形图层提供者，仅baseLayerPicker设为false有意义
    skyBox : new Cesium.SkyBox({
        sources : {
          positiveX : 'Cesium-1.7.1/Skybox/px.jpg',
          negativeX : 'Cesium-1.7.1/Skybox/mx.jpg',
          positiveY : 'Cesium-1.7.1/Skybox/py.jpg',
          negativeY : 'Cesium-1.7.1/Skybox/my.jpg',
          positiveZ : 'Cesium-1.7.1/Skybox/pz.jpg',
          negativeZ : 'Cesium-1.7.1/Skybox/mz.jpg'
        }
    }),//用于渲染星空的SkyBox对象
    fullscreenElement : document.body,//全屏时渲染的HTML元素,
    useDefaultRenderLoop : true,//如果需要控制渲染循环，则设为true
    targetFrameRate : undefined,//使用默认render loop时的帧率
    showRenderLoopErrors : false,//如果设为true，将在一个HTML面板中显示错误信息
    automaticallyTrackDataSourceClocks : true,//自动追踪最近添加的数据源的时钟设置
    contextOptions : undefined,//传递给Scene对象的上下文参数（scene.options）
    sceneMode : Cesium.SceneMode.SCENE3D,//初始场景模式
    mapProjection : new Cesium.WebMercatorProjection(),//地图投影体系
    dataSources : new Cesium.DataSourceCollection()
    //需要进行可视化的数据源的集合
} );
var scene = viewer.scene;
var canvas = viewer.canvas;
var clock = viewer.clock;
var camera = viewer.scene.camera;
var entities = viewer.entities;</pre>
<p>可以加快时间的运行，并且模拟日光照射效果：</p>
<pre class="crayon-plain-tag">//加快时钟的运行
clock.multiplier = 0.1 * 60 * 60;
//阳光照射区域高亮
scene.globe.enableLighting = true;</pre>
<p>通过以下代码，可以设置镜头位置与指向，Cesium的Camera对象提供了多种操控镜头的方法：</p>
<pre class="crayon-plain-tag">//设置镜头位置与方向
camera.setView( {
    //镜头的经纬度、高度。镜头默认情况下，在指定经纬高度俯视（pitch=-90）地球
    position : Cesium.Cartesian3.fromDegrees( 116.3, 39.9, 100000000 ),//北京100000公里上空
    //下面的几个方向正好反映默认值
    heading : Cesium.Math.toRadians( 0 ),
    pitch : Cesium.Math.toRadians( -90 ),
    roll : Cesium.Math.toRadians( 0 )
} );
//让镜头飞行（动画）到某个地点和方向
setTimeout( function()
{
    camera.flyTo( {
        destination : Cesium.Cartesian3.fromDegrees( 116, 15, 6000000 ),
        orientation : {
            heading : Cesium.Math.toRadians( -15 ),
            pitch : Cesium.Math.toRadians( -65 ),
            roll : Cesium.Math.toRadians( 0 )
        },
        duration : 3,//动画持续时间
        complete : function()//飞行完毕后执行的动作
        {
            addEntities();
        }
    } );
}, 1000 );

//监听键盘事件，用于平移或者旋转镜头
var ellipsoid = scene.globe.ellipsoid;
canvas.onclick = function()
{
    canvas.focus();
};
var flags = {
    looking : false,
    rotateLeft : false,
    rotateRight : false,
    moveUp : false,
    moveDown : false,
    moveLeft : false,
    moveRight : false
};
var handler = new Cesium.ScreenSpaceEventHandler( canvas );
function getFlagForKeyCode( keyCode )
{
    switch ( keyCode )
    {
        case 'W'.charCodeAt( 0 ) : //向下平移镜头
            return 'moveDown';
        case 'S'.charCodeAt( 0 ) : //向上平移镜头
            return 'moveUp';
        case 'A'.charCodeAt( 0 ) : //向右平移镜头
            return 'moveRight';
        case 'D'.charCodeAt( 0 ) : //向左平移镜头
            return 'moveLeft';
        case 'Q'.charCodeAt( 0 ) : //向右旋转镜头
            return 'rotateRight';
        case 'E'.charCodeAt( 0 ) : //向左旋转镜头
            return 'rotateLeft';
        default :
            return undefined;
    }
}
document.addEventListener( 'keydown', function( e )
{
    var flagName = getFlagForKeyCode( e.keyCode );
    if ( typeof flagName !== 'undefined' )
    {
        flags[flagName] = true;
    }
}, false );
document.addEventListener( 'keyup', function( e )
{
    var flagName = getFlagForKeyCode( e.keyCode );
    if ( typeof flagName !== 'undefined' )
    {
        flags[flagName] = false;
    }
}, false );
viewer.clock.onTick.addEventListener( function( clock )
{
    var cameraHeight = ellipsoid.cartesianToCartographic( camera.position ).height;
    var moveRate = cameraHeight / 100.0;

    if ( flags.rotateLeft )
    {
        camera.rotateLeft( 0.01 );
    }
    if ( flags.rotateRight )
    {
        camera.rotateRight( 0.01 );
    }
    if ( flags.moveUp )
    {
        camera.moveUp( moveRate );
    }
    if ( flags.moveDown )
    {
        camera.moveDown( moveRate );
    }
    if ( flags.moveLeft )
    {
        camera.moveLeft( moveRate );
    }
    if ( flags.moveRight )
    {
        camera.moveRight( moveRate );
    }
} );</pre>
<p>可以添加若干实体，实体可以用于组织多个可视化对象，下面的例子模拟了卫星波束的覆盖范围：</p>
<pre class="crayon-plain-tag">/**
 * 根据偏移量计算目标点经纬度
 * @param {} start  起始点经纬度数组，单位度
 * @param {} offset 东北方向的偏移量，单位米
 * @param {} 目标点经纬度数组，单位度
 */
function offsetToLongLat( start, offset )
{
    var er = 6378137;
    var lat = parseFloat( start[1] );
    var lon = parseFloat( start[0] );
    var dn = parseFloat( offset[1] );
    var de = parseFloat( offset[0] );

    dLat = dn / er;
    var pi = Math.PI;
    var dLon = de / ( er * Math.cos( pi * lat / 180 ) )
    return [
        lon + dLon * 180 / pi, lat + dLat * 180 / pi
    ];
}
/**
 * 通过绘制三角形模拟卫星光束效果
 * @param {} entities 实体集
 * @param {} stltPos 卫星三维坐标数组
 * @param {} points 地面点
 * @param {} color CSS颜色代码，例如#FF0000
 */
function lightShinePolygon( entities, stltPos, points, color )
{
    for ( var i = 0; i &lt; points.length; i += 2 )
    {
        var array = [
            stltPos[0], stltPos[1], stltPos[2], points[i], points[i + 1], 0
        ];
        if ( i + 2 == points.length )
        {
            array.push( points[0] );
            array.push( points[1] );
        }
        else
        {
            array.push( points[i + 2] );
            array.push( points[i + 3] );
        }
        array.push( 0 );
        entities.add( {
            polygon : {
                hierarchy : Cesium.Cartesian3.fromDegreesArrayHeights( array ),
                perPositionHeight : true,
                outline : false,
                material : Cesium.Color.fromAlpha( Cesium.Color.fromCssColorString( color ), .1 )
            }
        } );
    }
}
/**
 * 添加实体
 */
function addEntities()
{
    //卫星一
    {
        var stltPos = [
            110.0, 40.0, 2500000
        ];
        entities.add( {
            position : Cesium.Cartesian3.fromDegrees.apply( this, stltPos ),
            billboard : {
                image : 'images/satellite-1.png',
                horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
                verticalOrigin : Cesium.VerticalOrigin.BOTTOM, //垂直方向位置计算基准设为底部，默认中心
                width : 92,
                height : 36
            }
        } );
        //一个多边形覆盖范围
        {
            var color = '#FF0000';
            //模拟光照效果的若干多边形
            var points = [
                100, 48, 110, 40, 115, 40, 120, 43, 120, 55
            ];
            lightShinePolygon( entities, stltPos, points, color );
            //地面多边形
            entities.add( {
                polygon : {
                    hierarchy : Cesium.Cartesian3.fromDegreesArray( points ),
                    outline : true,
                    outlineColor : Cesium.Color.fromAlpha( Cesium.Color.fromCssColorString( color ), .4 ),
                    material : Cesium.Color.fromAlpha( Cesium.Color.fromCssColorString( color ), .3 )
                }
            } );
        }

        //一个圆形覆盖范围
        {
            var r = 600000;//半径
            var color = '#0000FF';
            //圆心
            var ecLong = 110.0;
            var ecLat = 30.0;
            var ec = Cesium.Cartesian3.fromDegrees( ecLong, ecLat, 0 );
            //模拟光照效果的若干多边形
            var points = [];
            for ( var i = 0; i &lt; 360; i += 30 )
            {
                var coord = offsetToLongLat( [
                    ecLong, ecLat
                ], [
                    Math.cos( Math.PI * i / 180 ) * r, Math.sin( Math.PI * i / 180 ) * r
                ] );
                points.push( coord[0] );
                points.push( coord[1] );
            }
            lightShinePolygon( entities, stltPos, points, color );
            //圆
            viewer.entities.add( {
                position : ec,
                ellipse : {
                    semiMinorAxis : r,
                    semiMajorAxis : r,
                    height : 0.0,
                    outline : true,
                    outlineColor : Cesium.Color.fromAlpha( Cesium.Color.fromCssColorString( color ), .4 ),
                    material : Cesium.Color.fromAlpha( Cesium.Color.fromCssColorString( color ), .3 )
                }
            } );
        }
    }
}</pre>
<div class="blog_h1"><span class="graybg">空间数据可视化</span></div>
<p>Cesium提供Entity API来绘制空间数据，例如点、标记、标签、线、3D模型、形状、立体形状（volume）。</p>
<div class="blog_h2"><span class="graybg">Entity API简介</span></div>
<p>Cesium提供两类API：</p>
<ol>
<li>面向图形开发人员的底层API，通常称为“Primitive API”。该API暴露最小限度的抽象，使用图形学术语，具有很大的灵活性，需要具有图形学编程的知识</li>
<li>高级别的数据驱动的API，称为“Entity API”。该API使用一致性设计的、高级别的对象来管理一组相关性的可视化对象，其底层使用Primitive API</li>
</ol>
<p>下面是Entity API的简单例子，用红色半透明区域标记出美国怀俄明州：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer'); //创建一个查看器（Viewer widget）
var wyoming = viewer.entities.add({  //添加一个实体，仅需要传递一个简单JSON对象，返回值是一个Entity对象
  name : 'Wyoming',
  polygon : {
    hierarchy : Cesium.Cartesian3.fromDegreesArray([//一组地理坐标
                              -109.080842,45.002073,
                              -105.91517,45.002073,
                              -104.058488,44.996596,
                              -104.053011,43.002989,
                              -104.053011,41.003906,
                              -105.728954,40.998429,
                              -107.919731,41.003906,
                              -109.04798,40.998429,
                              -111.047063,40.998429,
                              -111.047063,42.000709,
                              -111.047063,44.476286,
                              -111.05254,45.002073]),
    material : Cesium.Color.RED.withAlpha(0.5), //材质
    outline : true, //是否显示轮廓
    outlineColor : Cesium.Color.BLACK //轮廓的颜色
  }
});
viewer.zoomTo(wyoming);//缩放、平移视图使实体可见 </pre>
<div class="blog_h2"><span class="graybg">形状与立体（Shapes and Volumes）</span></div>
<div class="blog_h3"><span class="graybg">支持的形状与立体列表</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 120px; text-align: center;">形状 </td>
<td style="text-align: center;"> 代码示例</td>
</tr>
</thead>
<tbody>
<tr>
<td>立方体<br />(Boxes)</td>
<td>
<pre class="crayon-plain-tag">var blueBox = viewer.entities.add({
    name : 'Blue box',
     //中心的位置
    position: Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
    box : {
        //长宽高
        dimensions : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0),
        material : Cesium.Color.BLUE
    }
});

var redBox = viewer.entities.add({
    name : 'Red box with black outline',
    position: Cesium.Cartesian3.fromDegrees(-107.0, 40.0, 300000.0),
    box : {
        dimensions : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0),
        material : Cesium.Color.RED,
        outline : true, //显示轮廓
        outlineColor : Cesium.Color.BLACK
    }
});

var outlineOnly = viewer.entities.add({
    name : 'Yellow box outline',
    position: Cesium.Cartesian3.fromDegrees(-100.0, 40.0, 300000.0),
    box : {
        dimensions : new Cesium.Cartesian3(400000.0, 300000.0, 500000.0),
        fill : false,  //不显示填充
        outline : true,
        outlineColor : Cesium.Color.YELLOW
    }
});</pre>
</td>
</tr>
<tr>
<td>圆和椭圆<br />(Circles Ellipses)</td>
<td>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');
//浮空的绿色圆形
var greenCircle = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-111.0, 40.0, 150000.0),
    name : 'Green circle at height',
    ellipse : {
        semiMinorAxis : 300000.0,
        semiMajorAxis : 300000.0,
        height: 200000.0, //浮空
        material : Cesium.Color.GREEN
    }
});
//红色椭圆形，位于地表，带轮廓
var redEllipse = viewer.entities.add({
    //不带高度
    position: Cesium.Cartesian3.fromDegrees(-103.0, 40.0),
    name : 'Red ellipse on surface with outline',
    ellipse : {
        semiMinorAxis : 250000.0,
        semiMajorAxis : 400000.0,
        material : Cesium.Color.RED.withAlpha(0.5),
        outline : true,
        outlineColor : Cesium.Color.RED
    }
});
//蓝色椭圆柱，旋转了角度
var blueEllipse = viewer.entities.add({
    position: Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 100000.0),
    name : 'Blue translucent, rotated, and extruded ellipse',
    ellipse : {
        semiMinorAxis : 150000.0,
        semiMajorAxis : 300000.0,
        extrudedHeight : 200000.0,  //拉伸
        rotation : Cesium.Math.toRadians(45), //旋转
        material : Cesium.Color.BLUE.withAlpha(0.7),
        outline : true
    }
});

viewer.zoomTo(viewer.entities);</pre>
</td>
</tr>
<tr>
<td>走廊<br />(Corridor)</td>
<td>
<pre class="crayon-plain-tag">var redCorridor = viewer.entities.add({
    name : 'Red corridor on surface with rounded corners and outline',
    corridor : {
        positions : Cesium.Cartesian3.fromDegreesArray([
                                                        -100.0, 40.0,
                                                        -105.0, 40.0,
                                                        -105.0, 35.0
                                                    ]),
                                                    width : 200000.0,
        material : Cesium.Color.RED.withAlpha(0.5),
        outline : true,
        outlineColor : Cesium.Color.RED
    }
});

var greenCorridor = viewer.entities.add({
    name : 'Green corridor at height with mitered corners',
    corridor : {
        positions : Cesium.Cartesian3.fromDegreesArray(
        [    -90.0, 40.0,
             -95.0, 40.0,
             -95.0, 35.0
        ]),
        height: 100000.0,
        width : 200000.0,
        cornerType: Cesium.CornerType.MITERED,
        material : Cesium.Color.GREEN
    }
});

var blueCorridor = viewer.entities.add({
    name : 'Blue extruded corridor with beveled corners and outline',
    corridor : {
        positions : Cesium.Cartesian3.fromDegreesArray(
        [    80.0, 40.0,
             -85.0, 40.0,
             -85.0, 35.0
        ]),
        height : 200000.0,
        extrudedHeight : 100000.0,
        width : 200000.0,
        cornerType: Cesium.CornerType.BEVELED,
        material : Cesium.Color.BLUE.withAlpha(0.5),
        outline : true,
        outlineColor : Cesium.Color.BLUE
    }
});</pre>
</td>
</tr>
<tr>
<td>圆柱和圆锥<br />(Cylinder Cones)</td>
<td> <br />
<pre class="crayon-plain-tag">var greenCylinder = viewer.entities.add({
    name : 'Green cylinder with black outline',
    position: Cesium.Cartesian3.fromDegrees(-100.0, 40.0, 200000.0),
    cylinder : { //圆柱
        length : 400000.0,
        topRadius : 200000.0,
        bottomRadius : 200000.0,
        material : Cesium.Color.GREEN,
        outline : true,
        outlineColor : Cesium.Color.DARK_GREEN
    }
});

var redCone = viewer.entities.add({
    name : 'Red cone',
    position: Cesium.Cartesian3.fromDegrees(-105.0, 40.0, 200000.0),
    cylinder : {//圆锥
        length : 400000.0,
        topRadius : 0.0,
        bottomRadius : 200000.0,
        material : Cesium.Color.RED
    }
});</pre>
</td>
</tr>
<tr>
<td>多边形<br />(Polygons)</td>
<td>
<pre class="crayon-plain-tag">var redPolygon = viewer.entities.add({
    name : '贴着地表的多边形',
    polygon : {
        hierarchy : Cesium.Cartesian3.fromDegreesArray([-115.0, 37.0,
                                                        -115.0, 32.0,
                                                        -107.0, 33.0,
                                                        -102.0, 31.0,
                                                        -102.0, 35.0]),
        material : Cesium.Color.RED
    }
});

var greenPolygon = viewer.entities.add({
    name : '绿色拉伸多边形',
    polygon : {
        hierarchy : Cesium.Cartesian3.fromDegreesArray([-108.0, 42.0,
                                                        -100.0, 42.0,
                                                        -104.0, 40.0]),
        extrudedHeight: 500000.0,
        material : Cesium.Color.GREEN
    }
});

var orangePolygon = viewer.entities.add({
    name : '每个顶点具有不同拉伸高度的橘色多边形',
    polygon : {
        hierarchy : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-108.0, 25.0, 100000,
             -100.0, 25.0, 100000,
             -100.0, 30.0, 100000,
             -108.0, 30.0, 300000]),
        extrudedHeight: 0,
        perPositionHeight : true,
        material : Cesium.Color.ORANGE,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});

var bluePolygon = viewer.entities.add({
    name : '具有挖空效果的蓝色多边形',
    polygon : {
        hierarchy : {
            positions : Cesium.Cartesian3.fromDegreesArray(
                [-99.0, 30.0,
                 -85.0, 30.0,
                 -85.0, 40.0,
                 -99.0, 40.0]),
            holes : [{
                positions : Cesium.Cartesian3.fromDegreesArray([
                    -97.0, 31.0,
                    -97.0, 39.0,
                    -87.0, 39.0,
                    -87.0, 31.0
                ]),
                holes : [{
                    positions : Cesium.Cartesian3.fromDegreesArray([
                        -95.0, 33.0,
                        -89.0, 33.0,
                        -89.0, 37.0,
                        -95.0, 37.0
                    ]),
                    holes : [{
                        positions : Cesium.Cartesian3.fromDegreesArray([
                            -93.0, 34.0,
                            -91.0, 34.0,
                            -91.0, 36.0,
                            -93.0, 36.0
                        ])
                    }]
                }]
            }]
        },
        material : Cesium.Color.BLUE,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});</pre>
</td>
</tr>
<tr>
<td>多段线<br />(Polylines)</td>
<td>
<pre class="crayon-plain-tag">var redLine = viewer.entities.add({
    name : '沿着地球表面的红线',
    polyline : {
        positions : Cesium.Cartesian3.fromDegreesArray(
            [-75, 35,
             -125, 35]),
        width : 5,
        material : Cesium.Color.RED
    }
});

var glowingLine = viewer.entities.add({
    name : '具有发光效果的线',
    polyline : {
        positions : Cesium.Cartesian3.fromDegreesArray(
            [-75, 37, -125, 37]
        ),
        width : 10,
        material : new Cesium.PolylineGlowMaterialProperty({
            glowPower : 0.2,
            color : Cesium.Color.BLUE
        })
    }
});

var orangeOutlined = viewer.entities.add({
    name : '具有一定高度的线',
    polyline : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-75, 39, 250000,-125, 39, 250000]
        ),
        width : 5,
        material : new Cesium.PolylineOutlineMaterialProperty({
            color : Cesium.Color.ORANGE,
            outlineWidth : 2,
            outlineColor : Cesium.Color.BLACK
        })
    }
});

var yellowLine = viewer.entities.add({
    name : '不贴着地表的线',
    polyline : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-75, 43, 500000,-125, 43, 500000]
        ),
        width : 3,
        followSurface : false,  //是否贴着地表
        material : Cesium.Color.PURPLE
    }
});</pre>
</td>
</tr>
<tr>
<td>多段线体<br />(Polyline Volumes)</td>
<td>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');

function computeCircle(radius) {
    var positions = [];
    for (var i = 0; i &lt; 360; i++) {
        var radians = Cesium.Math.toRadians(i);
        positions.push(new Cesium.Cartesian2(
            radius * Math.cos(radians), radius * Math.sin(radians)));
    }
    return positions;
}

function computeStar(arms, rOuter, rInner) {
    var angle = Math.PI / arms;
    var length = 2 * arms;
    var positions = new Array(length);
    for (var i = 0; i &lt; length; i++) {
        var r = (i % 2) === 0 ? rOuter : rInner;
        positions[i] = new Cesium.Cartesian2(
            Math.cos(i * angle) * r, Math.sin(i * angle) * r);
    }
    return positions;
}

var redTube = viewer.entities.add({
    name : 'Red tube with rounded corners',
    polylineVolume : {
        positions : Cesium.Cartesian3.fromDegreesArray(
            [-85.0, 32.0,
             -85.0, 36.0,
             -89.0, 36.0]),
        shape : computeCircle(60000.0),
        material : Cesium.Color.RED
    }
});

var greenBox = viewer.entities.add({
    name : 'Green box with beveled corners and outline',
    polylineVolume : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-90.0, 32.0, 0.0,
             -90.0, 36.0, 100000.0,
             -94.0, 36.0, 0.0]),
        shape :[new Cesium.Cartesian2(-50000, -50000),
                new Cesium.Cartesian2(50000, -50000),
                new Cesium.Cartesian2(50000, 50000),
                new Cesium.Cartesian2(-50000, 50000)],
        cornerType : Cesium.CornerType.BEVELED,
        material : Cesium.Color.GREEN,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});

var blueStar = viewer.entities.add({
    name : 'Blue star with mitered corners and outline',
    polylineVolume : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-95.0, 32.0, 0.0,
             -95.0, 36.0, 100000.0,
             -99.0, 36.0, 200000.0]),
        shape : computeStar(7, 70000, 50000),
        cornerType : Cesium.CornerType.MITERED,
        material : Cesium.Color.BLUE,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});

viewer.zoomTo(viewer.entities);</pre>
</td>
</tr>
<tr>
<td>矩形<br />(Rectangles)</td>
<td>
<pre class="crayon-plain-tag">//红色矩形
var redRectangle = viewer.entities.add({
    name : 'Red translucent rectangle with outline',
    rectangle : {
        coordinates : Cesium.Rectangle.fromDegrees(-110.0, 20.0, -80.0, 25.0),
        material : Cesium.Color.RED.withAlpha(0.5),
        outline : true,
        outlineColor : Cesium.Color.RED
    }
});
//绿色旋转、拉伸的矩形
var greenRectangle = viewer.entities.add({
    name : 'Green translucent, rotated, and extruded rectangle',
    rectangle : {
        coordinates : Cesium.Rectangle.fromDegrees(-100.0, 30.0, -90.0, 40.0),
        material : Cesium.Color.GREEN.withAlpha(0.5),
        rotation : Cesium.Math.toRadians(45),
        extrudedHeight : 300000.0,
        height : 100000.0,
        outline : true,
        outlineColor : Cesium.Color.GREEN
    }
});</pre>
</td>
</tr>
<tr>
<td>球和椭球<br />(Spheres Ellipsoids)</td>
<td> <br />
<pre class="crayon-plain-tag">var blueEllipsoid = viewer.entities.add({
    name : 'Blue ellipsoid',
    position: Cesium.Cartesian3.fromDegrees(-114.0, 40.0, 300000.0),
    ellipsoid : {
        //可以指定三个轴的半径
        radii : new Cesium.Cartesian3(200000.0, 200000.0, 300000.0),
        material : Cesium.Color.BLUE
    }
});

var redSphere = viewer.entities.add({
    name : 'Red sphere with black outline',
    position: Cesium.Cartesian3.fromDegrees(-107.0, 40.0, 300000.0),
    ellipsoid : {
        //正球体
        radii : new Cesium.Cartesian3(300000.0, 300000.0, 300000.0),
        material : Cesium.Color.RED,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});

var outlineOnly = viewer.entities.add({
    name : 'Yellow ellipsoid outline',
    position: Cesium.Cartesian3.fromDegrees(-100.0, 40.0, 300000.0),
    ellipsoid : {
        radii : new Cesium.Cartesian3(200000.0, 200000.0, 300000.0),
        fill : false,
        outline : true,
        outlineColor : Cesium.Color.YELLOW,
        slicePartitions : 24, //横向切割线
        stackPartitions : 36  //纵向切割线
    }
});</pre>
</td>
</tr>
<tr>
<td>墙<br />(Walls)</td>
<td>
<pre class="crayon-plain-tag">//东西方向的横墙
var redWall = viewer.entities.add({
    name : 'Red wall at height',
    wall : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
             [-115.0, 44.0, 200000.0,//坐标点
              -90.0, 44.0, 200000.0]
        ),
        minimumHeights : [100000.0, 100000.0], //按坐标点的最小高度数组
        material : Cesium.Color.RED
    }
});
//四边围墙
var greenWall = viewer.entities.add({
    name : 'Green wall from surface with outline',
    wall : {
        positions : Cesium.Cartesian3.fromDegreesArrayHeights(
            [-107.0, 43.0, 100000.0,
             -97.0, 43.0, 100000.0,
             -97.0, 40.0, 100000.0,
             -107.0, 40.0, 100000.0,
             -107.0, 43.0, 100000.0]),
        material : Cesium.Color.GREEN,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});
//曲折的墙
var blueWall = viewer.entities.add({
    name : 'Blue wall with sawtooth heights and outline',
    wall : {
        //坐标点，不指定高度
        positions : Cesium.Cartesian3.fromDegreesArray(
            [-115.0, 50.0,
             -112.5, 50.0,
             -110.0, 50.0,
             -107.5, 50.0,
             -105.0, 50.0,
             -102.5, 50.0,
             -100.0, 50.0,
             -97.5, 50.0,
             -95.0, 50.0,
             -92.5, 50.0,
             -90.0, 50.0]),
        maximumHeights : [ //上高
            100000, 200000, 100000, 200000, 100000, 200000, 
            100000, 200000, 100000, 200000, 100000],
        minimumHeights : [  //下高
            0, 100000,  0, 100000, 0, 100000, 0, 100000, 0,
            100000, 0],
        material : Cesium.Color.BLUE,
        outline : true,
        outlineColor : Cesium.Color.BLACK
    }
});</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">材质（Material）与轮廓（Outline）</span></div>
<p>多数形状均支持通过一致的方式来设置属性、控制外观：</p>
<ol>
<li>fill：布尔型，用于指定目标形状是否被填充</li>
<li>outline：布尔型，用于指定是否绘制形状的边缘</li>
<li>material：如果fill为true，该属性可以控制填充的材质类型</li>
</ol>
<p>一个例外是多段线，可以设置outlineWidth 、outlineColor、glowPower 等属性。</p>
<div class="blog_h3"><span class="graybg">高度与拉伸（Extrusion）</span></div>
<p>所有的形状均默认均是沿着地表的，目前圆形、椭圆、矩形可以在一定高度浮空显示，或者拉伸为Volume。</p>
<p>需要注意：Cesium总是使用米、弧度、秒为度量单位。下面是一个例子：</p>
<pre class="crayon-plain-tag">wyoming.polygon.height = 200000;         //设置高度
wyoming.polygon.extrudedHeight = 250000; //设置拉伸高度</pre>
<div class="blog_h2"><span class="graybg">在Viewer中可用的Entity特性</span></div>
<p>除非显式禁用，点击实体后会显示SelectionIndicator小器件，以及一个信息框。通过设置Entity.description，可以在信息框中显示任何HTML内容。</p>
<div class="blog_h2"><span class="graybg">镜头控制</span></div>
<p>zoomTo方法可以立即定位到某个位置，而flyTo则是通过动画方式转移到某个位置，这两个方法均可以传递EntityCollection对象，并且均是异步方法，返回一个Promises对象</p>
<p>默认情况下，镜头是<span style="background-color: #c0c0c0;">朝北、45度倾斜</span>查看地球。下面的代码可以让镜头朝东、倾斜三十度查看：</p>
<pre class="crayon-plain-tag">//镜头顺时针旋转九十度，即东向
var heading = Cesium.Math.toRadians(90);
//镜头倾斜30度俯视
var pitch = Cesium.Math.toRadians(-30);
viewer.zoomTo(wyoming, new Cesium.HeadingPitchRange(heading, pitch)).then(function(result){
    //执行完毕后，进行的动作
    if (result) { //如果镜头切换成功，则result=true
        viewer.selectedEntity = wyoming;
    }
});</pre>
<p>有时需要镜头跟踪某个实体（使居中）而不是地球，可以使用如下代码：</p>
<pre class="crayon-plain-tag">wyoming.position = Cesium.Cartesian3.fromDegrees(-107.724, 42.68);
viewer.trackedEntity = wyoming;  //跟踪某个实体。如果调用zoomTo、flyTo自动取消跟踪</pre>
<div class="blog_h2"><span class="graybg">管理Entity</span></div>
<p>EntityCollection对象是一个从Entity Id到Entity的关联数组，其提供例如add、remove、removeAll之类的常规函数，用于添加或者删除某个Entity：</p>
<pre class="crayon-plain-tag">//添加一个实体，并且提供ID
viewer.entities.add({
  id : '182bdba4-2b3e-47ae-bf0b-83f6fde285fd'
});
//获取一个实体
var entity = viewer.entities.getById('uniqueId')
//获取一个实体，如果不存在则创建之
var entity = viewer.entities.getOrCreateEntity('uniqueId');

//当添加、删除、修改EntityCollection中的Entity时，可以获得事件通知
function onChanged(collection, added, removed, changed){
    //add、removed、changed是增删改的Entity数组
    for(var i = 0; i &lt; added.length; i++) {
        
    }
}
viewer.entities.collectionChanged.addEventListener(onChanged);

//大批量操作时，临时禁用事件可以提高性能
viewer.entities.suspendEvents();
//执行各种Entity操作
viewer.entities.resumeEvents();</pre>
<div class="blog_h2"><span class="graybg">点、图标和标签</span></div>
<p>添加一个点、标签或者图标很简单：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer' );

var citizensBankPark = viewer.entities.add( {
    name : 'Citizens Bank Park',
    position : Cesium.Cartesian3.fromDegrees( -75.166493, 39.9060534 ),
    point : { //点
        pixelSize : 5,
        color : Cesium.Color.RED,
        outlineColor : Cesium.Color.WHITE,
        outlineWidth : 2
    },
    label : { //文字标签
        text : 'Citizens Bank Park',
        font : '14pt monospace',
        style : Cesium.LabelStyle.FILL_AND_OUTLINE,
        outlineWidth : 2,
        verticalOrigin : Cesium.VerticalOrigin.BOTTOM, //垂直方向以底部来计算标签的位置
        pixelOffset : new Cesium.Cartesian2( 0, -9 )   //偏移量
    }
    billboard : { //图标
        image : 'http://localhost:81/images/2015/02-02/Philadelphia_Phillies.png',
        width : 64,
        height : 64
    },
} );

viewer.zoomTo( viewer.entities );</pre>
<div class="blog_h2"><span class="graybg">3D模型</span></div>
<p>Cesium支持glTF格式的3D模型，glTF是WebGL、 OpenGL ES、 OpenGL的一种运行时模型格式，在Cesium中创建3D模型很简单：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');
var entity = viewer.entities.add({
    position : Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706),
    model : {
        uri : '../../SampleData/models/CesiumGround/Cesium_Ground.gltf'
    },
    scale : 1,//和原始大小相比的缩放比例
    minimumPixelSize :100 //最小尺寸，防止太小而看不见
});
viewer.trackedEntity = entity;</pre>
<p>默认情况下，模型竖直放置、并且面向东面。可以指定四元组（Quaternion）给Entity.orientation属性，以改变放置的方向：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706); //位置
var heading = Cesium.Math.toRadians(45.0);//绕垂直于地心的轴旋转
var pitch = Cesium.Math.toRadians(15.0);  //绕纬度线旋转
var roll = Cesium.Math.toRadians(0.0);    //绕经度线旋转
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);

var entity = viewer.entities.add({
    position : position,
	orientation : orientation,
    model : {
        uri : '../../SampleData/models/CesiumGround/Cesium_Ground.gltf'
    }
});
viewer.trackedEntity = entity;</pre>
<p>例子中的<span style="background-color: #c0c0c0;">heading（yaw）、pitch、roll</span>对应了绕<span style="background-color: #c0c0c0;">Z（垂直轴）、Y（维度方向）、X（经度方向）</span>进行旋转，正数表示顺时针旋转（由于相对运动，在浏览器上看起来是地球在逆时针旋转），可以参考下图理解（人面向北面，摇头heading、点头pitch、歪头roll）：<img class="aligncenter wp-image-4799 size-full" src="https://blog.gmem.cc/wp-content/uploads/2015/03/head-pitch-roll1.png" alt="head-pitch-roll" width="400" height="455" /><a href="/wp-content/uploads/2015/03/head-pitch-roll.png"><br /></a></p>
<div class="blog_h2"><span class="graybg">属性系统</span></div>
<p>Cesium提供了一些快捷方式来设置属性，例如outline:true，但是尝试使用e.polygon.outline这样的形式来获取轮廓时，会得到一个ConstantProperty对象，如果不使用快捷方式，则需要编写更多的代码，例如：</p>
<pre class="crayon-plain-tag">polygon.outline = new Cesium.ConstantProperty(true);
polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.BLACK);</pre>
<p>所有属性的实例均是Property的子类型，引入属性类层次而不是使用基本类型的原因是，某些属性是随着时间而变化的。</p>
<p>要得到属性的原始值，需要调用Property.getValue()方法，例如：</p>
<pre class="crayon-plain-tag">//获取当前时间点，多边形轮廓是否存在
polygon.outline.getValue(viewer.clock.currentTime)</pre>
<div class="blog_h1"><span class="graybg">几何图形与外观</span></div>
<p>我们可以通过Primitive API来操控几何图形及其外观，或者绘制各种特殊的形状。需要先得到Scene对象，然后在其上添加Primitive对象：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

scene.primitives.add(new Cesium.RectanglePrimitive({
    //绘制矩形
    rectangle : Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
    material : Cesium.Material.fromType('Dot')  //设置材质
}));</pre>
<p>Primitive由两个部分组成：</p>
<ol>
<li>几何形状（Geometry）：定义了Primitive的结构，例如三角形、线条、点等</li>
<li>外观（Appearance ）：定义Primitive的着色（Sharding），包括GLSL（OpenGL着色语言，OpenGL Shading Language）顶点着色器和片段着色器（ vertex and fragment shaders），以及渲染状态（render state）</li>
</ol>
<p>Cesium支持以下几何图形：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 220px; text-align: center;">几何图形 </td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>BoxGeometry</td>
<td>立方体</td>
</tr>
<tr>
<td>BoxOutlineGeometry</td>
<td>仅有轮廓的立方体</td>
</tr>
<tr>
<td>CircleGeometry</td>
<td>圆形或者拉伸的圆形</td>
</tr>
<tr>
<td>CircleOutlineGeometry</td>
<td>只有轮廓的圆形</td>
</tr>
<tr>
<td>CorridorGeometry</td>
<td>走廊：沿着地表的多段线，且具有一定的宽度，可以拉伸到一定的高度</td>
</tr>
<tr>
<td>CorridorOutlineGeometry</td>
<td>只有轮廓的走廊</td>
</tr>
<tr>
<td>CylinderGeometry</td>
<td>圆柱、圆锥或者截断的圆锥</td>
</tr>
<tr>
<td>CylinderOutlineGeometry</td>
<td>只有轮廓的圆柱、圆锥或者截断的圆锥</td>
</tr>
<tr>
<td>EllipseGeometry</td>
<td>椭圆或者拉伸的椭圆</td>
</tr>
<tr>
<td>EllipseOutlineGeometry</td>
<td>只有轮廓的椭圆或者拉伸的椭圆</td>
</tr>
<tr>
<td>EllipsoidGeometry</td>
<td>椭球体</td>
</tr>
<tr>
<td>EllipsoidOutlineGeometry</td>
<td>只有轮廓的椭球体</td>
</tr>
<tr>
<td>RectangleGeometry</td>
<td>矩形或者拉伸的矩形</td>
</tr>
<tr>
<td>RectangleOutlineGeometry</td>
<td>只有轮廓的矩形或者拉伸的矩形</td>
</tr>
<tr>
<td>PolygonGeometry</td>
<td>多边形，可以具有空洞或者拉伸一定的高度</td>
</tr>
<tr>
<td>PolygonOutlineGeometry</td>
<td>只有轮廓的多边形</td>
</tr>
<tr>
<td>PolylineGeometry</td>
<td>多段线，可以具有一定的宽度</td>
</tr>
<tr>
<td>SimplePolylineGeometry</td>
<td>简单的多段线</td>
</tr>
<tr>
<td>PolylineVolumeGeometry</td>
<td>多段线柱体</td>
</tr>
<tr>
<td>PolylineVolumeOutlineGeometry</td>
<td>只有轮廓的多段线柱体</td>
</tr>
<tr>
<td>SphereGeometry</td>
<td>球体</td>
</tr>
<tr>
<td>SphereOutlineGeometry</td>
<td>只有轮廓的球体</td>
</tr>
<tr>
<td>WallGeometry</td>
<td>墙</td>
</tr>
<tr>
<td>WallOutlineGeometry</td>
<td>只有轮廓的墙</td>
</tr>
</tbody>
</table>
<p>使用Geometry和Appearance 具有以下优势：</p>
<ol>
<li>性能：绘制大量Primitive时，可以将其合并为单个Geometry以减轻CPU负担、更好的使用GPU。合并Primitive由web worker线程执行，UI保持响应性</li>
<li>灵活性：Geometry与Appearance 解耦，两者可以分别进行修改</li>
<li>低级别访问：易于编写GLSL 顶点、片段着色器、使用自定义的渲染状态 </li>
</ol>
<p>同时，具有以下劣势：</p>
<ol>
<li>需要编写更多地代码</li>
<li>需要对图形编程有更多的理解，特别是OpenGL的知识</li>
</ol>
<p>使用来Geometry、Appearance 改写上面的例子，代码为：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
//GeometryInstance是Geometry的一个容器
var instance = new Cesium.GeometryInstance({
  geometry : new Cesium.RectangleGeometry({
    rectangle : Cesium.Rectangle.fromDegrees(-100.0, 20.0, -90.0, 30.0),
    vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
  })
});
//使用抽象的Primitive而不是RectanglePrimitive
scene.primitives.add(new Cesium.Primitive({
  geometryInstances : instance,
  //使用该外观，可以使矩形覆盖在地球表面，或者悬浮一定的高度
  appearance : new Cesium.EllipsoidSurfaceAppearance({
    material : Cesium.Material.fromType('Dot')
  })
}));</pre>
<div class="blog_h2"><span class="graybg">合并几何图形（Combing Geometries）</span></div>
<p>合并多个GeometryInstances 为一个Primitive可以极大的提高性能，下面的例子创建了2592一颜色各异的矩形，覆盖整个地球 ：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer' );
var scene = viewer.scene;

var instances = [];

for ( var lon = -180.0; lon &lt; 180.0; lon += 5.0 )
{
    for ( var lat = -90.0; lat &lt; 90.0; lat += 5.0 )
    {
        instances.push( new Cesium.GeometryInstance( {
            geometry : new Cesium.RectangleGeometry( {
                rectangle : Cesium.Rectangle.fromDegrees( lon, lat, lon + 5.0, lat + 5.0 )
            } ),
            attributes : {
                color : Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.fromRandom( {
                    alpha : 0.5
                } ) )
            }
        } ) );
    }
}

scene.primitives.add( new Cesium.Primitive( {
    geometryInstances : instances, //合并
    //某些外观允许每个几何图形实例分别指定某个属性，例如：
    appearance : new Cesium.PerInstanceColorAppearance()
} ) );</pre>
<div class="blog_h2"><span class="graybg">选取几何图形（Picking）</span></div>
<p>即使多个 GeometryInstance被合并为单个Primitive，让然可以独立的被访问。我们可以为每一个GeometryInstance指定一个id，并且可以通过Scene.pick来判断该实例是否被选取：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer' );
var scene = viewer.scene;

var instance = new Cesium.GeometryInstance( {
    geometry : new Cesium.RectangleGeometry( {
        rectangle : Cesium.Rectangle.fromDegrees( -100.0, 30.0, -90.0, 40.0 )
    } ),
    id : 'rectangle-1',
    attributes : {
        color : Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.RED )
    }
} );

scene.primitives.add( new Cesium.Primitive( {
    geometryInstances : instance,
    appearance : new Cesium.PerInstanceColorAppearance()
} ) );

var handler = new Cesium.ScreenSpaceEventHandler( scene.canvas );
//设置单击事件的处理句柄
handler.setInputAction( function( movement )
{
    var pick = scene.pick( movement.position );
    if ( Cesium.defined( pick ) &amp;&amp; ( pick.id === 'rectangle-1' ) )
    {
        console.log( '矩形被选取' );
    }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK );</pre>
<div class="blog_h2"><span class="graybg">几何图形实例（Geometry Instances）</span></div>
<p>上面的例子中，我们已经用到了GeometryInstances，注意GeometryInstance与Geometry的关系：前者是后者的容器，多个Instance可以共用一个Geometry，并且可以通过GeometryInstances<span style="background-color: #c0c0c0;">.modelMatrix</span>属性提供不同position、scale、rotate等位置、缩放、旋转信息。例如，下面的例子使用同一个Geometry绘制了两个Instance，一个位于另一个的上方：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer' );
var scene = viewer.scene;

var ellipsoidGeometry = new Cesium.EllipsoidGeometry( {
    vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
    radii : new Cesium.Cartesian3( 300000.0, 200000.0, 150000.0 )//三轴半径
} );
//下方的实例
var cyanEllipsoidInstance = new Cesium.GeometryInstance( {
    geometry : ellipsoidGeometry,
    modelMatrix : Cesium.Matrix4.multiplyByTranslation( Cesium.Transforms.eastNorthUpToFixedFrame( Cesium.Cartesian3.fromDegrees( -100.0, 40.0 ) ), new Cesium.Cartesian3( 0.0, 0.0, 150000.0 ) ),
    attributes : {
        color : Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.CYAN )
    }
} );
//上方的实例
var orangeEllipsoidInstance = new Cesium.GeometryInstance( {
    geometry : ellipsoidGeometry,
    modelMatrix : Cesium.Matrix4.multiplyByTranslation( Cesium.Transforms.eastNorthUpToFixedFrame( Cesium.Cartesian3.fromDegrees( -100.0, 40.0 ) ), new Cesium.Cartesian3( 0.0, 0.0, 450000.0 ) ),
    attributes : {
        color : Cesium.ColorGeometryInstanceAttribute.fromColor( Cesium.Color.ORANGE )
    }
} );

scene.primitives.add( new Cesium.Primitive( {
    geometryInstances : [
        cyanEllipsoidInstance, orangeEllipsoidInstance
    ],
    appearance : new Cesium.PerInstanceColorAppearance( {
        translucent : false,
        closed : true
    } )
} ) );</pre>
<div class="blog_h2"><span class="graybg">更新单个GeometryInstance的属性</span></div>
<p>在添加到Primitive中以后，让然可以修改几何图形的某些属性：</p>
<ol>
<li>颜色：如果Primitive设置了PerInstanceColorAppearance外观，则可以修改ColorGeometryInstanceAttribute类型的颜色</li>
<li>可见性：任何实例可以修改可见性</li>
</ol>
<p>示例代码：</p>
<pre class="crayon-plain-tag">var viewer = new Cesium.Viewer( 'cesiumContainer' );
var scene = viewer.scene;

var circleInstance = new Cesium.GeometryInstance( {
    geometry : new Cesium.CircleGeometry( {
        center : Cesium.Cartesian3.fromDegrees( -95.0, 43.0 ),
        radius : 250000.0,
        vertexFormat : Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
    } ),
    attributes : {
        color : Cesium.ColorGeometryInstanceAttribute.fromColor( new Cesium.Color( 1.0, 0.0, 0.0, 0.5 ) ),
        show : new Cesium.ShowGeometryInstanceAttribute( true ) //显示或者隐藏
    },
    id : 'circle'
} );
var primitive = new Cesium.Primitive( {
    geometryInstances : circleInstance,
    appearance : new Cesium.PerInstanceColorAppearance( {
        translucent : false,
        closed : true
    } )
} );
scene.primitives.add( primitive );

//定期修改颜色
setInterval( function()
{
    var attributes = primitive.getGeometryInstanceAttributes( 'circle' );//获取某个实例的属性集
    attributes.color = Cesium.ColorGeometryInstanceAttribute.toValue( Cesium.Color.fromRandom( {
        alpha : 1.0
    } ) );
}, 2000 );</pre>
<div class="blog_h2"><span class="graybg">外观（Appearances）</span></div>
<p>Primitive由两个重要部分组成：几何图形实例、外观，一个Primitive只能有一个外观，而可以有多个实例。几何图形定义了结构，外观定义了每个像素被如何着色，外观可能使用材质（Material）。这些对象的关系如下图所示：<img class="aligncenter size-full wp-image-4810" src="https://blog.gmem.cc/wp-content/uploads/2015/03/highleveldesign.png" alt="highleveldesign" width="504" height="400" /></p>
<p>Cesium支持下表列出的外观：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 220px; text-align: center;"> 外观</td>
<td style="text-align: center;"> 说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>MaterialAppearance</td>
<td>支持各种Geometry类型的外观，支持使用材质来定义着色</td>
</tr>
<tr>
<td>EllipsoidSurfaceAppearance</td>
<td>MaterialAppearance的一个版本。假设几何图形与地表是平行的，并且依此来进行顶点属性（vertex attributes）的计算</td>
</tr>
<tr>
<td>PerInstanceColorAppearance</td>
<td>让每个实例使用自定义的颜色来着色</td>
</tr>
<tr>
<td>PolylineMaterialAppearance</td>
<td>支持使用材质来着色多段线</td>
</tr>
<tr>
<td>PolylineColorAppearance</td>
<td>使用每顶点或者每片段（per-vertex or per-segment ）的颜色来着色多段线</td>
</tr>
</tbody>
</table>
<p>外观定义了需要在GPU上执行的完整的GLSL顶点、片段着色器，通常不需要修改这一部分，除非需要定义自己的外观。</p>
<p>外观还定义了完整的render state，用于在绘制Primitive时控制GPU的状态，可以直接或者通过高层API来定义render state：</p>
<pre class="crayon-plain-tag">//下面的外观可用于定义一个Viewer不可进入的不透明盒子
var appearance = new Cesium.PerInstanceColorAppearance( {
    translucent : false,
    closed : true
} );
//下面的代码效果同上
var translucent = new Cesium.PerInstanceColorAppearance( {
    renderState : {
        depthTest : {
            enabled : true
        },
        cull : {
            enabled : true,
            face : Cesium.CullFace.BACK
        }
    }
} );</pre>
<p>一旦外观被创建，其render state就不可再变，但是其材质是可以替换的。另外Primitive的外观也是不可修改的。</p>
<p>大部分外观具有flat、faceForward属性，可以间接的控制GLSL 着色器：</p>
<ol>
<li>flat：扁平化着色，不考虑光线的作用</li>
<li>faceForward：布尔值，控制光照效果</li>
</ol>
<div class="blog_h2"><span class="graybg">Geometry与Appearance的兼容性</span></div>
<p>需要注意，不是所有外观和所有几何图形可以搭配使用，例如EllipsoidSurfaceAppearance与WallGeometry就不能搭配，原因是后者是垂直于地表的。</p>
<p>即使外观与几何图形兼容，它们还必须有匹配的顶点格式（vertex formats）—— 即几何图形必须具有外观可以作为输入的数据格式，在创建Geometry时可以提供VertexFormat。</p>
<p>为了简便，可以让Geometry计算所有顶点属性（vertex attributes），以使之适用于任何外观，但这样做效率较差：</p>
<pre class="crayon-plain-tag">var geometry = new Cesium.RectangleGeometry( {
    vertexFormat : Cesium.VertexFormat.ALL
} );</pre>
<p> 而如果我们使用外观EllipsoidSurfaceAppearance，其实只需要知道位置：</p>
<pre class="crayon-plain-tag">var geometry = new Ceisum.RectangleGeometry( {
    vertexFormat : Ceisum.VertexFormat.POSITION_ONLY
} );</pre>
<p>大部分外观具有vertexFormat属性或者VERTEX_FORMAT 静态常量，创建形状时只需要使用这些顶点格式即可：</p>
<pre class="crayon-plain-tag">var geometry = new Ceisum.RectangleGeometry( {
    vertexFormat : Ceisum.EllipsoidSurfaceAppearance.VERTEX_FORMAT
} );

var geometry2 = new Ceisum.RectangleGeometry( {
    vertexFormat : Ceisum.PerInstanceColorAppearance.VERTEX_FORMAT
} );

var appearance = new Ceisum.MaterialAppearance();
var geometry3 = new Ceisum.RectangleGeometry( {
    vertexFormat : appearance.vertexFormat
} );</pre>
<p>此外，两个形状必须具有匹配的vertexFormat，才能被合并到一个Primitive中。</p>
<div class="blog_h1"><span class="graybg">3D模型</span></div>
<p>我们可以转换、加载并且在Cesium中使用3D模型。Cesium支持glTF（一个新兴的Web 3D模型工业标准）格式的3D模型，并且提供在线的 COLLADA - glTF转换工具。Cesium针对3D模型支持关键帧动画、皮肤、单独节点选取等特性。</p>
<p>Cesium自带了三个模型：飞机、车辆、人。下面的例子载入一个车辆模型：</p>
<pre class="crayon-plain-tag">var scene = viewer.scene;
//创建坐标
var coord = Cesium.Cartesian3.fromDegrees( -75.62898254394531, 40.02804946899414, 0.0 );
//创建一个东（X，红色）北（Y，绿色）上（Z，蓝色）的本地坐标系统
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame( coord );
// 改变3D模型的模型矩阵，可以用于移动物体
// 物体的世界坐标 = 物体的模型坐标 * 世界矩阵
var model = scene.primitives.add( Cesium.Model.fromGltf( {//异步的加载模型
    url : '../../SampleData/models/CesiumGround/Cesium_Ground.gltf',
    modelMatrix : modelMatrix, //模型矩阵
    scale : 200.0 //缩放
} ) );</pre>
<p>Cesium自带的3个模型已经内嵌了动画关键桢，如果需要播放动画，可以在调用Model.fromGltf后添加以下代码：</p>
<pre class="crayon-plain-tag">Cesium.when( model.readyPromise ).then( function( model )
{
    model.activeAnimations.addAll( {//播放模型中全部动画，如果需要播放单个动画，可以调用add，传入动画id
        loop : Cesium.ModelAnimationLoop.REPEAT, //直到被移出activeAnimations，一直播放
         speedup : 0.5,  //加速播放
         reverse : true  //逆序播放
    } );
} );</pre>
<p>动画与Cesium的时钟系统同步化。</p>
<p>与其它Primitive一样，对3D模型的选取也是被支持的，当前点击的glTF node id、glTF mess一并被获取：</p>
<pre class="crayon-plain-tag">var handler = new Cesium.ScreenSpaceEventHandler( scene.canvas );
handler.setInputAction( function( movement )
{
    var pick = scene.pick( movement.endPosition );
    if ( Cesium.defined( pick ) &amp;&amp; Cesium.defined( pick.node ) &amp;&amp; Cesium.defined( pick.mesh ) )
    {
        console.log( 'node: ' + pick.node.name + '. mesh: ' + pick.mesh.name );
    }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE );</pre>
<div class="blog_h1"><span class="graybg">栅格图层</span></div>
<p>Cesium支持多种标准化格式的GIS瓦片服务，可以把栅格图层绘制到地球的表面。这些图层的亮度、对比度、色相均可以动态调整：</p>
<pre class="crayon-plain-tag">//初始化一个查看器，并且提供一个栅格图层
var viewer = new Cesium.Viewer( 'cesiumContainer', {
    imageryProvider : new Cesium.ArcGisMapServerImageryProvider( {
        url : 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer'
    } ),
    baseLayerPicker : false
} );
//添加另外一个图层
var layers = viewer.scene.imageryLayers;
var blackMarble = layers.addImageryProvider( new Cesium.TileMapServiceImageryProvider( {
    url : '//cesiumjs.org/tilesets/imagery/blackmarble',
    maximumLevel : 8,
    credit : 'Black Marble imagery courtesy NASA Earth Observatory'
} ) );
//设置图层的透明度
blackMarble.alpha = 0.5;
//设置图层的亮度
blackMarble.brightness = 2.0;

//添加一个图层，在特定位置绘制一个图片
layers.addImageryProvider(new Cesium.SingleTileImageryProvider({
    url : '../images/Cesium_Logo_overlay.png',
    rectangle : Cesium.Rectangle.fromDegrees(-75.0, 28.0, -67.0, 29.75)
}));</pre>
<div class="blog_h1"><span class="graybg">3D地形图</span></div>
<p>Cesium支持3D地形图、水体特效，下面的代码添加该特性：</p>
<pre class="crayon-plain-tag">var terrainProvider = new Cesium.CesiumTerrainProvider( {
    url : '//assets.agi.com/stk-terrain/world'
} );
viewer.terrainProvider = terrainProvider;</pre>
<p>需要注意的是，地形图、栅格图层是分别处理的，默认的栅格图层覆盖在地形图的上面。任何栅格图层均可与地形图搭配使用。</p>
<p>下面的代码可以启用光照、水体效果：</p>
<pre class="crayon-plain-tag">var terrainProvider = new Cesium.CesiumTerrainProvider( {
    url : '//assets.agi.com/stk-terrain/world',
    requestVertexNormals : true
} );
viewer.terrainProvider = terrainProvider;
viewer.scene.globe.enableLighting = true;</pre>
<div class="blog_h1"><span class="graybg">镜头</span></div>
<p>Cesium提供了以下默认鼠标行为：</p>
<ol>
<li>单击并拖拽球体：旋转地球，镜头俯角不变</li>
<li>单击并拖拽空间：滚动roll、俯仰pitch镜头</li>
<li>右击并拖拽、中键滚动：缩放镜头</li>
<li>中键拖拽：沿着地表的点旋转镜头</li>
</ol>
<p>调用camera.setView()可以设置相机的位置和方向：</p>
<pre class="crayon-plain-tag">camera.setView( {
    positionCartographic : new Cesium.Cartographic( longitude, latitude, height ),
    heading : headingAngle,
    pitch : pitchAngle,
    roll : rollAngle
} );

//确保指定的东西南北范围进入视野
var west = Cesium.Math.toRadians( -77.0 );
var south = Cesium.Math.toRadians( 38.0 );
var east = Cesium.Math.toRadians( -72.0 );
var north = Cesium.Math.toRadians( 42.0 );
var extent = new Cesium.Extent( west, south, east, north );
camera.viewExtent( extent, Cesium.Ellipsoid.WGS84 );</pre>
<p>Camera</p>
<p>相机对象表示当前镜头的位置（position）、方向（orientation）、参考坐标系（reference frame）、视见体（View Frustum）。</p>
<p>move*、zoom*方法用于沿着镜头的原点（orientation ）或者一个给定的矢量来变换（translate）镜头的位置。移动过程中方向保持固定：<img class="aligncenter size-full wp-image-4826" src="https://blog.gmem.cc/wp-content/uploads/2015/03/direction-up-right.png" alt="direction-up-right" width="497" height="442" /></p>
<p>look*、twist*方法用于依照direction、up、right向量来旋转方向，旋转过程中位置保持不变：<img class="aligncenter size-full wp-image-4827" src="https://blog.gmem.cc/wp-content/uploads/2015/03/look-twist.png" alt="look-twist" width="612" height="177" /></p>
<p>rotate*方法用于依据给定的矢量来变换位置、旋转方向。</p>
<div class="blog_h2"> </div>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/cesium-study-note">Cesium学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/cesium-study-note/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
	</channel>
</rss>
