[js&threeJS] Get started with three, and realize 3D car showroom, with full code

First put a final rendering:

 

Three-Dimensional (3D) Concepts:

Three-dimensional (3D) is a concept that describes the position and shape of an object on three spatial coordinate axes. Compared to two-dimensional (2D) planes that only have length and width, three-dimensionality adds the dimension of height or depth

In 3D space, we use three independent coordinate axes to describe the position of objects. Usually a Cartesian coordinate system is used, ie the X, Y and Z axes. Wherein, the X axis represents the horizontal direction, the Y axis represents the vertical direction, and the Z axis represents the depth or vertical direction. By combining different values ​​on these axes, the position of a point or object in three-dimensional space can be determined

You can feel the three-dimensional in the three editor: three.js editor

ps: default English, you can switch Chinese language

three premise concepts

Take the stage display as an example:

  • The scene  Sence is equivalent to a stage, where the scene items are arranged and the performers perform
  • The camera  Carma is equivalent to the eyes of the audience to watch
  • Geometry  Geometry is the performer on the stage
  • Lighting  light is equivalent to stage lighting control 
  • Controls Equivalent to the chief director of this stage play

Create scene and camera

<html>

<head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
</head>

<body>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js"
            }
        }
    </script>
    <script type="module">
        import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'
        let scene,renderer,camera

        //创建场景
        const setScene = () => {
            scene = new Scene()
            renderer = new WebGLRenderer()
            //调用 setSize() 方法设置渲染器的大小为当前窗口的宽度和高度
            renderer.setSize(window.innerWidth, window.innerHeight)
            //将渲染器的 DOM 元素添加到页面的 <body> 元素中
            document.body.appendChild(renderer.domElement)
        }

        //相机的默认坐标
        const defaultMap = {
            x: 0,
            y: 10,
            z: 20,
        }
        //创建相机  
        const setCamera = () => {
            const { x, y, z } = defaultMap
            //创建一个 PerspectiveCamera 对象,并传入参数来设置透视相机的属性:视野角度为 45 度,宽高比为窗口的宽高比,近裁剪面为 1,远裁剪面为 1000
            camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
            //用 position.set() 方法将相机的位置设置为之前从 defaultMap 中提取的坐标
            camera.position.set(x, y, z)
        }

        (function () {
            setScene()
            setCamera()
        })()

    </script>
</body>

</html>

PerspectiveCamera的详细说明:

The new THREE.PerspectiveCamera constructor is used to create a perspective projection camera. This constructor has a total of four parameters, namely fov, aspect, near, and far.

fov indicates the vertical field of view angle of the camera frustum, the minimum value is 0, the maximum value is 180, and the default value is 50. In actual projects, 45 is generally defined, because 45 is the closest to the normal eye opening angle of a person; aspect indicates the length of the camera frustum Aspect ratio, the default aspect ratio is 1, which means that what you see is a square, and the aspect ratio of the screen is used in the actual project; near indicates the near end surface of the camera viewing frustum, and this value defaults to 0.1, which will be used in the actual project Set it to 1; far means the far end of the camera’s viewing frustum. The default is 2000. This value can be infinite. To put it simply, it is the farthest distance that our vision can see.

import model

A foreign 3D model download website, there are many free model downloads, click the red box to download

Log in to your Sketchfab account - Sketchfab

<html>

<head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
</head>

