threejs学习笔记-01

开源库下载地址:https://github.com/mrdoob/three.js

官方文档的src目录里面包括了threejs的所有实现代码和接口文件,实际项目中可以只拷贝这个,docs目录里包含了threejs的所有帮助文档,example包含了threejs的所有样例程序,editor包含了threejs自带的场景编辑工具,其他文档暂时用不到先不介绍。

scene–场景是一个容器,主要用于保存、跟踪所要渲染的物体和使用的光源,如果没有场景就无法渲染任何物体。

camera–摄像机决定了我们在场景中能看到什么,我们基于摄像机的角度来计算场景对象在浏览器中会渲染成什么样子,摄像机有透视摄像机和正交投影摄像机。

render–渲染器有很多种,下面先用一种最简单的试试。

1.渲染第一个三维对象

<!DOCTYPE html>//渲染第一个三维对象
<html>

<head>
    <title>threejs-002</title>
    <meta charset="UTF-8" />
    <style>
        body {
    
    
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <!--我们将把threejs渲染的效果显示在这个div中-->
    <div id="webgl-output">

    </div>

    <script type="module">//javascript加type="module"的作用是允许执行导入导出操作export import
        //我们需要使用哪个对象类型关键词,就在代码最上面将该关键词导入,比如这里就是导入场景、透视摄像机、渲染器、正方形几何体、网格基础材质、网格关键词,from后面写上threejs的路径,我们可以将官方文档的src目录拷贝到自己的工程依赖库,这里的路径就是src目录下的three.js
        import {
    
     Scene, PerspectiveCamera, WebGLRenderer, BoxGeometry, MeshBasicMaterial, Mesh } from "../../libs/three.js/three.js";

        var scene = new Scene();//创建一个场景对象
        var camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);//创建一个透视的摄像机,初始化参数中第二个参数为宽高比,整个页面的宽度除以高度

        var render = new WebGLRenderer();//WebGLRenderer只是其中一种渲染器
        render.setSize(window.innerWidth, window.innerHeight);//将渲染器大小设置为页面窗口大小 window.innerWidth,window.innerHeight是整个页面的宽度和高度
        document.getElementById("webgl-output").appendChild(render.domElement);//我们需要把这个渲染器中的DOM元素对象添加到指定的div中去,通过id号获取div,然后再往这个dom元素中追加渲染器的dom元素作为他的儿子

        var geometry = new BoxGeometry();//创建一个立方几何体,threejs中有很多内置的几何体,也可以自己定义几何体
        var material = new MeshBasicMaterial({
    
     color: 0x0000ff });//创造一个网格基础材质,并设置颜色,这里是最基础的材质,颜色的格式为RGB16进制,一共是6位,前面2位代表红色,中间俩位代表绿色,最后俩位代表蓝色
        var cube = new Mesh(geometry, material);//把立方几何体与基础材质进行组合后创建出一个新的网格对象
        scene.add(cube);//把立方体网格添加到场景中

        camera.position.z = 5;//设置透视摄像机在Z轴上的距离,也就是它与我们屏幕的距离

        //再让立方体旋转一下,感受一下它是一个立体,沿着x轴y轴旋转一定的角度
        cube.rotation.x += 0.50;
        cube.rotation.y += 0.50;

        render.render(scene, camera);//最后一步,将场景和摄像机俩个对象传入渲染器中

    </script>

</body>

</html>

渲染效果图:
请添加图片描述
2.添加threejs坐标轴、光源、阴影效果

<!DOCTYPE html>
<html>

