Three.js原始碼閱讀筆記(物件是如何組織的)_基礎知識
這是Three.js原始碼閱讀筆記第三篇。之前兩篇主要是關於核心物件的,這些物件主要圍繞著向量vector3物件和矩陣matrix4物件展開的,關注的是空間中的單一頂點的位置和變化。這篇將主要討論Three.js中的物體是如何組織的:即如何將頂點、表面、材質組合成為一個具體的物件。
Object::Mesh
這個建構子建構了一個空間中的物體。之所以叫「網格」是因為,實際上具有體積的物體基本上都是建模成為「網格」的。
THREE.Mesh = function ( geometry, material ) {
THREE.Object3D.call( this );
this.geometry = geometry;
this.material = ( material !== undefined ) ? material : new THREE.MeshBasicMaterial( {omrand: Mathaterial(om) * 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.. widthSegments, heightSegments, depthSegments ) {
THREE.Geometry.call( this );
var scope = this;
this.width = width;
this.height;
this.width = width;
this.height = height. depth = depth;
var width_half = this.width / 2;
var height_half = this.height / 2;
var depth_half = this.depth / 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.mergefVertices(); };
建構子做的最重要的事在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個表面組成,正如同網格一樣。
函數buildPlane( u, v, udir, vdir, 寬度, 高度, 深度, MaterialIndex ) {
var w, ix, iy,
gridX =scope.widthSegments,
gridY scope.heightSegments,
width_half = 寬度/ 2,
height_half = 高度/ 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 = range.depthSegments;} else if ( ( u === 'z ' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) {w = 'x';gridX = range.depthSegments;}
var gridX1 = gridX 1,
gridY1 = gridY 1,
segment_width = 寬度/ gridX,
segment_height = 高度/ gridY,
正常= 新三.Vector3();
> 0 ? 1 : - 1;
for ( iy = 0; iy for ( ix = 0; ix var vector = new THREE.Vector33 ();
向量[u]=(ix*segment_width-width_half)*udir;
向量[v]=(iy*segment_height-height_half)*vdir;
向量[ w ] = 深度;
scope.vertices.push(向量);
}
}
for ( iy = 0; iy for ( ix = 0; ix var a = ix gridX1 * 我;
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 偏移量, b 偏移量, c 偏移量, d 偏移量);
face.normal.copy(正常) ;
face.vertexNormals.push( 正常. 克隆(), 正常. 克隆(), 正常. 克隆(), 正常. 克隆() );
face.materialIndex =materialIndex;
scope.facesface>face.materialIndex =materialIndex;
scope.facesfaces .push( 臉);
scope.faceVertexUvs[ 0 ].push( [
new THREE.UV( ix / gridX, 1 - iy / gridY ),
new THREE.UV( ix / gridX, 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 .CylinderGeomeius = fightius, Botius radiusSegments, heightSegments, openEnded ) {
/* 略*/
}
有了CubeGeometry建構函式的基礎,自己也應該能夠實作CylinderGeometry,我們只需要注意一下建構子各參數的意義。 radiusTop和radiusBottom表示頂部和底部的半徑,height表示高度。 radiusSegments定義了將圓週橫斷面多少份(該數字越大,圓柱更圓), heightSegments定義了需要將整個身高高度縮短多少份,openEnded指定是否產生頂面和底面。
源碼中還有兩點註解的:該模型的本地原點是中關節炎的中點,而不是重心之類的,遇到上圓面的高度(y軸值)是height/ 2,下圓面是-height/2,這一點對圓柱體來說沒有差異,但對於上下半徑不同的圓台體存在差異了;還有該模型的頂面和採用Groundface3 類型表面,而第三種Face4 類型表面。 Geometry::SphereGeometry
這個建構子建立一個球體。 程式碼如下:
THREE.SphereGeometry = f腳>
THREE.SphereGeometry = function (Seg.S)w. phiStart, phiLength, thetaStart, thetaLength ){
}
各參數的意義:radius指定半徑,widthSegments表示「經度」分帶數量,heightSegments “緯度”分帶數目。後面四個參數是任選的,表示經度的初始值和緯度的初始值。熟悉極座標的都了解,通常用希臘文φ(phi)表示緯度圈角度(經度) ),而用θ(theta)表示經圈角度(緯度)。這四個數的預設值分別為0,2π,0,π,藉由改變它們的值,可以創造出殘缺的球面(但是邊緣必須平行) )。 源碼中,除了北極和南極的極圈內的區域是用face3類型表面,其他位置都是用face4類型表面。本地原點為球心。
幾何::PlaneGeometry 程式碼如下:
THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ){
/* 略*/
}
各參數意義:依次為寬度>高度、寬度分段數、高度分段數。想必讀者對這種構造「格網」的方式應該很熟悉了吧。
原始碼中得到一些其他資訊:平面被構造在x-y平面上,原點即矩形中心點。
Geometry::ExtrudeGeometry
該物件現在是構造一般幾何形體的方法,但是通常我們是將建模好的物件儲存在某種格式的檔案中,並透過loader加載進來,所以似乎鮮有直接用到該函數的機會。而且這個函數看起來還是半成品,很多設定一股腦地堆在options物件裡,我也沒有仔細研究。
Material::Material
Material物件是所有其他種類Material的原型物件。
THREE.Material = function (>
THREE.Material = function (>
THREE.Material = function (>
. .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.OneMinusMinus.SrcAlphaFactor;
this.blendDst = THREE.OneMinusMinus.SrcAlphaFactor;
this.blendDst = THREE.OneMinusMinus.Srcphathisation.SrcLquetDst = THREE。 AddEquation;
this.depthTest = true;
this.depthWrite = true;
this.polygonOffset = false;
this.polygonOffsetFactor = 0;
this.polygonOfmm>Units = 0; this.alphaTest = 0;
this.overdraw = false; // Boolean for fixing antialiasing gaps in CanvasRenderer
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 (Material = function (Naterial = function (Material = function) THREE.Material.call( this );
this.color = new THREE.Color( 0xffffff );
this.linewidth = 1;
this.linecap = 'round';
thisline. = 'round';
this.vertexColors = false;
this.fog = true;
this.setValues( parameters );
屬性和linewidth顧名思義,指線的顏色和線的寬度(線沒有寬度,這裡的寬度是用來渲染的)。
屬性linecap和linejoin指定線條端點和連接點的樣式。 屬性fog指定該種材質是否收到霧的影響。
Material::MeshBasicMaterial
程式碼如下:
THREE.MeshBasicMaterial = function ( parameters ) {
THREE.Material.call( this );
this.color = new THREE.Color( 0xffffff ); // emissive . map = null;
this.lightMap = null;
this.specularMap = null;
this.envMap = null;
this.combine = THREE.MultiplyOperation;
this.reflectivity = 1.reflectivity = 1.reflectivity = 1.reflectivity = 1.reflectivity = 1.reflectivity = 1.reflectivity。 ;
this.refractionRatio = 0.98;
this.fog = true;
this.shading = THREE.SmoothShading;
this.wireframe = false;
this.wireframeLiidth = 1; >this.wireframeLinecap = 'round';
this.wireframeLinejoin = 'round';
this.vertexColors = THREE.NoColors;
this.skinning = false;
this.morphTargets = fsese; 🎜>this.setValues( parameters );
};
這裡出現了最重要的紋理屬性,包括map,lightMap和specularMap,他們都是texture類型的物件。
屬性wireframe指定表面的邊界線是否渲染,如果渲染,後面的若干以wireframe開頭的屬性表示如果渲染邊界線,將如何渲染。
Texture::Texture
這個建構函式用來建立紋理物件。
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.ClampToEdgeWrap; .wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping;
this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter;
this.minter = min .LinearMipMapLinearFilter;
this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
this.format = format !== undefined ? format : THREE.RGBAFormat; 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()建構函數,產生紋理。

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。
