Three.js+TypeScript+Webpack learning record (5)

Use environment reference

Node.js v16.19.1

text

Make a presentation board for the model

In three.js, the canvas rendering content can be passed in as a texture for display. Here is a simple example, using canvas to draw text and then pasting it on a box model:

import * as THREE from 'three'
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {
    
     EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import {
    
     RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import {
    
     OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'
import {
    
     mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'
import {
    
     initCamera, initScene, initWebGLRenderer } from './init'
import {
    
     loadGLTF } from './load'
import {
    
     AnimationManager } from './animation'

class Game {
    
    
    scene: THREE.Scene
    camera: THREE.PerspectiveCamera
    renderer: THREE.WebGLRenderer
    orbitControls: OrbitControls

    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    animationManager = new AnimationManager()

    composer: EffectComposer
    outlinePass: OutlinePass

    constructor() {
    
    
        this.scene = initScene()
        this.camera = initCamera()
        this.scene.add(this.camera)
        this.renderer = initWebGLRenderer()

        this.orbitControls = this.addOrbitControls(this.camera, this.renderer)
        this.addModel()
        this.addCanvasModel()
        this.addResizeEventListener()
        this.addClickEvent()

        this.composer = new EffectComposer(this.renderer)
        const renderPass = new RenderPass(this.scene, this.camera)
        this.composer.addPass(renderPass)

        const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera)
        outlinePass.edgeStrength = 4
        outlinePass.edgeGlow = 1
        outlinePass.edgeThickness = 1
        outlinePass.visibleEdgeColor.set('#ffffff')
        outlinePass.hiddenEdgeColor.set('#190a05')
        this.composer.addPass(outlinePass)

        this.outlinePass = outlinePass
    }

    addClickEvent() {
    
    
        this.renderer.domElement.addEventListener('pointermove', (ev) => {
    
    
            this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1
            this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1
            this.raycaster.setFromCamera(this.mouse, this.camera)
            const intersects = this.raycaster.intersectObject(this.scene, true)
            if (intersects.length) {
    
    
                this.outlinePass.selectedObjects = [intersects[0].object]
            } else {
    
    
                this.outlinePass.selectedObjects = []
            }
        })
    }

    addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {
    
    
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.autoRotate = true
        controls.enableDamping = true
        controls.update()
        return controls
    }

    addCanvasModel() {
    
    
        // 创建一个 canvas 元素,并在其中绘制内容
        const canvas = document.createElement('canvas')
        canvas.width = 256
        canvas.height = 256
        const context = canvas.getContext('2d')
        context.fillStyle = 'rgba(255, 255, 255, 0)' // 设置透明背景色
        context.fillRect(0, 0, canvas.width, canvas.height) // 绘制透明矩形
        context.font = '42px Arial'
        context.fillStyle = '#ffffff'
        context.fillText('kuokuo的椅子', 0, 100)
        context.fillText('售价:666元', 0, 160)
        // 将 canvas 元素创建为纹理,并应用到模型上
        const texture = new THREE.CanvasTexture(canvas)
        // 创建一个正方体模型
        const geometry = new THREE.BoxGeometry(0.65, 0.65, 0.001)
        const material = new THREE.MeshBasicMaterial({
    
    
            map: texture,
            transparent: true
        })
        const cube = new THREE.Mesh(geometry, material)
        cube.position.set(0, 1.2, 0)
        this.scene.add(cube)
    }

    async addModel() {
    
    
        const model = await loadGLTF('gltf/SheenChair.glb')
        const geometryArray = []
        const materialArray = []
        model.traverse(obj => {
    
    
            if (obj instanceof THREE.Mesh) {
    
    
                geometryArray.push(obj.geometry)
                materialArray.push(obj.material)
            }
        })
        const mergeGeo = mergeBufferGeometries(geometryArray, true)
        const group = new THREE.Mesh(mergeGeo, materialArray)
        this.scene.add(group)
    }

    addResizeEventListener() {
    
    
        window.addEventListener('resize', () => {
    
    
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
        })
    }

    startMainLoop() {
    
    
        // 等待一帧用于初始化
        Promise.resolve().then(() => {
    
    
            this.step()
        })
    }

    step() {
    
    
        requestAnimationFrame(this.step.bind(this))
        this.orbitControls && this.orbitControls.update()
        this.animationManager.step()
        // this.renderer.render(this.scene, this.camera)
        this.composer.render()
    }
}

const game = new Game()
game.startMainLoop()

Do more cool chart display

Since canvas can be introduced as a rendering texture, wouldn't it be more cool to introduce Echarts in terms of chart display!

install npm i echarts, followed by the declaration filenpm i @types/echarts --save-dev

import * as THREE from 'three'
import * as echarts from 'echarts'
import {
    
     OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {
    
     EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import {
    
     RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import {
    
     OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'
import {
    
     mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'
import {
    
     initCamera, initScene, initWebGLRenderer } from './init'
import {
    
     loadGLTF } from './load'
import {
    
     AnimationManager } from './animation'

class Game {
    
    
    scene: THREE.Scene
    camera: THREE.PerspectiveCamera
    renderer: THREE.WebGLRenderer
    orbitControls: OrbitControls

    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    animationManager = new AnimationManager()

    composer: EffectComposer
    outlinePass: OutlinePass

    updateTexture: THREE.Texture

    constructor() {
    
    
        this.scene = initScene()
        this.camera = initCamera()
        this.scene.add(this.camera)
        this.renderer = initWebGLRenderer()

        this.orbitControls = this.addOrbitControls(this.camera, this.renderer)
        this.addModel()
        this.addCanvasModel()
        this.addResizeEventListener()
        this.addClickEvent()

        this.composer = new EffectComposer(this.renderer)
        const renderPass = new RenderPass(this.scene, this.camera)
        this.composer.addPass(renderPass)

        const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), this.scene, this.camera)
        outlinePass.edgeStrength = 4
        outlinePass.edgeGlow = 1
        outlinePass.edgeThickness = 1
        outlinePass.visibleEdgeColor.set('#ffffff')
        outlinePass.hiddenEdgeColor.set('#190a05')
        this.composer.addPass(outlinePass)

        this.outlinePass = outlinePass
    }

    addClickEvent() {
    
    
        this.renderer.domElement.addEventListener('pointermove', (ev) => {
    
    
            this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1
            this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1
            this.raycaster.setFromCamera(this.mouse, this.camera)
            const intersects = this.raycaster.intersectObject(this.scene, true)
            if (intersects.length) {
    
    
                this.outlinePass.selectedObjects = [intersects[0].object]
            } else {
    
    
                this.outlinePass.selectedObjects = []
            }
        })
    }

    addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {
    
    
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.autoRotate = true
        controls.enableDamping = true
        controls.update()
        return controls
    }

    addCanvasModel() {
    
    
        // 创建一个 canvas 元素,并在其中绘制内容
        const canvas = document.createElement('canvas')
        canvas.width = 256
        canvas.height = 256

        const data = []
        for (let i = 0; i < 5; ++i) {
    
    
            data.push(Math.round(Math.random() * 200))
        }

        const dataArr1 = [1, 2, 3, 2, 1, 3]
        const dataArr2 = [2, 1, 1, 2, 1, 2]

        const chart = echarts.init(canvas)
        const option: echarts.EChartOption<{
    
     name: string, type: string, data: number[] }> = {
    
    
            title: {
    
    
                text: 'World Population'
            },
            tooltip: {
    
    
                trigger: 'axis',
                axisPointer: {
    
    
                    type: 'shadow'
                }
            },
            legend: {
    
    },
            grid: {
    
    
                left: '3%',
                right: '4%',
                bottom: '3%',
                containLabel: true
            },
            xAxis: {
    
    
                type: 'value',
                boundaryGap: [0, 0.01]
            },
            yAxis: {
    
    
                type: 'category',
                data: ['Brazil', 'Indonesia', 'USA', 'India', 'China', 'World']
            },
            series: [
                {
    
    
                    name: '2011',
                    type: 'bar',
                    data: dataArr1
                },
                {
    
    
                    name: '2012',
                    type: 'bar',
                    data: dataArr2
                }
            ]
        }
        chart.setOption(option)

        // 定时器变化
        setInterval(() => {
    
    
            // 随机加减
            const newArr1 = dataArr1.map(v => (v + Math.random()))
            const newArr2 = dataArr1.map(v => (v + Math.random()))
            option.series[0].data = newArr1
            option.series[1].data = newArr2
            chart.setOption(option)
        }, 600)

        // 将 canvas 元素创建为纹理,并应用到模型上
        const texture = new THREE.CanvasTexture(canvas)
        this.updateTexture = texture
        // 创建一个正方体模型
        const geometry = new THREE.BoxGeometry(1, 0.65, 0.001)
        const material = new THREE.MeshBasicMaterial({
    
    
            map: texture,
            transparent: true
        })
        const cube = new THREE.Mesh(geometry, material)
        cube.position.set(0, 1.2, 0)
        this.scene.add(cube)
    }

    async addModel() {
    
    
        const model = await loadGLTF('gltf/SheenChair.glb')
        const geometryArray = []
        const materialArray = []
        model.traverse(obj => {
    
    
            if (obj instanceof THREE.Mesh) {
    
    
                geometryArray.push(obj.geometry)
                materialArray.push(obj.material)
            }
        })
        const mergeGeo = mergeBufferGeometries(geometryArray, true)
        const group = new THREE.Mesh(mergeGeo, materialArray)
        this.scene.add(group)
    }

    addResizeEventListener() {
    
    
        window.addEventListener('resize', () => {
    
    
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
        })
    }

    startMainLoop() {
    
    
        // 等待一帧用于初始化
        Promise.resolve().then(() => {
    
    
            this.step()
        })
    }

    step() {
    
    
        requestAnimationFrame(this.step.bind(this))
        this.updateTexture && (this.updateTexture.needsUpdate = true)
        this.orbitControls && this.orbitControls.update()
        this.animationManager.step()
        // this.renderer.render(this.scene, this.camera)
        this.composer.render()
    }
}

const game = new Game()
game.startMainLoop()

More articles and shares

Three study project link: https://github.com/KuoKuo666/threejs-study

Personal website: www.kuokuo666.com

2023!Day Day Up!

Guess you like

Origin blog.csdn.net/kuokuo666/article/details/130368913