<head>
    <title>threejs-004</title>
    <meta charset="UTF-8" />
    <style>
        body {
    
    
            margin: 0;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <!--我们将把threejs渲染的效果显示在这个div中-->
    <div id="webgl-output">

    </div>

    <script type="module">
        import {
    
    
            Scene, PerspectiveCamera, WebGLRenderer, BoxGeometry,
            MeshBasicMaterial, Mesh, PlaneGeometry, MeshLambertMaterial,
            AmbientLight, SpotLight, Vector2, AxesHelper, Color
        } from "../../libs/three.js/three.js";//若有新关键词,别忘记加入导入列表中
        var scene = new Scene();
        var camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

        var render = new WebGLRenderer();
        render.setClearColor(new Color(0x000000));//设置渲染器颜色为黑色,即整个屏幕背景是黑色的,新建渲染器默认颜色就是黑色的
        render.setSize(window.innerWidth, window.innerHeight);
        render.shadowMap.enabled = true;//为了实现阴影效果,必须在渲染器的shadowMap中的enabled属性设置为真
        document.getElementById("webgl-output").appendChild(render.domElement);

        //上来先添加一个三维坐标轴以方便后面对具体位置属性的理解
        var axes = new AxesHelper(50);//参数代表轴的长度,默认为1,我们设置为50 默认红绿蓝色线条对应XYZ轴
        //这个轴对象还可以自定义轴的颜色 三个参数格式为16进制RGB值 比如:
        //axes.setColors(0x000000,0xFFFFFF,0xAAAAAA);
        scene.add(axes);//把轴添加到场景对象中去

        var geometry = new BoxGeometry(8, 8, 8);//把立方体放大一些方便我们观察
        var material = new MeshLambertMaterial({
    
     color: 0xff2288 });//为了实现阴影效果,要将立方体的材质修改为Lmbert
        var cube = new Mesh(geometry, material);
        cube.castShadow = true;//为了实现阴影效果,必须把立方体的castShadow属性设置为真

        //把立方体挪一下位置
        cube.position.x = 10;//往X轴右侧移动10个单位
        cube.position.y = 8;//沿Y轴向上移动8个单位
        cube.position.z = -10;//沿Z轴向后移动10个单位

        scene.add(cube);

        //为了实现阴影效果,我们要创建一个地面来接受立方体的投影
        var planeGeometry = new PlaneGeometry(100, 100);//新建一个平面几何体,宽高为100
        var planeMaterial = new MeshLambertMaterial({
    
     color: 0xcccccc });//新建一个Lambert材质,指定它的颜色,这个材质可以接受并反射场景中各种光源发射出来的光线,之前声明立方体的基础材质与它不同,各种光源对基础材质是没有作用的,所以我们需要把前面的声明立方体的基础材质也修改为Lambert材质
        var plane = new Mesh(planeGeometry, planeMaterial);//通过平面几何体对象和Lambert材质来新建一个地面网格对象

        plane.rotation.x = -0.5 * Math.PI;//将地面网格沿着X轴往里旋转90度
        plane.position.set(15, 0, 0);//设地面网格的位置为X轴向右15个单位,Y与Z轴保持初始值
        plane.receiveShadow = true;//作为地面接受阴影的对象,必须设置receiveShadow的属性为真

        scene.add(plane);//把地面网格对象添加到场景中

        //因为我们摄像机的机位正好在Z轴上,所以我们需要把摄像机的机位移动一下
        //否则我们只能看到X和Y轴 
        camera.position.x = 30;//ps:坐标轴值也可以赋成负数,代表数轴负数方向
        camera.position.y = 40;
        camera.position.z = 30;
        //X轴向右代表正数,向左代表负数 
        //Y轴向上代表正数,向下代表负数
        //Z轴从我们的视角看向电脑屏幕,靠近我们的代表正数,远离我们的代表负数
        camera.lookAt(scene.position);//把摄像机的方向对准场景的中心点,这样我们就可以看到XYZ轴了

        cube.rotation.x += 0.80;
        cube.rotation.y += 0.80;

        //要生成投影的效果,我们要有产生投影的光源,这里我们生成一个聚光灯光源对象
        var spotLight = new SpotLight(0xFFFFFF);//设置聚光灯的颜色为白色,这样会让它射到的空间显得更亮一些
        spotLight.position.set(-60, 40, -65);//设置聚光灯的位置在离我们更远的左上角
        spotLight.castShadow = true;//要想让聚光灯产生阴影,必须设置castShadow属性为真
        spotLight.shadow.mapSize = new Vector2(1024, 1024);//下面的三行代码用于设置阴影效果
        spotLight.shadow.camera.far = 130;
        spotLight.shadow.camera.near = 40;

        // If you want a more detailled shadow you can increase the 
        // mapSize used to draw the shadows.
        // spotLight.shadow.mapSize = new THREE.Vector2(1024, 1024);
        scene.add(spotLight);//将聚光灯添加到场景

        //我们采用Lambert材质创建的地面网格,所以必须要有一个Lambert光源才能看得到,之前的立方体可以正常显示是因为它采用的是基础材质,不需要光源就能显示
        var ambienLight = new AmbientLight(0xcccccc);//新建一个AmbientLight光源对象,颜色设置为灰色,它的颜色会应用到整个场景中的对象,该光源并没有特殊来源方向,并且不会生成阴影,所以它不能作为唯一光源来使用
        scene.add(ambienLight);//将光源添加到场景

        render.render(scene, camera);

    </script>

</body>

</html>

渲染效果图:
请添加图片描述
3.添加threejs动态、简化试验、自适应场景

<!DOCTYPE html>
<html>

<head>
    <title>threejs-005</title>
    <meta charset="UTF-8" />
    <style>
        body {
    
    
            margin: 0;
            overflow: hidden;
        }
    </style>

</head>

<body>
    <!--我们将把threejs渲染的效果显示在这个div中-->
    <div id="webgl-output">

    </div>
    <!--添加一个div用来显示动画运行时的效率-->
    <div id="myStats"></div>

    <script type="module">
        import {
    
    
            Scene, PerspectiveCamera, WebGLRenderer, BoxGeometry,
            MeshBasicMaterial, Mesh, PlaneGeometry, MeshLambertMaterial,
            AmbientLight, SpotLight, Vector2, AxesHelper, Color
        } from "../../libs/three.js/three.js"

        //为了给接下来的动画显示运行时的效率,我们需要引入一个辅助库
        import {
    
     Stats } from "../../libs/Stats/Stats.js"//Stats库主要用于检测动画运行时的帧数

        import {
    
     dat } from "../../libs/dat.gui/dat.gui.js"//引入gui库更快的实现动画效果

        var scene = new Scene();
        var camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

        var render = new WebGLRenderer();
        render.setClearColor(new Color(0x000000));
        render.setSize(window.innerWidth, window.innerHeight);
        render.shadowMap.enabled = true;
        document.getElementById("webgl-output").appendChild(render.domElement);

        var axes = new AxesHelper(50);
        scene.add(axes);

        var geometry = new BoxGeometry(8, 8, 8);
        var material = new MeshLambertMaterial({
    
     color: 0xff2288 });
        var cube = new Mesh(geometry, material);
        cube.castShadow = true;

        cube.position.x = 4;
        cube.position.y = 8;
        cube.position.z = 10;

        scene.add(cube);

        var planeGeometry = new PlaneGeometry(100, 100);
        var planeMaterial = new MeshLambertMaterial({
    
     color: 0xAAAAAA });
        var plane = new Mesh(planeGeometry, planeMaterial);

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.set(15, 0, 0);
        plane.receiveShadow = true;

        scene.add(plane);

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        cube.rotation.x += 0.80;
        cube.rotation.y += 0.80;

        var spotLight = new SpotLight(0xFFFFFF);
        spotLight.position.set(-60, 40, -65);
        spotLight.castShadow = true;
        spotLight.shadow.mapSize = new Vector2(1024, 1024);
        spotLight.shadow.camera.far = 130;
        spotLight.shadow.camera.near = 40;

        scene.add(spotLight);

        var ambienLight = new AmbientLight(0xcccccc);
        scene.add(ambienLight);

        var stats = addStats();//获取动画运行效率统计对象,函数实现写在下面

        var ctrlObj = {
    
     rotationSpeed: 0.01, jumpSpeed: 0.01 };//为实现运动效果,我们需要一个对象来保存我们修改的属性值,其有俩个属性值,一个保存正方体旋转速度,一个保存正方体跳跃速度,初始值(速度)为0.1
        var ctrl = new dat.GUI();//创造一个dat.gui对象,然后将前面用于保存属性的对象传入该对象中
        ctrl.add(ctrlObj, "rotationSpeed", 0, 1);//并且对速度属性进行范围设置,范围是0到1
        ctrl.add(ctrlObj, "jumpSpeed", 0, 1);

        // render.render(scene,camera);
        renderScene();//将render方法替换成renderScene方法

        var gap = 0;
        function renderScene() {
    
    //为了刷新页面,我们封装一个刷新页面的方法 同时在该方法中实现立方体的旋转和跳跃
            // cube.rotation.x += 0.01;实现立方体的旋转效果很简单,只需要在每次调用renderScene方法时对rotation属性值进行修改
            // cube.rotation.y += 0.01;每次增加0.01,这是立方体旋转的速度,我们通过调试可以找到一个适合的速度
            // cube.rotation.z += 0.01;这里我们使用gui库实现,省的慢慢调试,同时固定速度也变成在页面上显示为可调节的一定范围内的速度,即速度可由用户修改

            cube.rotation.x += ctrlObj.rotationSpeed;
            cube.rotation.y += ctrlObj.rotationSpeed;
            cube.rotation.z += ctrlObj.rotationSpeed;

            //再来实现跳跃动态效果 gap存放跳跃的速度 通过position属性来实现立方体跳跃 在XY轴方向上进行位置变化 gap += 0.01
            gap += ctrlObj.jumpSpeed;//通过对gap值的修改实现位置的变化,这里同样使用更简单的gui库

            cube.position.x = 25 + (20 * (Math.sin(gap)));//通过Math.sin()方法创建正方体在x轴方向上的运行轨迹
            cube.position.y = 6 + (20 * Math.abs(Math.cos(gap)));//通过Math.cos()方法创建正方体在y轴方向上的运行轨迹

            stats.update();//为了通知stats统计对象需要被刷新,我们需要在该方法中调用stats的update方法
            requestAnimationFrame(renderScene);//就是这个方法能使得浏览器平滑高效的进行绘制,这个函数我个人理解是不断调用这个renderScene函数来获取每个动画帧以此达到刷新的目的
            render.render(scene, camera);//为了在场景创建完毕后不在调用render方法,而是调用renderSence方法,我们需要在renderSence中调用render方法
        }

        function addStats() {
    
    //引入Stats库我们需要添加统计对象,因此封装一个addStats方法
            var stats = new Stats();//创建一个统计对象
            stats.domElement.style.position = 'absolute';//初始化统计对象的位置
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            stats.setMode(0);//因为我们想要检测画面每秒传输帧数,所以我们需要调用setMode方法,参数设置为0;我们也可以通过修改setMode方法的参数直接显示渲染时间

            document.getElementById("myStats").appendChild(stats.domElement);//将创建的统计对象元素放置在我们的div上
            return stats;//返回该统计对象 方便后序调用
        }

        //下面我们要求通过浏览器的缩放来查看三维场景时,场景要能根据浏览器窗口大小进行自动适应功能.首先要监听浏览器的resize事件,并且通过回调函数来重写事件
        window.addEventListener('resize', onWindowResize, false);

        function onWindowResize() {
    
    //我们需要在回调函数中重新设置camera的aspect属性,用于重新确定浏览器需要渲染的长宽比
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();//修改完aspect,我们要调用这个方法以此来手动更新相机的投影矩阵
            render.setSize(window.innerWidth, window.innerHeight);//最后通过render的setSize方法重置场景的渲染尺寸
        }

    </script>

</body>

</html>

渲染效果图:
请添加图片描述
4.threejs中scene的基本方法和属性

<!DOCTYPE html>
<html>

<head>
    <title>threejs-006</title>
    <meta charset="UTF-8" />
    <style>
        body {
    
    
            margin: 0;
            overflow: hidden;
        }
    </style>

</head>

<body>
    <div id="webgl-output">

    </div>

    <div id="myStats"></div>

    <script type="module">
        import {
    
    
            Scene, PerspectiveCamera, WebGLRenderer, BoxGeometry,
            MeshBasicMaterial, Mesh, PlaneGeometry, MeshLambertMaterial,
            AmbientLight, SpotLight, Vector2, AxesHelper, Color, Fog, FogExp2
        } from "../../libs/three.js/three.js"

        import {
    
     Stats } from "../../libs/Stats/Stats.js"

        import {
    
     dat } from "../../libs/dat.gui/dat.gui.js"

        var scene = new Scene();
        //了解一下场景的fog属性,其可以为整个场景添加雾化效果,雾化效果就是场景中的物体离的越远就会变得越模糊
        //下面是第一种雾化方式,引入一个新的类型Fog型,创建该类型对象要三个参数,第一个是雾化的颜色,二三分布是雾化近处和远处的值
        scene.fog = new Fog(0xff0000, 1, 100);

        //下面是第二种雾化方式,引入一个新的类型FogExp2型,俩个参数,第一个是雾化的颜色,第二个是雾化的浓度
        //scene.fog = new FogExp2(0x0000ff,0.02);

        //下面介绍场景的overrideMaterial属性,该属性可以强制场景中所有物体采用相同的材质
        //这里我们为场景的overrideMaterial属性赋上了一个eshLambertMaterial材质,且使用蓝色
        //scene.overrideMaterial = new MeshLambertMaterial({color:0x0000ff});
        var camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

        var render = new WebGLRenderer();
        render.setClearColor(new Color(0x000000));
        render.setSize(window.innerWidth, window.innerHeight);
        render.shadowMap.enabled = true;

        document.getElementById("webgl-output").appendChild(render.domElement);

        var axes = new AxesHelper(50);
        scene.add(axes);

        var geometry = new BoxGeometry(8, 8, 8);
        var material = new MeshLambertMaterial({
    
     color: 0xff2288 });
        var cube = new Mesh(geometry, material);
        cube.castShadow = true;

        cube.position.x = 4;
        cube.position.y = 8;
        cube.position.z = 10;

        scene.add(cube);//再前面的课程中我们已经使用过场景的add方法,现在我们再往场景中添加一个正方体

        var geometry2 = new BoxGeometry(4, 4, 4);//先创建一个长宽高均为4的几何体
        var material2 = new MeshLambertMaterial({
    
     color: 0x00ff00 });//接着创建其外观材质,我们想要其呈现绿色的效果
        var cube2 = new Mesh(geometry2, material2);//然后将几何体和材质合并成能够添加到场景中的网格
        cube2.castShadow = true;//我们希望该正方体也能够有阴影效果,所以将castShadow设置为真

        cube2.position.x = -10;//接下来再调整一下其在场景中的位置
        cube2.position.y = 10;
        cube2.position.z = 0;
        cube2.name = "cube2";
        scene.add(cube2);//调用场景的add方法将合并的网格放入场景

        //ps:我们可以用console.log(scene.children.length);来输出此时场景中有几个对象,不同位置输出的数目不同,比如这个代码段之前没有声明光源,那么就不会把光源这个对象包含进去

        var findObj = scene.getObjectByName("cube2", false);//场景的这个方法通过指定唯一的标志name来找到特定的对象,第二个参数为false时表示仅在调用者的子元素中查找,为ture时表示在调用者的所有后代对象中查找
        console.log(findObj.position);//可以通过打印一下该对象的相关属性比如位置信息验证一下查找到的对象是不是正确的

        var planeGeometry = new PlaneGeometry(100, 100);
        var planeMaterial = new MeshLambertMaterial({
    
     color: 0xAAAAAA });
        var plane = new Mesh(planeGeometry, planeMaterial);

        plane.rotation.x = -0.5 * Math.PI;
        plane.position.set(15, 0, 0);
        plane.receiveShadow = true;

        scene.add(plane);

        camera.position.x = -30;
        camera.position.y = 40;
        camera.position.z = 30;
        camera.lookAt(scene.position);

        cube.rotation.x += 0.80;
        cube.rotation.y += 0.80;

        var spotLight = new SpotLight(0xFFFFFF);
        spotLight.position.set(-60, 40, -65);
        spotLight.castShadow = true;
        spotLight.shadow.mapSize = new Vector2(1024, 1024);
        spotLight.shadow.camera.far = 130;
        spotLight.shadow.camera.near = 40;

        scene.add(camera);
        scene.add(spotLight);

        var ambienLight = new AmbientLight(0xcccccc);
        scene.add(ambienLight);

        var stats = addStats();


        //下面介绍remove方法,该方法可以将指定对象从场景中删除,现在我想将前面查找到的cube2对象从场景中删除,之前查找到的对象赋值给了findObj.
        //这里为了能够更方便的调用我们封装的方法我们可以依然使用上节课的dat.gui这个库,我们创建一个对象来保存我们封装的方法
        var ctrlObj = new function () {
    
    //我们需要封装一个删除cube2的方法,在封装的方法中通过场景调用remove方法删除查找到的对象
            this.removeFindCube = function () {
    
    //将封装的方法挂载到该对象上
                if (findObj instanceof Mesh)//为了防止将相机和光源错误的删除,我们先要对查找到的对象进行检查,尽管我们前面已经验证过findObj是我们需要删除的对象,但是为了养成良好编码习惯,我们依然要为该对象检查是否为Mesh对象
                {
    
    
                    scene.remove(findObj);//然后通过场景的remove方法将查找到的场景删除
                }

            }

            this.addNewCube = function () {
    
    //封装一个动态添加立方体的方法,同时可以验证下面的traverse方法是否可以对所有场景中的对象进行旋转动画
                var geometryTemp = new BoxGeometry(4, 4, 4);//创建方法基本和上面创建一个正方体网格对象一样
                var materialTemp = new MeshLambertMaterial({
    
     color: 0x0000ff });
                var cubeTemp = new Mesh(geometryTemp, materialTemp);
                cubeTemp.castShadow = true;
                //为了区分场景中其他正方体,我们需要对动态创建的正方体进行一些变化
                cubeTemp.position.x = -Math.random() * 10;//通过随机数对正方体的位置进行调整
                cubeTemp.position.y = Math.random() * 10;
                cubeTemp.position.z = Math.random() * 10;
                scene.add(cubeTemp);
            }
        }

        var ctrl = new dat.GUI();//接下来我们要创建一个dat.GUI对象,然后将上面的对象添加到dat.GUI对象中,将方法在dat.GUI中进行注册
        ctrl.add(ctrlObj, "removeFindCube");//第二个参数需要和我们想要调用的方法同名
        ctrl.add(ctrlObj, "addNewCube");

        // render.render(scene,camera);
        renderScene();
        //console.log(scene.children.length);
        var gap = 0;
        function renderScene() {
    
    
            stats.update();

            //回顾一下上节课动画,想要让正方体旋转,可以对对象的rotation属性进行修改
            // cube.rotation.x += 0.01;//但是这种方法太繁琐了,什么方法可以一劳永逸呢
            // cube.rotation.y += 0.01;//接下来我们介绍场景的traverse方法
            // cube.rotation.z += 0.01;//该方法可以遍历调用者和调用者的所有后代

            // cube2.rotation.x += 0.01;//traverse方法的参数是个函数,被调用者和每个后代对象都会调用该函数
            // cube2.rotation.y += 0.01;
            // cube2.rotation.z += 0.01;

            scene.traverse(function (obj) {
    
    
                if (obj instanceof Mesh && obj != plane)//我们需要和removeFindCube方法一样对对象进行判断,因为我们只需要对其中的立方体进行旋转
                {
    
    //所以我们判断该对象是否为Mesh对象并且该对象不能是地板对象
                    obj.rotation.x += 0.01;//然后对该对象进行旋转
                    obj.rotation.y += 0.01;
                    obj.rotation.z += 0.01;
                }
            })

            requestAnimationFrame(renderScene);
            render.render(scene, camera);
        }

        function addStats() {
    
    
            var stats = new Stats();
            stats.domElement.style.position = 'absolute';
            stats.domElement.style.left = '0px';
            stats.domElement.style.top = '0px';
            stats.setMode(0);

            document.getElementById("myStats").appendChild(stats.domElement);
            return stats;
        }

        window.addEventListener('resize', onWindowResize, false);

        function onWindowResize() {
    
    
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            render.setSize(window.innerWidth, window.innerHeight);
        }
    </script>

</body>

</html>

渲染效果图:
请添加图片描述
5.threejs的几何体和网格(各种几何体、几何体的克隆方法、给几何体添加线条以及对几何体属性的动态控制)

<!DOCTYPE html>
<html>

<head>
    <title>threejs-007</title>
    <meta charset="UTF-8" />
    <!-- <script type="module" charset="UTF-8" src="../../libs/three.js/Three.js"></script> -->
    <style>
        body {
    
    
            margin: 0;
            overflow: hidden;
        }
    </style>

</head>

<body>
    <!--我们将把threejs渲染的效果显示在这个div中-->
    <div id="webgl-output">

    </div>

    <div id="myStats"></div>

    <script type="module">
        import {
    
    Scene,PerspectiveCamera,WebGLRenderer,BoxGeometry,
                MeshBasicMaterial,Mesh,PlaneGeometry,MeshLambertMaterial,
                AmbientLight,SpotLight,Vector2,AxesHelper,Color,Fog,FogExp2,
                TorusGeometry,CylinderGeometry,BufferGeometry,BufferAttribute,
                DoubleSide,WireframeGeometry,LineSegments} from "../../libs/three.js/three.js"

                import {
    
    dat} from "../../libs/dat.gui/dat.gui.js"
                
            var scene = new Scene();
            var camera = new PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000);

            var render = new WebGLRenderer();
            render.setClearColor(new Color(0x000000));
            render.setSize(window.innerWidth,window.innerHeight);
            render.shadowMap.enabled = true;
            
            document.getElementById("webgl-output").appendChild(render.domElement);

            var axes = new AxesHelper(50);
            scene.add(axes);

            var geometry = new BoxGeometry(8,8,8);      //几何体
            var material = new MeshLambertMaterial({
    
    color:0xff2288});   //材质
            var cube = new Mesh(geometry,material);     //网格
            cube.castShadow = true;

            cube.position.x = 34;
            cube.position.y = 8;
            cube.position.z = 10;

            // scene.add(cube); 
			
			//下面学习网格对象的方法和属性 先添加一个对象refcube
            var refGeo = new BoxGeometry(8,8,8);      //几何体
            var refmaterial = new MeshLambertMaterial({
    
    color:0xff2288});   //材质
            var refcube = new Mesh(refGeo,refmaterial);     //网格
            refcube.castShadow = true;

            refcube.position.x = 0;
            refcube.position.y = 10;
            refcube.position.z = 0;
            scene.add(refcube);
            //再添加一个我们控制的对象showcube 为了区分颜色选择不同的
            var showGeo = new BoxGeometry(8,8,8);      //几何体
            var showmaterial = new MeshLambertMaterial({
    
    color:0x00ff00});   //材质
            var showcube = new Mesh(showGeo,showmaterial);     //网格
            showcube.castShadow = true;

            showcube.position.x = 0;
            showcube.position.y = 10;
            showcube.position.z = 0;
            scene.add(showcube);     
            
            var ctrlObj = {
    
    //我们先创建一个对象用于保存网格的属性值
                scaleX:1,//我们先保存一份scale X的属性值 默认值为1
                scaleY:1,//再来保存Y和Z方向的拉伸属性值
                scaleZ:1,//scale表示几何体大小
                positionX:0,//位置信息同理,声明初始值并在下面设置移动范围
                positionY:10,
                positionZ:0,
                rotationX:0,
                rotationY:0,//旋转弧度同理
                rotationZ:0,
                visible:true,//该属性可以控制场景中对象的显示与隐藏
                translateX:0,//通过调用translate函数可以沿着xyz轴将指定对象进行一定距离的平移
                translateY:0,
                translateZ:0,
            }

            var ctrl = new dat.GUI();//再通过dat.GUI对属性进行控制
            ctrl.add(ctrlObj,"scaleX",0,5);//scaleX属性可调范围设为0到5
            ctrl.add(ctrlObj,"scaleY",0,5);//这里不过多介绍,之前章节已经介绍过
            ctrl.add(ctrlObj,"scaleZ",0,5);//我们会在下面的renderScene中使用对象的scale属性中的set方法对对象进行拉伸刷新

            ctrl.add(ctrlObj,"positionX",-30,30);
            ctrl.add(ctrlObj,"positionY",-30,30);
            ctrl.add(ctrlObj,"positionZ",-30,30);

            ctrl.add(ctrlObj,"rotationX",-5,5);
            ctrl.add(ctrlObj,"rotationY",-5,5);
            ctrl.add(ctrlObj,"rotationZ",-5,5);

            ctrl.add(ctrlObj,"visible");//布尔类型不需要写范围

            ctrl.add(ctrlObj,"translateX",-25,25);
            ctrl.add(ctrlObj,"translateY",-25,25);
            ctrl.add(ctrlObj,"translateZ",-25,25);

            //下面介绍一些常用几何体 首先圆环几何体
            var torusgeo = new TorusGeometry(10,2,10,10);//一共有五个构造参数.第一个参数表示圆环半径默认是1,第二个表示管道的半径默认是0.4,
			//第三个表示圆环的分段数默认是8,第四个参数表示管道的分段数默认是6,第五个表示圆环的圆心角默认是Math.PI*2
            var torusmaterial = new MeshLambertMaterial({
    
    color:0xff2288});
            var torus = new Mesh(torusgeo,torusmaterial);
            torus.castShadow = true;
            torus.position.x = 20;
            torus.position.y = 10;
            torus.position.z = -20;
            // scene.add(torus); 

            //圆柱几何体
            var cylindergeo = new CylinderGeometry(4,4,14);//一共有八个构造参数.第一个表圆柱的顶部半径默认是1,第二个表圆柱的底部半径默认是1,第三个表圆柱的高度默认是1,第四个表圆柱侧面周围分段数默认是8,
			//第五个表圆柱侧面沿着其高度的分段数默认是1,第六个表明圆柱地面是开放的还是封闭的默认是false,第七个表第一个分段的起始角度默认是0,第八个表圆柱底面圆扇区的中心角默认是2*PI
            var cylindermaterial = new MeshLambertMaterial({
    
    color:0xff2288});
            var cylinder = new Mesh(cylindergeo,cylindermaterial);
            cylinder.castShadow = true;
            cylinder.position.x = -10;
            cylinder.position.y = 10;
            cylinder.position.z = -20;
            // scene.add(cylinder); 

            //自定义几何体
            var geometry = new BufferGeometry();
			//下面创建点数组,和其他大多数库一样,在threejs中几何体基本上是三维空间中的点集合将这些点连接起来的面,再将这些面组合成一个几何体
			//以立方体为例,一个立方体有八个角,每个角都可以用XYZ坐标定义,一个立方体又同时包含6个面,每个面包含了3个顶点的三角形,所以立方体每个面
			//都是由俩个三角形组成的,每个面也就需要6个点来确定2个三角形,这里需要连续输入6个面的顶点坐标
            var vertices = new Float32Array([
                //第一个面
                0,0,0,
                10,0,0,
                0,10,0,
                10,0,0,
                0,10,0,
                10,10,0,
                //第二个面
                0,0,0,
                0,10,0,
                0,10,10,
                0,10,10,
                0,0,0,
                0,0,10,
                //第三个面
                10,0,0,
                10,10,0,
                10,10,10,
                10,0,0,
                10,0,10,
                10,10,10,
                //第四个面
                0,0,0,
                0,0,10,
                10,0,0,
                10,0,0,
                10,0,10,
                0,0,10,
                //第五个面
                0,10,0,
                0,10,10,
                10,10,0,
                10,10,0,
                10,10,10,
                0,10,10,
                //第六个面
                0,0,10,
                10,0,10,
                0,10,10,
                10,0,10,
                0,10,10,
                10,10,10,
                //第七个面
                10,10,0,
                0,10,0,
                2,20,5,
                //第八个面
                0,10,0,
                0,10,10,
                2,20,5,
                //第九个面
                0,10,10,
                10,10,10,
                2,20,5,
                //第十个面
                10,10,10,
                10,10,0,
                2,20,5,
            ])
            var attribute = new BufferAttribute(vertices,3);//这个类存储与BufferGeometry关联的属性,第一个参数是保存在缓冲器中的数据的数组,第二个参数是保存在数组中矢量的长度
            geometry.attributes.position = attribute;//把BufferGeometry对象设置几何体的位置
            var material = new MeshBasicMaterial({
    
    //给自定义几何体新建材质
                color:0x0000ff,
                side:DoubleSide,//我们需要该几何体俩面均可视
            });

            var mesh = new Mesh(geometry,material);//最后把自定义几何体和材质合并成网格
            // scene.add(mesh);

            var wireFrame = new WireframeGeometry(geometry);//为了让这个立方体能够看起来更加立体,我们给它添加线条,这里先通过几何体对象来构造一个线条框架
            var line = new LineSegments(wireFrame);//再新建框架的线条
            line.material.depthTest = true;//然后我们对线条的材质属性进行一下修改,depthTest表示允许修改深度测试,即看不到的那些面的线条是否可视
            // line.material.transparent = false;//transparent表示该线条的透明与否
            // line.material.opacity = 0.5;//当该线条的transparent为真时opacity才有效,否则无效
            // scene.add(line);//最后把线条添加到场景

            //再学习一下几何体的克隆方法,为了能够清楚的表示,我们依然通过dat.GUI来实现该方法
            // var ctrl = new dat.GUI();
            // ctrl.add(new function(){
    
    
            //     this.clone = function(){//先封装一个clone方法
            //         var cloneGeometry = mesh.geometry.clone();//我们通过网格中的几何体属性获取我们需要克隆的几何体,再通过clone方法,克隆出一个cloneGeometry对象
            //         var clonematerial = new MeshBasicMaterial({//再创建一个不一样的颜色材质进行区分
            //             color:0xff0000,
            //             side:DoubleSide,
            //         });

            //         var clonemesh = new Mesh(cloneGeometry,clonematerial);//接下来把几何体和材质合并成网格
            //         clonemesh.translateZ(20);//对克隆几何体的位置进行偏移
            //         clonemesh.name = "copy";//我们给这个克隆几何体命名为copy为了下一步可以删除它 为了在重复调用该方法时将之前的克隆对象进行删除 我们需要先将该克隆对象从场景中进行删除
            //         scene.remove(scene.getChildByName("copy"));//删除场景里名字是copy的子对象
            //         scene.add(clonemesh);//再将该网格添加至场景中

            //         var clonewireFrame = new WireframeGeometry(cloneGeometry);//最后再为该几何体再添加一些线条使得看起来更立体
            //         var cloneline = new LineSegments(clonewireFrame);
            //         cloneline.material.depthTest = true;
            //         cloneline.translateZ(20);
            //         scene.add(cloneline);

            //     }
            // },"clone")

            var planeGeometry = new PlaneGeometry(100,100);
            var planeMaterial = new MeshLambertMaterial({
    
    color:0xAAAAAA});
            var plane = new Mesh(planeGeometry,planeMaterial);

            plane.rotation.x = -0.5 * Math.PI;
            plane.position.set(15,0,0);
            plane.receiveShadow = true;

            scene.add(plane);

            camera.position.x = -30;
            camera.position.y = 40;
            camera.position.z = 30;
            camera.lookAt(scene.position);
            
            cube.rotation.x += 0.80;
            cube.rotation.y += 0.80;

            var spotLight = new SpotLight(0xFFFFFF);
            spotLight.position.set(-60, 40, -65);
            spotLight.castShadow = true;
            spotLight.shadow.mapSize = new Vector2(1024, 1024);
            spotLight.shadow.camera.far = 130;
            spotLight.shadow.camera.near = 40;

            scene.add(camera);
            scene.add(spotLight);

            var ambienLight = new AmbientLight(0xcccccc);
            scene.add(ambienLight);

            renderScene();
            var gap = 0;
            function renderScene(){
    
     //在renderScene中使用对象的scale属性中的set方法对对象进行拉伸刷新
                showcube.scale.set(ctrlObj.scaleX,ctrlObj.scaleY,ctrlObj.scaleZ);
                showcube.position.set(ctrlObj.positionX,ctrlObj.positionY,ctrlObj.positionZ);
                showcube.rotation.set(ctrlObj.rotationX,ctrlObj.rotationY,ctrlObj.rotationZ);
                showcube.visible = ctrlObj.visible;                
                showcube.translateX(ctrlObj.translateX);
                showcube.translateY(ctrlObj.translateY);
                showcube.translateZ(ctrlObj.translateZ);

                requestAnimationFrame(renderScene);//在renserScene中对几何体的尺寸、位置、旋转弧度、可视与否、平移位置等信息进行刷新
                render.render(scene,camera);
            }


            window.addEventListener('resize',onWindowResize,false);

            function onWindowResize(){
    
    
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                render.setSize(window.innerWidth,window.innerHeight);
            }
        </script>

</body>

</html>

渲染效果图:
请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43739821/article/details/123902187