<body>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js"
            }
        }
    </script>
    <script type="module">
        import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
        import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'
        let scene, renderer, camera, directionalLight, dhelper
        let isLoading = true
        let loadingWidth = 0


        //创建场景
        const setScene = () => {
            scene = new Scene()
            renderer = new WebGLRenderer()
            renderer.setSize(window.innerWidth, window.innerHeight)
            document.body.appendChild(renderer.domElement)
        }

        //相机的默认坐标
        const defaultMap = {
            x: 0,
            y: 10,
            z: 20,
        }
        //创建相机  
        const setCamera = () => {
            const { x, y, z } = defaultMap
            camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
            camera.position.set(x, y, z)
        }

        //通过Promise处理一下loadfile函数
        const loader = new GLTFLoader() //引入模型的loader实例
        const loadFile = (url) => {
            return new Promise(((resolve, reject) => {
                loader.load(url,
                    (gltf) => {
                        resolve(gltf)
                    }, ({ loaded, total }) => {
                        let load = Math.abs(loaded / total * 100)
                        loadingWidth = load
                        if (load >= 100) {
                            setTimeout(() => {
                                isLoading = false
                            }, 1000)
                        }
                        console.log((loaded / total * 100) + '% loaded')
                    },
                    (err) => {
                        reject(err)
                    }
                )
            }))
        }

        (async function () {
            const gltf = await loadFile('./assets/scene.gltf')
            setScene()
            setCamera()
            scene.add(gltf.scene)
        })()

    </script>
</body>

</html>

Explanation of loading model code:

loader.load is used to load and parse glTF files, accepting four parameters:

  • The first parameter  url is the path to the glTF model file to load.
  • The second parameter is a callback function that will be called when the model is loaded successfully
  • The third parameter is a callback function used to track the loading progress. The parameters of the callback function  { loaded, total } indicate the number of files that have been loaded and the total number of files that need to be loaded, and the current loading progress can be obtained by calculating the percentage.
  • The fourth parameter is a callback function that will be called when an error occurs while loading.

create lights

<html>

<head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
</head>

<body>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js"
            }
        }
    </script>
    <script type="module">
        import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
        import { Scene, WebGLRenderer, PerspectiveCamera } from 'three'
        let scene, renderer, camera, directionalLight, dhelper
        let isLoading = true
        let loadingWidth = 0


        //创建场景
        const setScene = () => {
            scene = new Scene()
            renderer = new WebGLRenderer()
            renderer.setSize(window.innerWidth, window.innerHeight)
            document.body.appendChild(renderer.domElement)
        }

        //相机的默认坐标
        const defaultMap = {
            x: 0,
            y: 10,
            z: 20,
        }
        //创建相机  
        const setCamera = () => {
            const { x, y, z } = defaultMap
            camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
            camera.position.set(x, y, z)
        }

        // 设置灯光
        const setLight = () => {
            // 创建一个颜色为白色(0xffffff),强度为 0.5 的平行光对象
            directionalLight = new DirectionalLight(0xffffff, 0.5)
            //设置平行光的位置,这里将其放置在三维坐标 (-4, 8, 4) 的位置
            directionalLight.position.set(-4, 8, 4)
            //创建一个平行光辅助对象,用于可视化平行光的方向和强度
            dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
            //创建一个颜色为白色(0xffffff),半球颜色为白色(0xffffff),强度为 0.4 的半球光对象
            hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
            hemisphereLight.position.set(0, 8, 0)
            //创建一个半球光辅助对象,用于可视化半球光的方向和强度
            hHelper = new HemisphereLightHelper(hemisphereLight, 5)
            //添加到场景
            scene.add(directionalLight)
            //添加到场景
            scene.add(hemisphereLight)
        }

        //使场景、照相机、模型不停调用
        const loop = () => {
            //requestAnimationFrame(loop) 是浏览器提供的方法,用于在下一次重绘页面之前调用回调函数 loop。这样可以创建一个循环,使场景、相机和模型不断被渲染更新
            requestAnimationFrame(loop)
            //使用渲染器 renderer 渲染场景 scene 中的模型,使用相机 camera 进行投影
            renderer.render(scene, camera)
        }

        //通过Promise处理一下loadfile函数
        const loader = new GLTFLoader() //引入模型的loader实例
        const loadFile = (url) => {
            return new Promise(((resolve, reject) => {
                // loader.load 用来加载和解析 glTF 文件
                loader.load(url,
                    (gltf) => {
                        resolve(gltf)
                    }, ({ loaded, total }) => {
                        let load = Math.abs(loaded / total * 100)
                        loadingWidth = load
                        if (load >= 100) {
                            setTimeout(() => {
                                isLoading = false
                            }, 1000)
                        }
                        console.log((loaded / total * 100) + '% loaded')
                    },
                    (err) => {
                        reject(err)
                    }
                )
            }))
        }

        (async function () {
            const gltf = await loadFile('./assets/scene.gltf')
            setScene()
            setCamera()
            setLight()
            scene.add(gltf.scene)
            loop()
        })()

    </script>
