环境:

  • three.js 0.159.0

一、基础知识

  • geometry:决定物体的几何形状、轮廓;
  • material:决定物体呈现的色彩、光影特性、贴图皮肤;
  • mesh:场景中的物体,由geometrymateria组成;
  • texture:贴图,用于将一个jpg等格式的图片贴到material上面(当然,material也可以不贴texture);

另外,如果material上是定义的color,那么说明,物体是自发光的,不需要灯光就能看到,
materia如果整个是靠texture贴上去的,则需要光照才能看到它,最简单的是用环境光。

另外,对于一张图片,无论它有多大或多小,左下角是(0,0),右上角是(1,1),这就是uv,宽用u表示,高用v表示。

另外,无论一个物体形状有多复杂,其表面也可以分割成很多三角面。

下面使用的示例图片如下:

在这里插入图片描述

二、简单实例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
    </style>
    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.159.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/"
            }
        }
    </script>
</head>

<body>
    <script type="module">

        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        let camera, scene, renderer;
        //基础对象
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.set(50, 50, 50);
        camera.updateProjectionMatrix();
        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.minDistance = 5;
        controls.maxDistance = 300;
        controls.update()
        scene = new THREE.Scene();

        // 环境光
        const light = new THREE.AmbientLight(0x404040); // soft white light
        scene.add(light);
        //坐标轴
        const axesHelper = new THREE.AxesHelper(5);
        scene.add(axesHelper);
		
		//准备geometry
        //点位
        var position = [
            10, 10, 0,
            10, 0, 0,
            0, 0, 0
        ]
        //贴图
        var uvs = [
            1, 1,
            1, 0,
            0, 0,
        ]        
        //构造geometry
        let geometry = new THREE.BufferGeometry();
        geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
        geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));
        
        //加载贴图
        const texture = new THREE.TextureLoader().load('number.png');
        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.set(1, 1);
        //准备material
        const material = new THREE.MeshBasicMaterial({
            side: THREE.DoubleSide,
            map: texture,
            transparent: true,
            opacity: 0.7,
        });
        //组成mesh
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh);

        function animate() {
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>

</html>

效果如下:
在这里插入图片描述

三、探索贴图原理

先说上面示例:

  • position 表达了三个点位的坐标和顺序(看向-z轴,逆时针),此时面的法方向是 (0,0,-1);
  • uv 表达了position每个点位在贴图上的点;
  • 又因为,将material设为了透明和双面渲染,所以我们看向-z轴也是可以看到效果的(我们默认看到的效果其实是背面,可以将上面的 side: THREE.DoubleSide, 注释掉试试);

这里是否有一个疑问:怎么知道贴图的正面是朝向哪的呢?为什么这里是朝向+z轴,而不是-z轴呢?

其实,因为position和uv,webgl的插值计算只能将贴图的正面朝向+z轴,如下图示意:
在这里插入图片描述

四、考虑index作用

上面只是3个点位,所以仅用position和uv即可表达,但如果有很多点位,再这么写的话position会很多,而且很多都是重复的,
比如:立方体有8个点位,如果每个面分成两个三角面,那么总共需要36 = 6*2*3个点位坐标(每个3角面要3个点)。

如果使用index的写法, 则:
position:8个点位
index: 列出每个三角面的点位序号, 共计 24 个
uv:和position一一对应

注意: 如果是为立方体的每个面贴不同的材质, 那么应该是为每个面建立4个点位, 总共是: 6*4=24, 而不是8, 参照: THREE.BoxGeometry

将上面示例中geometry部分改造如下:

//准备geometry
//点位
var position = [
    10, 10, 0,
    10, 0, 0,
    0, 0, 0,
    0, 10, 0,
]
//贴图
var uvs = [
    1, 1,
    1, 0,
    0, 0,
    0, 1,
]
//点位序号
var index = [
    0, 1, 2,
    0, 2, 3,
]        
//构造geometry
let geometry = new THREE.BufferGeometry();
geometry.setIndex(index);
geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));

此时,我们看到的效果:
在这里插入图片描述

可以看到,我们仅用了4个点位,便描述了两个3角面。

五、看一个特殊的示例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
    </style>
    <script type="importmap">
        {
            "imports": {
                "three": "https://unpkg.com/three@0.159.0/build/three.module.js",
                "three/addons/": "https://unpkg.com/three@0.159.0/examples/jsm/"
            }
        }
    </script>
</head>

<body>
    <script type="module">

        import * as THREE from 'three';
        import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

        let camera, scene, renderer;
        //基础对象
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
        camera.position.set(50, 50, 50);
        camera.updateProjectionMatrix();
        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.minDistance = 5;
        controls.maxDistance = 300;
        controls.update()
        scene = new THREE.Scene();

        // 环境光
        const light = new THREE.AmbientLight(0x404040); // soft white light
        scene.add(light);
        //坐标轴
        const axesHelper = new THREE.AxesHelper(5);
        scene.add(axesHelper);

        //准备geometry
        //点位
        var position = [
            10, 10, 0,
            10, 0, 0,
            0, 0, 0,
        ]
        //贴图
        var uvs = [
            0, 1,
            1, 1,
            0, 0,
        ]
        //点位序号
        var index = [
            0, 1, 2
        ]
        //构造geometry
        let geometry = new THREE.BufferGeometry();
        geometry.setIndex(index);
        geometry.setAttribute('position', new THREE.Float32BufferAttribute(position, 3));
        geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));

        //加载贴图
        const texture = new THREE.TextureLoader().load('number.png');
        texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        texture.repeat.set(1, 1);
        //准备material
        const material = new THREE.MeshBasicMaterial({
            side: THREE.DoubleSide,
            map: texture,
            transparent: true,
            opacity: 0.7,
        });
        //组成mesh
        const mesh = new THREE.Mesh(geometry, material);
        scene.add(mesh);

        function animate() {
            requestAnimationFrame(animate);
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>

</html>

效果如下:
在这里插入图片描述

六、最后看一个用立方体实现全景图六面切图的预览效果

效果图如下:
在这里插入图片描述

代码下载请看博文顶部。。。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