一、前言
概述
:基于WebGL
的三維引擎,目前是國內資料最多、使用最廣泛的三維引擎
,可以制作一些3D
可視化項目
目前隨著元宇宙
概念的爆火,THREE
技術已經深入到了物聯網、VR、游戲、數據可視化等多個平臺,今天我們主要基于THREE
實現一個三維的VR
看房小項目
二、基礎知識
Three.js
一般分為三個部分:場景、相機、渲染器,這三個主要的分支就構成了THREE.JS
的主要功能區,這三大部分還有許多細小的分支,這些留到我們后續抽出一些章節專門講解一下。
工作流程
:場景——相機——渲染器
從實際生活
中拍照角度立方體網格模型和光照組成了一個虛擬的三維場景
,相機對象就像你生活中使用的相機一樣可以拍照,只不過一個是拍攝真實的景物
,一個是拍攝虛擬的景物。拍攝一個物體的時候相機的位置和角度需要設置,虛擬的相機還需要設置投影方式
,當你創建好一個三維場景,相機也設置好,就差一個動作“咔”,通過渲染器
就可以執行拍照動作。
三、場景
概述
:場景主要由網絡模型與光照組成,網絡模型分為幾何體與材質
3.1 網絡模型
幾何體就像我們小時候學我們就知道點線面體四種概念,點動成線,線動成面,面動成體
,而材質就像是是幾何體上面的涂鴉,有不同的顏色、圖案…
例子如下:
for (let i = 0; i < 50; i++) { const geometry = new THREE.BufferGeometry(); const arr = new Float32Array(9); for (let j = 0; j < 9; j++) { arr[j] = Math.random() * 5; } geometry.setAttribute('position', new THREE.BufferAttribute(arr, 3)); let randomColor = new THREE.Color(Math.random(), Math.random(), Math.random()); const material = new THREE.MeshBasicMaterial({ color: randomColor, transparent: true, opacity:0.5, }); const mesh = new THREE.Mesh(geometry, material); scene.add(mesh); }
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UlBSgxKr-1666681292595)(https://gitee.com/riskbaby/picgo/raw/master/blog/202209211037215.png#pic_center)]
const geometry = new THREE.BoxGeometry(100, 100, 100); const material = new THREE.MeshStandardMaterial({ color: 0x0000ff }); const cube = new THREE.Mesh(geometry, material); scene.add(cube);
const geometry = new THREE.ConeGeometry(5, 15, 32); const material = new THREE.MeshStandardMaterial({ color: 0x0000ff }); const clone = new THREE.Mesh(geometry, material); scene.add(clone);
3.2 光照
3.2.1 環境光
概念
:光照對three.js
的物體全表面進行光照測試,有可能會發生光照融合
const ambient = new THREE.AmbientLight(0x404040); scene.add(ambient);
3.2.2 平行光
概念
:向特定方向發射的光,太陽光
也視作平行的一種,和上面比較,物體變亮了
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(100, 100, 100); directionalLight.target = cube; scene.add(directionalLight);
3.2.3 點光源
概念
:由中間向四周發射光、強度比平行光小
const pointLight = new THREE.PointLight(0xff0000, 1, 100, 1); pointLight.position.set(50, 50, 50); scene.add(pointLight);
3.2.4 聚光燈
概念
:家里面的節能燈泡,強度較好
const spotLigth = new THREE.PointLight(0xffffff); spotLigth.position.set(50, 50, 50); spotLigth.target = cube; spotLigth.angle = Math.PI / 6; scene.add(spotLigth);
3.2.5 半球光
概念
:光源直接放置于場景之上,光照顏色從天空光線顏色漸變到地面光線顏色
const light = new THREE.HemisphereLight(0xffffbb, 0x080820, 1); scene.add(light);
四、相機
4.1 正交相機
參數(屬性)
|
含義
|
left
|
渲染空間的左邊界
|
right
|
渲染空間的右邊界
|
top
|
渲染空間的上邊界
|
bottom
|
渲染空間的下邊界
|
near
|
near屬性表示的是從距離相機多遠的位置開始渲染,一般情況會設置一個很小的值。 默認值0.1
|
far
|
far屬性表示的是距離相機多遠的位置截止渲染,如果設置的值偏小小,會有部分場景看不到。 默認值1000
|
let width = window.innerWidth; let height = window.innerHeight; const camera = new THREE.OrthographicCamera(width / - 2, width / 2, height / 2, height / - 2, 1, 1000); scene.add(camera); camera.position.set(100, 200, 100);
4.2 透視相機
參數
|
含義
|
默認值
|
fov
|
fov表示視場,所謂視場就是能夠看到的角度范圍,人的眼睛大約能夠看到180度的視場,視角大小設置要根據具體應用,一般游戲會設置60~90度
|
45
|
aspect
|
aspect表示渲染窗口的長寬比,如果一個網頁上只有一個全屏的canvas畫布且畫布上只有一個窗口,那么aspect的值就是網頁窗口客戶區的寬高比
|
window.innerWidth/window.innerHeight
|
near
|
near屬性表示的是從距離相機多遠的位置開始渲染,一般情況會設置一個很小的值。
|
0.1
|
far
|
far屬性表示的是距離相機多遠的位置截止渲染,如果設置的值偏小,會有部分場景看不到
|
1000
|
let width = window.innerWidth; let height = window.innerHeight; const camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); camera.position.set(150, 100, 300); camera.lookAt(scene.position);
五、渲染器
概述
:從WEBGL
的角度來看,three
就是對它的進一步封裝,想要進一步了解渲染器
這方面的知識點還需要了解一下WEBGL
,這里我們就不做過多介紹了。
六、貼圖紋理
6.1 基礎介紹
概述
:這部分對于我們是否能夠給別人呈現一個真實的渲染場景
來說,很重要,比如下面一個普普通通的正方體,我們只要一加上貼圖,立馬不一樣了。
以前
之后
6.2 環境貼圖
概述
:目前有許許多多的貼圖
,比如基礎、透明、環境、法線、金屬、粗糙、置換等等,今天我們呢主要講解一下環境
和一點 HDR處理
在THREE
的世界里面,坐標抽x、y、z
的位置關系圖如下所示:
紅、綠、藍
分別代表x、z、y
,我們的貼圖就是在px nx py ny pz nz
這六個方向防止一張圖片,其中p就代表坐標軸的正方向
CubeTextureLoader
:加載CubeTexture
的一個類。 內部使用ImageLoader
來加載文件。
const sphereTexture = new THREE.CubeTextureLoader().setPath('./textures/course/environmentMaps/0/'); const envTexture= sphereTexture.load([ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ]); scene.background = envTexture; scene.environment = envTexture; const sphereGeometry = new THREE.SphereGeometry(5, 30, 30); const sphereMaterial = new THREE.MeshStandardMaterial({ roughness: 0, metalness: 1, envMap:envTexture, }); const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); scene.add(sphere);
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
gif
圖片有點大上傳不了,我就截了幾張圖
6.3 HDR處理
概述
:高動態范圍圖像,相比普通的圖像,能夠提供更多的動態范圍和圖像細節,一般被運用于電視顯示產品以及圖片視頻拍攝制作當中。
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader; const rgbeLoader = new RGBELoader().setPath('./textures/course/hdr/'); rgbeLoader.loadAsync('002.hdr').then((texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; scene.background = texture; })
七、拓展
7.1 坐標系
概述
:坐標軸能夠更好的反饋物體的位置信息,紅、綠、藍
分別代表x、z、y
const axesHelper = new THREE.AxesHelper(20); scene.add(axesHelper);
7.2 控制器
概述
:通過鼠標控制物體和相機的移動、旋轉、縮放
導包
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
應用
const controls = new OrbitControls(camera, renderer.domElement)
自旋轉
controls.autoRotate = true
必須在render
函數調用update
實時更新才奏效
7.3 自適應
概述
:根據屏幕大小自適應場景
window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) renderer.setPixelRatio(window.devicePixelRatio) })
設置相機的寬高比、重新更新渲染相機、渲染器的渲染大小、設備的像素比
7.4 全屏響應
概述
:雙擊進入全屏
,再次雙擊/ESC退出全屏
window.addEventListener('dblclick', () => { let isFullScreen = document.fullscreenElement if (!isFullScreen) { renderer.domElement.requestFullscreen() } else { document.exitFullscreen() } })
7.5 信息面板
概述
;通過操作面板完成界面的移動物體
的相關應用
鏈接
:https://www.npmjs.com/package/dat.gui
npm install --save dat.gui npm i --save-dev @types/dat.gui
const gui = new dat.GUI(); gui .add(cube.position, 'x') .min(0) .max(10) .step(0.1) .name('X軸移動') .onChange((value) => { console.log('修改的值為' + value); }) .onFinishChange((value) => { console.log('完全停止' + value); }); const colors = { color: '#0000ff', }; gui .addColor(colors, 'color') .onChange((value) => { cube.material.color.set(value); });
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
7.6 頻率檢測
概述
:檢測幀率
導包
import Stats from 'three/addons/libs/stats.module.js';
應用
const stats = new Stats(); document.body.appendChild(stats.dom);
自變化
stats.update()
必須在render
函數調用update
實時更新才奏效
7.7 導航網格
概述
:底部二維平面的網格化,幫助我們更好的創建場景
const gridHelper = new THREE.GridHelper(10, 20) scene.add(gridHelper)
八、源碼
import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import * as dat from 'dat.gui'; import Stats from 'three/addons/libs/stats.module.js'; let scene,camera,renderer scene = new THREE.Scene(); const axesHelper = new THREE.AxesHelper(20); scene.add(axesHelper); const sphereTexture = new THREE.CubeTextureLoader().setPath('./textures/course/environmentMaps/0/'); const envTexture= sphereTexture.load([ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ]); scene.background = envTexture; scene.environment = envTexture; const sphereGeometry = new THREE.SphereGeometry(5, 30, 30); const sphereMaterial = new THREE.MeshStandardMaterial({ roughness: 0, metalness: 1, envMap:envTexture, }); const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); scene.add(sphere); const ambient = new THREE.AmbientLight(0xffffff); scene.add(ambient); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.05); directionalLight.position.set(10,10,10); directionalLight.lookAt(scene.position); scene.add( directionalLight ); camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 2000, ); camera.position.set(10,10,20); camera.lookAt(scene.position); scene.add(camera); renderer = new THREE.WebGLRenderer({ antialias: true, }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); controls.dampingFactor = true; controls.autoRotate=true const stats=new Stats() document.body.appendChild(stats.dom); function render () { renderer.render(scene, camera); requestAnimationFrame(render); controls.update(); stats.update() } render(); window.addEventListener('dblclick', () => { let isFullScene = document.fullscreenElement; console.log(isFullScene); if (!isFullScene) { renderer.domElement.requestFullscreen(); } else { document.exitFullscreen(); } }) window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); }) const gui = new dat.GUI(); gui .add(sphere.position, 'x') .min(0) .max(10) .step(0.1) .name('X軸移動') .onChange((value) => { console.log('修改的值為' + value); }) .onFinishChange((value) => { console.log('完全停止' + value); }); const colors = { color: '#0000ff', }; gui .addColor(colors, 'color') .onChange((value) => { sphere.material.color.set(value); });
-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143