第7章 3D模型的加载与使用

1、学会常见的3D模型格式,vtk,stl,了解他们的顶点,面,法线,纹理及纹理坐标的表示方法。

2、学会怎么加载3D模型,并显示模型,最主要的是学习模型在计算机中的表示。

3、学习three.js中的geometry的faces和vertices数组。

1、感谢词

亲爱的朋友:

您们好。感谢您阅读WebGL中文网的初级课程,学习到本课,我们假设您已经学习完了前面的课程,如果您跳过了前面的课程,那么我建议你,静下心来,重新学习前面的课程。只有扎扎实实的学习,才能打好基础,在未来有质的飞跃。

本课在不久前还属于中级课程的第一课,属于收费课程。鉴于许多非VIP学员对3D模型有浓烈的学习兴趣,所以,我们经过考虑,将这一节课转换为免费课程。

感谢大家的阅读,也感谢大家尊重我们的劳动成果。源码请到第一课去下载。

2、开篇

经常有网友在QQ群里面问,three.js如何加载模型。有的人是为了做游戏需要加载一些人物模型,有的人是为了毕业设计(模拟一些物理运动,周围需要一些物体做陪衬),无论基于什么样的需求,他们都想在自己的应用中,加载3D模型。有了3D模型才能让他们的程序更有创造力。

下面就有一个网友在咨询怎么加载模型:

估计有朋友有类似的疑惑,那么这一课,我们就来仔细的讲一讲,关于模型的加载。这是一个有趣的事情,那现在我们开始吧。

3、关于模型的基础知识

既然在讲3D世界,那么我们这里提到的模型就是3D模型。我不想将一些书上的定义摘抄给大家,那些概念对大家的理解帮助并不大,而接下来,我会将我最直接通俗的理解告诉大家。

我们知道,在3DMAX,MAYA等软件(这是一些三维编辑软件)中,可以制作出3D模型。这些模型可用于室内设计,三维影视,三维游戏等领域。那么3D模型是怎么定义的呢?看看下面我的定义:

3D模型由顶点(vertex)组成,顶点之间连成三角形或四边形(在一个平面上),多个三角形或者四边形就能够组成复杂的立体模型.

如下图所示:

上图就是一辆汽车的3D模型(立体模型),因为是由一个个网格组成,所以,我们也叫其为网格模型。

网格模型很像我们小时候学习的素描,想想我们画素描是不是先描点,然后画线,由线组成面,最后由面组成现实生活中的物体呢?

在这里,概念总是那么难以理解,我们不做深抠概念的学究,我们需要感性的去认识3D模型。

首先,我们来欣赏一些3D模型,这里收集了的一些3D模型,当然其中一些因为加上了纹理,非常诱人。

除此之外,你可以在google的3D模型库找到更多的模型,网址是:http://sketchup.google.com/3dwarehouse/(这个网站可能被墙,无法访问,使用代理吧),这是一个很有用的网站,请大家惠存。

4、怎么看模型?模型查看器

有了3D模型,我们怎么去看它。我们在编写程序之前,总是希望先看到一些实际的场景,这样,当我们写起程序来,才会有一些感觉,依葫芦画瓢的感觉。所以,这里我们先教大家怎么观察3D模型。

你可以下载一个3D-Max,或者Maya之类的软件,然后安装上它。但是他们太重量级,动辄就是几个G,不太实用,我们也没有耐心去安装这样一个巨大的程序,而且这些程序过于专业,您并不一定能很快使用它。

所以,在工程领域,我们一般使用一些轻量级的查看器,如Blender、ParaView。这些都是一个几十M的软件,且功能足够可用。大家可以到网上下载一个ParaView,各大网站均有下载。

ParaView是一个模型查看器,打开软件,会看到如下的界面,当然这只兔子是我们打开一个模型文件后的结果。

例如,我们要打开一个vtk模型文件。你可以在three.js的源码包中找到一个bunny.vtk的文件(在源代码中搜索一下这个文件),也可以在我们提供的代码中找到【初级教程\chapter7A\models\vtk】,然后使用ParaView的File菜单打开,就会在Pipeline Browser中看到一个bunny.vtk的文件,选中它,然后点Apply,这样就可以再右边看到一个兔子模型了。

ParaView的使用不是本章重点,这里就不累述了。你可以在这里看到ParaView的使用,地址如下:http://wenku.baidu.com/view/f360cc7102768e9951e7386a.html

