SOURCE

console 命令行工具 X clear

                    
>
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;
}

本项目引用的自定义外部资源