09-three.js Materials

Three.js Journey — Learn WebGL with Three.jsThe ultimate Three.js course whether you are a beginner or a more advanced developerhttps://threejs-journey.com/?c=p3

MeshBasicMaterial

添加3個網格體:

/*** Object*/
// MashBasicMaterial
const material = new THREE.MeshBasicMaterial()const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16),material
)const plane = new THREE.Mesh(new THREE.PlaneGeometry(1,1),material
)const torus = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.2, 16, 32),material
)sphere.position.x = -1.5
torus.position.x = 1.5scene.add(sphere, plane, torus)

tick 函數中旋轉對象:

/*** Animate*/
const clock = new THREE.Clock()const tick = () =>
{const elapsedTime = clock.getElapsedTime()// Update objectssphere.rotation.y = 0.1 * elapsedTimeplane.rotation.y = 0.1 * elapsedTimetorus.rotation.y = 0.1 * elapsedTimesphere.rotation.x = -0.15 * elapsedTimeplane.rotation.x = -0.15 * elapsedTimetorus.rotation.x = -0.15 * elapsedTime// ...
}tick()

【復習點】作為 `map``matcap` 使用的紋理應該以 `sRGB` 編碼

// 作為 `map` 和 `matcap` 使用的紋理應該以 `sRGB` 編碼
const matcapTexture = textureLoader.load('./textures/matcaps/1.png')
const doorColorTexture = textureLoader.load('./textures/door/color.jpg')doorColorTexture.colorSpace = THREE.SRGBColorSpace
matcapTexture.colorSpace = THREE.SRGBColorSpace

可以在材質的 map 屬性中使用所有紋理:

const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })

Map

map 屬性將在幾何體表面應用紋理,下面2種賦值式是相等的:

const material = new THREE.MeshBasicMaterial({map: doorColorTexture
})// Equivalent
const material = new THREE.MeshBasicMaterial()
material.map = doorColorTexture

Color

當直接更改 color 屬性時,必須實例化一個 Color 類。color 屬性將在幾何體表面應用統一顏色:

material.color = new THREE.Color('#ff0000')
material.color = new THREE.Color('#f00')
material.color = new THREE.Color('red')
material.color = new THREE.Color('rgb(255, 0, 0)')
material.color = new THREE.Color(0xff0000)

將 `color` 和 `map` 結合起來會用顏色給紋理上色:

wireframe

(有些屬性,比如?wireframe?或?opacity,可以與大多數后續材質一起使用。)

線框模式,`wireframe` 屬性會以 1px 的細線顯示組成幾何體的三角形:

material.wireframe = true

【注意】平面由兩個三角形組成

opacity

(有些屬性,比如?wireframe?或?opacity,可以與大多數后續材質一起使用。)

需要先將 transparent 屬性設置為 true,告知 Three.js 這種材質現在支持透明度,再用opacity 屬性控制透明度。

material.transparent = true
material.opacity = 0.5

不透明度設置為0.5:

不透明度設置為1:

AlphaMap

配合 .transparent,可以使用?alphaMap?屬性通過紋理來控制透明度:

material.transparent = true
// material.opacity = 0.5
material.alphaMap = doorAlphaTexture

Side

side 屬性決定允許哪些面的哪一面可見。盡量避免在渲染時使用?THREE.DoubleSide,因為即使這個面不可見,它也會消耗更多的資源。

正面可見 (THREE.FrontSide),背面可見 (THREE.BackSide) 或兩面可見 (THREE.DoubleSide):

material.side = THREE.DoubleSide

MeshNormalMaterial

MeshNormalMaterial與“法線”有關:

// MeshNormalMaterial
const material = new THREE.MeshNormalMaterial()

法線是編碼在每個頂點中的信息,包含了面的外法線方向。如果將這些法線顯示為箭頭,會看到從每個頂點延伸出來的直線,這些直線構成了幾何形狀。

【擴展】使用Three.js內置的VertexNormalsHelper

import { VertexNormalsHelper } from 'three/examples/jsm/helpers/VertexNormalsHelper.js';const mesh = new THREE.Mesh(geometry, material);
const normalsHelper = new VertexNormalsHelper(mesh, 1, 0xff0000);
scene.add(mesh);
scene.add(normalsHelper);

flatShading

flatShading 會使面變得平坦,這意味著法線不會在頂點之間進行插值。

material.flatShading = true

MeshMatcapMaterial

MeshMatcapMaterial 在保持高性能的同時還能呈現出極佳的效果,它需要一個看起來像球體的參考紋理:

// MeshMatcapMaterial
const material = new THREE.MeshMatcapMaterial()
// 材質會根據法線相對于相機的方向從紋理中選擇顏色
material.matcap = matcapTexture

【注意】網格將顯得被照亮,但這只是由紋理創造的幻象,場景中沒有光源。

唯一的問題是,結果無論相機方向如何都相同。此外,無法更新光源,因為根本就沒有光源。

MeshDepthMaterial

MeshDepthMaterial 會將幾何體在接近相機的near值時著色為白色,在接近相機的far值時著色為黑色:

// MeshDepthMaterial
const material = new THREE.MeshDepthMaterial()

MeshLambertMaterial

MeshLambertMaterial 是列表中第一個需要光源才能看到的材質,我們需要加一些光源。

// MeshLambertMaterial
const material = new THREE.MeshLambertMaterial() // 沒有燈源,畫布漆黑

添加燈光

創建一個?AmbientLight?并將其添加到場景中:

/*** Lights*/
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)

創建一個?PointLight?并將其添加到場景中:

/*** Lights*/
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
scene.add(ambientLight)const pointLight = new THREE.PointLight(0xffffff, 30)
pointLight.position.x = 2
pointLight.position.y = 3
pointLight.position.z = 4
scene.add(pointLight)

MeshPhongMaterial

MeshPhongMaterialMeshLambertMaterial 非常相似,但奇怪的圖案不那么明顯,可以看到幾何體表面的光反射:

// MeshPhongMaterial
const material = new THREE.MeshPhongMaterial()

可以通過 shininess 屬性來控制光反射,數值越大,表面越光滑。

還可以通過 specular 屬性來改變反射的顏色:

// MeshPhongMaterial
const material = new THREE.MeshPhongMaterial()
material.shininess = 100
material.specular = new THREE.Color(0x1188ff)

MeshToonMaterial

MeshToonMaterial 在屬性上類似于 MeshLambertMaterial,但具有卡通風格。默認情況下,只能得到兩部分著色,一部分用于陰影,另一部分用于光照。

// MeshToonMaterial
const material = new THREE.MeshToonMaterial()

如果想增加著色的步驟,可以在 gradientMap 屬性上使用在課程開始時加載的 gradientTexture,卡通效果將不再起作用,因為 gradientTexture 實際上是一個非常非常小的 3x1 像素的紋理。從這個紋理中提取像素時,GPU 會將它們混合。

// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
material.gradientMap = gradientTexture

可以通過?minFilter?和?magFilter?控制 GPU 處理這些紋理的方式,將?minFilter?和?magFilter?更改為?THREE.NearestFilter:

// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
material.gradientMap = gradientTexture

【優化】由于?THREE.NearestFilter?實際上并沒有使用任何.mipmap 版本的紋理,我們可以通過將?gradientTexture.generateMipmaps?設置為?false?來禁用 mipmaps 的生成,從而釋放一些內存:

// MeshToonMaterial
const material = new THREE.MeshToonMaterial()
gradientTexture.minFilter = THREE.NearestFilter
gradientTexture.magFilter = THREE.NearestFilter
gradientTexture.generateMipmaps = false
material.gradientMap = gradientTexture

MeshStandardMaterial

MeshStandardMaterial支持光源,但使用更真實的算法和更好的參數,如粗糙度和金屬度。

因為 PBR 已經成為許多軟件、引擎和庫的標準,其目的是獲得真實的結果,并使用真實參數。

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()

可以直接更改?粗糙度?和?金屬度?屬性:

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.45
material.roughness = 0.65

添加調試界面

可以創建一個?GUI?的實例,在創建材質之后,添加調整項:

import GUI from 'lil-gui'// ...// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.45
material.roughness = 0.65gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)

RGBELoader 與 environmentMap

添加一個名為 環境貼圖 的額外功能,環境貼圖就像是場景周圍環境的圖像。可以使用它來添加反射、折射以及光照,除此之外,還可以使用當前的?DirectionalLight?和?AmbientLight。

為了加載之前提到的環境貼圖文件,需要使用?RGBELoader,接下來,需要實例化它為?rgbeLoader,并使用其?load()?方法加載?./textures/environmentMap/2k.hdr?文件。

RGBELoader 與?textureLoader?不同,我們需要將回調函數作為第二個參數傳遞,我們可以在該函數的參數中獲取加載的環境貼圖。

(由于環境貼圖已經足夠,可以移除或注釋掉?AmbientLight?和?PointLight)