简单的使用一下,你就知道怎么观察模型了,并且你也应该学会像保存图片一样,将一种3D模型的格式转换为另一种3D模型的格式,如obj、stl等。

5、模型在three.js中的表示

我们的最终目的是要讲解怎么将模型导入three.js中,让three.js能够显示我们的模型。

模型是由面组成,面分为三角形和四边形面。三角形和四边形面组成了网格模型。在Three.js中用THREE.Mesh来表示网格模型。THREE.Mesh可以和THREE.Line相提并论,区别是THREE.Line表示的是线条。THREE.Mesh表示面的集合。

Ok,让我们来认识一下THREE.Mesh,它的构造函数是:

THREE.Mesh = function ( geometry, material )

其中第一个参数geometry:是一个THREE.Geometry类型的对象,他是一个包含顶点和顶点之间连接关系的对象。

第二个参数Material:就是定义的材质。有了材质就能够让模型更好看,材质会影响光照、纹理对Mesh的作用效果。

6、模型的加载

在three.js中,模型是怎么加载到浏览器中的呢?

为了让大家更明白原理,我们首先来看看three.js加载一个简单模型的过程。这个过程是这样的:

上图的顺序是:

1、服务器上的模型文件以文本的方式存储,除了以three.js自定义的文本方式存储之外,当然也可以以二进制的方式存储,不过这里暂时不讲。

2、浏览器下载文件到本地

3、Javascript解析模型文件,生成Mesh网格模型

4、显示在场景中。

对照上面这幅图,我们对需要注意的几点重点说明一下:

1、服务器上的模型文件大多是存储的模型的顶点信息,这些信息可以以文本的方式存储的(并不一定需要用文本的方式存储)。Three.js支持很多种3D模型格式,例如ply,stl,obj,vtk等等。随着three.js的升级,会支持越来越多的文件格式,到目前为止,three.js已经能够支持市面上大多数3D模型格式了。

同时需要重点说明的是,如果认真理解完three.js对模型的加载、解析方法,那么写一种自己的3D文件解析器是非常便利的。

2、第二步是浏览器下载文本文件,这是一件很普通的事情,只需要使用javascript的异步请求就可以实现了。

3、Javascript解析文本文件并生成一个geometry,最终生成Mesh,也是一件简单的事情。我们会在后面介绍这个过程。

4、当产生Mesh后,将其加入到场景中,那就非常简单了。

Ok,整个模型的加载思路就是这样。

7、一个简单的实例

本课,我们先来加载一个简单的模型,这个模型是一只兔子,如下图所示,你能在代码清单【初级教程\chapter7A】中找到这课的源码。建议你先运行一下,如果因为交叉域访问错误,请按照扩展阅读的第二课设置一下浏览器,或者搭建一个Web服务器,将源码放到Web服务器中。

这个兔子的模型文件是【初级教程\chapter7A\models\vtk\bunny.vtk】这个文件,他是一个后缀名为vtk的文件,表示这是vtk格式。可能很多童鞋都不知道什么是vtk,那么这里给大家补一下vtk文件模式吧。

至于为什么选择vtk,因为vtk是一种简单的3D文件格式,而且是以文本的方式存储的,所以理解起来非常方便。你可以打开刚才那个vtk文件,看一下是不是很简单。

8、VTK文件格式

什么是vtk文件?

Vtk模型是一种以文本方式表示的3D模型文件,其能够表示点面信息,而且能够以人类易读易懂的方式以文本的形式存储下来。在科学研究中,这种文件格式使用得非常多,我们这里详细的讲解一下,这种文件格式。

vtk是3D模型的一种格式,现在版本已经到4.0了。你可以在网上找到这种格式的详细解释,当然最好去google搜索。

打开bunny.vtk文件,我们以它来解释vtk文件的格式,其中#是注释的开始

