console
function main() {
const infoElem = document.querySelector('#info')
const canvas = document.querySelector('#c')
const renderer = new THREE.WebGLRenderer({ canvas })
renderer.setClearColor(0xAAAAAA)
renderer.shadowMap.enabled = true
function makeCamera(fov = 40) {
const aspect = 2 // the canvas default
const zNear = 0.1
const zFar = 1000
// 透视相机更贴近人眼所示
return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar)
}
const camera = makeCamera()
camera.position.set(8, 4, 10).multiplyScalar(3)
camera.lookAt(0, 0, 0)
const scene = new THREE.Scene()
{
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(0, 20, 0)
scene.add(light)
light.castShadow = true // 是否产生阴影
// mapSize影响的阴影的质量,值须为2的幂,越高质量越好,计算损耗越大
// 且宽高不必相等, 最大值为renderer.capabilities.maxTextureSize
light.shadow.mapSize.width = 2048
light.shadow.mapSize.height = 2048
// 设置阴影相关
const d = 50
light.shadow.camera.left = -d
light.shadow.camera.right = d
light.shadow.camera.top = d
light.shadow.camera.bottom = -d
light.shadow.camera.near = 1
light.shadow.camera.far = 50
light.shadow.bias = 0.001
}
{
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(1, 2, 4)
scene.add(light)
}
const groundGeometry = new THREE.PlaneBufferGeometry(50, 50)
const groundMaterial = new THREE.MeshPhongMaterial({ color: 0xCC8866 })
const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial)
groundMesh.rotation.x = Math.PI * -.5
groundMaterial.receiveShadow = true
scene.add(groundMesh)
const carWidth = 4
const carHeight = 1
const carLength = 8
const tank = new THREE.Object3D()
scene.add(tank)
const bodyGeometry = new THREE.BoxBufferGeometry(carWidth, carHeight, carLength)
const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x6688AA })
const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial)
bodyMesh.position.y = 1.4
bodyMesh.castShadow = true
tank.add(bodyMesh)
const tankCameraFov = 75
const tankCamera = makeCamera(tankCameraFov)
tankCamera.position.y = 3
tankCamera.position.z = -6
tankCamera.rotation.y = Math.PI
bodyMesh.add(tankCamera)
const wheelRadius = 1
const wheelThickness = .5
const wheelSegments = 5
const wheelGeometry = new THREE.CylinderBufferGeometry(
wheelRadius, // top radius
wheelRadius, // bottom radius
wheelThickness, // height of cylinder
wheelSegments
)
const wheelMaterial = new THREE.MeshPhongMaterial({ color: 0x888888 })
const wheelPositions = [
[-carWidth / 2 - wheelThickness / 2, -carHeight / 2, carLength / 3],
[carWidth / 2 + wheelThickness / 2, -carHeight / 2, carLength / 3],
[-carWidth / 2 - wheelThickness / 2, -carHeight / 2, 0],
[carWidth / 2 + wheelThickness / 2, -carHeight / 2, 0],
[-carWidth / 2 - wheelThickness / 2, -carHeight / 2, -carLength / 3],
[carWidth / 2 + wheelThickness / 2, -carHeight / 2, -carLength / 3],
]
const wheelMeshes = wheelPositions.map(position => {
const mesh = new THREE.Mesh(wheelGeometry, wheelMaterial)
mesh.position.set(...position)
mesh.rotation.z = Math.PI * .5
mesh.castShadow = true
bodyMesh.add(mesh)
return mesh
})
const domeRadius = 2
const domeWidthSubdivisions = 12
const domeHeightSubdivisions = 12
const domePhiStart = 0
const domePhiEnd = Math.PI * 2
const domeThetaStart = 0
const domeThetaEnd = Math.PI * .5
const domeGeometry = new THREE.SphereBufferGeometry(
domeRadius,
domeWidthSubdivisions,
domeHeightSubdivisions,
domePhiStart,
domePhiEnd,
domeThetaStart,
domeThetaEnd
)
const domeMesh = new THREE.Mesh(domeGeometry, bodyMaterial)
domeMesh.castShadow = true
bodyMesh.add(domeMesh)
domeMesh.position.y = .5
const turretWidth = .1
const turretHeight = .1
const turretLength = carLength * .75 * .2
const turretGeometry = new THREE.BoxBufferGeometry(
turretWidth, turretHeight, turretLength
)
const turretMesh = new THREE.Mesh(turretGeometry, bodyMaterial)
const turretPivot = new THREE.Object3D()
turretMesh.castShadow = true
turretPivot.scale.set(5, 5, 5)
turretPivot.position.y = .5
turretMesh.position.z = turretLength * .5
turretPivot.add(turretMesh)
bodyMesh.add(turretPivot)
const turretCamera = makeCamera()
turretCamera.position.y = .75 * .2
turretMesh.add(turretCamera)
const targetGeometry = new THREE.SphereBufferGeometry(.5, 6, 3)
const targetMaterial = new THREE.MeshPhongMaterial({
color: 0x00FF00,
flatShading: true /* 是否使用平面着色进行渲染 */
})
const targetMesh = new THREE.Mesh(targetGeometry, targetMaterial)
const targetOrbit = new THREE.Object3D()
const targetElevation = new THREE.Object3D()
const targetBob = new THREE.Object3D()
targetMesh.castShadow = true
scene.add(targetOrbit)
targetOrbit.add(targetElevation)
targetElevation.position.z = carLength * 2
targetElevation.position.y = 8
targetElevation.add(targetBob)
targetBob.add(targetMesh)
const targetCamera = makeCamera()
const targetCameraPivot = new THREE.Object3D()
targetCamera.position.y = 1
targetCamera.position.z = -2
targetCamera.rotation.y = Math.PI
targetBob.add(targetCameraPivot)
targetCameraPivot.add(targetCamera)
// Create a sing-like wave
const curve = new THREE.SplineCurve([
new THREE.Vector2(-10, 0),
new THREE.Vector2(-5, 5),
new THREE.Vector2(0, 0),
new THREE.Vector2(5, -5),
new THREE.Vector2(10, 0),
new THREE.Vector2(5, 10),
new THREE.Vector2(-5, 10),
new THREE.Vector2(-10, -10),
new THREE.Vector2(-15, -8),
new THREE.Vector2(-10, 0),
]) // 利用一些列点创建平滑二维样条曲线
const points = curve.getPoints(50) // 获取50 + 1个细分点
// 通过点队列生成 BufferGeometry
// 用此种方法创建曲线几何体还是值得关注下的
const geometry = new THREE.BufferGeometry().setFromPoints(points)
const material = new THREE.LineBasicMaterial({ color: 0xff0000 })
const splineObject = new THREE.Line(geometry, material)
splineObject.rotation.x = Math.PI * .5
splineObject.position.y = 0.05
scene.add(splineObject)
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const cameras = [
{ cam: camera, desc: 'detached camera' },
{ cam: turretCamera, desc: 'on turret looking at target' },
{ cam: targetCamera, desc: 'near target looking at tank', },
{ cam: tankCamera, desc: 'above back of tank' },
]
const targetPosition = new THREE.Vector3()
const tankPosition = new THREE.Vector2()
const tankTarget = new THREE.Vector2()
function render(time) {
time *= 0.001
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
cameras.forEach(cameraInfo => {
const camera = cameraInfo.cam
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
})
}
// move target
targetOrbit.rotation.y = time * .27
targetBob.position.y = Math.sin(time * 2) * 4
targetMesh.rotation.x = time * 7
targetMesh.rotation.y = time * 13
targetMaterial.emissive.setHSL(time * 10 % 1, 1, .25)
targetMaterial.color.setHSL(time * 10 % 1, 1, .25)
// move tank
const tankTime = time * .05
curve.getPointAt(tankTime % 1, tankPosition) // 获取点位置
curve.getPointAt((tankTime + 0.01) % 1, tankTarget) // 获取切线点位置以生成切线
tank.position.set(tankPosition.x, 0, tankPosition.y) // 更新位置
tank.lookAt(tankTarget.x, 0, tankTarget.y) // 更新朝向
// face turret at target
// 返回一个表示该物体在世界空间中位置的矢量,且将其复制到参数中去
targetMesh.getWorldPosition(targetPosition)
turretPivot.lookAt(targetPosition)
// make the turretCamera look at target
// 使炮管相机始终朝向靶球
turretCamera.lookAt(targetPosition)
// make the targetCameraPiovt look at the at the tank
// 同理
tank.getWorldPosition(targetPosition)
targetCamera.lookAt(targetPosition)
wheelMeshes.forEach(obj => {
obj.rotation.x = time * 3
})
const camera = cameras[time * .25 % cameras.length | 0]
infoElem.textContent = camera.desc
renderer.render(scene, camera.cam);
requestAnimationFrame(render);
}
requestAnimationFrame(render)
}
main()
<canvas id="c"></canvas>
<div id="info"></div>
html, body {
height: 100%;
margin: 0;
}
#c {
width: 100%;
height: 100%;
display: block;
}
#info {
position: absolute;
left: 1em;
top: 1em;
background: rgba(0,0,0,.8);
padding: .5em;
color: white;
font-family: monospace;
}