import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'// ...// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)/*** Environment map*/
const rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/environmentMap/2k.hdr', (environmentMap) =>
{environmentMap.mapping = THREE.EquirectangularReflectionMappingscene.background = environmentMapscene.environment = environmentMap
})

MeshStandardMaterial?的其他屬性

Map

該屬性允許應用一個簡單的紋理:

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
material.map = doorColorTexture

aoMap

直譯為“環境遮擋圖”,會在紋理較暗的地方添加陰影。

aoMap 只會影響由 AmbientLight、環境貼圖以及 HemisphereLight 創建的光線。

使用 doorAmbientOcclusionTexture 紋理添加 aoMap,并通過 aoMapIntensity 屬性控制其強度:

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1

displacementMap

該屬性會移動頂點以創建真實的浮雕效果,因為我們的幾何體頂點數量不足,且浮雕效果過于強烈,現在增加幾何體的細分層級,再使用 displacementScale 屬性來控制這一點:

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 0.7
material.roughness = 0.2
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1// ... const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 64, 64),material
)const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 100, 100),material
)const torus = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.2, 64, 128),material
)

metalnessMap與roughnessMap

我們不需要為整個幾何體指定統一的?metalness?和?roughness,而是可以使用?metalnessMap?和?roughnessMap。

由于?metalness?和?roughness?屬性仍然會影響?metalnessMap?和?roughnessMap,需要將?metalness?和?roughness?都設置為?1:

terial.metalness = 1
material.roughness = 1
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture

normalMap

通過 .normalMap 可以在門的金屬部分增加反射效果,normalMap 將模擬法線方向并在細分無關的情況下為表面添加細節,可以使用 normalScale 屬性更改法線強度。請注意,這個值是 Vector2

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 1
material.roughness = 1
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
material.normalMap = doorNormalTexture
material.normalScale.set(0.5, 0.5)

alphaMap

可以使用 alphaMap 屬性來控制透明度,前提是將 transparent 屬性設置為 true

// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.metalness = 1
material.roughness = 1
material.map = doorColorTexture
material.aoMap = doorAmbientOcclusionTexture
material.aoMapIntensity = 1
material.displacementMap = doorHeightTexture
material.displacementScale = 0.1
material.metalnessMap = doorMetalnessTexture
material.roughnessMap = doorRoughnessTexture
material.normalMap = doorNormalTexture
material.normalScale.set(0.5, 0.5)material.transparent = true
material.alphaMap = doorAlphaTexture

MeshPhysicalMaterial

MeshPhysicalMaterialMeshStandardMaterial 類似,但支持額外的效果,如清漆、光澤、彩虹色和透射。

Clearcoat 清漆

// Clearcoat
material.clearcoat = 1
material.clearcoatRoughness = 0gui.add(material, 'clearcoat').min(0).max(1).step(0.0001)
gui.add(material, 'clearcoatRoughness').min(0).max(1).step(0.0001)

sheen 光澤度

當從狹窄的角度觀察時,光澤會突出材料的質感,通常可以在像織物這樣的蓬松材料上看到這種效果。

注釋掉清漆部分,添加?sheen、sheenRoughness?和?sheenColor?屬性及其相應的調整:

// Sheen
material.sheen = 1
material.sheenRoughness = 0.25
material.sheenColor.set(1, 1, 1)gui.add(material, 'sheen').min(0).max(1).step(0.0001)
gui.add(material, 'sheenRoughness').min(0).max(1).step(0.0001)
gui.addColor(material, 'sheenColor')

iridescence 彩虹色效果

彩虹色效果是指可以看到類似燃料油池、肥皂泡、激光影碟的效果,這些顏色的偽影。就像前兩個效果一樣,當從狹窄的角度觀察時,光澤主要在材質上可見。

添加?iridescence、iridescenceIOR?和?iridescenceThicknessRange?屬性及其相應的調整:

// Iridescence
material.iridescence = 1
material.iridescenceIOR = 1
material.iridescenceThicknessRange = [ 100, 800 ]gui.add(material, 'iridescence').min(0).max(1).step(0.0001)
gui.add(material, 'iridescenceIOR').min(1).max(2.333).step(0.0001)
gui.add(material.iridescenceThicknessRange, '0').min(1).max(1000).step(1)
gui.add(material.iridescenceThicknessRange, '1').min(1).max(1000).step(1)

Transmission 透射

投射,傳輸將使光線穿過材料。這不僅僅是透明度,帶有?opacity?時,因為對象后面的圖像會被變形。

