最近在搗鼓 Three.js 相關, 覺得做一個微場景是一個很不錯的選擇,結果做下來發現效果還可以。
下面放幾張不同角度的效果圖
預覽地址 luosijie.github.io/threejs-exa…
原始碼地址 github.com/luosijie/th…
配置較差的裝置上預覽效果不是很理想, 待優化中
實現過程
接下來大致說明一下實現過程
準備工作
首先你要清楚自己想實現的效果, 就需要一張效果圖, 然後才能根據效果圖來建模。 我在網上找到一張自己喜歡的風格的建築圖做參考,明確自己大概要實現的效果,但結果不一定要一模一樣。
然後根據效果圖,畫出自己想要的模型的平面圖,方便模型中物體的精確定位
開發
Three.js來構建一個類似的微場景, 還是比較簡單的。基本上就是在 場景 中 打好燈光 和 攝像機位置, 然後往 場景 新增各種幾何圖形,通過幾個圖形和材質的組合,構建出不同的模型和場景。對開發者的空間想象力有一定的要求。
新建一個空場景與設定
var scene, camera
var renderer
var width, width
var cars = []
// var stats
var config = {
isMobile: false,
background: 0x282828
}
width = window.innerWidth
height = window.innerHeight
scene = new THREE.Scene() // 新建一個場景
camera = new THREE.PerspectiveCamera(45, width / height, 1, 5000) // 新建一個透視攝像機, 並設定 視場, 視野長寬比例, 可見遠近範圍
camera.position.set(330,330,330) // 設定攝像機的位置
camera.lookAt(scene.position) // 設定攝像機觀察的方向
scene.add(camera)
renderer = new THREE.WebGLRenderer({antialias: true}) // 新建一個渲染器, 渲染器用來輸出最終結果
renderer.setSize(width, height) // 設定渲染的尺寸, 在這裡是瀏覽器尺寸
renderer.setClearColor(config.background) // 設定背景的顏色
renderer.shadowMap.enabled = true // 設定是否開啟投影, 開啟的話, 光照會產生投影
renderer.shadowMap.type = THREE.PCFSoftShadowMap // 設定投影型別, 這邊的柔和投影
document.body.appendChild(renderer.domElement) // renderer.domElement 是渲染器用來顯示結果的 canvas 標籤
// checkUserAgent()
// buildAuxSystem()
// buildLightSystem()
// buildbuilding()
// buildRoad()
// buildStaticCars()
// buildMovingCars()
loop()
function loop () {
cars.forEach(function(car){
carMoving(car)
})
renderer.render(scene, camera) // 渲染器開始渲染, scene 和 camera 是必須引數, 因為場景裡有動畫, 所以放在 loop 裡迴圈
requestAnimationFrame(loop)
}
複製程式碼
設定一下光照系統
在3D世界裡, 光 是非常重要的元素, 直接影響到場景內物體的顯示效果, 通常會通過組合使用幾種不同的 光 來調出自己滿意的效果。在我的案例中,我分別在PC端和非PC端使用了不同的 光, 主要是出於效能考慮
if (!config.isMobile) {
// PC端
var directionalLight = new THREE.DirectionalLight( 0xffffff , 1.1); // 新建一個平行光, 平行光照射到的每個點的強度都一樣
directionalLight.position.set( 300, 1000, 500 );
directionalLight.target.position.set( 0, 0, 0 );
directionalLight.castShadow = true; // 開啟平行光的投影
// 下面是設定投影的效果
var d = 300;
directionalLight.shadow.camera = new THREE.OrthographicCamera( -d, d, d, -d, 500, 1600 ); // 投影的可視範圍
directionalLight.shadow.bias = 0.0001;
directionalLight.shadow.mapSize.width = directionalLight.shadow.mapSize.height = 1024; // 投影的精度
scene.add(directionalLight)
// 再新增一個環境光, 目的是為了調和平行光的投影區域, 防止投影過度黑
var light = new THREE.AmbientLight( 0xffffff, 0.3 )
scene.add( light )
}else{
// 非PC端
// 只新增一個天空光, 天空光從正上方往下照, 可以照出明暗對比, 但是不產生陰影
var hemisphereLight = new THREE.HemisphereLight( 0xffffff, 1.6)
scene.add( hemisphereLight)
}
複製程式碼
新增場景中的物體
Three.js中,通過 幾何體(geometry) 和 材質(material) 來生成一個可視物體,我舉2個例子簡單說明一下
樹
在我的案例中, 樹圍繞在建築周圍, 所以需要一個 單體樹, 還有樹的座標(可以通過平面圖得出), 然後根據座標,在每個位置放置一個樹
// 種樹函式
function addTrees () {
// 樹的座標
var treesPosition = [
[-110, -110], [-90, -110],[-70, -110],[-50, -110],[-30, -110],[ -10, -110],[10, -110],[30, -110],[50, -110],[70, -110],[90, -110],
[-110, 110], [-110, 90],[-110, 70],[-110, 50],[-110, 30],[ -110, 10],[-110, -10],[-110, -30],[-110, -50],[-110, -70],[-110, -90],
[ 110, 110], [90, 110], [70, 110], [50, 110], [30, 110],[-30, 110], [-50, 110], [-70, 110], [-90, 110],
[ 110, -110], [ 110, -90], [ 110, -70], [ 110, -50], [ 110, -30], [ 110, -10], [ 110, 10], [ 110, 30], [ 110, 50], [ 110, 70], [ 110, 90],
]
treesPosition.forEach(function (elem) {
var x = elem[0],
y = 1,
z = elem[1]
var tree = createTree(x, y, z)
scene.add(tree)
})
}
// 單體樹
function createTree (x, y, z) {
var x = x || 0
var y = y || 0
var z = z || 0
var tree = new THREE.Object3D() // 新建一個空物件用來放 樹幹 和 樹葉 部分
var treeTrunkGeometry = new THREE.BoxGeometry(2,16,2) // 樹幹
var treeTrunk = utils.makeMesh('lambert', treeTrunkGeometry, 0x8a613a)
treeTrunk.position.y = 8 // 樹幹 y 軸位置
tree.add(treeTrunk) // 樹幹新增到空物件中
var treeLeafsGeometry = new THREE.BoxGeometry(8, 8, 8) // 樹葉
var treeLeafs = utils.makeMesh('lambert', treeLeafsGeometry, 0x9c9e5d)
treeLeafs.position.y = 13 // 樹葉 y 軸的位置
tree.add( treeLeafs) // 樹葉新增到空物件中
tree.position.set(x, y, z)
return tree // 返回 樹 = 樹幹 + 樹葉 物件
}
複製程式碼
汽車
汽車是場景中唯一運動的元素, 也是相對複雜的物體, 除了車身的構建, 還需要開放 前進 , 後退, 轉彎 等方法, 方便以後實現運動效果, 所以我單獨封裝
function Car (color) {
// 可以自定義車身的顏色, 預設隨機
var colors = [0x2cbab2, 0x47a700, 0xd60000, 0x087f87, 0x37ad0e, 0x4d4d4d, 0xce7e00, 0xe0a213, 0x87bcde]
var index = Math.floor(Math.random() * colors.length)
this.color = color || colors[index]
this.mesh = new THREE.Object3D()
this.wheels = []
this.startAngle = 0
var that = this
addBody() // 新增車身到 this.mesh
addWindows() // 新增車窗到 this.mesh
addLights() // 新增車燈到 this.mesh
addWheels() // 新增車輪到 this.mesh
...
}
Car.prototype = {
// 設定車的位置
setPosition: function (x,y,z) {
this.mesh.position.set(x,y,z)
},
// 前進, 實現不管車旋轉的角度怎樣, 車都能按車頭的方向前進
forward: function (speed) {
var speed = speed || 1
this._moving(speed, true)
},
// 後退, 實現不管車旋轉的角度怎樣, 車都能按車尾的方向後退
backward: function (speed) {
var speed = speed || 1
this._moving(speed, false)
},
// 左轉
turnLeft: function (angle, speed) {
this._turn(angle, true, speed)
},
// 右轉
turnRight: function (angle, speed) {
this._turn(angle, false, speed)
},
_turn: function (angle, direction, speed) {
var direction = direction ? 1 : -1
if (speed) {
if(this.startAngle < angle) {
this.mesh.rotation.y += speed
this.startAngle += speed
if (angle - this.startAngle < speed) {
var originAngle = this.mesh.rotation.y - this.startAngle
this.mesh.rotation.y = originAngle + angle
this.startAngle = 0
return
}
}
} else {
this.mesh.rotation.y += angle * direction
}
},
_moving: function (speed, direction) {
var rotation = this.mesh.rotation.y
var direction = direction ? 1 : -1
var xLength = speed * Math.cos(rotation) * direction,
zLength = speed * Math.sin(rotation) * direction
this.mesh.position.x += xLength
this.mesh.position.z -= zLength
this._rotateWheels(speed)
},
_rotateWheels: function (speed) {
this.wheels.forEach(function (elem) {
elem.rotation.z -= 0.1*speed
})
}
}
複製程式碼
// 生成汽車
function buildStaticCars () {
var carsPosition = [
[-84, 82, 1.5], [-58, 82, 1.5], [-32, 82, 1.5] , [84, 82, 1.5]
]
carsPosition.forEach(function (elem) {
var car = new Car()
var x = elem[0],
z = elem[1],
r = elem[2]
car.setPosition(x, 0, z)
car.mesh.rotation.y = r * Math.PI
scene.add(car.mesh)
})
}
複製程式碼
最後
作為自己入門Three.js的一個案例, 還是以實現基本效果為目標, 最終效果和期望的還是有一定的差距, 所以未來還將不斷探索Three.js的一些高階技巧。
大概就到這裡了, 歡迎star