</body>

</html>

DirectionalLight and  HemisphereLight are two light types in Three.js, representing parallel light and hemispherical light respectively. They are used to simulate lighting effects in the real world

Now the model can be seen, why you can only see a black area and cannot see the model, there are generally two reasons:

  • Whether the model is loaded successfully
try {
     gltf = await loadFile('./assets/scene.gltf');
     console.log('Model loading completed:', gltf);
} catch (error) {
     console.error('Error loading model:', error);
}
  • Camera position deviation, adjust the position of the camera (defaultMap)

control model

After this step is completed, the model can be moved and rotated by the mouse

<html>

<head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
</head>

<body>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js"
            }
        }
    </script>
    <script type="module">
        import { Scene, WebGLRenderer, PerspectiveCamera, DirectionalLight, HemisphereLight, DirectionalLightHelper, HemisphereLightHelper } from 'three'
        import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'
        import { OrbitControls } from './jsm/controls/OrbitControls.js'

        let scene, renderer, camera, directionalLight, hemisphereLight, dhelper, hHelper, controls

        let isLoading = true
        let loadingWidth = 0


        //创建场景
        const setScene = () => {
            scene = new Scene()
            renderer = new WebGLRenderer()
            renderer.setSize(window.innerWidth, window.innerHeight)
            document.body.appendChild(renderer.domElement)
        }

        //相机的默认坐标
        const defaultMap = {
            x: 0,
            y: 10,
            z: 20,
        }
        //创建相机  
        const setCamera = () => {
            const { x, y, z } = defaultMap
            camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
            camera.position.set(x, y, z)
        }

        // 设置灯光
        const setLight = () => {
            directionalLight = new DirectionalLight(0xffffff, 0.5)
            directionalLight.position.set(-4, 8, 4)
            dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
            hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
            hemisphereLight.position.set(0, 8, 0)
            hHelper = new HemisphereLightHelper(hemisphereLight, 5)
            scene.add(directionalLight)
            scene.add(hemisphereLight)
        }

        //使场景、照相机、模型不停调用
        const loop = () => {
            requestAnimationFrame(loop)
            renderer.render(scene, camera)
            controls.update()
        }

        // 设置模型控制
        const setControls = () => {
            // 创建一个新的 OrbitControls 对象,并将它绑定到相机 camera 和渲染器的 DOM 元素 renderer.domElement 上
            controls = new OrbitControls(camera, renderer.domElement)
            // 设置相机的最大仰角(上下旋转角度),这里将其限制为 0.9 * π / 2
            controls.maxPolarAngle = 0.9 * Math.PI / 2
            //启用相机的缩放功能,允许用户通过鼠标滚轮或触摸手势进行缩放操作
            controls.enableZoom = true
            //监听控制器的变化事件,当用户操作控制器导致相机位置发生改变时,触发渲染函数 render
            controls.addEventListener('change', render)
        }
        //在相机位置发生变化时,将新的相机位置保存到 defaultMap 对象中
        const render = () => {
            defaultMap.x = Number.parseInt(camera.position.x)
            defaultMap.y = Number.parseInt(camera.position.y)
            defaultMap.z = Number.parseInt(camera.position.z)
        }

        //通过Promise处理一下loadfile函数
        const loader = new GLTFLoader() //引入模型的loader实例
        const loadFile = (url) => {
            return new Promise(((resolve, reject) => {
                // loader.load 用来加载和解析 glTF 文件
                loader.load(url,
                    (gltf) => {
                        resolve(gltf)
                    }, ({ loaded, total }) => {
                        let load = Math.abs(loaded / total * 100)
                        loadingWidth = load
                        if (load >= 100) {
                            setTimeout(() => {
                                isLoading = false
                            }, 1000)
                        }
                        console.log((loaded / total * 100) + '% loaded')
                    },
                    (err) => {
                        reject(err)
                    }
                )
            }))
        }

        (async function () {
            setScene()
            setCamera()
            setLight()
            setControls()
            const gltf = await loadFile('./assets/scene.gltf')
            scene.add(gltf.scene)
            loop()
        })()

    </script>