添加?transmission、ior?和?thickness?屬性及其相應的調整:

// Transmission
material.transmission = 1
material.ior = 1.5
material.thickness = 0.5gui.add(material, 'transmission').min(0).max(1).step(0.0001)
gui.add(material, 'ior').min(1).max(10).step(0.0001)
gui.add(material, 'thickness').min(0).max(1).step(0.0001)

ior?表示折射率,取決于你想要模擬的材料類型。

鉆石的?ior?為?2.417,水的?ior?為?1.333,而空氣的?ior?為?1.000293

厚度是一個固定值,并未考慮對象的實際厚度。

目前,很多地圖干擾了我們的材質,但純材質的透傳效果非常好。

移除或注釋所有地圖,并將金屬度?metalness?和粗糙度?roughness?設置為?0(仍然可以調整?粗糙度?并得到光澤效果):

const material = new THREE.MeshPhysicalMaterial()
material.metalness = 0
material.roughness = 0
// material.map = doorColorTexture
// material.aoMap = doorAmbientOcclusionTexture
// material.aoMapIntensity = 1
// material.displacementMap = doorHeightTexture
// material.displacementScale = 0.1
// material.metalnessMap = doorMetalnessTexture
// material.roughnessMap = doorRoughnessTexture
// material.normalMap = doorNormalTexture
// material.normalScale.set(0.5, 0.5)
// material.transparent = true
// material.alphaMap = doorAlphaTexture// Transmission
material.transmission = 1
material.ior = 1.5
material.thickness = 0.5

【完整代碼】

