three.js 入门三:buffergeometry贴图属性(position、index和uvs)
·
环境:
- three.js 0.159.0
一、基础知识
geometry
:决定物体的几何形状、轮廓;material
:决定物体呈现的色彩、光影特性、贴图皮肤;mesh
:场景中的物体,由geometry
和materia
组成;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>
效果如下:
六、最后看一个用立方体实现全景图六面切图的预览效果
效果图如下:
代码下载请看博文顶部。。。
更多推荐
已为社区贡献13条内容
所有评论(0)