# 这里表示使用的是vtk的3.0版本。虽然4.0版本已经出来了,不过目前广泛使用的仍然是3.0
# vtk DataFile Version 3.0
# 这一行是输出vtk文件的软件写的文字,无论什么都可以。
vtk output
# ASCII,表示这份vtk使用的标准ASCII码字符集
ASCII
# “DATASET POLYDATA”表示多边形面集,面是由一个个点组成的
DATASET POLYDATA
# 这里表示这个模型由35947个点组成,每个坐标的分量是一个浮点型
POINTS 35947 float
# 下面是35947个点的数据
-0.0378297 0.12794 0.00447467 -0.0447794 0.128887 0.00190497 -0.0680095 0.151244 0.0371953 
-0.00228741 0.13015 0.0232201 -0.0226054 0.126675 0.00715587 -0.0251078 0.125921 0.00624226 
-0.0371209 0.127449 0.0017956 0.033213 0.112692 0.0276861 0.0380425 0.109755 0.0161689 
-0.0255083 0.112568 0.0366767 -0.0245306 0.112636 0.0373469 0.0274031 0.12156 0.0212208 
-0.0628961 0.158419 -0.0175871 0.0400813 0.104202 0.0221684 0.0451532 0.0931968 0.0111604 
..........................................
..........................................
POLYGONS 69451 277804
3 21216 21215 20399 
3 9186 9280 14838 
3 16020 13433 5187 
3 16021 16020 5187 
3 20919 20920 21003 
3 23418 15239 23127 
3 30553 27378 30502 
..................
....................
CELL_DATA 69451
POINT_DATA 35947

你可以在 http://wenku.baidu.com/view/a67cdad049649b6648d747fc.htm 这个网站找到vtk 4.0版本的解释。下面对几个重要的段落进行一下解释。

1、# vtk DataFile Version 3.0表示这个vtk文件的版本是3.0。最新版本是4.0,不过改变不大。

2、vtk output表示该文件是名字,一般写成vtk output就可以了,基本上,你没有必要去改变它。

3、ASCII表示该文件的格式,是ascii版本,该位置也可以写binary,那么这个文件就是二进制格式的了。

4、DATASET POLYDATA中的DATASET是关键字表示数据集的意思,POLYDATA表示数据的类型,可以取STRUCTED_POINTS、STRUCTURED_GRID、UNSTRUCTURED_GRID、POLYDATA、FIELD等。这里取的是POLYDATA,表示三角形或者四边形数据。

5、POINTS 35947 float 表示这个模型由35947个点组成,每个点的分量,其数据类型是浮点型。这一行后面就是35947*3个float型数字了。每三个数字表示一个点。

6、POLYGONS 69451 277804,POLYGONS是关键字,69451表示模型有69451个多边形组成,后面行的3 21216 21215 20399中的3表示每个多边形由三个顶点组成,如果等于4,那么每个多边形有4个顶点组成。277804表示整个POLYGONS占据的数组的长度,长度计算公式是69451*4 = 277804,乘数4是3 21216 21215 20399这组元素的长度(一共有4个元素),也就每一行元素的个数,这主要是用来计算存储空间的。

7、接下来后面是69451行数据,每一行是一个多边形面。每个面由3个顶点组成,如3 21216 21215 20399这一行,后面的21216 21215 20399这三个数字,表示在上面的POINTS 35947 float段的顶点索引。

8、CELL_DATA 69451 表示面的个数,和上面定义的面数目必须一致。

9、POINT_DATA 35947表示点的个数,和“POINTS 35947 float”定义的也必须相同。 Ok,vtk格式就这么多精髓了,虽然有些关键字这里我们并没有讲到,但是已经足够了,在以后遇到如果不明白,可以质询我们,也可以查查网上的文档,用一下google。

了解了VTK模型的格式,我们下面来写一个解析器,解析这个模型。

9、顶点和面索引之间的关系

加载vtk模型,主要分为2步:

1、将vtk文件中的点,转换为geometry的vertices数组中。

2、将vtk文件中每个点的索引,转换到geometry的faces中。

1、点和索引之间的关系

上面提到点和索引,如果对点和索引的关系不太理解,那么后面学习起来将非常痛苦,所以,我们这里先简单的讲一讲点和索引之间的密切关系。

我们先出一道题目,大家准备纸和笔了,如果你没有动手,在纸和笔上画出来,没有理解,就不好怪我了。

题目:6个点,要求每个三角形不重用点,那么最多可以组成多少个三角形?

好了,画出来了吗?是不是只能画出2个来。Ok,这是点不能重复的情况。在点不能重复的情况下,画2个三角形,需要6个点。

如果在点能够重复的情况下,6个点可以画多少三角形呢?

这个数学题,我们就不解释了,但是只要你在纸上认真的画一下,你就会发现,在允许点重复的情况下,6个点能画出远远超过2个三角形。

结论:同样的数目的点,如果允许重用,那么能够画出更多的三角形。能够画出更多三角形,是不是就能形成更复杂的几何体呢?

再反过来想一想,同样数目的点,是不是占用的存储空间一样呢?同样个数的三角形,是不是重复利用点的三角形,会比不重复利用点的三角形占用的存储空间多呢?这里主要指的内存空间。

