This is the third article of Three.js source code reading notes. The previous two articles were mainly about core objects. These core objects mainly revolve around vector vector3 objects and matrix matrix4 objects, focusing on the position and change of a single vertex in space. This article will mainly discuss how objects in Three.js are organized: that is, how to combine vertices, surfaces, and materials into a specific object.
Object::Mesh
This constructor constructs an object in space. The reason why it is called "grid" is that in fact, objects with volume are basically modeled as "grids".
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 } );
/* Some other content not related to this section*/
}
Actually, the Mesh class has only two attributes, representing geometry The geometry object of the shape and the material object representing the material. The geometry object has been introduced in the previous blog post, and some derived classes will be introduced in this blog post (through the construction process of these derived classes, you can more clearly understand the working principle of the Mesh object); matrial objects and their Derived classes will also be covered in this note. These two properties of the Mesh object are closely related to each other. In the face array in the geometry object, the materialIndex of each face object is used to match the material attribute object, and the vertexUVs array of the face object is used to match the value of each vertex in the array in turn. . It is worth noting that Mesh can only have one material object (I don’t know what the purpose of this design is). If multiple materials need to be used, the materials should be initialized in the materials attribute of the geometry itself in order of materialIndex.
Geometry::CubeGeometry This constructor creates a cube object.
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;
/* Omit*/
buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px
/* Omit*/
function buildPlane( u , v, udir, vdir, width, height, depth, materialIndex ) {
/* Omit*/
}
this.computeCentroids();
this.mergeVertices();
};
The most important thing the constructor does is in the buildPlane. The most important thing about this function is the operation of scope (in the above code block, scope is this), including: calling scope.vertices.push(vector) to add vertices to the geometry object; calling scope.faces.push(face) to add The surface is added to the geometry object, and the scope.faceVertexUvs[i].push(uv) method is called to add the material coordinates corresponding to the vertices to the geometry object. Most of the code is about the logic of generating cubes, which is easy to understand and easy to extend to other types of geometry objects.
The parameters of the constructor include length, width, height and the number of segments in three directions. The so-called segmentation means that if the three parameters such as widthSeqments are all set to 2, then each face will be represented as 2×2=4 faces, and the entire cube is composed of 24 surfaces, just like a grid.
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 < gridY1; iy ) {
for ( ix = 0; ix < gridX1; 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 < gridY; iy ) {
for ( ix = 0; ix < gridX; 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){
/* slightly*/
}
The meaning of each parameter: width, Height, number of width segments, and number of height segments. Readers must be familiar with this way of constructing a "grid".
Some other information is obtained from the source code: the plane is constructed on the x-y plane, and the origin is the center point of the rectangle.
Geometry::ExtrudeGeometry This object is now a method of constructing general geometric shapes, but usually we store the modeled objects in a file in a certain format and load it through the loader Come in, so there seems to be little opportunity to use this function directly. Moreover, this function seems to be a semi-finished product. Many settings are piled in the options object, and I did not study it carefully.
Material::Material The Material object is the prototype object for all other kinds of 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;
};
Let’s take a look at some of the more important attributes:
The attribute opacity is a value in the range of 0-1, indicating transparency. The attribute transparent specifies whether to use transparency. Only when the value is true, it will be mixed with it (transparent means that when rendering pixels, the value to be rendered and the existing value work together to calculate the pixel value after rendering to achieve a mixing effect) .
The attributes blending, blendSrc, blendDst, and blendEquation specify the blending method and the weight specification method of the blending source Src and the existing pixel value Dst of the blending pixel. By default (such as the default value assigned in the constructor), the new pixel value is equal to: new value × alpha old value × (1-alpha).
I was confused as to why there was no most important object in the Material class, which represents the properties of the texture image. Later I understood that there is actually a difference between materials and textures. It can only be said that certain materials have textures, but there are also materials that do not have textures. The material affects the rendering effect of the entire shape. For example: "Render a line as 5px wide, with both endpoints as squares, opaque red" This description can be considered a material and does not involve any texture.
Like many Geometry objects, the Material object has no other methods except the general clone(), dellocate() and setValues() methods. The following are the two most basic material objects.
Material::LineBasicMaterial This constructor creates a material for rendering linear shapes.
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 );
};
Attribute color and As the name suggests, linewidth refers to the color of the line and the width of the line (the line has no width, the width here is used for rendering).
The attributes linecap and linejoin specify the style of line endpoints and connection points.
The attribute fog specifies whether this material is affected by fog.
Material::MeshBasicMaterial
This constructor creates the material used to render the Mesh surface.
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 );
};
The most important texture attributes appear here, including map, lightMap and specularMap, they are all texture type objects.
The attribute wireframe specifies whether the boundary line of the surface is rendered. If it is rendered, the following attributes starting with wireframe indicate how it will be rendered if the boundary line is rendered.
Texture::Texture This constructor is used to create texture objects.
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;
};
The most important attribute is image, which is a JavaScript Image type object. The first parameter passed in is the object. How to create the object will be described later. The objects following
are all optional. If left out, the default value will be filled in, and the default value is often filled in.
The attributes magFileter and minFileter specify the filtering method of the texture when zooming in and out: nearest neighbor point, bilinear interpolation, etc.
To generate a texture from the URL, you need to call Three.ImageUtils.loadTexture(paras). This function returns a texture type object. The THREE.ImageLoader.load(paras) function is called inside the function, and the THREE.Texture() constructor is called inside this function to generate the texture.