three.js 画线比较繁琐一些,我们先展示正常的操作,先看效果图:
本案例用到的方法是:
LineBasicMaterial 和 LineSegments。
1、材质
Three.js中提供了两种线条材质:
LineDashedMaterial
const material = new THREE.LineDashedMaterial( {
color: 0xffffff,
linewidth: 1,
scale: 1,
dashSize: 3, // 破折号的大小 。这是与笔划之间的间隙
gapSize: 1 // 间隙的大小
} );
LineBasicMaterial
const material = new THREE.LineBasicMaterial( {
color: 0xffffff,
linewidth: 1
} );
2、geometry
Three.js中比较常用的三种线条Line,LineLoop,LineSegments
Line
Line用于将一系列点绘制成一条连续的线
LineLoop
用于将一系列点绘制成一条连续的线,它和Line几乎一样,唯一的区别就是所有点连接之后会将第一个点和最后一个点相连接
LineSegments
用于将两个点连接为一条线,它会将我们传递的一系列点自动分配成两个为一组,然后将分配好的两个点连接,这种先天实际项目中主要用于绘制具有相同开始点,结束点不同的线条
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
width: 100%;
height: 100%;
}
* {
margin: 0;
padding: 0;
}
.label {
font-size: 20px;
color: #000;
font-weight: 700;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
{
"imports": {
"three": "../three-155/build/three.module.js",
"three/addons/": "../three-155/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GPUStatsPanel } from 'three/addons/utils/GPUStatsPanel.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
let stats, labelRenderer, gpuPanel, material;
let camera, scene, renderer, controls;
const group = new THREE.Group();
const r = 50;
let widthImg = 200;
let heightImg = 200;
init();
// initHelp();
initLight();
// axesHelperWord();
animate();
let parameters = [
[0.25, 0xff7700, 1, 5],
[0.5, 0xff9900, 1, 5],
[0.75, 0xffaa00, 0.75, 5],
[1, 0xffaa00, 0.5, 5],
[1.25, 0x000833, 0.8, 5],
[3.0, 0xaaaaaa, 0.75, 50],
[3.5, 'blue', 0.5, 1],
[4.5, 'yellow', 0.25, 1],
[5.5, 0xffffff, 0.125, 50]
];
let geometry = createGeometry();
for (let i = 0; i < parameters.length; ++i) {
let p = parameters[i];
material = new THREE.LineBasicMaterial({color: p[1], opacity: p[2], linewidth: p[3]});
let line = new THREE.LineSegments(geometry, material);
line.scale.x = line.scale.y = line.scale.z = p[0];
line.userData.originalScale = p[0];
// line.rotation.y = Math.random() * Math.PI;
line.updateMatrix();
scene.add(line);
}
function createGeometry() {
let geometry = new THREE.BufferGeometry();
let vertices = [];
let vertex = new THREE.Vector3();
for (let i = 0; i < 2000; i++) {
vertex.x = Math.random() * 2 - 1;
vertex.y = Math.random() * 2 - 1;
vertex.z = Math.random() * 2 - 1;
vertex.normalize();
vertex.multiplyScalar(r);
vertices.push(vertex.x, vertex.y, vertex.z);
vertex.multiplyScalar(Math.random() * 0.09 + 1);
vertices.push(vertex.x, vertex.y, vertex.z);
}
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
return geometry;
}
function init() {
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 10, 3000 );
camera.up.set(0, 1, 0);
camera.position.set(60, 700, 60);
camera.lookAt(0, 0, 0);
scene = new THREE.Scene();
scene.background = new THREE.Color( '#000' );
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize( window.innerWidth, window.innerHeight );
labelRenderer.domElement.style.position = 'absolute';
labelRenderer.domElement.style.top = '0px';
labelRenderer.domElement.style.pointerEvents = 'none';
document.getElementById( 'container' ).appendChild( labelRenderer.domElement );
controls = new OrbitControls( camera, renderer.domElement );
// 设置最大最小视距
controls.minDistance = 20;
controls.maxDistance = 3000;
window.addEventListener( 'resize', onWindowResize );
stats = new Stats();
stats.setMode(1); // 0: fps, 1: ms
document.body.appendChild( stats.dom );
gpuPanel = new GPUStatsPanel( renderer.getContext() );
stats.addPanel( gpuPanel );
stats.showPanel( 0 );
scene.add( group );
}
function initLight() {
const AmbientLight = new THREE.AmbientLight(new THREE.Color('rgb(255, 255, 255)'), 0.2);
scene.add( AmbientLight );
}
function initHelp() {
// const size = 100;
// const divisions = 5;
// const gridHelper = new THREE.GridHelper( size, divisions );
// scene.add( gridHelper );
// The X axis is red. The Y axis is green. The Z axis is blue.
const axesHelper = new THREE.AxesHelper( 100 );
scene.add( axesHelper );
}
function axesHelperWord() {
let xP = addWord('X轴');
let yP = addWord('Y轴');
let zP = addWord('Z轴');
xP.position.set(50, 0, 0);
yP.position.set(0, 50, 0);
zP.position.set(0, 0, 50);
}
function addWord(word) {
let name = `<span>${word}</span>`;
let moonDiv = document.createElement( 'div' );
moonDiv.className = 'label';
// moonDiv.textContent = 'Moon';
// moonDiv.style.marginTop = '-1em';
moonDiv.innerHTML = name;
const label = new CSS2DObject( moonDiv );
group.add( label );
return label;
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
let time = Date.now() * 0.0001;
for (let i = 0; i < scene.children.length; i++) {
let object = scene.children[i];
if (object.isLine) {
object.rotation.y = time * (i < 4 ? (i + 1) : -(i + 1));
// if (i < 5) {
// let scale = object.userData.originalScale * (i / 5 + 1) * (1 + 0.5 * Math.sin(7 * time));
let scale = object.userData.originalScale * (i / 5 + 1) * (1 + 0.5 * Math.sin(10 * time));
object.scale.x = object.scale.y = object.scale.z = scale;
// }
}
}
stats.update();
controls.update();
labelRenderer.render( scene, camera );
renderer.render( scene, camera );
}
</script>
</body>
</html>
我们看到,并不那么复杂,但这里有个问题,我们会发现:LineBasicMaterial 的 linewidth 怎么设置都没有起作用,按照官方解释是:由于opengl核心库文件的限制,webgl渲染器在大部分平台上linewidth一直是1,无视你设置的值;上面两种材质都是这样,至于有宽度的线 后面我们会讲到