讲到这里,我们为大家揭穿最后一点秘密,索引就是为了重复利用顶点而诞生的。索引数组中存放的整形数,每三个整形数,就能够决定一个三角形。

索引中存放的整形数表示顶点在geometry.vertices数组中的位置。例如,geometry的faces的第0,1,2个元素分别是6,100,62,那么表示取geometry.vertices中第6,100,62个点,组成一个三角形。是不是恍然大悟了。Ok,画一下geometry.vertices和geometry.faces这两个数据结构,并将里面的关系画出来,你就明白了。

10、vtk文件的加载

我们本课要加载一只兔子模型,这只兔子使用的是vtk模型,大家可以打开这个文件看一下。

Vtk的加载需要使用VTKLoader.js这个文件,你可以在three.js-master\examples\js\loaders文件夹或者在【初级教程\chapter7A\js\loaders】中找到。由于源码不太长,我们将其列在这里:

// 构造函数
THREE.VTKLoader = function () {
    THREE.EventDispatcher.call( this );	// 继承自监听器,使这个类有监听的功能
};

// VTKLoader的原型函数,里面包含了VTKloader的成员函数,成员变量的定义
THREE.VTKLoader.prototype = {
	// 构造函数
    constructor: THREE.VTKLoader,
	// 加载函数,url表示要加载的vtk文件的url路径,callback表示加载完成后要调用的后续处理函数,这里是异步操作,加载需要一个过程,不能将程序阻塞在这里,所以需要异步回调
    load: function ( url, callback ) {
// 将类自身保存在scope中,scope表示域的意思,这里是为了避免this的歧义,因为,每一个地方使用this,其意义不一样。
        var scope = this;
// ajax 异步请求
        var request = new XMLHttpRequest();
// 加载完成的监听器,加载完成后,将调用第二个参数定义的回调函数
        request.addEventListener( 'load', function ( event ) {
			// 对服务器加载下来的数据进行解析,后面详细解释
            var geometry = scope.parse( event.target.responseText );
// 解析完成后,发一个load事件,表示数据解析完成
            scope.dispatchEvent( { type: 'load', content: geometry } );
// 如果设置了回调函数,那么调用回调函数
            if ( callback ) callback( geometry );
        }, false );
// 加载过程中,向自身发送进度progress信息,信息中包含已经加载的数据的字节数和文件总共的字节数,通过两者的百分比能够了解加载进度。
        request.addEventListener( 'progress', function ( event ) {
// 发送正在加载的消息,两个参数分别是已经加载了多少字节,总共多少字节
            scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
        }, false );
// 加载出错的监听器,加载的过程也可能出错,这里如果出错,进行错误处理,
        request.addEventListener( 'error', function () {
// 加载出错之后需要发布的错误消息,
            scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );

        }, false );
// 初始化 HTTP 请求参数,例如 URL 和 HTTP 方法,但是并不发送请求。
        request.open( 'GET', url, true );
//发送 HTTP 请求,开始下载
        request.send( null );
},

// parse函数在上面调用过,主要负责解析数据的功能,我们将在后面详细介绍解析函数,这里就不介绍了。
    parse: function ( data ) {
        var geometry = new THREE.Geometry();
        function vertex( x, y, z ) {
            geometry.vertices.push( new THREE.Vector3( x, y, z ) );
        }

        function face3( a, b, c ) {
            geometry.faces.push( new THREE.Face3( a, b, c ) );
        }

        function face4( a, b, c, d ) {
            geometry.faces.push( new THREE.Face4( a, b, c, d ) );
        }

        var pattern, result;

        // float float float

        pattern = /([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["1.0 2.0 3.0", "1.0", "2.0", "3.0"]

            vertex( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );

        }

        // 3 int int int

        pattern = /3[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["3 1 2 3", "1", "2", "3"]

            face3( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ) );

        }

        // 4 int int int int

        pattern = /4[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {

            // ["4 1 2 3 4", "1", "2", "3", "4"]

            face4( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ), parseInt( result[ 4 ] ) );

        }

        geometry.computeCentroids();
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        geometry.computeBoundingSphere();

        return geometry;

    }

}

在注释中,我们解释了大部分代码,请仔细阅读。在上面的代码中,以下的部分代码需要注意:

request.addEventListener( 'load', function ( event ) {

            var geometry = scope.parse( event.target.responseText );

            scope.dispatchEvent( { type: 'load', content: geometry } );

            if ( callback ) callback( geometry );

        }, false );