</body>

</html>

ps: This code is fine and can run normally. The first two or three may have some missing introductions or missing declaration variables. Please refer to this to fill in, and I will not check for omissions and fill in vacancies

change body color

scene There is a traversefunction that calls back the sub-model information of all models. As long as we find the corresponding name attribute, we can change the color, add textures, etc.

        //设置车身颜色
        const setCarColor = (index) => {
            //Color 是 Three.js 中的一个类,用于表示颜色。它的作用是创建和管理三维场景中物体的颜色
            const currentColor = new Color(colorAry[index])
            // 使用 Three.js 中的 traverse 方法遍历场景中的每个子对象
            scene.traverse(child => {
                if (child.isMesh) {
                    console.log(child)
                    if (child.name) {
                        //将当前子对象的材质颜色设置为 currentColor,实现改变颜色的效果
                        child.material.color.set(currentColor)
                    }
                }
            })
        }

The entire complete code:

<html>

<head>
    <meta charset="utf-8">
    <title>My first three.js app</title>
    <style>
        body {
            margin: 0;
        }

        .maskLoading {
            background: #000;
            position: fixed;
            display: flex;
            justify-content: center;
            align-items: center;
            top: 0;
            left: 0;
            bottom: 0;
            right: 0;
            z-index: 1111111;
            color: #fff;
        }

        .maskLoading .loading {
            width: 400px;
            height: 20px;
            border: 1px solid #fff;
            background: #000;
            overflow: hidden;
            border-radius: 10px;

        }

        .maskLoading .loading div {
            background: #fff;
            height: 20px;
            width: 0;
            transition-duration: 500ms;
            transition-timing-function: ease-in;
        }

        canvas {
            width: 100%;
            height: 100%;
            margin: auto;
        }

        .mask {
            color: #fff;
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
        }

        .flex {
            display: flex;
            flex-wrap: wrap;
            padding: 20px;

        }

        .flex div {
            width: 10px;
            height: 10px;
            margin: 5px;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div class="boxs">
        <div class="maskLoading">
            <div class="loading">
                <div class="oneDiv"></div>
            </div>
            <div style="padding-left: 10px;" class="twoDiv"></div>
        </div>
        <div class="mask">
            <p class="realTimeDate"></p>
            <button class="rotatingCar">转动车</button>
            <button class="stop">停止</button>
            <div class="flex" id="colorContainer">
            </div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js"
            }
        }
    </script>
    <script type="module">
        import {
            Color,
            DirectionalLight,
            DirectionalLightHelper,
            HemisphereLight,
            HemisphereLightHelper,
            PerspectiveCamera,
            Scene,
            WebGLRenderer
        } from 'three'
        import { GLTFLoader } from './jsm/loaders/GLTFLoader.js'
        import { OrbitControls } from './jsm/controls/OrbitControls.js'

        //车身颜色数组
        const colorAry = [
            "rgb(216, 27, 67)", "rgb(142, 36, 170)", "rgb(81, 45, 168)", "rgb(48, 63, 159)", "rgb(30, 136, 229)", "rgb(0, 137, 123)",
            "rgb(67, 160, 71)", "rgb(251, 192, 45)", "rgb(245, 124, 0)", "rgb(230, 74, 25)", "rgb(233, 30, 78)", "rgb(156, 39, 176)",
            "rgb(0, 0, 0)"]
        let scene, camera, renderer, controls, floor, dhelper, hHelper, directionalLight, hemisphereLight
        let gltf
        let isLoading = true
        let loadingWidth = 0
        //相机的默认坐标
        const defaultMap = {
            x: 0,
            y: 10,
            z: 20,
        }
        //遮罩层
        const maskLayer = () => {
            if (isLoading) {
                $('.maskLoading').hide();
            } else {
                $('.maskLoading').show()
            }
        }
        maskLayer()
        // 进度
        const schedule = () => {
            let timer = setInterval(function () {
                $('oneDiv').css('width', `${loadingWidth}%`);
                $('twoDiv').text(`${loadingWidth}%`);
                if (loadingWidth == 100) {
                    clearInterval(timer);
                }
            }, 10);
        }
        schedule()
        //实时更新x,y,z
        const realTime = () => {
            let timer = setInterval(function () {
                $('realTimeDate').text(`x:${defaultMap.x} y:${defaultMap.y} z:${defaultMap.z}`);
            }, 10);
        }
        // 生成颜色旋转块
        $.each(colorAry, function (index, item) {
            $('<div>').appendTo('#colorContainer') // 在 #colorContainer 中创建一个 <div> 元素
                .css('background-color', item) // 设置背景颜色
                .click(function () {
                    setCarColor(index); // 调用 setCarColor 函数并传递索引参数
                });
        });
        //创建场景
        const setScene = () => {
            scene = new Scene()
            renderer = new WebGLRenderer()
            renderer.setSize(window.innerWidth, window.innerHeight)
            document.body.appendChild(renderer.domElement)
        }
        //创建相机  
        const setCamera = () => {
            const { x, y, z } = defaultMap
            camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
            camera.position.set(x, y, z)
        }

        //引入模型的loader实例
        const loader = new GLTFLoader()
        //通过Promise处理一下loadfile函数
        const loadFile = (url) => {
            return new Promise(((resolve, reject) => {
                loader.load(url,
                    (gltf) => {
                        resolve(gltf)
                    }, ({ loaded, total }) => {
                        let load = Math.abs(loaded / total * 100)
                        loadingWidth = load
                        if (load >= 100) {
                            setTimeout(() => {
                                isLoading = false
                            }, 1000)
                        }
                        console.log((loaded / total * 100) + '% loaded')
                    },
                    (err) => {
                        reject(err)
                    }
                )
            }))
        }
        // 设置灯光
        const setLight = () => {
            directionalLight = new DirectionalLight(0xffffff, 0.8)
            directionalLight.position.set(-4, 8, 4)
            dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
            hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
            hemisphereLight.position.set(0, 8, 0)
            hHelper = new HemisphereLightHelper(hemisphereLight, 5)
            scene.add(directionalLight)
            scene.add(hemisphereLight)
        }
        // 设置模型控制
        const setControls = () => {
            controls = new OrbitControls(camera, renderer.domElement)
            controls.maxPolarAngle = 0.9 * Math.PI / 2
            controls.enableZoom = true
            controls.addEventListener('change', render)
        }
        //返回坐标信息
        const render = () => {
            defaultMap.x = Number.parseInt(camera.position.x)
            defaultMap.y = Number.parseInt(camera.position.y)
            defaultMap.z = Number.parseInt(camera.position.z)
        }

        (async function () {
            setScene()
            setCamera()
            setLight()
            setControls()
            try {
                gltf = await loadFile('./assets/scene.gltf');
                console.log('Model loading completed:', gltf);
            } catch (error) {
                console.error('Error loading model:', error);
            }
            scene.add(gltf.scene)
            loop()
        })()
        //使场景、照相机、模型不停调用
        const loop = () => {
            requestAnimationFrame(loop)
            renderer.render(scene, camera)
            controls.update()
        }
        //是否自动转动
        $('.rotatingCar').click(function () {
            console.log("旋转")
            controls.autoRotate = true
        })

        //停止转动
        $('.stop').click(function () {
            console.log("停止")
            controls.autoRotate = false
        })

        //设置车身颜色
        const setCarColor = (index) => {
            const currentColor = new Color(colorAry[index])
            scene.traverse(child => {
                if (child.isMesh) {
                    console.log(child)
                    if (child.name) {
                        child.material.color.set(currentColor)
                    }
                }
            })
        }

    </script>
</body>

</html>

End sprinkle flowers *★,°*:.☆( ̄▽ ̄)/$:*.°★*. 

Guess you like

Origin blog.csdn.net/weixin_52479803/article/details/132190710