• threejs视频教程学习(5):水天一色小岛


    前言

    内容来源:老陈threejs入门教程

    陈老师讲的还是很不错的,贴图材料可以加视频里的qq群,或者用我下载的这个(微云):https://share.weiyun.com/6uA7pnYc

    基础内容

    <template>
        <div class="container" id="container">
        </div>
    </template>
    
    <script setup lang="ts">
    // 引入threejs
    import * as THREE from 'three';
    // 导入轨道控制器,模块化开发导入的是jsm不是js
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    import { onMounted } from 'vue';
    
    onMounted(() => {
        // 创建场景
        const scene = new THREE.Scene();
        // 创建透视相机,通过相机来观察
        const camera = new THREE.PerspectiveCamera(
            75, // 角度
            window.innerWidth / window.innerHeight, // 长宽比
            1, // 近端
            2000 // 远端
        );
        // 设置相机的位置
        camera.position.set(-50, 50, 130);
        // 设置摄像机视锥体的长宽比
        camera.aspect = window.innerWidth / window.innerHeight;
        // 更新摄像头投影矩阵
        camera.updateProjectionMatrix();
        // 将相机添加到场景中
        scene.add(camera);
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({
            antialias: true // 抗锯齿
        });
        // 设置渲染的尺寸
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 设置渲染的输出编码
        renderer.outputEncoding = THREE.sRGBEncoding;
        // 将wbgl渲染的canvas内容添加到dom元素中
        document.getElementById('container')?.appendChild(renderer.domElement);
        // 使用渲染器,通过相机将场景渲染进来
        renderer.render(scene, camera);
        // 监听平面大小的改变,修改相机的比例、渲染器的宽高
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        // 创建轨道控制器
        const controls = new OrbitControls(camera, renderer.domElement);
        // 设置控制器阻尼,让物体拥有惯性,必须在动画循环里调用update()
        controls.enableDamping = true;
        // 添加坐标辅助器
        const axesHelper = new THREE.AxesHelper(10);
        scene.add(axesHelper);
        // 创建一个渲染函数,当场景发生变化后重新渲染
        const render = () => {
            controls.update();
            renderer.render(scene, camera);
            // 使用浏览器自带的请求动画帧函数不断的进行渲染
            requestAnimationFrame(render);
        };
    
        render();
       /** ******************创建物体的相关逻辑开始**************************** */
    
     /** ********************创建物体的相关逻辑结束********************************/
    });
    
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    天空

    天空是通过创建一个大型的球体,然后添加天空贴图来实现的。

     // 创建纹理加载器
     const textureLoader = new THREE.TextureLoader();
     // 创建天空球
     const skyGeometry = new THREE.SphereGeometry(1000, 60, 60);
     const skyTexture = textureLoader.load('../../../../public/textures/sky.jpg'); // 天空纹理
     const skyMaterial = new THREE.MeshBasicMaterial({
         map: skyTexture // 颜色贴图
     });
     const sky = new THREE.Mesh(skyGeometry, skyMaterial);
     scene.add(sky);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    将球体反转

    将球体进行反转,使其球里面是亮的外面是黑的

    // 反转球体,主要是反转z轴
    skyGeometry.scale(1, 1, -1);
    
    • 1
    • 2

    在这里插入图片描述
    注: 天空贴图有点问题,不是完全的360度,会出现重合的问题
    在这里插入图片描述
    将天空贴图替换为视频纹理
    图片中云是静态的,可以通过替换为视频纹理来解决这个问题。

     // 添加视频纹理
     const skyVideo = document.createElement('video');
     skyVideo.src = '../../../../public/textures/sky.mp4';
     skyVideo.loop = true; // 设置视频循环播放
    
     // 添加鼠标点击事件,当鼠标点击时开始播放视频
     window.addEventListener('click', () => {
         if (skyVideo.paused && skyVideo.readyState === 4) {
             skyVideo.play();
             // 更新材质
             skyMaterial.map = new THREE.VideoTexture(skyVideo);
             skyMaterial.map.needsUpdate = true;
         }
     });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    水面和小岛

    创建水面

    // 导入水面
    import { Water } from 'three/examples/jsm/objects/Water2';
    
     // 创建水面
     const waterGeometry = new THREE.CircleGeometry(300, 30);
     const water = new Water(waterGeometry, {
         // 材质宽高
         textureWidth: 1024,
         textureHeight: 1024,
         // 水面颜色
         color: 0xeeeeff,
         // 水面流动的方向
         flowDirection: new THREE.Vector2(1, 1),
         // 波纹大小
         scale: 1
     });
     // 水面旋转至水平
     water.rotation.x = -Math.PI / 2;
     // 添加水面
     scene.add(water);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    感觉我这个水面乖乖的
    在这里插入图片描述

    添加小岛

    // 导入gltf模型加载库
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
    // 导入模型解压库
    import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
    
     // 添加小岛
     const loader = new GLTFLoader();
     const dracoLoader = new DRACOLoader();
     // 设置路径
     dracoLoader.setDecoderPath('../../../../public/draco/');
     // 设置加载器
     loader.setDRACOLoader(dracoLoader);
     loader.load('../../../../public/island2.glb', gltf => {
         scene.add(gltf.scene);
     });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    场景是黑色的是因为我们没有设置环境纹理,没有环境反射的光,所以看不见场景。另外我们的天空贴图只是普通的图片,是无法反射光的,还需要导入hdr的环境纹理

    import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
    
    // 导入hdr环境纹理
    const hdrLoader = new RGBELoader();
    hdrLoader.loadAsync('../../../../public/050.hdr')
     .then((texture) => {
         // 设置纹理映射
         texture.mapping = THREE.EquirectangularReflectionMapping;
         // 设置环境背景和环境纹理
         scene.background = texture;
         scene.environment = texture;
     });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    添加平行光
    添加平行光,让模型更亮一点。如果只添加平行光不添加环境光也可以看见模型,但是平行光照射不到的地方,模型依然是黑色的,因此环境光还是很重要的。

     // 添加平行光
     const light = new THREE.DirectionalLight(0xffffff, 1);
     light.position.set(100, 100, 10);
     scene.add(light);
    
    • 1
    • 2
    • 3
    • 4

    最终效果

    总感觉怪怪的,好像是电脑渲染的问题吧,显得有点呆😳
    在这里插入图片描述
    完整代码

    <template>
        <div class="container" id="container">
        </div>
    </template>
    
    <script setup lang="ts">
    import { onMounted } from 'vue';
    // 引入threejs
    import * as THREE from 'three';
    // 导入轨道控制器,模块化开发导入的是jsm不是js
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    // 导入水面
    import { Water } from 'three/examples/jsm/objects/Water2';
    // 导入gltf模型加载库
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
    // 导入模型解压库
    import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
    
    import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader';
    
    onMounted(() => {
        // 创建场景
        const scene = new THREE.Scene();
        // 创建透视相机,通过相机来观察
        const camera = new THREE.PerspectiveCamera(
            75, // 角度
            window.innerWidth / window.innerHeight, // 长宽比
            1, // 近端
            2000 // 远端
        );
        // 设置相机的位置
        camera.position.set(-50, 50, 130);
        // 设置摄像机视锥体的长宽比
        camera.aspect = window.innerWidth / window.innerHeight;
        // 更新摄像头投影矩阵
        camera.updateProjectionMatrix();
        // 将相机添加到场景中
        scene.add(camera);
        // 初始化渲染器
        const renderer = new THREE.WebGLRenderer({
            antialias: true, // 抗锯齿
            logarithmicDepthBuffer: true // 对数深度缓冲区
        });
        // 设置渲染的尺寸
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 设置渲染的输出编码
        renderer.outputEncoding = THREE.sRGBEncoding;
        // 将wbgl渲染的canvas内容添加到dom元素中
        document.getElementById('container')?.appendChild(renderer.domElement);
        // 使用渲染器,通过相机将场景渲染进来
        renderer.render(scene, camera);
        // 监听平面大小的改变,修改相机的比例、渲染器的宽高
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        });
        // 创建轨道控制器
        const controls = new OrbitControls(camera, renderer.domElement);
        // 设置控制器阻尼,让物体拥有惯性,必须在动画循环里调用update()
        controls.enableDamping = true;
        // 添加坐标辅助器
        // const axesHelper = new THREE.AxesHelper(100);
        // scene.add(axesHelper);
    
        // 创建一个渲染函数,当场景发生变化后重新渲染
        const render = () => {
            controls.update();
            renderer.render(scene, camera);
            // 使用浏览器自带的请求动画帧函数不断的进行渲染
            requestAnimationFrame(render);
        };
        render();
    
        /** ******************创建物体的相关逻辑开始**************************** */
        // 创建纹理加载器
        const textureLoader = new THREE.TextureLoader();
        // 创建天空球
        const skyGeometry = new THREE.SphereGeometry(1000, 60, 60);
        const skyTexture = textureLoader.load('../../../../public/textures/sky.jpg'); // 天空纹理
        const skyMaterial = new THREE.MeshBasicMaterial({
            map: skyTexture // 颜色贴图
        });
        // 反转球体,主要是反转z轴
        skyGeometry.scale(1, 1, -1);
        const sky = new THREE.Mesh(skyGeometry, skyMaterial);
        scene.add(sky);
    
        // 添加视频纹理
        const skyVideo = document.createElement('video');
        skyVideo.src = '../../../../public/textures/sky.mp4';
        skyVideo.loop = true; // 设置视频循环播放
    
        // 添加鼠标点击事件,当鼠标点击时开始播放视频
        window.addEventListener('click', () => {
            if (skyVideo.paused && skyVideo.readyState === 4) {
                skyVideo.play();
                // 更新材质
                const texture = new THREE.VideoTexture(skyVideo);
                skyMaterial.map = texture;
                skyMaterial.map.needsUpdate = true;
            }
        });
    
        // 导入hdr环境纹理
        const hdrLoader = new RGBELoader();
        hdrLoader.loadAsync('../../../../public/050.hdr')
            .then((texture) => {
                // 设置纹理映射
                texture.mapping = THREE.EquirectangularReflectionMapping;
                // 设置环境背景和环境纹理
                scene.background = texture;
                scene.environment = texture;
            });
        // 添加平行光
        const light = new THREE.DirectionalLight(0xffffff, 1);
        light.position.set(100, 100, 10);
        scene.add(light);
    
        // 创建水面
        const waterGeometry = new THREE.CircleGeometry(300, 30);
        const water = new Water(waterGeometry, {
            // 材质宽高
            textureWidth: 1024,
            textureHeight: 1024,
            // 水面颜色
            color: 0xeeeeff,
            // 水面流动的方向
            flowDirection: new THREE.Vector2(1, 1),
            // 波纹大小
            scale: 1
        });
        // 水面旋转至水平
        water.rotation.x = -Math.PI / 2;
        // 调整水面的位置,使水面能够掩盖一部分模型
        water.position.y = 3;
        // 添加水面
        scene.add(water);
    
        // 添加小岛
        const loader = new GLTFLoader();
        const dracoLoader = new DRACOLoader();
        // 设置路径
        dracoLoader.setDecoderPath('../../../../public/draco/');
        // 设置加载器
        loader.setDRACOLoader(dracoLoader);
        loader.load('../../../../public/island2.glb', gltf => {
            scene.add(gltf.scene);
        });
        /** ********************创建物体的相关逻辑结束********************************/
    });
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
  • 相关阅读:
    深入探讨I/O模型:Java中的阻塞和非阻塞和其他高级IO应用
    艾美捷ICT FLICA天冬氨酸蛋白酶(Caspase)活性检测试剂盒说明书
    springboot网络招聘服务系统毕业设计源码121727
    python基于PHP+MySQL家政管理系统的设计与实现
    c# xml 参数读取读取的简单使用
    Java 变量作用域、构造方法官方教程
    解析Vue3源码(二)——ref
    基于springboot的张仲景药房(药店)管理系统
    二手车价格预测 | 构建AI模型并部署Web应用 ⛵
    小白学java进阶工程师路线图
  • 原文地址:https://blog.csdn.net/weixin_41897680/article/details/127719164