// Texture Link: https://polyhaven.com/a/metal_plateimport * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js';
import gsap from 'gsap'
import GUI from 'lil-gui'
// import { VertexNormalsHelper } from 'three/examples/jsm/helpers/VertexNormalsHelper.js';
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
// const mesh = new THREE.Mesh(geometry, material);/*** Debug*/
const gui = new GUI()// Canvas
const canvas = document.querySelector('canvas.webgl')// Scene
const scene = new THREE.Scene()/*** Textures*/
const textureLoader = new THREE.TextureLoader()// doorColorTexture,作為 `map` 和 `matcap` 使用的紋理應該以 `sRGB` 編碼
const doorColorTexture = textureLoader.load('./textures/door/color.jpg')
const doorAlphaTexture = textureLoader.load('./textures/door/alpha.jpg')
const doorAmbientOcclusionTexture = textureLoader.load('./textures/door/ambientOcclusion.jpg')
const doorHeightTexture = textureLoader.load('./textures/door/height.jpg')
const doorNormalTexture = textureLoader.load('./textures/door/normal.jpg')
const doorMetalnessTexture = textureLoader.load('./textures/door/metalness.jpg')
const doorRoughnessTexture = textureLoader.load('./textures/door/roughness.jpg')
// matcapTexture,作為 `map` 和 `matcap` 使用的紋理應該以 `sRGB` 編碼
const matcapTexture = textureLoader.load('./textures/matcaps/3.png')
const gradientTexture = textureLoader.load('./textures/gradients/5.jpg')doorColorTexture.colorSpace = THREE.SRGBColorSpace
matcapTexture.colorSpace = THREE.SRGBColorSpace/*** Object*/// MashBasicMaterial
// 方式一
// const material = new THREE.MeshBasicMaterial({ map: doorColorTexture })// 方式二
// const material = new THREE.MeshBasicMaterial()
// `map` 屬性將在幾何體表面應用紋理
// material.map = textures.diffTexture// `color` 屬性將在幾何體表面應用統一顏色
// material.color = new THREE.Color('#f00')// 線框模式
// material.wireframe = true// 透明度
// material.transparent = true
// material.opacity = 0.5
// 現在透明效果已經生效,可以使用 alphaMap 屬性通過紋理來控制透明度
// material.alphaMap = doorAlphaTexture // 此處代碼沒有 alpha 材質// Side 可決定哪些面可見,THREE.FrontSide 正面可見,THREE.BackSide 背面, THREE.DoubleSide 兩面皆可
// 【注意】盡量避免在渲染時使用 THREE.DoubleSide,因為即使這個面不可見,它實際上也會消耗更多的資源
// material.side = THREE.DoubleSide// MeshNormalMaterial
// const material = new THREE.MeshNormalMaterial()
// material.flatShading = true// MeshNormalMaterial 可以使用 MeshBasicMaterial 中的一些屬性,比如 `wireframe`、`transparent`、`opacity` 和 `side`
// `flatShading` 會使面變得平坦,這意味著法線不會在頂點之間進行插值
// material.flatShading = true// MeshMatcapMaterial 
// const material = new THREE.MeshMatcapMaterial()
// material.matcap = matcapTexture// MeshMatcapMaterial
// const material = new THREE.MeshMatcapMaterial()
// // 材質會根據法線相對于相機的方向從紋理中選擇顏色
// material.matcap = matcapTexture// MeshDepthMaterial
// const material = new THREE.MeshDepthMaterial()// MeshLambertMaterial
// const material = new THREE.MeshLambertMaterial()// MeshPhongMaterial
// const material = new THREE.MeshPhongMaterial()
// material.shininess = 100
// material.specular = new THREE.Color(0x1188ff)// MeshToonMaterial
// const material = new THREE.MeshToonMaterial()
// gradientTexture.minFilter = THREE.NearestFilter
// gradientTexture.magFilter = THREE.NearestFilter
// gradientTexture.generateMipmaps = false
// material.gradientMap = gradientTexture// MeshStandardMaterial
// const material = new THREE.MeshStandardMaterial()
// material.metalness = 1
// material.roughness = 1
// material.map = doorColorTexture
// material.aoMap = doorAmbientOcclusionTexture
// material.aoMapIntensity = 1
// material.displacementMap = doorHeightTexture
// material.displacementScale = 0.1
// material.metalnessMap = doorMetalnessTexture
// material.roughnessMap = doorRoughnessTexture
// material.normalMap = doorNormalTexture
// material.normalScale.set(0.5, 0.5)// material.transparent = true
// material.alphaMap = doorAlphaTexture/*** MeshPhysicalMaterial*/
// Base material
const material = new THREE.MeshPhysicalMaterial()
material.metalness = 0
material.roughness = 0
// material.map = doorColorTexture
// material.aoMap = doorAmbientOcclusionTexture
// material.aoMapIntensity = 1
// material.displacementMap = doorHeightTexture
// material.displacementScale = 0.1
// material.metalnessMap = doorMetalnessTexture
// material.roughnessMap = doorRoughnessTexture
// material.normalMap = doorNormalTexture
// material.normalScale.set(0.5, 0.5)gui.add(material, 'metalness').min(0).max(1).step(0.0001)
gui.add(material, 'roughness').min(0).max(1).step(0.0001)// Clearcoat
// material.clearcoat = 1
// material.clearcoatRoughness = 0// gui.add(material, 'clearcoat').min(0).max(1).step(0.0001)
// gui.add(material, 'clearcoatRoughness').min(0).max(1).step(0.0001)// Sheen
// material.sheen = 1
// material.sheenRoughness = 0.25
// material.sheenColor.set(1, 1, 1)// gui.add(material, 'sheen').min(0).max(1).step(0.0001)
// gui.add(material, 'sheenRoughness').min(0).max(1).step(0.0001)
// gui.addColor(material, 'sheenColor')// Iridescence
// material.iridescence = 1
// material.iridescenceIOR = 1
// material.iridescenceThicknessRange = [ 100, 800 ]// gui.add(material, 'iridescence').min(0).max(1).step(0.0001)
// gui.add(material, 'iridescenceIOR').min(1).max(2.333).step(0.0001)
// gui.add(material.iridescenceThicknessRange, '0').min(1).max(1000).step(1)
// gui.add(material.iridescenceThicknessRange, '1').min(1).max(1000).step(1)// Transmission
// material.transmission = 1
// material.ior = 1.5
// material.thickness = 0.5// gui.add(material, 'transmission').min(0).max(1).step(0.0001)
// gui.add(material, 'ior').min(1).max(10).step(0.0001)
// gui.add(material, 'thickness').min(0).max(1).step(0.0001)/*** Environment map*/
const rgbeLoader = new RGBELoader()
rgbeLoader.load('./textures/environmentMap/2k.hdr', (environmentMap) =>
{environmentMap.mapping = THREE.EquirectangularReflectionMappingscene.background = environmentMapscene.environment = environmentMap
})const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 64, 64),material
)const plane = new THREE.Mesh(new THREE.PlaneGeometry(1, 1, 100, 100),material
)const torus = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.2, 64, 128),material
)sphere.position.x = -1.5
torus.position.x = 1.5scene.add(sphere, plane, torus)// const normalsHelper1 = new VertexNormalsHelper(sphere, 1, 0xff0000);
// const normalsHelper2 = new VertexNormalsHelper(plane, 1, 0xff0000);
// const normalsHelper3 = new VertexNormalsHelper(torus, 1, 0xff0000);
// scene.add(normalsHelper1,normalsHelper2,normalsHelper3);/*** Lights*/
// const ambientLight = new THREE.AmbientLight(0xffffff, 1)
// scene.add(ambientLight)// const pointLight = new THREE.PointLight(0xffffff, 30)
// pointLight.position.x = 2
// pointLight.position.y = 3
// pointLight.position.z = 4
// scene.add(pointLight)/*** Sizes*/
const sizes = {width: window.innerWidth,height: window.innerHeight
}/*** Camera*/// 自定義控制
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 1000)
camera.position.z = 3
scene.add(camera)/*** Controls*/
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true/*** Renderer*/
const renderer = new THREE.WebGLRenderer({canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.render(scene, camera)/*** Animate*/
const clock = new THREE.Clock()
let isRotate = true
let pauseTime = 0 // 記錄暫停時的時間
let totalPausedTime = 0 // 記錄總共暫停的時間const tick = () => {let elapsedTimeif (isRotate) {// 如果是恢復旋轉,調整時間計算elapsedTime = clock.getElapsedTime() - totalPausedTime// update objectsphere.rotation.y = 0.1 * elapsedTimeplane.rotation.y = 0.1 * elapsedTimetorus.rotation.y = 0.1 * elapsedTimesphere.rotation.x = -0.15 * elapsedTimeplane.rotation.x = -0.15 * elapsedTimetorus.rotation.x = -0.15 * elapsedTime} else {// 如果是第一次暫停,記錄暫停時間if (pauseTime === 0) {pauseTime = clock.getElapsedTime()}// 保持對象旋轉為0sphere.rotation.y = 0plane.rotation.y = 0torus.rotation.y = 0sphere.rotation.x = 0plane.rotation.x = 0torus.rotation.x = 0}// Update controlscontrols.update()renderer.render(scene, camera)window.requestAnimationFrame(tick)
}
tick()window.addEventListener('resize', () => {// 1. 更新 sizessizes.width = window.innerWidthsizes.height = window.innerHeight// 2.1 更新 camera aspect 縱橫比camera.aspect = sizes.width / sizes.height// 2.2 更新 aspect 時要配合更新投影矩陣 updateProjectionMatrixcamera.updateProjectionMatrix()// 3. 更新 rendererrenderer.setSize(sizes.width, sizes.height)renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))})window.addEventListener('dblclick', () => {// 注釋原來的雙擊全屏if (!document.fullscreenElement) {canvas.requestFullscreen();} else {document.exitFullscreen();}})// 生成頁面大標題
let labelIndex = 8
const headerArr = ['MashBasicMaterial', 'MeshNormalMaterial', 'MeshMatcapMaterial', 'MeshDepthMaterial', 'MeshLambertMaterial', 'MeshPhongMaterial', 'MeshToonMaterial', 'MeshStandardMaterial', 'MeshPhysicalMaterial']
const headerHTML = `
<div class="action-buttons" style="position: absolute; left: 16px;">${headerArr.map((label, index) => {if (labelIndex == index) {return `<h1 class="action-btn" style="color: #fff;">${label}</h1>`}
}
).join('')}
</div>
`;
// 添加到文檔
document.body.insertAdjacentHTML('beforeend', headerHTML);// 生成按鈕,控制切換const buttonLabels = ['停止旋轉', '重新旋轉',]
const buttonsHTML = `
<div class="action-buttons" style="position: absolute; top: 36px;">${buttonLabels.map(label =>`<button class="action-btn" style="width: 110px; margin: 16px; cursor: pointer;">${label}</button>`
).join('')}
</div>
`;
// 添加到文檔
document.body.insertAdjacentHTML('beforeend', buttonsHTML);// 生成按鈕,控制切換 END// 為按鈕添加事件
document.querySelectorAll('.action-btn').forEach(btn => {btn.addEventListener('click', function () {// 停止旋轉 if (this.textContent == '停止旋轉') {if (isRotate) {// 暫停時記錄當前時間pauseTime = clock.getElapsedTime() // 如果重新開始旋轉,這個變量就用不上isRotate = !isRotate}}// 旋轉 if (this.textContent == '重新旋轉') {if (!isRotate) {// 恢復時計算總共暫停的時間// totalPausedTime += clock.getElapsedTime() - pauseTime // 從上次旋轉結束的位置開始totalPausedTime = clock.getElapsedTime() // 重新開始旋轉pauseTime = 0  // 如果重新開始旋轉,這個變量就用不上isRotate = !isRotate}}});
});


項目創建參考

01-three.js vite基礎示例_three.js示例-CSDN博客文章瀏覽閱讀400次。three.js 基本示例代碼_three.js示例 https://blog.csdn.net/gaowxx/article/details/147954918?spm=1001.2014.3001.5501

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/89371.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/89371.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/89371.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Netty介紹和基本代碼演示

什么是Netty&#xff1f;Netty是一個基于Java NIO的異步事件驅動的網絡應用框架&#xff0c;主要用于快速開發高性能、高可靠性的網絡服務器和客戶端程序。它簡化了網絡編程的復雜性&#xff0c;提供了豐富的協議支持&#xff0c;被廣泛應用于各種高性能網絡應用中。為什么選擇…

[BrowserOS] Nxtscape瀏覽器核心 | 瀏覽器狀態管理 | 瀏覽器交互層

第三章&#xff1a;Nxtscape瀏覽器核心 歡迎回來&#xff01; 在前兩章中&#xff0c;我們了解了名為專用AI代理的專家團隊及其管理者AI代理協調器&#xff0c;它們協同解析需求并規劃執行步驟。 但這些代理與協調器實際運行的平臺是什么&#xff1f;答案正是本章的核心——…

時序數據庫處理的時序數據獨特特性解析

時序數據&#xff08;Time-Series Data&#xff09;作為大數據時代增長最快的數據類型之一&#xff0c;正在物聯網、金融科技、工業監控等領域產生爆炸式增長。與傳統數據相比&#xff0c;時序數據具有一系列獨特特性&#xff0c;這些特性直接影響了時序數據庫&#xff08;Time…

uniapp各端通過webview實現互相通信

目前網上&#xff0c;包括官方文檔針對uniapp的webview的內容都是基于vue2的&#xff0c;此文章基于vue3的composition API方式網頁對網頁 由于uniapp中的webview只支持引入h5頁面&#xff0c;不支持互相通信&#xff0c;所以要條件編譯&#xff0c;用iframe導入頁面&#xf…

【Vue】tailwindcss + ant-design-vue + vue-cropper 圖片裁剪功能(解決遇到的坑)

1.安裝 vue-cropper pnpm add vue-cropper1.1.12.使用 vue-cropper <template><div class"user-info-head" click"editCropper()"><img :src"options.img" title"點擊上傳頭像" class"img-circle" /><…

【Java】【力扣】101.對稱二叉樹

思路遞歸大問題&#xff1a;對比 左 右 是否對稱參數 左和右todo 先湊合看代碼/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* …

前端 oidc-client 靜默刷新一直提示:Error: Frame window timed out 問題分析與解決方案

引言 在現代前端開發中&#xff0c;OAuth 2.0 和 OpenID Connect (OIDC) 協議已成為身份驗證和授權的標準解決方案。oidc-client-js 是一個流行的 JavaScript 庫&#xff0c;用于在前端應用中實現 OIDC 協議。其中&#xff0c;靜默刷新&#xff08;Silent Renew&#xff09;是一…

DAY02:【ML 第一彈】KNN算法

一、算法簡介 1.1 算法思想 如果一個樣本在特征空間中的 k 個最相似的樣本中的大多數屬于某一個類別&#xff0c;則該樣本也屬于這個類別。 1.2 樣本相似性 樣本都是屬于一個任務數據集的&#xff0c;樣本距離越近則越相似。 二維平面上點的歐氏距離 二維平面上點 a(x1,y1)a(x_…

wpf 實現窗口點擊關閉按鈕時 ??隱藏?? 而不是真正關閉,并且只有當 ??父窗口關閉時才真正退出?? 、父子窗口順序控制與資源安全釋放?

文章目錄實現方法**方法 &#xff1a;重寫 OnClosing 方法****子窗口&#xff08;SettingView&#xff09;代碼****父窗口&#xff08;MainWindow&#xff09;代碼****關鍵點****適用場景**為什么if (Owner null || !Owner.IsLoaded)能夠判斷父窗口已經關閉**1. Owner null 檢…

硬件設計學習DAY4——電源完整性設計:從概念到實戰

每日更新教程&#xff0c;評論區答疑解惑&#xff0c;小白也能變大神&#xff01;" 目錄 一.電源完整性 1.1電源完整性的核心概念 1.2電源完整性的三個關鍵目標 1.3地彈現象的通俗解釋 1.4總結要點 二.電源分配網絡&#xff08;PDN&#xff09;的作用 電源與GND網絡…

QT跨平臺應用程序開發框架(8)—— 多元素控件

目錄 一&#xff0c;關于多元素控件 二&#xff0c;QListWidget 2.1 主要方法 2.2 實現新增刪除 三&#xff0c;Table Widget 3.1 主要方法 3.2 代碼演示 四&#xff0c;Tree Widget 4.1 主要方法 4.2 代碼演示 一&#xff0c;關于多元素控件 多元素控件就是一個控件里面包含了…

【React Native】環境變量和封裝 fetch

環境變量和封裝fetch 環境變量 一般做開發&#xff0c;都會將接口地址配置到環境變量里。在Expo建的項目里&#xff0c;也可以使用環境變量。 在項目根目錄新建一個.env文件&#xff0c;里面添加上&#xff1a; EXPO_PUBLIC_API_URLhttp://localhost:3000如果你用手機真機等…

Linux 基礎命令詳解:從入門到實踐(1)

Linux 基礎命令詳解&#xff1a;從入門到實踐&#xff08;1&#xff09; 前言 在 Linux 操作系統中&#xff0c;命令行是高效管理系統、操作文件的核心工具。無論是開發者、運維工程師還是Linux愛好者&#xff0c;掌握基礎命令都是入門的第一步。本文將圍繞Linux命令的結構和常…

基于 SpringBoot+VueJS 的私人牙科診所管理系統設計與實現

基于 SpringBootVueJS 的私人牙科診所管理系統設計與實現摘要隨著人們對口腔健康重視程度的不斷提高&#xff0c;私人牙科診所的數量日益增多&#xff0c;對診所管理的信息化需求也越來越迫切。本文設計并實現了一個基于 SpringBoot 和 VueJS 的私人牙科診所管理系統&#xff0…

華為云Flexus+DeepSeek征文|體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建天氣預報大模型

華為云FlexusDeepSeek征文&#xff5c;體驗華為云ModelArts快速搭建Dify-LLM應用開發平臺并創建天氣預報大模型 什么是華為云ModelArts 華為云ModelArts ModelArts是華為云提供的全流程AI開發平臺&#xff0c;覆蓋從數據準備到模型部署的全生命周期管理&#xff0c;幫助企業和開…

Mysql系列--0、數據庫基礎

目錄 一、概念 1.1什么是數據庫 1.2什么是mysql 1.3登錄mysql 1.4主流數據庫 二、Mysql與數據庫 三、Mysql架構 四、SQL分類 五、存儲引擎 5.1概念 5.2查看引擎 5.3存儲引擎對比 一、概念 1.1什么是數據庫 由于文件保存數據存在文件的安全性問題 文件不利于數據查詢和管理…

深度學習和神經網絡的介紹

一.前言本期不涉及任何代碼&#xff0c;本專欄剛開始和大家介紹了一下機器學習&#xff0c;而本期就是大家介紹一下深度學習還有神經網絡&#xff0c;作為一個了解就好。二.深度學習2.1 什么是深度學習&#xff1f;在介紹深度學習之前&#xff0c;我們先看下??智能&#xff0…

AI驅動的軟件工程(下):AI輔助的質檢與交付

&#x1f4da; 系列文章導航 AI驅動的軟件工程&#xff08;上&#xff09;&#xff1a;人機協同的設計與建模 AI驅動的軟件工程&#xff08;中&#xff09;&#xff1a;文檔驅動的編碼與執行 AI驅動的軟件工程&#xff08;下&#xff09;&#xff1a;AI輔助的質檢與交付 大家好…

【WRFDA實操第一期】服務器中安裝 WRFPLUS 和 WRFDA

目錄在服務器上下載并解壓 WRF v4.6.1編譯 WRFDA 及相關庫安裝和配置所需庫安裝 WRFPLUS 和 WRFDA 以運行 4DVAR 數據同化一、安裝 WRFPLUS&#xff08;適用于 WRF v4.0 及以上版本&#xff09;二、安裝 WRFDA&#xff08;用于 4DVAR&#xff09;WRFDA 和 WRFPLUS 的安裝說明另…

【機器學習【6】】數據理解:數據導入、數據審查與數據可視化方法論

文章目錄一、機器學習數據導入1、 Pandas&#xff1a;機器學習數據導入的最佳選擇2、與其他方法的差異二、機器學習數據理解的系統化方法論1、數據審查方法論&#xff1a;六維數據畫像技術維度1&#xff1a;數據結構審查維度2&#xff1a;數據質量檢查維度3&#xff1a;目標變量…