这里event.target.responseText是服务器返回的文本数据,也就是vtk文件里的所有数据,我们通过scope.parse方法将其转换为geometry。

转换完后,我们会通过dispathEvent向自身发送一个加载完成的消息,消息中返回了geometry几何体。这个几何体是可以和Mesh合体,最终显示在场景中的。

最后,如果callback不为null的话,那么我们就调用这个回调函数。在这个回调函数中,会做一些模型加载完成后,应该做的事情,例如,将模型放到某一个位置。

接下来的重点就是parse函数如何实现的了。

11、伟大的解析函数parse

上一节,我们讲了THREE.VTKLoader这个类,那么接着我们将parse给补充一下,parse函数主要完成了从vtk到geometry的转换。我们将代码复制下来重新解释一下:

// data是从服务器传过来的数据,其实就是vtk文件中的文本数据 ,打开平【初级教程\chapter7A\models\vtk\bunny.vtk】看一下,你就知道是什么了?一定打开这个文件哦。
parse: function ( data ) {
		// new 一个几何体
        var geometry = new THREE.Geometry();
// 定义一个内部函数vertex,用参数x,y,z生成一个顶点,并放入geometry的vertices数组中,
        function vertex( x, y, z ) {
            geometry.vertices.push( new THREE.Vector3( x, y, z ) );
        }

// 定义一个面索引函数face3,将面的3个点的索引放入geometry的faces数组中。
        function face3( a, b, c ) {
            geometry.faces.push( new THREE.Face3( a, b, c ) );
        }
// 定义一个面索引函数。如果一个面由4个顶点组成,那么我们构造一个Face4放入faces中,注意面可以由在同一平面的3个点组成,也可以由同一平面的4个顶点组成。
        function face4( a, b, c, d ) {
            geometry.faces.push( new THREE.Face4( a, b, c, d ) );
        }
// pattern存放模式字符串,result是临时变量
        var pattern, result;

        // float float float
// pattern是一个正则表达式,能够匹配3个空格隔开的float,如-0.0378297 0.12794 0.00447467都是pattern的菜。对正则表达式不了解,请一定补习一下哦。
        pattern = /([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)[ ]+([\+|\-]?[\d]+[\.][\d|\-|e]+)/g;
// exec是正则表达式的执行匹配函数,result返回一个包含3个字符串的数组,如果data读到了最后,那么result将返回null
// while 循环在data中,寻找符合正则表示式的数据,将符合条件的数据,转换为一个顶点
        while ( ( result = pattern.exec( data ) ) != null ) {
            // ["1.0 2.0 3.0", "1.0", "2.0", "3.0"]
// 将字符串转换为float,并放入geometry中
            vertex( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
        }

        // 3 int int int
// 这里匹配面数据,如3 21216 21215 20399,这类数据是面索引数据
        pattern = /3[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;
// 取出data中的所有面索引数据,
        while ( ( result = pattern.exec( data ) ) != null ) {
            // ["3 1 2 3", "1", "2", "3"]
// 将面数据放入geometry的faces中
            face3( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ) );

        }

        // 4 int int int int
// 这里是4个顶点一个面的情况,本例的vtk文件,没有这种情况
        pattern = /4[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)[ ]+([\d]+)/g;

        while ( ( result = pattern.exec( data ) ) != null ) {
            // ["4 1 2 3 4", "1", "2", "3", "4"]
            face4( parseInt( result[ 1 ] ), parseInt( result[ 2 ] ), parseInt( result[ 3 ] ), parseInt( result[ 4 ] ) );

        }

// 这里的4个函数,在后面解释
        geometry.computeCentroids();
        geometry.computeFaceNormals();
        geometry.computeVertexNormals();
        geometry.computeBoundingSphere();

        return geometry;

    }


结合源代码和注释,我们能容易的理解上面的代码。这个pasrse函数主要用到了正则表达式,有一本书书名叫《程序员的6种武器》,其中就有一种是正则表达式。所以,如果不明白的可以问我,或者自己买本书看看。记住,当你年轻时,投资教育是最合算的。进入社会后,你就没那么多时间来投资教育了,现在开始换学习吧。

除了最后的4行,我们没有解释之外,其他的我们都清楚的解释了。下面来看看最后的4个函数:

1、geometry.computeCentroids()

这个函数是算geometry中每一个面的重心,无论在平面坐标系还是空间坐标系中,重心可以求坐标的平均值来得到,如A点(X1,Y1,Z1),B点(X2,Y2,Z2)和C点(X3,Y3,Z3),他们形成的三角面的中心是:

重心的横坐标:(X1+X2+X3)/3

重心的纵坐标:(Y1+Y2+Y3)/3

重心的竖坐标:(z1+z2+z3)/3

computeCentroids函数内部就这样处理的,重心计算出来了,被存储在face.centroid这个变量中。

computeCentroids的函数代码如下:


computeCentroids: function () {
	var f, fl, face;
	for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
		face = this.faces[ f ];
		face.centroid.set( 0, 0, 0 );

		face.centroid.add( this.vertices[ face.a ] );
		face.centroid.add( this.vertices[ face.b ] );
		face.centroid.add( this.vertices[ face.c ] );
		face.centroid.divideScalar( 3 );
	}
}


从以上的代码可以看出,每一个面都有一个重心。

2、geometry. computeFaceNormals ()

这个函数用来计算每一个面归一化后的法向量,法向量垂直于面。计算之后的法向量被存放在了face.normal中。法向量与模型受光情况有关,目前,我们还没有对关照原理进行详细讲解,这里先略过。

3、geometry. computeVertexNormals ()

这个函数计算每一个顶点的法向量。

4、geometry. computeBoundingSphere ()

这个函数这个计算一个可以包围geometry的一个椭圆,中心点就是geometry的中心,sphere的最大半径是离中心点最远的那一个点。

这4个函数的计算结果,在threejs引擎中,会用到,所以,你自己写加载某种模型的解析器时,最好也调用一下,这几个函数。

至此,关于parse函数,我们已经清晰的讲完了,parse函数完整的返回了一个geometry对象。你可以在Mesh中使用了。

12、完整的加载兔子vtk模型

最后,讲一个实例。和前面课程的程序大同小异,这里的不同是我们需要从服务器加载一些模型数据,然后转换为Mesh网格模型,最后显示在场景中。同样,按照以前的讲课思路,我们先看看本科最终的效果图,你可以打开本课的代码看看效果,下面是效果图:

这是一只可爱的兔子,在图形学的很多硕士论文中都会出现这只可爱的兔子。我师兄当年就是研究的将这只兔子签名,三维加密,是一个很不错的课题。有兴趣的童鞋可以研究一下。

Ok,接下来,我们展示一下全部的代码,代码不是很多,请不要害怕。


if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
	var container, stats;
	var camera, controls, scene, renderer;
	var cross;
	init();
	animate();

	function init() {
		camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 1e10 );
		camera.position.z = 0.2;
		controls = new THREE.TrackballControls( camera );

		controls.rotateSpeed = 5.0;
		controls.zoomSpeed = 5;
		controls.panSpeed = 2;

		controls.noZoom = false;
		controls.noPan = false;

		controls.staticMoving = true;
		controls.dynamicDampingFactor = 0.3;

		scene = new THREE.Scene();
		scene.add( camera );

		// light
		var dirLight = new THREE.DirectionalLight( 0xffffff );
		dirLight.position.set( 200, 200, 1000 ).normalize();

		camera.add( dirLight );
		camera.add( dirLight.target );
// A begin
		var material = new THREE.MeshLambertMaterial( { color:0xffffff, side: THREE.DoubleSide } );
		var loader = new THREE.VTKLoader();
		loader.addEventListener( 'load', function ( event ) {
			var geometry = event.content;
			var mesh = new THREE.Mesh( geometry, material );
			mesh.position.setY( - 0.09 );
			scene.add( mesh );
		} );
		loader.load( "models/vtk/bunny.vtk" );
// A end
		// renderer
		renderer = new THREE.WebGLRenderer( { antialias: false } );
		renderer.setClearColorHex( 0x000000, 1 );
		renderer.setSize( window.innerWidth, window.innerHeight );
		container = document.createElement( 'div' );
		document.body.appendChild( container );
		container.appendChild( renderer.domElement );

		stats = new Stats();
		stats.domElement.style.position = 'absolute';
		stats.domElement.style.top = '0px';
		container.appendChild( stats.domElement );
		//
		window.addEventListener( 'resize', onWindowResize, false );
	}

	function onWindowResize() {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		renderer.setSize( window.innerWidth, window.innerHeight );
		controls.handleResize();
	}

	function animate() {
		requestAnimationFrame( animate );
		controls.update();
		renderer.render( scene, camera );
		stats.update();
	}


