three.js 入门三:buffergeometry贴图属性(position、index和uvs)

环境:

  • 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表示。

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

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

二、简单实例

html 复制代码
<!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个点位,如果每个面分成两个三角面,那么总共需要24 = 6*2*3个点位坐标(每个3角面要3个点)。

此时使用index的写法:

position:8个点位

index: 列出每个三角面的点位序号, 共计 24 个元素

uv:和position一一对应

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

js 复制代码
//准备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角面。

五、看一个特殊的示例

html 复制代码
<!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>

效果如下:

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

效果图如下:

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

相关推荐
max5006001 小时前
本地部署开源数据生成器项目实战指南
开发语言·人工智能·python·深度学习·算法·开源
q567315231 小时前
手把手教你用Go打造带可视化的网络爬虫
开发语言·爬虫·信息可视化·golang
Bling_Bling_11 小时前
面试常考:js中 Map和 Object 的区别
开发语言·前端·javascript
前端小巷子1 小时前
JS实现丝滑文字滚动
前端·javascript·面试
写不出来就跑路2 小时前
基于 HTML+CSS+JavaScript 的薪资实时计算器(含本地存储和炫酷动画)
javascript·css·html
程序喵大人2 小时前
写C++十年,我现在怎么设计类和模块?(附真实项目结构)
开发语言·c++·类和模板
黄焖鸡能干四碗2 小时前
信息系统安全保护措施文件方案
大数据·开发语言·人工智能·web安全·制造
醉方休2 小时前
React Fiber 风格任务调度库
前端·javascript·react.js
liulilittle3 小时前
Unix/Linux 平台通过 IP 地址获取接口名的 C++ 实现
linux·开发语言·c++·tcp/ip·unix·编程语言
华仔啊3 小时前
摸鱼神器!前端大佬私藏的 11 个 JS 神级 API,复制粘贴就能用,效率翻倍
前端·javascript