一、前言
最近,在github上面找到了一個(gè)不錯(cuò)的技術(shù)介紹網(wǎng)站,主要使用html+css+js原生三件套寫(xiě)的。我在此基礎(chǔ)之上利用three.js加了一點(diǎn)3D元素在里面,讓這個(gè)網(wǎng)站看起來(lái)更炫酷。
改的時(shí)候,感覺(jué)原生還是比不上框架來(lái)的方便,后續(xù)有時(shí)間我會(huì)抽離一個(gè)vue組件的版本。
在線訪問(wèn):個(gè)人簡(jiǎn)歷
國(guó)內(nèi)鏡像:InsCode
github源碼:個(gè)人簡(jiǎn)歷
二、模型準(zhǔn)備
2.1 資源尋找
對(duì)于模型來(lái)說(shuō),我們可以自己慢慢的建一個(gè)模型,但是很費(fèi)時(shí)間。不想自己建模的話,自己可以直接在網(wǎng)上找到一些資源,導(dǎo)入到blender進(jìn)行相關(guān)修改。
以下是一些好用的3D資源網(wǎng)站:
- TurboSquid - https://www.turbosquid.com/
- CGTrader - https://www.cgtrader.com/
- Sketchfab - https://sketchfab.com/
- 3DExport - https://3dexport.com/
- Free3D - https://free3d.com/
- Unity Asset Store - https://assetstore.unity.com/
- Poly by Google - https://poly.google.com/
- Clara.io - https://clara.io/
- Blend Swap - https://www.blendswap.com/
- 3D Warehouse by SketchUp - https://3dwarehouse.sketchup.com/
這些網(wǎng)站提供了各種類型的3D模型和紋理,包括游戲資源、建筑物、人物、動(dòng)物、車(chē)輛等。有些網(wǎng)站提供免費(fèi)的資源,而有些網(wǎng)站則需要付費(fèi)才能下載高質(zhì)量的資源。希望這些網(wǎng)站可以幫助您找到所需的3D資源。
2.2 資源處理
修改好想要的模型之后,由于網(wǎng)頁(yè)端要追求性能,所以我們要對(duì)模型進(jìn)行壓縮(一般可以壓縮到原來(lái)的1/10)。壓縮后使用three.js特定的DRACOLoader解壓文件。
2.3 Draco壓縮
雖然我們可能會(huì)覺(jué)得使用Draco壓縮是個(gè)雙贏局面,但實(shí)際上并非如此。
確實(shí)它會(huì)讓幾何體更輕量,但首先要使用的時(shí)候必須加載DracoLoader類和解碼器。其次,我們計(jì)算機(jī)解碼一個(gè)壓縮文件需要時(shí)間和資源,這可能會(huì)導(dǎo)致頁(yè)面打開(kāi)時(shí)有短暫凍結(jié),即便我們使用了worker和WebAssembly。
因此我們必須根據(jù)實(shí)際來(lái)決定使用什么解決方案。如果一個(gè)模型具有100kb的幾何體,那么則不需要Draco壓縮,但是如果我們有MB大小的模型要加載,并且不關(guān)心在開(kāi)始運(yùn)行時(shí)有些許頁(yè)面凍結(jié),那么便可能需要用到Draco壓縮。
三、基礎(chǔ)場(chǎng)景
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
const canvas = document.querySelector('.webgl')
const rect = canvas.getBoundingClientRect();
const sizes = {
width: rect.width,
height: rect.height
}
let scene, camera, renderer
let controls, gui
let init = () => {
//場(chǎng)景
scene = new THREE.Scene()
// 相機(jī)
camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 1000)
camera.position.set(-2.85, 4.37, 2.49)
camera.lookAt(scene.position)
// 渲染器
renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true })
renderer.setSize(sizes.width, sizes.height)
// renderer.setClearColor('lightsalmon', 0.5)
renderer.setPixelRatio(window.devicePixelRatio)
renderer.useLegacyLights = true
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
// 真實(shí)性物理渲染
renderer.physicallyCorrectLights = true
renderer.outputEncoding = THREE.sRGBEncoding
renderer.toneMapping = THREE.ACESFilmicToneMapping
//控制器
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = false
controls.enableZoom = false
controls.enablePan = false
controls.minPolarAngle = Math.PI / 6
controls.maxPolarAngle = Math.PI / 3
controls.minAzimuthAngle = -Math.PI / 6
controls.maxAzimuthAngle = Math.PI / 2
}
//加載模型
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./assets/js/three/draco/')
const gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
let loadModel = () => {
gltfLoader.load('./assets/model/office.glb', (gltf) => {
const office = gltf.scene
office.rotation.y = Math.PI / 2
office.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true
child.receiveShadow = true
if (child.name === 'mac-screen') {
screen = child
} else if (child.name === 'Chair') {
chair = child
} else if (child.name === 'lamp-top') {
// console.log(child.position);
}
}
})
setScreenVideo()
setChairRotate()
scene.add(office)
})
}
//渲染
let animate = () => {
controls.update()
renderer.render(scene, camera)
requestAnimationFrame(animate)
}
init()
loadModel()
animate()
three.js的場(chǎng)景基礎(chǔ)包括:
- Scene(場(chǎng)景):包含所有的3D對(duì)象、光源和攝像機(jī)。
- Camera(相機(jī)):定義了視角和投影方式,控制著我們從場(chǎng)景中看到的內(nèi)容。
- Renderer(渲染器):將場(chǎng)景和相機(jī)中的對(duì)象渲染到屏幕上。
- Mesh(網(wǎng)格):3D對(duì)象的基本組成部分,由三角形構(gòu)成??梢允褂貌煌牟馁|(zhì)和紋理來(lái)給網(wǎng)格添加顏色和紋理。
- Material(材質(zhì)):定義了網(wǎng)格的顏色、紋理、光照等屬性。
- Light(光源):在場(chǎng)景中添加光源可以讓物體更加真實(shí)地呈現(xiàn)。
- Texture(紋理):可以給網(wǎng)格添加圖片或者其他圖案。
- Geometry(幾何體):描述了網(wǎng)格的形狀和大小。
四、燈光
4.1 介紹
在 three.js 中,燈光用來(lái)模擬現(xiàn)實(shí)中的光照條件,可以讓場(chǎng)景中的物體更加真實(shí)地呈現(xiàn)。燈光可以為物體提供不同的光照效果,如明亮的陽(yáng)光、柔和的夜燈、閃爍的蠟燭等。
three.js 中的燈光有以下幾種類型:
- AmbientLight(環(huán)境光):在整個(gè)場(chǎng)景中均勻地分布著光源,使得整個(gè)場(chǎng)景看起來(lái)更加明亮。
- DirectionalLight(平行光):模擬來(lái)自于一個(gè)方向的太陽(yáng)光線,具有方向性,可以產(chǎn)生明暗的效果。
- PointLight(點(diǎn)光源):模擬來(lái)自于一個(gè)點(diǎn)的光源,可以產(chǎn)生明暗的效果,也可以產(chǎn)生陰影。
- SpotLight(聚光燈):模擬來(lái)自于一個(gè)點(diǎn)的光源,具有方向性,可以產(chǎn)生明暗的效果,也可以產(chǎn)生錐形的陰影。
通過(guò)設(shè)置不同的燈光類型、顏色、強(qiáng)度、位置等屬性,可以在 three.js 中模擬出各種不同的光照效果,使得場(chǎng)景中的物體看起來(lái)更加真實(shí)。
4.2 陰影相機(jī)
本例,我們要盡可能的模擬燈光效果,所以對(duì)于陰影也要考慮在內(nèi)。對(duì)于陰影來(lái)說(shuō),會(huì)先用一個(gè)特殊的相機(jī)(稱為陰影相機(jī))從光源的位置來(lái)渲染場(chǎng)景,并將渲染結(jié)果保存到一個(gè)深度紋理中。這個(gè)深度紋理記錄了場(chǎng)景中每個(gè)像素距離光源的距離,也即是場(chǎng)景中哪些物體遮擋了該像素。
當(dāng)渲染場(chǎng)景時(shí),系統(tǒng)會(huì)根據(jù)陰影相機(jī)生成的深度紋理來(lái)計(jì)算每個(gè)像素是否在陰影中。具體來(lái)說(shuō),對(duì)于每個(gè)像素,系統(tǒng)會(huì)根據(jù)它在陰影相機(jī)中的位置、深度信息和光源的位置和方向來(lái)計(jì)算它是否被遮擋。如果該像素被遮擋,則它的顏色值將被調(diào)整以模擬出陰影效果,否則它的顏色值不變。
4.3 燈光渲染
在 three.js 中,有三種燈光類型可以被渲染,分別是平行光(DirectionalLight)、點(diǎn)光源(PointLight)和聚光燈(SpotLight)。
這些燈光可以被添加到場(chǎng)景中,并通過(guò)設(shè)置它們的屬性來(lái)控制它們的位置、顏色、強(qiáng)度、范圍等參數(shù),從而實(shí)現(xiàn)不同的光照效果。
這些燈光可以被渲染到不同類型的相機(jī)中,具體如下:
- 平行光(DirectionalLight):平行光模擬的是來(lái)自于一個(gè)方向的光線,具有方向性,可以產(chǎn)生明暗的效果。平行光只能被渲染到正交相機(jī)(OrthographicCamera)中
- 點(diǎn)光源(PointLight):點(diǎn)光源模擬的是來(lái)自于一個(gè)點(diǎn)的光線,可以產(chǎn)生明暗的效果,也可以產(chǎn)生陰影。點(diǎn)光源只能被渲染到透視相機(jī)(PerspectiveCamera)中
- 聚光燈(SpotLight):聚光燈模擬的是來(lái)自于一個(gè)點(diǎn)的光線,具有方向性,可以產(chǎn)生明暗的效果,也可以產(chǎn)生錐形的陰影。聚光燈只能被渲染到透視相機(jī)(PerspectiveCamera)中
五、動(dòng)畫(huà)
5.1 多媒體
使用 Three.js 的 VideoTexture 可以將視頻作為紋理應(yīng)用到 3D 對(duì)象上,實(shí)現(xiàn)很酷的效果。首先,在一開(kāi)始加載模型的時(shí)候要把需要貼圖的對(duì)象找到傳入函數(shù),第二步添加視頻紋理。
// 屏幕播放音頻
let screen = null
let setScreenVideo = () => {
const video = document.createElement('video')
video.src = './assets/video/kda.mp4'
video.muted = true
video.playsInline = true
video.autoplay = true
video.loop = true
video.play()
const videoTexture = new THREE.VideoTexture(video)
// 添加真實(shí)性渲染,后面改
screen.material = new THREE.MeshBasicMaterial({
map: videoTexture,
})
}
5.2 椅子旋轉(zhuǎn)
這里對(duì)于過(guò)渡的效果統(tǒng)一使用gsap完成,GSAP是一個(gè)JavaScript動(dòng)畫(huà)庫(kù),用于創(chuàng)建高性能、流暢的動(dòng)畫(huà)效果。
文章參考:GSAP的香,我來(lái)帶你get~時(shí)入1k算少的?。?/p>
// 添加椅子旋轉(zhuǎn)
let chair = null
let setChairRotate = () => {
gsap.to(chair.rotation, {
y: Math.PI / 4,
duration: 10,
ease: 'power1.inOut',
repeat: -1,
yoyo: true,
})
}
5.3 明暗變換
當(dāng)我們切換界面的明暗時(shí),模型理應(yīng)跟著變換。所以模型要響應(yīng)變化,一開(kāi)始我們?yōu)闊艄馓砑?*debug-gui,**可以時(shí)刻調(diào)試燈光。
let debugUI = () => {
//gui控制器
gui = new GUI()
let folder1 = gui.addFolder('環(huán)境光')
folder1.addColor(ambientLight, 'color')
folder1.add(ambientLight, 'intensity', 0, 10, 0.01)
folder1.close()
let folder2 = gui.addFolder('太陽(yáng)光')
folder2.add(sunLight.position, 'x', -5, 5, 0.01)
folder2.add(sunLight.position, 'y', -5, 5, 0.01)
folder2.add(sunLight.position, 'z', -5, 5, 0.01)
folder2.addColor(sunLight, 'color')
folder2.add(sunLight, 'intensity', 0, 10, 0.01)
folder2.close()
let folder3 = gui.addFolder('臺(tái)燈')
folder3.add(spotLight.position, 'x', -5, 5, 0.01)
folder3.add(spotLight.position, 'y', -5, 5, 0.01)
folder3.add(spotLight.position, 'z', -5, 5, 0.01)
folder3.addColor(spotLight, 'color')
folder3.add(spotLight, 'intensity', 0, 10, 0.01)
folder3.close()
let folder4 = gui.addFolder('相機(jī)')
folder4.add(camera.position, 'x', -10, 10, 0.01)
folder4.add(camera.position, 'y', -10, 10, 0.01)
folder4.add(camera.position, 'z', -10, 10, 0.01)
// folder4
folder4.add(params, 'showCmeraInfo')
// gui.close()
}
let gsapTheme = () => {
if (getCurrentTheme() === 'light') {
gsap.to(ambientLight, { intensity: 2.5 })
gsap.to(ambientLight.color, {
...ambientLightColor,
duration: durationTime
})
gsap.to(sunLight, { intensity: 2.5, duration: durationTime })
gsap.to(spotLight, { intensity: 0, duration: durationTime })
} else {
gsap.to(ambientLight, { intensity: 3.8, duration: durationTime })
gsap.to(ambientLight.color, {
...ambientDarkColor,
duration: durationTime
})
gsap.to(sunLight, { intensity: 0, duration: durationTime })
gsap.to(spotLight, { intensity: 3.5, duration: durationTime })
}
}
六、真實(shí)性渲染
6.1 uv貼圖
這里的技術(shù)并沒(méi)有手動(dòng)貼上uv貼圖,但是作為一項(xiàng)基本的理論,還是要掌握一下。
UV貼圖是將紋理圖像映射到三維物體表面上的一種技術(shù),它依賴于UV坐標(biāo)系來(lái)確定紋理圖像在物體表面上的位置。其中,U和V分別表示紋理圖像在水平和垂直方向上的坐標(biāo),取值通常是0到1之間(水平方向的第U個(gè)像素/圖片寬度,垂直方向的第V個(gè)像素/圖片高度)。
對(duì)于某些特殊的3D貼圖技術(shù),可能會(huì)使用到W坐標(biāo),但在一般情況下,UV坐標(biāo)系就足夠描述紋理映射了。展UV是將物體表面展開(kāi)成二維平面,以便進(jìn)行紋理貼圖,這一過(guò)程也需要使用到UV坐標(biāo)系來(lái)確定各個(gè)點(diǎn)在展開(kāi)平面上的位置。
6.2 光照
在three.js中,physicallyCorrectLights是一個(gè)屬性,用于指定渲染器是否使用物理正確的光照模型。當(dāng)該屬性設(shè)置為true時(shí),渲染器會(huì)使用基于物理的光照模型,以便更準(zhǔn)確地模擬真實(shí)世界中的光照效果。
renderer.physicallyCorrectLights = true
6.3 渲染器
盡管目前看起來(lái)效果還行,但在顏色方面還是有點(diǎn)欠缺需要下點(diǎn)工夫。這是因?yàn)閃ebGLRenderer 屬性的問(wèn)題。
6.3.1 utputEncoding
outputEncoding屬性控制輸出渲染編碼。默認(rèn)情況下,outputEncoding的值為T(mén)HREE.LinearEncoding,看起來(lái)還行但是不真實(shí),建議將值改為T(mén)HREE.sRGBEncoding
6.3.2 Tone mapping
色調(diào)映射Tone mapping旨在將超高的動(dòng)態(tài)范圍HDR轉(zhuǎn)換到我們?nèi)粘o@示的屏幕上的低動(dòng)態(tài)范圍LDR的過(guò)程。
說(shuō)明一下HDR和LDR(摘自知乎LDR和HDR):
- 因?yàn)椴煌膹S家生產(chǎn)的屏幕亮度(物理)實(shí)際上是不統(tǒng)一的,那么我們?cè)谡f(shuō)LDR時(shí),它是一個(gè)0到1范圍的值,對(duì)應(yīng)到不同的屏幕上就是匹配當(dāng)前屏幕的最低亮度(0)和最高亮度(1)
- 自然界中的亮度差異是非常大的。例如,蠟燭的光強(qiáng)度大約為15,而太陽(yáng)光的強(qiáng)度大約為10w。這中間的差異是非常大的,有著超級(jí)高的動(dòng)態(tài)范圍。
- 我們?nèi)粘J褂玫钠聊?,其最高亮度是?jīng)過(guò)一系列經(jīng)驗(yàn)積累的,所以使用、用起來(lái)不會(huì)對(duì)眼睛有傷害;但自然界中的,比如我們直視太陽(yáng)時(shí),實(shí)際上是會(huì)對(duì)眼睛產(chǎn)生傷害的。
那為了改變色調(diào)映射tone mapping,則要更新WebGLRenderer上的toneMapping屬性,有以下這些值
THREE.NoToneMapping (默認(rèn))
THREE.LinearToneMapping
THREE.ReinhardToneMapping
THREE.CineonToneMapping
THREE.ACESFilmicToneMapping
盡管我們的貼圖不是HDR,但使用tone mapping可以塑造更真實(shí)的效果。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-429418.html
6.4 陰影失真
在計(jì)算曲面是否處于陰影中時(shí),由于精度原因,陰影失真可能會(huì)發(fā)生在平滑和平坦表面上。
而現(xiàn)在在漢堡包上發(fā)生的是漢堡包在它自己的表面上投射了陰影。因此我們必須調(diào)整燈光陰影shadow的“偏移bias”和“法線偏移normalBias”屬性來(lái)修復(fù)此陰影失真。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-429418.html
- bias通常用于平面,因此不適用于我們的漢堡包。但如果你有在一塊平坦的表面上出現(xiàn)陰影失真,可以試著增加偏差直到失真消失。
- normalBias通常用于圓形表面,因此我們?cè)黾臃ㄏ蚱钪钡疥幱笆д嫦А?/li>
到了這里,關(guān)于THREE.JS實(shí)現(xiàn)個(gè)人簡(jiǎn)歷網(wǎng)站的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!