请大家仔细阅读一下代码,我想大多数的代码大家都能明白意思。本节课的重点集中在A bengin和A end之处。

A begin和A end包含的代码,实现了vtkload加载vtk模型的功能。在前面,我们已经仔细讲解过了,这里相信大家都能够很容易明白了。

如有不明,请与我们联系,谢谢大家。

最后,在强调一下模型加载完成后的回调函数,如下:

模型加载完成后,通过Mesh函数生成一个Mesh,并设置位置,最终显示在scene中。现在,你可以移动鼠标,全方位的看一下模型了。

13、小结

感谢大家的阅读,本课主要讲解了vtk模型的解析,vtk是最简单的模型之一,第一,他是一个文本模式的模型,容器被人类阅读,第二,VTKLoader只对点和索引进行了解析,生成了Geometry,vtk模型并不包括纹理,光照,所以非常简单,在后面的课程中,我们将学习更多的模型,希望大家努力学习,在不久的将来,能够对任何一种三维模型进行解析。感谢大家.

再见。中高级课程更加精彩,希望能让大家学到很多很多知识。

给WebGL中文网团队的女程序员"小果妹妹"发一个鸡腿吧,微信扫一扫赞赏,感谢。

亲爱的读者,如果你觉得WebGL中文网的课程不错,您可以购买《WebGL中文网视频课程》 课程支持我们哦,购买后记得给我们好评哦!我们强烈建议您不要在iphone上的网易云课堂软件中购买,这样苹果会收取31%左右的服务费,虽然这是明码标价,我们也表示认可和理解,具体选择权在您自己了。

