object::mesh
该构造函数构造了一个空间中的物体。之所以叫“网格”是因为,实际上具有体积的物体基本都是建模成为“网格”的。
复制代码 代码如下:
three.mesh = function ( geometry, material ) {
three.object3d.call( this );
this.geometry = geometry;
this.material = ( material !== undefined ) ? material : new three.meshbasicmaterial( { color: math.random() * 0xffffff, wireframe: true } );
/* 一些其他的与本节无关的内容 */
}
实际上,mesh类只有两个属性,表示几何形体的geometry对象和表示材质的material对象。geometry对象在上一篇博文中已经介绍过,还有部分派生类会在这篇博文中介绍(通过这些派生类的构造过程,能更加清晰地了解到mesh对象的工作原理);matrial对象及其派生类也将在这篇笔记中介绍。mesh对象的这两个属性相互紧密关联,geometry对象中的face数组中,每个face对象的materialindex用来匹配material属性对象,face对象的vertexuvs数组用以依次匹配每个顶点在数组上的取值。值得注意的是,mesh只能有一个material对象(不知这样设计的意图何在),如果需要用到多个材质,应当将材质按照materialindex顺序初始化在geometry本身的materials属性中。
geometry::cubegeometry
该构造函数创建了一个立方体对象。
复制代码 代码如下:
three.cubegeometry = function ( width, height, depth, widthsegments, heightsegments, depthsegments ) {
three.geometry.call( this );
var scope = this;
this.width = width;
this.height = height;
this.depth = depth;
var width_half = this.width / 2;
var height_half = this.height / 2;
var depth_half = this.depth / 2;
/* 略去 */
buildplane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px
/* 略去 */
function buildplane( u, v, udir, vdir, width, height, depth, materialindex ) {
/* 略去 */
}
this.computecentroids();
this.mergevertices();
};
构造函数做的最重要的事在buildplane中。该函数最重要的事情就是对scope的操作(上面的代码块中,scope就是this),包括:调用scope.vertices.push(vector)将顶点加入geometry对象;调用scope.faces.push(face)将表面加入到geometry对象,调用scope.facevertexuvs[i].push(uv)方法将对应于顶点的材质坐标加入geometry对象。代码的大部分都是关于生成立方体的逻辑,这些逻辑很容易理解,也很容易扩展到其他类型的geometry对象。
构造函数的参数包括长、宽、高和三个方向的分段数。所谓分段,就是说如果将widthseqments等三个参数都设定为2的话,那么每个面将被表现成2×2=4个面,整个立方体由24个表面组成,正如同网格一样。
复制代码 代码如下:
function buildplane( u, v, udir, vdir, width, height, depth, materialindex ) {
var w, ix, iy,
gridx = scope.widthsegments,
gridy = scope.heightsegments,
width_half = width / 2,
height_half = height / 2,
offset = scope.vertices.length;
if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) {w = 'z';}
else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) {w = 'y';gridy = scope.depthsegments;} else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) {w = 'x';gridx = scope.depthsegments;}
var gridx1 = gridx + 1,
gridy1 = gridy + 1,
segment_width = width / gridx,
segment_height = height / gridy,
normal = new three.vector3();
normal[ w ] = depth > 0 ? 1 : - 1;
for ( iy = 0; iy for ( ix = 0; ix var vector = new three.vector3();
vector[ u ] = ( ix * segment_width - width_half ) * udir;
vector[ v ] = ( iy * segment_height - height_half ) * vdir;
vector[ w ] = depth;
scope.vertices.push( vector );
}
}
for ( iy = 0; iy for ( ix = 0; ix var a = ix + gridx1 * iy;
var b = ix + gridx1 * ( iy + 1 );
var c = ( ix + 1 ) + gridx1 * ( iy + 1 );
var d = ( ix + 1 ) + gridx1 * iy;
var face = new three.face4( a + offset, b + offset, c + offset, d + offset );
face.normal.copy( normal );
face.vertexnormals.push( normal.clone(), normal.clone(), normal.clone(), normal.clone() );
face.materialindex = materialindex;
scope.faces.push( face );
scope.facevertexuvs[ 0 ].push( [
new three.uv( ix / gridx, 1 - iy / gridy ),
new three.uv( ix / gridx, 1 - ( iy + 1 ) / gridy ),
new three.uv( ( ix + 1 ) / gridx, 1- ( iy + 1 ) / gridy ),
new three.uv( ( ix + 1 ) / gridx, 1 - iy / gridy )
] );
}
}
}
除了一个大部分对象都具有的clone()方法,cubegeometry没有其他的方法,其他的xxxgeometry对象也大抵如此。没有方法说明该对象负责组织和存储数据,而如何利用这些数据生成三维场景和动画,则是在另外的对象中定义的。
geometry::cylindergeometry
顾名思义,该构造函数创建一个圆柱体(或圆台)对象。
复制代码 代码如下:
three.cylindergeometry = function ( radiustop, radiusbottom, height, radiussegments, heightsegments, openended ) {
/* 略 */
}
有了cubegeometry构造函数的基础,自己也应当能够实现cylindergeometry,我们只需要注意一下构造函数各参数的意义。radiustop和radiusbottom表示顶部和底部的半径,height表示高度。radiussegments定义了需要将圆周分成多少份(该数字越大,圆柱更圆),heightsegments定义了需要将整个高度分成多少份,openended指定是否生成顶面和底面。
源码中还有两点值得注意的:该模型的本地原点是中轴线的中点,而不是重心之类的,也就是说上圆面的高度(y轴值)是height/2,下圆面是-height/2,这一点对圆柱体来说没有差异,但对于上下半径不同的圆台体就有差异了;还有就是该模型的顶面和地面采用face3类型表面,而侧面采用face4类型表面。
geometry::spheregeometry
该构造函数创建一个球体。
复制代码 代码如下:
three.spheregeometry = function ( radius, widthsegments, heightsegments, phistart, philength, thetastart, thetalength ){
/* 略 */
}
各参数的意义:radius指定半径,widthsegments表示“经度”分带数目,heightsegments表示“纬度”分带数目。后面四个参数是可选的,表示经度的起始值和纬度的起始值。熟悉极坐标的都了解,通常用希腊字母φ(phi)表示纬圈角度(经度),而用θ(theta)表示经圈角度(纬度)。这四个数的默认值分别为0,2π,0,π,通过改变他们的值,可以创造出残缺的球面(但是边缘必须整齐)。
源码中,除了北极和南极的极圈内的区域是用face3类型表面,其他部位都是用的face4型表面。本地原点为球心。
geometry::planegeometry
该构造函数创建一个平面。
复制代码 代码如下:
three.planegeometry = function ( width, height, widthsegments, heightsegments ){
/* 略 */
}
各参数意义:依次为宽度、高度、宽度分段数、高度分段数。想必读者对这种构造“格网”的方式应该很熟悉了吧。
源码中得到一些其他信息:平面被构造在x-y平面上,原点即矩形中心点。
geometry::extrudegeometry
该对象现在是构造一般几何形体的方法,但是通常我们是将建模好的对象存储在某种格式的文件中,并通过loader加载进来,所以似乎鲜有直接用到该函数的机会。而且这个函数看上去还是半成品,很多设定一股脑地堆在options对象里,我也没有仔细研究。
material::material
material对象是所有其他种类material的原型对象。
复制代码 代码如下:
three.material = function () {
three.materiallibrary.push( this );
this.id = three.materialidcount ++;
this.name = '';
this.side = three.frontside;
this.opacity = 1;
this.transparent = false;
this.blending = three.normalblending;
this.blendsrc = three.srcalphafactor;
this.blenddst = three.oneminussrcalphafactor;
this.blendequation = three.addequation;
this.depthtest = true;
this.depthwrite = true;
this.polygonoffset = false;
this.polygonoffsetfactor = 0;
this.polygonoffsetunits = 0;
this.alphatest = 0;
this.overdraw = false; // boolean for fixing antialiasing gaps in canvasrenderer
this.visible = true;
this.needsupdate = true;
};
先看一些较为重要的属性:
属性opacity为一个0-1区间的值,表明透明度。属性transparent指定是否使用透明,只有在该值为真的时候,才会将其与混合(透明是渲染像素时,待渲染值与已存在值共同作用计算出渲染后像素值,达到混合的效果)。
属性blending,blendsrc,blenddst,blendequation指定了混合方式和混合源src和混合像素已有的像元值dst的权重指定方式。默认情况下(如构造函数中赋的缺省值),新的像元值等于:新值×alpha+旧值×(1-alpha)。
我曾困惑为何material类中没有最重要的对象,表示纹理图片的属性。后来我理解了,其实材质和纹理还是有区别的,只能说某种材质有纹理的,但也有材质是没有纹理的。材质影响的是整个形体的渲染效果,比如:“对一根线渲染为5px宽,两端点为方块,不透明的红色”这段描述就可以认为是材质,而没有涉及任何纹理。
和众多geometry对象一样,material对象除了通用的clone(),dellocate()和setvalues()方法,没有其他方法。以下是两种最基本的材质对象。
material::linebasicmaterial
该构造函数创建用于渲染线状形体的材质。
复制代码 代码如下:
three.linebasicmaterial = function ( parameters ) {
three.material.call( this );
this.color = new three.color( 0xffffff );
this.linewidth = 1;
this.linecap = 'round';
this.linejoin = 'round';
this.vertexcolors = false;
this.fog = true;
this.setvalues( parameters );
};
属性color和linewidth顾名思义,指线的颜色和线的宽度(线没有宽度,这里的宽度是用来渲染的)。
属性linecap和linejoin指定线条端点和连接点的样式。
属性fog指定该种材质是否收到雾的影响。
material::meshbasicmaterial
该构造函数创建用于渲染mesh表面的材质。
复制代码 代码如下:
three.meshbasicmaterial = function ( parameters ) {
three.material.call( this );
this.color = new three.color( 0xffffff ); // emissive
this.map = null;
this.lightmap = null;
this.specularmap = null;
this.envmap = null;
this.combine = three.multiplyoperation;
this.reflectivity = 1;
this.refractionratio = 0.98;
this.fog = true;
this.shading = three.smoothshading;
this.wireframe = false;
this.wireframelinewidth = 1;
this.wireframelinecap = 'round';
this.wireframelinejoin = 'round';
this.vertexcolors = three.nocolors;
this.skinning = false;
this.morphtargets = false;
this.setvalues( parameters );
};
这里出现了最重要的纹理属性,包括map,lightmap和specularmap,他们都是texture类型的对象。
属性wireframe指定表面的边界线是否渲染,如果渲染,后面的若干以wireframe开头的属性表示如果渲染边界线,将如何渲染。
texture::texture
该构造函数用来创建纹理对象。
复制代码 代码如下:
three.texture = function ( image, mapping, wraps, wrapt, magfilter, minfilter, format, type, anisotropy ) {
three.texturelibrary.push( this );
this.id = three.textureidcount ++;
this.name = '';
this.image = image;
this.mapping = mapping !== undefined ? mapping : new three.uvmapping();
this.wraps = wraps !== undefined ? wraps : three.clamptoedgewrapping;
this.wrapt = wrapt !== undefined ? wrapt : three.clamptoedgewrapping;
this.magfilter = magfilter !== undefined ? magfilter : three.linearfilter;
this.minfilter = minfilter !== undefined ? minfilter : three.linearmipmaplinearfilter;
this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
this.format = format !== undefined ? format : three.rgbaformat;
this.type = type !== undefined ? type : three.unsignedbytetype;
this.offset = new three.vector2( 0, 0 );
this.repeat = new three.vector2( 1, 1 );
this.generatemipmaps = true;
this.premultiplyalpha = false;
this.flipy = true;
this.needsupdate = false;
this.onupdate = null;
};
最重要的属性是image,这是一个javascript image类型对象。传入的第一个参数就是该对象,如何创建该对象在后面说。
后面的对象都是可选的,如果缺省就会填充默认值,而且往往都是填充默认值。
属性magfileter和minfileter指定纹理在放大和缩小时的过滤方式:最临近点、双线性内插等。
从url中生成一个texture,需要调用three.imageutils.loadtexture(paras),该函数返回一个texture类型对象。在函数内部又调用了three.imageloader.load(paras)函数,这个函数内部又调用了three.texture()构造函数,生成纹理。