感谢大家的支持,下面是课程的截图之一

[1楼] love** 2016-06-03 23:32

如果打开本课的代码为黑屏,那么请看《扩展阅读第二章》

如果使用的IIS服务器,那么请配置你的服务器支持下载相应的文件,为了服务器安全,默认情况下,服务器只能下载css,html,js之类的文件。简单的方法是将模型的扩展名变为js

使用其他服务器类似。

[2楼] 1549** 2016-08-02 10:52

根据例子出现list:320 Uncaught TypeError: loader.addEventListener is not a function 错误

loader.addEventListener("load", function(event) {

在下愚笨,没有找到改方法,那么在哪里找呢

WebGL中文网老师回答:

版本变化

[3楼] 1549** 2016-08-02 12:44

TOMCAT
依然黑屏,不报错了,

直接贴的阁下的代码

WebGL中文网老师回答:

应该是可以的,请再尝试一下。

[4楼] Haib** 2016-08-19 15:50

代码跟你一样的会出现这种情况是怎么回事.  TypeError: loader.addEventListener is not a function   直接用你的工程可以运行

[5楼] lanz** 2016-10-26 17:00



运行代码报Uncaught ReferenceError: Detector is not defined(…)的错是为什么

[6楼] 怎地炒了** 2016-11-24 18:13

用webstorm或者hBuilder打开吧

[7楼] anjo** 2016-11-30 15:39

文章都太旧了,不更新么。。

官方提供最新的VTKLoader 和这不一样了已经

[8楼] wl11** 2017-07-24 15:04

老师,你好,要是引入obj格式模型,除了引入文档中ObjectLoader.js,还需要引入其他文件吗?为什么我这会出现THREE.ObjectLoader is not a constructor这样的错误呢?

[9楼] Z。** 2017-08-08 16:33

老师,你好,我按着你的代码只把bunny.vtk换成我自己的另一个.vtk文件,报了这个错Uncaught TypeError: THREE.FileLoader is not a constructor  希望老师帮忙解答一下,谢谢

[10楼] Z。** 2017-08-08 16:42

老师,你好,不好意思上条粘错了,我按着你的代码只把bunny.vtk换成我自己的另一个.vtk文件,报的这个错Uncaught TypeError: Cannot read property 'x' of undefined  希望老师帮忙解答一下,谢谢

[11楼] lirc** 2018-04-09 17:31

如果无法加载模型可以使用EDGE浏览器打开,opera应该也可以不过没试过

[12楼] 杯莫停** 2018-05-03 15:39

老师您好,我想问下为什么我加载兔子的模型程序,一直是黑屏?

[13楼] 杯莫停** 2018-05-03 15:43

老师您好,我想问下为什么我加载兔子的模型程序,一直是黑屏?

[14楼] jerr** 2018-08-03 16:52

同样报错:loader.addEventListener is not a function

请老师解答

WebGL中文网老师回答:

注意three.js的版本,一切is not a function都是版本问题。

[15楼] 前端菜鸟** 2018-12-26 18:24

老师你好,我按照您这边的代码,已经成功在tomcat上运行了代码。但是,根据您提供的3D模型下载网址,我下载了几个模型,但是都是.skp的格式,即使放到tomcat上也不能运行,您这边是怎样操作的呢?google的3D模型库:http://sketchup.google.com/3dwarehouse/,下载的模型无法使用

[16楼] nang** 2019-02-19 11:15

var camera, scene, renderer, mesh, geometry, material; var w = window.innerWidth; var h = window.innerHeight; (function () { renderer = new THREE.WebGLRenderer(); renderer.setSize(w, h); document.body.appendChild(renderer.domElement); camera = new THREE.PerspectiveCamera(70, w / h, 0.01, 1e10) camera.position.z = 10; scene = new THREE.Scene(); scene.add(camera) var dirLight = new THREE.DirectionalLight(0xffffff); dirLight.position.set(200,200,1000).normalize(); camera.add(dirLight); camera.add(dirLight.target); material = new THREE.MeshLambertMaterial({ color : 0xffffff, side : THREE.DoubleSide }) var loader = new THREE.VTKLoader(); loader.load("../bunny.vtk", function (geometry) { console.log(geometry); geometry.computeVertexNormals(); var mesh = new THREE.Mesh(geometry, material) mesh.position.setY(-0.09) scene.add(mesh) }); function render() { requestAnimationFrame(render) renderer.render(scene, camera) } render(); })()

[17楼] nang** 2019-02-19 11:16

老师闲暇时间请指教一下我的为什么黑屏显示不出来,谢谢。

加载回调已经显示出来了geometry 

打印结果

indexUint16BufferAttribute {name""array: Uint16Array(208353), itemSize1count208353normalizedfalse

[18楼] zhan** 2019-03-07 14:11

老师 请问加载FBX格式的模型  案例哪里有啊?

[19楼] favo** 2019-04-29 16:20

灯光应该加到场景中,为什么加到camera上?

[20楼] yuu2** 2019-06-13 23:18

2019了 直接用node搭服务器吧  或者vscode里面装一个live server

[21楼] chen** 2019-06-27 16:00

你好,代码 chapter7A,   <script src="js/stats.js"></script>   =>>    <script src="js/Stats.js"></script>

[22楼] liyu** 2019-08-25 12:58

回19楼:我试了下把灯光加在场景中,加在场景中调整视角后会看到阴影;加在相机上不会——我猜测加在场景中方向灯光不会变化,就是一个固定的方向,调整方向到背光角度就会看到阴影;但是加到相机上灯光的方向可能是相对相机的方向,调整视角灯光方向也随之变化,所以不会看到阴影

[23楼] bug5** 2019-09-24 12:15

老师现在报,TrackballControls不是构造函数的错误
vtkTest.html?_ijt=vq4se6elabvln1f2r4lki1sbq0:27 Uncaught TypeError: THREE.TrackballControls is not a constructor

[24楼] cgcg** 2019-10-18 17:59

老师您好,怎么在模型上添加可以点击的标记? 我做了一个地球仪的3D效果,想在上面添加标记,点击以后弹出对应的内容

[25楼] tuli** 2019-11-15 22:55

为什么把光加到相机上? 

为什么scene.add( camera );,难道不是 把scene和camera放到reenderer里渲染吗?

搞不清楚老师写的代码啥意思,,怎么没有任何解释。

[26楼] elyo** 2019-12-19 16:53

关于loader.addEventListener is not a function这个问题的产生原因是版本问题,解决方法是写成这样:

var loader = new THREE.VTKLoader();
loader.load( url,  function ( result ) {
      console.log( result );
})

[27楼] shan** 2020-02-14 11:49

楼上所有的人

关于loader.addEventListener isnot a function这个问题的产生原因

原因如下:一般来说动手的同学会容易出这个问题,原因是,在这个章节里,老师修改了three.js的版本。

之前的版本是没有写版本号的未知版本。

但是到了这个章节,使用的是version56版本

换成这个版本,问题就解决了。

版本再教程例子给的程序里有,所以说就会出现问题。

[28楼] Hicc** 2020-10-08 13:24

新版的dispatch真的把我搞崩溃了!!!!!网上也找不到解决方案啊啊啊啊!一直跳错!!!!诶,,,最后直接改three.js版本了。。。

提问或评论

登陆后才可留言或提问哦:) 登陆 | 注册 登陆后请返回本课提问
用户名
密   码
验证码