<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from '/three.js-r123/build/three.module.js';
import { OrbitControls } from '/three.js-r123/examples/jsm/controls/OrbitControls.js';
// 现在浏览器支持ES6语法,自然包括import方式引入js文件
// 引入Three.js扩展库
// 引入线宽设置相关库
import { LineGeometry } from '/three.js-r123/examples/jsm/lines/LineGeometry.js';
import { LineMaterial } from '/three.js-r123/examples/jsm/lines/LineMaterial.js';
import { Line2 } from '/three.js-r123/examples/jsm/lines/Line2.js';
function createLine() {
var geometry = new THREE.BufferGeometry(); //创建一个缓冲类型几何体
// 三维样条曲线
var curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(100, 0, -100),
new THREE.Vector3(0, 80, 0),
new THREE.Vector3(-100, 0, 100),
]);
var points = curve.getSpacedPoints(100); //分段数100,返回101个顶点
geometry.setFromPoints(points);
var material = new THREE.LineBasicMaterial({
color: 0x006666, //轨迹颜色
});
//线条模型对象
var line = new THREE.Line(geometry, material);
scene.add(line);
var index = 20; //取点索引位置
var num = 10; //从曲线上获取点数量
var points2 = points.slice(index, index + num); //从曲线上获取一段
var geometry2 = new LineGeometry();
var pointArr = []
points2.forEach(function (v3) {
pointArr.push(v3.x, v3.y, v3.z)
})
var material2 = new LineMaterial({
linewidth: 1, // 设置线宽
color: 0x7FE716
});
material2.resolution.set(window.innerWidth, window.innerHeight);
var line2 = new Line2(geometry2, material2);
scene.add(line2);
var indexMax = points.length - num;
function render() {
if (index > indexMax) index = 0;
index += 1
points2 = points.slice(index, index + num); //从曲线上获取一段
// geometry2.setFromPoints(points2);
var pointArr = []
//把圆弧曲线返回的顶点坐标Vector3中xyz坐标提取到pointArr数组中
points2.forEach(function (v3) {
pointArr.push(v3.x, v3.y, v3.z)
})
// 设置几何体顶点位置坐标
geometry2.setPositions(pointArr);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
}
</script>
</body>
</html>
简易版
let camera, scene, renderer, clock, controls, curve, boxMesh, points
let stats;
let step = 0
init();
animate();
function init() {
clock = new THREE.Clock();
const container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.set(0, 100, 100);
scene = new THREE.Scene();
scene.background = new THREE.Color("#87CEFA")
// ------------------------------------------------------------------------------------mesh
curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-100, 0, 100),
new THREE.Vector3(-50, 50, 50),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(50, -50, 50),
new THREE.Vector3(100, 0, 100)
]);
points = curve.getPoints(500);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const curveObject = new THREE.Line(geometry, material);
// curveObject.visible = false
scene.add(curveObject)
const boxGeometry = new THREE.BoxGeometry(10, 10, 10)
const boxMaterial = new THREE.MeshBasicMaterial({
color: 0x00cc00
})
boxMesh = new THREE.Mesh(boxGeometry, boxMaterial)
scene.add(boxMesh)
// ------------------------------------------------------------------------------------
var hemisphereLight = new THREE.HemisphereLight(0x808080, 0x606060)
scene.add(hemisphereLight);
var ambientLight = new THREE.AmbientLight(0xffffff);
scene.add(ambientLight);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
// renderer.outputEncoding = THREE.sRGBEncoding;
renderer.shadowMap.enabled = true;
controls = new OrbitControls(camera, renderer.domElement);
controls.dampingFactor = true
stats = new Stats();
container.appendChild(stats.dom);
var axes = new THREE.AxesHelper(20);
scene.add(axes);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
step += 1;
if (step >= points.length) {
step = 0
}
points[step].x && points[step].y && points[step].z && boxMesh.position.set(points[step].x, points[step].y, points[step].z)
controls.update()
requestAnimationFrame(animate);
render();
stats.update();
}
function render() {
renderer.render(scene, camera);
}
window.addEventListener('resize', onWindowResize);
function initContent() {
var text = "three.js"
var loader = new FontLoader();
loader.load('./js/gentilis_regular.typeface.json', function (font) {
var fontMaterial = new THREE.MeshLambertMaterial({
color: 0x912CEE,
side: THREE.DoubleSide
});
var shapes = font.generateShapes(text, 10, 1);
var fontGeometry = new THREE.ShapeGeometry(shapes);
fontGeometry.computeBoundingBox();
var font = new THREE.Mesh(fontGeometry, fontMaterial);
font.position.x = -1 - fontGeometry.boundingBox.max.x / 2;
font.position.y = 1 - fontGeometry.boundingBox.max.y / 2;
scene.add(font);
})
}
或者
function createFont (text, options={bgColor: '', fontColor: ''}) {
var canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 128;
var c = canvas.getContext('2d');
// 矩形区域填充背景
c.fillStyle = options.bgColor || '#ff0000';
c.fillRect(0, 0, 512, 128);
c.beginPath();
// 文字
c.beginPath();
c.translate(256,64);
c.fillStyle = options.fontColor || '#ffffff'; //文本填充颜色
c.font = 'bold 36px 微软雅黑'; //字体样式设置
c.textBaseline = 'middle'; //文本与fillText定义的纵坐标
c.textAlign = 'center'; //文本居中(以fillText定义的横坐标)
c.fillText(text, 0, 0);
var texture = new THREE.CanvasTexture(canvas);
var textMaterial = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
// side: THREE.DoubleSide,
transparent: true,
opacity: 0.9
});
textMaterial.map.needsUpdate = true
return textMaterial
}
<script src="../../libs/examples/js/utils/SceneUtils.js"></script>
function initContent() {
let vertices = [
new THREE.Vector3(1, 3, 1),
new THREE.Vector3(1, 3, -1),
new THREE.Vector3(1, -1, 1),
new THREE.Vector3(1, -1, -1),
new THREE.Vector3(-1, 3, -1),
new THREE.Vector3(-1, 3, 1),
new THREE.Vector3(-1, -1, -1),
new THREE.Vector3(-1, -1, 1)
];
let faces = [
new THREE.Face3(0, 2, 1),
new THREE.Face3(2, 3, 1),
new THREE.Face3(4, 6, 5),
new THREE.Face3(6, 7, 5),
new THREE.Face3(4, 5, 1),
new THREE.Face3(5, 0, 1),
new THREE.Face3(7, 6, 2),
new THREE.Face3(6, 3, 2),
new THREE.Face3(5, 7, 0),
new THREE.Face3(7, 2, 0),
new THREE.Face3(1, 3, 4),
new THREE.Face3(3, 6, 4)
];
let geometry = new THREE.Geometry();
geometry.vertices = vertices;
geometry.faces = faces;
geometry.computeFaceNormals();
let materials = [
new THREE.MeshLambertMaterial({transparent : true, opacity : 0.6, color : 0x9F79EE}),
new THREE.MeshBasicMaterial({color : 0x000, wireframe : true})
];
// 使用SceneUtils创建多材质对象
let mesh = new THREE.SceneUtils.createMultiMaterialObject(geometry, materials);
mesh.scale.set(50, 50, 50);
scene.add(mesh);
}
//
function AddTween() {
tween = new TWEEN.Tween(camera.position);
tween.to({x:500, y:200, z:100}, 5000);
tween.easing(TWEEN.Easing.Linear.None);
var tweenBack = new TWEEN.Tween(camera.position).to({x:100, y:200, z:100}, 5000);
tweenBack.easing(TWEEN.Easing.Linear.None);
tween.chain(tweenBack);
tweenBack.chain(tween);
tween.start();
tween.onUpdate(function () {
console.log(this.x);
})
}
function update() {
TWEEN.update();
stats.update();
controls.update();
}
tweenjs的使用
//引入tweenjs
<script src="./js/tween.min.js"></script>
tween = new TWEEN.Tween({})
console.log(tween);//能打印出来说明引入成功
动画运动算法(缓动函数)easing函数
缓动方式(效果)easing类型
使用公式
.easing(TWEEN.Easing.easing函数.easing类型)
tween = new TWEEN.Tween(camera.position);
tween.to({ x: 100, y: 200, z: 100 }, 2000);//从起始值改变到{ x: 100, y: 200, z: 100 },2秒时间
tween.easing(TWEEN.Easing.Linear.None);
var tweenBack = new TWEEN.Tween(camera.position).to({ x: 0, y: 100, z: 100 }, 2000);//从上个位置回到{ x: 0, y: 100, z: 100 },2秒时间
tweenBack.easing(TWEEN.Easing.Linear.None);
tween.chain(tweenBack);//tween结束执行tweenBack
tweenBack.chain(tween);//tweenBack结束执行tween
tween.start();//激活动画:
// tween.delay(1000);//第二次补间动画延迟1秒
// tween.repeat(2); // tween动画先执行2次,tween.repeat(Infinity); // repeats forever
tween.onUpdate(function () { })
常用先快后慢
tween.easing(TWEEN.Easing.Quadratic.InOut);
github地址:https://github.com/tweenjs/tween.js
function initContent() {
/* 创建粒子 */
let spriteMaterial = new THREE.SpriteMaterial({color: 0x9A32CD});
for (let x = -5; x < 5; x++ ) {
for (let y = -5; y < 5; y++ ) {
let sprite = new THREE.Sprite(spriteMaterial);/* Sprite 默认的材质是 SpriteMaterial */
sprite.position.set(x * 10, y * 10, 0);
scene.add(sprite);
}
}
}
function initContent() {
/* 雪花图片 */
let texture = new THREE.TextureLoader().load('../../textures/particles/snowflake2.png');
let geometry= new THREE.Geometry();
let pointsMaterial = new THREE.PointsMaterial({
size:2,
transparent:true,
opacity:0.8,
map:texture,
blending:THREE.AdditiveBlending,
sizeAttenuation:true,
depthTest: false
});
let range = 100;
for (let i = 0; i < 1500; i++ ) {
let vertice = new THREE.Vector3(
Math.random() * range - range / 2,
Math.random() * range * 1.5,
Math.random() * range - range / 2);
/* 纵向移动速度 */
vertice.velocityY = 0.1 + Math.random() / 15;
/* 横向移动速度 */
vertice.velocityX = (Math.random() - 0.5) / 3;
/* 将顶点加入几何 */
geometry.vertices.push(vertice);
}
geometry.center();
points = new THREE.Points(geometry, pointsMaterial);
points.position.y = -30;
scene.add(points);
}
/* 数据更新 */
function update() {
stats.update();
let vertices = points.geometry.vertices;
vertices.forEach(function (v) {
v.y = v.y - (v.velocityY);
v.x = v.x - (v.velocityX);
//控制速度可以这样写
//v.y = v.y - (v.velocityY * 0.1);
//v.x = v.x - (v.velocityX * 0.1);
if (v.y <= 0) v.y = 60;
if (v.x <= -20 || v.x >= 20) v.velocityX = v.velocityX * -1;
});
/* 顶点变动之后需要更新,否则无法实现雨滴特效 */
points.geometry.verticesNeedUpdate = true;
}
let group = new THREE.Group();
function initContent() {
/* 创建一个球体 */
let boxGeometry = new THREE.BoxGeometry(5, 10, 5, 20, 30, 20);
let cylinderGeometry = new THREE.CylinderGeometry(2, 2, 10, 30, 30);
let box = createPointsCloud(boxGeometry);
let cylinder = createPointsCloud(cylinderGeometry);
box.position.x = -10;
cylinder.position.x = 10;
group.add(cylinder);
group.add(box);
scene.add(group);
let loader = new THREE.OBJLoader();
loader.load('../../models/walt/WaltHead.obj', function (object) {
let geometry = createPointsCloud(object.children[0].geometry);
let points = createPointsCloud(geometry.geometry);
points.scale.set(0.15, 0.15, 0.15);
points.position.y = -6;
group.add(points);
});
}
/* 创建点云 */
function createPointsCloud(geometry) {
/* 精灵材质 */
let spriteMaterial = new THREE.PointsMaterial({
color: 0xffffff,
size:0.23,
transparent: true,
map: generateSprite()
});
let points = new THREE.Points(geometry, spriteMaterial);
return points;
}
/* 创建canvas纹理 */
function generateSprite() {
/* 常见画布并设置宽高 */
let canvas = document.createElement('canvas');
canvas.width = 8;
canvas.height = 8;
/* 创建图形 */
let ctx = canvas.getContext("2d");
let gradient = ctx.createRadialGradient(
canvas.width / 2, canvas.height / 2, 0,
canvas.width / 2, canvas.height / 2, canvas.width / 2
);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.2, 'rgba(0, 255, 255, 1)');
gradient.addColorStop(0.6, 'rgba(0, 0, 64, 1)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);
let texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.CubeTextureLoader().setPath('../../textures/cube/Park2/')
.load( [
'posx.jpg',
'negx.jpg',
'posy.jpg',
'negy.jpg',
'posz.jpg',
'negz.jpg'
] );
}
function initContent() {
//材质贴图
let material = new THREE.MeshPhongMaterial();
material.envMap = scene.background;
let sphereGeometry = new THREE.SphereGeometry(20, 60, 60);
let boxGeometry = new THREE.BoxGeometry(30, 30, 30);
let box = new THREE.Mesh(boxGeometry, material);
let sphere = new THREE.Mesh(sphereGeometry, material);
sphere.translateX(-45);
box.translateX(45);
let loader = new THREE.GLTFLoader();
loader.load('../../models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', function (gltf) {
gltf.scene.traverse(function (child) {
if (child.isMesh) {
child.scale.set(25, 25, 25);
child.material.envMap = scene.background;
}
});
scene.add(gltf.scene);
scene.add(box);
scene.add(sphere);
});
}
<!-- 导入 Reflector.js -->
<script src="../../libs/examples/js/objects/Reflector.js"></script>
let planeGeometry = new THREE.PlaneBufferGeometry(10, 10);
let options = {
clipBias: 0.03,
textureWidth: window.innerWidth * window.devicePixelRatio,
textureHeight: window.innerHeight * window.devicePixelRatio,
color: 0x889999,
recursion: 1
};
let mirror = new THREE.Reflector(planeGeometry, options);
scene.add(mirror);
//css
body {
margin: 0;
overflow: hidden; /* 溢出隐藏 */
background: url("../../images/bgc-map.jpg") center no-repeat;
-webkit-background-size: cover;
background-size: cover;
}
//js
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearAlpha(0.34);//设置alpha。合法参数是一个0.0到 1.0之间的浮点数
//renderer.getClearAlpha () : Float//返回一个表示当前alpha值的float,范围0到1
//css
#video {
position: absolute;
width: 0;
height: 0;
}
//html
<video id="video" autoplay loop muted>
<source src="../../textures/video/sintel.ogv">
<source src="../../textures/video/sintel.mp4">
</video>
//js
/* 场景中的内容 */
function initContent() {
let planeGeometry = new THREE.PlaneGeometry(10, 5);
let material = new THREE.MeshPhongMaterial();
material.side = THREE.DoubleSide;
let mesh = new THREE.Mesh(planeGeometry, material);
scene.add(mesh);
let video = document.getElementById('video');
console.log(video);
let texture = new THREE.VideoTexture(video);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
material.map = texture;
}
第二种方法
//css
#video {
position: absolute;
width: 0;
height: 0;
}
//js
/* 场景中的内容 */
function initContent() {
var video_dom = document.createElement('video');
video_dom.setAttribute('loop', '');
video_dom.setAttribute('autoplay', '');
video_dom.setAttribute('src', '../../textures/video/sintel.mp4');
console.log(video_dom);
// document.body.appendChild(video_dom);
let planeGeometry = new THREE.PlaneGeometry(10, 5);
let material = new THREE.MeshPhongMaterial();
material.side = THREE.DoubleSide;
let mesh = new THREE.Mesh(planeGeometry, material);
scene.add(mesh);
let video = document.getElementById('video');
console.log(video);
let texture = new THREE.VideoTexture(video_dom);
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
material.map = texture;
}
/* 场景中的内容 */
var texture_left;
var texture_up;
function initContent() {
texture_left = new THREE.TextureLoader().load('../../textures/arrows/arrow-right.png');
texture_up = new THREE.TextureLoader().load('../../textures/arrows/arrow-up.png');
texture_left.wrapS = THREE.RepeatWrapping;
texture_left.wrapT=THREE.RepeatWrapping;
texture_up.wrapS = THREE.RepeatWrapping;
texture_up.wrapT = THREE.RepeatWrapping;
texture_left.repeat.x = 10;
texture_left.repeat.y =1;
texture_up.repeat.x = 20;
texture_up.repeat.y =2;
var planeGeometry = new THREE.PlaneGeometry(200, 10);
var plane_left = new THREE.MeshBasicMaterial();
plane_left.color = new THREE.Color(0x00ff00);
plane_left.map = texture_left;
plane_left.transparent = true;
plane_left.side = THREE.DoubleSide;
var plane_up = new THREE.MeshBasicMaterial();
plane_up.color = new THREE.Color(0x00ff00);
plane_up.map = texture_up;
plane_up.transparent = true;
plane_up.side = THREE.DoubleSide;
var plane_left = new THREE.Mesh(planeGeometry, plane_left);
plane_left.translateY(10);
scene.add(plane_left);
var plane_up = new THREE.Mesh(planeGeometry, plane_up);
plane_up.translateY(-10);
scene.add(plane_up);
}
/* 数据更新 */
function update() {
stats.update();
controls.update();
// 设置纹理偏移
texture_left.offset.x -= 0.02;
texture_up.offset.y -= 0.02;
}
function drawShape() {
var shape = new THREE.Shape();
shape.moveTo(10, 10); // moveTo( x, y )
shape.lineTo(10, 40); // lineTo( x, y ) - 线
shape.bezierCurveTo(15, 25, 25, 25, 30, 40); // bezierCurveTo( cp1X, cp1Y, cp2X, cp2Y, x, y ) - 贝塞尔曲线
shape.splineThru([
new THREE.Vector2(32, 30),
new THREE.Vector2(28, 20),
new THREE.Vector2(30, 10)
]); // splineThru ( vector2Array ) - 样条线
shape.quadraticCurveTo(20, 15, 10, 10); // quadraticCurveTo( cpX, cpY, x, y ) - 二次曲线
var hole = new THREE.Path(); // 添加“眼睛”孔洞1
hole.absellipse(16, 24, 2, 3, 0, Math.PI * 2, false);
shape.holes.push(hole);
hole = new THREE.Path(); // 添加“眼睛”孔洞2
hole.absellipse(23, 24, 2, 3, 0, Math.PI * 2, false);
shape.holes.push(hole);
hole = new THREE.Path(); // 添加“嘴巴”孔洞
hole.absarc(20, 16, 2, 0, Math.PI, false);
shape.holes.push(hole);
return shape;
}
const x = 0, y = 0;
const heartShape = new THREE.Shape();
heartShape.moveTo( x + 5, y + 5 );
heartShape.bezierCurveTo( x + 5, y + 5, x + 4, y, x, y );
heartShape.bezierCurveTo( x - 6, y, x - 6, y + 7,x - 6, y + 7 );
heartShape.bezierCurveTo( x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19 );
heartShape.bezierCurveTo( x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7 );
heartShape.bezierCurveTo( x + 16, y + 7, x + 16, y, x + 10, y );
heartShape.bezierCurveTo( x + 7, y, x + 5, y + 5, x + 5, y + 5 );
const geometry = new THREE.ShapeGeometry( heartShape );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const mesh = new THREE.Mesh( geometry, material ) ;
scene.add( mesh );
function initContent() {
const x = 0, y = 0;
const heartShape = new THREE.Shape();
heartShape.moveTo(x, y);
heartShape.lineTo(x + 20, y)
heartShape.lineTo(x + 20, y + 15)
heartShape.lineTo(x, y + 15)
var hole = new THREE.Path(); //门
hole.moveTo(x + 5, y)
hole.lineTo(x + 5, y + 10)
hole.lineTo(x + 10, y + 10)
hole.lineTo(x + 10, y)
heartShape.holes.push(hole);
var hole1 = new THREE.Path(); //窗户
hole1.moveTo(x + 12, y + 5)
hole1.lineTo(x + 12, y + 10)
hole1.lineTo(x + 17, y + 10)
hole1.lineTo(x + 17, y + 5)
heartShape.holes.push(hole1);
const geometry = new THREE.ShapeGeometry(heartShape);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
function initContent() {
const x = 0, y = 0;
const heartShape = new THREE.Shape();
heartShape.moveTo(x + 5, y + 5);
heartShape.bezierCurveTo(x + 5, y + 5, x + 4, y, x, y);
heartShape.bezierCurveTo(x - 6, y, x - 6, y + 7, x - 6, y + 7);
heartShape.bezierCurveTo(x - 6, y + 11, x - 3, y + 15.4, x + 5, y + 19);
heartShape.bezierCurveTo(x + 12, y + 15.4, x + 16, y + 11, x + 16, y + 7);
heartShape.bezierCurveTo(x + 16, y + 7, x + 16, y, x + 10, y);
heartShape.bezierCurveTo(x + 7, y, x + 5, y + 5, x + 5, y + 5);
var hole = new THREE.Path(); // 添加“眼睛”孔洞1
hole.absellipse(0, 5, 3, 3, 0, Math.PI * 2, false);
heartShape.holes.push(hole);
hole = new THREE.Path(); // 添加“眼睛”孔洞2
hole.absellipse(10, 5, 3, 3, 0, Math.PI * 2, false);
heartShape.holes.push(hole);
hole = new THREE.Path(); // 添加“嘴巴”孔洞
hole.absarc(5, 10, 2, 0, Math.PI, false);
heartShape.holes.push(hole);
const geometry = new THREE.ShapeGeometry(heartShape);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
}
var geometry = new THREE.PlaneBufferGeometry(30, 30); //默认在XOY平面上
var textureLoader = new THREE.TextureLoader(); // TextureLoader创建一个纹理加载器对象
var material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
map: textureLoader.load('/assest/circle.png'),
transparent: true, //使用背景透明的png贴图,注意开启透明计算
// side: THREE.DoubleSide, //双面可见
});
var mesh = new THREE.Mesh(geometry, material);
mesh.rotateX(-Math.PI / 2); //旋转到XOZ平面
scene.add(mesh);
// 光圈大小在1~2.5倍之间变化
var _s = 2.5;
// 渲染函数
function render() {
_s += 0.01;
mesh.scale.set(_s, _s, _s);
if (_s <= 1.3) {
mesh.material.opacity = (_s - 1.0) * 3.3;//3.3约等于1/(1.3-1.0),保证透明度在0~1之间变化
} else if (_s > 1.3 && _s <= 2.5) {
mesh.material.opacity = 1 - (_s - 1) / 1.5;//缩放2.5对应0 缩放1.0对应1
} else {
_s = 1.0;
}
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
import { Water } from './jsm/objects/Water.js';
function initContent() {
var waterGeometry = new THREE.PlaneBufferGeometry(10000, 10000);
var light = new THREE.DirectionalLight(0xffffff, 0.8);
scene.add(light);
water = new Water(
waterGeometry,
{
textureWidth: 512,
textureHeight: 512,
waterNormals: new THREE.TextureLoader().load('./assets/img/waternormals.jpg', function (texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
}),
alpha: 1.0,
sunDirection: light.position.clone().normalize(),
sunColor: 0xffffff,
waterColor: 0x00456e,
distortionScale: 5.7,
fog: scene.fog !== undefined
}
);
water.rotation.x = - Math.PI / 2;
scene.add(water);
}
function render() {
renderer.render(scene, camera);
controls.update()
water.material.uniforms[ 'time' ].value += 1.0 / 60.0;
}
鼠标移动或者点击到导入的模型, 如何捕获
官方提供了射线捕获的接口 raycaster.intersectObjects, 但是只能识别自建的Mesh模型, 对于导入的模型则无法捕获, 主要是因为导入的模型最外层包了一层, 没有把自己内部的Mesh暴露出来 所以我们需要在模型导入后, 在onProgress回调中对其进行递归获取子Mesh, 将所有Mesh存在一个全局数组中. 在鼠标事件触发时, 将全局数组提供给raycaster.intersectObjects
// 递归出所有mesh
var objectArr
function getMesh(s, arr, name = '') {
s.forEach(v => {
if (v.children && v.children.length > 0) {
getMesh(v.children, arr, v.name)
} else {
if (v instanceof THREE.Mesh) {
if (name) {
v.name = name
}
arr.push(v)
}
}
})
}
//加载模型
function initObjModel() {
var onProgress = function (xhr) {
if (xhr.lengthComputable) {
var percentComplete = xhr.loaded / xhr.total * 100;
// 每次加载完毕将mesh放进数组
if (percentComplete === 100) {
objectArr = []
scene.traverse(function (s) {
if (s && s.type === 'Scene') {
getMesh(s.children, objectArr)
}
})
}
}
};
var onError = function (xhr) {};
var mtlLoader = new THREE.MTLLoader();
mtlLoader.setPath('objs/');
mtlLoader.load('工程船.mtl', function (materials) {
materials.preload();
var objLoader = new THREE.OBJLoader();
objLoader.setMaterials(materials);
objLoader.setPath('objs/');
objLoader.load('工程船.obj', function (object) {
scene.add(object);
}, onProgress, onError);
});
}
//鼠标点击事件
function handleMouseDown(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
raycaster.setFromCamera(mouse, camera)
var intersects = raycaster.intersectObjects(objectArr)
// console.log('当前点击的Mash', intersects)
if (intersects && intersects.length > 0) {
// do something...
}
}
import { PointerLockControls } from './jsm/controls/PointerLockControls.js';
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let prevTime = performance.now()
let velocity = new THREE.Vector3() // 移动速度
let direction = new THREE.Vector3() // 移动方向
function initControls() {
controls = new PointerLockControls(camera, document.body);
controls.lock()//开启第一视角锁定
}
var onKeyDown = (event) => {
switch (event.keyCode) {
case 38: // up
case 87: // w
moveForward = true
break
case 37: // left
case 65: // a
moveLeft = true
break
case 40: // down
case 83: // s
moveBackward = true
break
case 39: // right
case 68: // d
moveRight = true
break
}
}
var onKeyUp = (event) => {
switch (event.keyCode) {
case 38: // up
case 87: // w
moveForward = false
break
case 37: // left
case 65: // a
moveLeft = false
break
case 40: // down
case 83: // s
moveBackward = false
break
case 39: // right
case 68: // d
moveRight = false
break
}
}
document.addEventListener('keydown', onKeyDown, false)
document.addEventListener('keyup', onKeyUp, false)
function animate() {
// 设置纹理偏移
requestAnimationFrame(animate);
// 实现第一人称视角
const time = performance.now() //eslint-disable-line
if (controls) {
if (controls.isLocked) {
var delta = (time - prevTime) / 1000
velocity.x -= velocity.x * 10.0 * delta
velocity.z -= velocity.z * 10.0 * delta
velocity.y -= 9.8 * 200.0 * delta // 控制跳跃的高度
direction.z = Number(moveForward) - Number(moveBackward)
direction.x = Number(moveRight) - Number(moveLeft)
direction.normalize() // 这确保了各个方向的一致运动
if (moveForward || moveBackward) velocity.z -= direction.z * 200.0 * delta // 可控制移动的速度
if (moveLeft || moveRight) velocity.x -= direction.x * 200.0 * delta // 可控制移动的速度
controls.moveRight(-velocity.x * delta)
controls.moveForward(-velocity.z * delta)
controls.getObject().position.y += (velocity.y * delta) // new behavior
if (controls.getObject().position.y < 100) {//100是y轴的高度,需要根据项目调整,与相机高度一致,同下面100
velocity.y = 0
controls.getObject().position.y = 100 // 视角锁定时y轴的高度
}
}
prevTime = time
}
render();
}
/**
* 使用方法:
* 1、调用setSplitModel函数将要拆分的模型传入预处理
* 然后两种控制爆炸方式
* (1)实时更新
* 调用startSplit()/quitSplit()函数开始爆炸/恢复
* 需要在three的animate函数中调用update函数
* (2)滑动条
* 调用setValue函数把滑动条的值传入。
* 例如:
* const modelSplit = new ModelSplit()
* modelSplit.setSplitModel(group)
* modelSplit.startSplit()//开始拆分
* 然后在在three的animate函数中调用update函数
*/
class ModelSplit {
constructor() {
this.meshList = [];
this.running = false;
this.targetSplitValue = 0;
this.currentSplitValue = 0;
this.offset = 1;
this.splitScale = 5; //影响拆分距离,就是mesh的包围盒中心与爆炸中心的距离的倍率
this.splitSpeed = 100;//影响拆分速度,反比例
this.mode = 2;
if (this.mode == 2) {
this.splitScale = 0.3; //影响拆分距离,就是mesh的包围盒中心与爆炸中心的距离的倍率
this.splitSpeed = 50;//影响拆分速度,反比例
}
}
setSplitModel(model) {
if (!model || model instanceof THREE.Mesh) {
console.warn("只能处理Scene、Object3D、Group")
return;
}
this.quit();
this.meshList = [];
//计算模型整体包围盒中心作为爆炸中心
model.updateMatrixWorld();
var box = new THREE.Box3().expandByObject(model);
var maxLength = box.max.clone().distanceTo(box.min);
var center;
if (this.mode == 1) {
center = box.getCenter(new THREE.Vector3());
}
else { //爆炸中心由子mesh的包围盒中心决定
center = new THREE.Vector3();
var subBox, subCenter, count = 0;;
model.traverse(node => {
if (node.type == "Mesh") {
//分别计算每个mesh的包围盒中心,其与爆炸中心连线作为爆炸方向
subBox = new THREE.Box3().expandByObject(node);
subCenter = subBox.getCenter(new THREE.Vector3());
center = center.clone().add(subCenter);
count++;
}
})
center = center.clone().multiplyScalar(1 / count);
}
model.traverse(node => {
if (node.type == "Mesh") {
//分别计算每个mesh的包围盒中心,其与爆炸中心连线作为爆炸方向
let subBox = new THREE.Box3().expandByObject(node);
let meshCenter = subBox.getCenter(new THREE.Vector3());
node._splitSrcPos = node.getWorldPosition(new THREE.Vector3())
let subSpeed;
if (this.mode == 1) {
subSpeed = {
x: (meshCenter.x - center.x) * this.splitScale / this.splitSpeed,
y: (meshCenter.y - center.y) * this.splitScale / this.splitSpeed,
z: (meshCenter.z - center.z) * this.splitScale / this.splitSpeed,
}
}
else {
let targetPos = meshCenter.clone().add(meshCenter.clone().sub(center).normalize().multiplyScalar(maxLength));
//这里计算各个轴向分速度,这样可以使用滑动条控制进度
subSpeed = {
x: (targetPos.x - center.x) * this.splitScale / this.splitSpeed,
y: (targetPos.y - center.y) * this.splitScale / this.splitSpeed,
z: (targetPos.z - center.z) * this.splitScale / this.splitSpeed,
// x:(meshCenter.x-center.x) * this.splitScale / this.splitSpeed,
// y:(meshCenter.y-center.y) * this.splitScale / this.splitSpeed,
// z:(meshCenter.z-center.z) * this.splitScale / this.splitSpeed,
}
}
node._splitSpeed = subSpeed;
this.meshList.push(node);
}
})
this.currentSplitValue = 0;
this.targetSplitValue = 0;
this.running = false;
}
/**
* 开始爆炸
*/
startSplit() {
this.targetSplitValue = this.splitSpeed;
this.currentSplitValue = 0;
this.offset = 1;
this.running = true;
this.isQuit = false;
}
/**
* 开始反向爆炸(还原)
*/
quitSplit() {
this.targetSplitValue = 0;
this.currentSplitValue = this.splitSpeed;
this.offset = -1;
this.running = true;
this.isQuit = true;
}
/**
* 退出拆分时还原
*/
quit() {
if (this.currentSplitValue != 0 && this.meshList.length > 0) {
for (var i = 0; i < this.meshList.length; i++) {
let node = this.meshList[i];
node.position.copy(node._splitSrcPos);
}
this.currentSplitValue = 0;
this.targetSplitValue = 0;
}
}
/**
* 如果用滑动条控制时将滑动条的值传入这个函数
* @param {*} value [0,1]的值,表示爆炸进度
*/
setValue(value) {
if (value < 0) value = 0;
if (value > 1) value = 1;
this.currentSplitValue = value * this.splitSpeed;
for (var i = 0; i < this.meshList.length; i++) {
let node = this.meshList[i];
let x = node._splitSpeed.x * this.currentSplitValue + node._splitSrcPos.x;
let y = node._splitSpeed.y * this.currentSplitValue + node._splitSrcPos.y;
let z = node._splitSpeed.z * this.currentSplitValue + node._splitSrcPos.z;
node.parent.updateMatrixWorld();
let invMat = node.parent.matrixWorld.clone().invert();
let pos = new THREE.Vector3(x, y, z).applyMatrix4(invMat);
node.position.copy(pos);
}
}
/**
* 更新
* @returns
*/
update() {
if (this.running && this.meshList.length > 0) {
if (this.currentSplitValue != this.targetSplitValue) {
this.currentSplitValue += this.offset;
}
for (var i = 0; i < this.meshList.length; i++) {
let node = this.meshList[i];
let x = node._splitSpeed.x * this.currentSplitValue + node._splitSrcPos.x;
let y = node._splitSpeed.y * this.currentSplitValue + node._splitSrcPos.y;
let z = node._splitSpeed.z * this.currentSplitValue + node._splitSrcPos.z;
node.parent.updateMatrixWorld();
let invMat = node.parent.matrixWorld.clone().invert();
let pos = new THREE.Vector3(x, y, z).applyMatrix4(invMat);
node.position.copy(pos);
}
if (this.currentSplitValue == this.targetSplitValue) {
this.running = false;
if (this.isQuit == true) {
this.quit();
this.isQuit = false;
}
}
}
}
}
实现起来很简单,就实现一个圆柱体,里面是空心的(通过设置openEnded为true,或者设置材质的时候用数组,只贴图外围那个面)就好了,然后纹理贴图,贴一个渐变色的图。
function initContent() {
var textureLoader = new THREE.TextureLoader();
const geometry = new THREE.CylinderGeometry(5, 5, 6, 20);
const material = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
transparent: true,
map: textureLoader.load('./assets/img/gradual_blue_01.png'),
})
const circle = new THREE.Mesh(geometry, [material]);//用数组只贴边面
scene.add(circle)
let s = 0, p = 0;
function render1() {
if (s > 160) {
s = 0
p = 160
}
if (true) {
circle.scale.set(1 + s / 60, 1, 1 + s / 60);
circle.material[0].opacity = p / 160;
s++;
p--;
}
requestAnimationFrame(render1);
}
render1();
}
import { FXAAShader } from './jsm/shaders/FXAAShader.js'
import { EffectComposer } from './jsm/postprocessing/EffectComposer.js'
import { RenderPass } from './jsm/postprocessing/RenderPass.js'
import { ShaderPass } from './jsm/postprocessing/ShaderPass.js'
import { OutlinePass } from './jsm/postprocessing/OutlinePass.js'
let composer, renderPass, outlinePass, effectFXAA;
window.addEventListener('click', onMouseClick, false);
var raycaster = new THREE.Raycaster()
var mouse = new THREE.Vector2()
function onMouseClick(event) {
var vector = new THREE.Vector3((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) *
2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length) {
addColor("0xDC143C", [intersects[0].object])
}
}
const addColor = (color, selectedObjects) => {
// 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道。
composer = new EffectComposer(renderer)
// 新建一个场景通道 为了覆盖到原理来的场景上
renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)
// 物体边缘发光通道
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera)
outlinePass.edgeStrength = 10.0 // 边框的亮度
outlinePass.edgeGlow = 1 // 光晕[0,1]
outlinePass.usePatternTexture = false // 是否使用父级的材质
outlinePass.edgeThickness = 1.0 // 边框宽度
outlinePass.downSampleRatio = 2 // 边框弯曲度
outlinePass.pulsePeriod = 5 // 呼吸闪烁的速度
outlinePass.visibleEdgeColor.set(parseInt(color)) // 呼吸显示的颜色
outlinePass.hiddenEdgeColor = new THREE.Color(0, 0, 0) // 呼吸消失的颜色
outlinePass.clear = true
composer.addPass(outlinePass)
// 自定义的着色器通道 作为参数
effectFXAA = new ShaderPass(FXAAShader)
effectFXAA.uniforms.resolution.value.set(1 / window.innerWidth, 1 / window.innerHeight)
effectFXAA.renderToScreen = true
composer.addPass(effectFXAA)
outlinePass.selectedObjects = selectedObjects
console.log(composer);
return {
composer, // composer在render循环函数中调用
outlinePass // 实例化一次后设置 outlinePass.selectedObjects = selectedObjects
}
}
function animate() {
controls.update()
requestAnimationFrame(animate);
render();
stats.update();
if (composer) {
composer.render()
}
}
import { FXAAShader } from './jsm/shaders/FXAAShader.js'
import { EffectComposer } from './jsm/postprocessing/EffectComposer.js'
import { RenderPass } from './jsm/postprocessing/RenderPass.js'
import { ShaderPass } from './jsm/postprocessing/ShaderPass.js'
import { OutlinePass } from './jsm/postprocessing/OutlinePass.js'
let composer, renderPass, outlinePass, effectFXAA;
function outlineObj(selectedObjects) {
console.log(selectedObjects);
composer = new EffectComposer(renderer); // 特效组件
var renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass); // 特效渲染
outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
composer.addPass(outlinePass); // 加入高光特效
outlinePass.pulsePeriod = 2; //数值越大,律动越慢
outlinePass.visibleEdgeColor.set(0xff0000); // 高光颜色
outlinePass.hiddenEdgeColor.set(0x000000);// 阴影颜色
outlinePass.usePatternTexture = false; // 使用纹理覆盖?
outlinePass.edgeStrength = 5; // 高光边缘强度
outlinePass.edgeGlow = 1; // 边缘微光强度
outlinePass.edgeThickness = 1; // 高光厚度
outlinePass.selectedObjects = selectedObjects; // 需要高光的obj
}
outlineObj([Mesh])
function animate() {
controls.update()
requestAnimationFrame(animate);
render();
stats.update();
if (composer) {
composer.render()
}
}
/* 渲染内容 */
let cone
function initContent() {
const geometry = new THREE.ConeGeometry(5, 20, 4);
const material = new THREE.MeshLambertMaterial({
color: 0xccff22,
transparent: true,
opacity: 0.5,
side: THREE.DoubleSide,
map: new THREE.TextureLoader().load('./assets/img/渐变.png'),
depthTest: true,
});
cone = new THREE.Mesh(geometry, material);
cone.rotation.x = Math.PI
scene.add(cone);
}
let num = 0
function render() {
renderer.render(scene, camera);
controls.update()
cone.rotateY(0.05);
num += 0.1
cone.position.y = Math.sin(num)
}
var mesh
function initContent() {
var radarGroup = new THREE.Group();
var geometry = new THREE.PlaneGeometry(200, 200);
var material = new THREE.MeshLambertMaterial({
color: 0x00ffff,
map: new THREE.TextureLoader().load('./assets/img/扫描雷达.png'),
side: THREE.DoubleSide,
transparent: true,
opacity: 0.5,
depthTest: false,
});
mesh = new THREE.Mesh(geometry, material);
var material2 = new THREE.MeshLambertMaterial({
color: 0x00cccc,
map: new THREE.TextureLoader().load('./assets/img/雷达刻度.png'),
side: THREE.DoubleSide,
transparent: true,
opacity: 0.5,
depthTest: true,
});
var mesh2 = new THREE.Mesh(geometry, material2);
mesh2.rotateX(-Math.PI / 2);
mesh2.add(mesh);
radarGroup.add(mesh2);
scene.add(radarGroup)
}
function render() {
renderer.render(scene, camera);
controls.update()
mesh.rotateZ(0.02);
}
var plane
var material
function initContent() {
var texLoad = new THREE.TextureLoader();
var L = 1;
var geometry = new THREE.PlaneGeometry(L, 0.6 * L);
geometry.translate(-L / 2, 0, 0);
geometry.rotateZ(Math.PI / 2);
material = new THREE.MeshLambertMaterial({
map: texLoad.load('./assets/img/信号波.png'),
color: 0xffffff, //设置颜色
transparent: true, //允许透明计算
side: THREE.DoubleSide,
});
plane = new THREE.Mesh(geometry, material);
scene.add(plane)
}
var S = 100;//波动范围倍数设置
var _s = 1;
function render() {
renderer.render(scene, camera);
controls.update()
_s += 1;
plane.scale.set(_s, _s, _s);
if (_s <= S * 0.2) {
material.opacity = (_s - 1) / (S * 0.2 - 1);//保证透明度在0~1之间变化
} else if (_s > S * 0.2 && _s <= S) {
material.opacity = 1 - (_s - S * 0.2) / (S - S * 0.2);//保证透明度在0~1之间变化
} else {
_s = 1.0;
}
}
var texture
function initContent() {
const geometry = new THREE.PlaneGeometry(150, 30);
const material = new THREE.MeshBasicMaterial({
color: 0x00DFDF,
side: THREE.DoubleSide,
transparent: true,
side: THREE.DoubleSide, //两面可见
map: new THREE.TextureLoader().load('./assets/img/渐变3.png'),
});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
texture = new THREE.TextureLoader().load('./assets/img/流动.png');
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.x = 2;// x方向阵列
var plane2 = plane.clone();
plane2.material = new THREE.MeshBasicMaterial({
color: 0x00ffff,
map: texture,
transparent: true,
side: THREE.DoubleSide, //两面可见
});
scene.add(plane2)
}
function render() {
renderer.render(scene, camera);
controls.update()
texture.offset.y -= 0.01;
}
import { Lensflare, LensflareElement } from './jsm/objects/Lensflare.js';
const pointLight = new THREE.PointLight(0xffffff, 1.2, 2000);
pointLight.color.setHSL(.995, .5, .9);
pointLight.position.set(0, 45, -2000);
const textureLoader = new THREE.TextureLoader();
const textureFlare0 = textureLoader.load("./island/lensflare0.png");
const textureFlare1 = textureLoader.load("./island/lensflare1.png");
// 镜头光晕
const lensflare = new Lensflare();
lensflare.addElement(new LensflareElement(textureFlare0, 600, 0, pointLight.color));
lensflare.addElement(new LensflareElement(textureFlare1, 60, .6));
lensflare.addElement(new LensflareElement(textureFlare1, 70, .7));
lensflare.addElement(new LensflareElement(textureFlare1, 120, .9));
lensflare.addElement(new LensflareElement(textureFlare1, 70, 1));
pointLight.add(lensflare);
scene.add(pointLight);
let ratio = {
value: 0
}
const pointArr = [
220, -220, 0,//流光起点
220, 220, 0,//流光终点
220, 220, 0,//流光起点
-220, 100, 0,//流光终点
-220, 100, 0,//流光起点
-220, -220, 0,//流光终点
-220, -220, 0,//流光起点
220, -220, 0,//流光终点
200, -200, 0,//流光起点
-200, -200, 0,//流光终点
-200, -200, 0,//流光起点
-200, 80, 0,//流光终点
-200, 80, 0,//流光起点
200, 190, 0,//流光终点
200, 190, 0,//流光起点
200, -200, 0,//流光终点
];
lineStreamerLight(pointArr, ratio)
// 直线流光效果封装
function lineStreamerLight(pointArr, ratio) {
// 流光配置数据
const flyConf = {
range: 120, // 飞线长度
color: '#fcfc55', // 颜色
speed: 30, // 速度
size: 8 // 飞线点点的大小
};
const vertexShader = `
// 接收js传入的attribute值,会经过线性插值
attribute float current;
// 接收js传入的uniform值
uniform float uSize;
uniform float uTime;
uniform float uRange;
uniform float uTotal;
uniform float uSpeed;
// 向片元着色器传值颜色和透明度
varying float vopacity;
void main () {
float size = uSize;
// 根据时间确定当前飞线的位置, 以结束点为准
float currentEnd = mod(uTime * uSpeed, uTotal);
// 判断当前像素点是否在飞线范围内,如果在范围内设置尺寸和透明度
if (current < currentEnd && current > currentEnd - uRange) {
// 设置渐变的尺寸,头大尾小
float sizePct = (uRange - (currentEnd - current)) / uRange;
// size *= sizePct;
vopacity = clamp(1.0 * sizePct, 0.2, 1.0);
} else if(current < currentEnd - uRange){
vopacity = 0.2;
} else {
vopacity = 0.2;
}
// 将颜色传递给片元着色器
// 设置点的大小
gl_PointSize = size * 0.4;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
// 接收顶点着色器传入的值
varying float vopacity;
uniform vec3 uColor;
void main () {
// 设置颜色
gl_FragColor = vec4(uColor, vopacity);
}
`;
for (let i = 0; i < pointArr.length; i += 6) {
let start = new THREE.Vector3(
pointArr[i],
pointArr[i + 1],
pointArr[i + 2]
);
let end = new THREE.Vector3(
pointArr[i + 3],
pointArr[i + 4],
pointArr[i + 5]
);
const curve = new THREE.LineCurve3(start, end);
const number = start.distanceTo(end);
const points = curve.getPoints(number);
const positions = [];
const current = [];
points.forEach((item, index) => {
current.push(index);
positions.push(
item.x,
item.y,
item.z
);
});
const flyGeo = new THREE.BufferGeometry();
flyGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
flyGeo.setAttribute('current', new THREE.Float32BufferAttribute(current, 1));
const flyMaterial = new THREE.ShaderMaterial({
transparent: true,
depthWrite: false,
depthTest: false,
// blending: THREE.AdditiveBlending,
uniforms: {
uSize: { // 点的大小
value: flyConf.size
},
uTime: ratio, // 时间
uColor: { // 颜色
value: new THREE.Color(flyConf.color)
},
uRange: { // 飞线长度
value: flyConf.range
},
uTotal: { // 轨迹总长度,(点的总个数)
value: number
},
uSpeed: { // 飞行速度
value: flyConf.speed
}
},
vertexShader,
fragmentShader
});
// 创建并添加到场景中
const flyPoints = new THREE.Points(flyGeo, flyMaterial);
scene.add(flyPoints);
}
let next = 0;
function render() {
next += 0.12
ratio.value = next;
renderer.render(scene, camera);
controls.update()
}
<style>
.selectBox {
border: 1px solid #55aaff;
background-color: rgba(75, 160, 255, 0.3);
position: fixed;
}
</style>
//js
import { SelectionBox } from './jsm/interactive/SelectionBox.js';
import { SelectionHelper } from './jsm/interactive/SelectionHelper.js';
const selectionBox = new SelectionBox(camera, scene);
const helper = new SelectionHelper(renderer, 'selectBox');
//取消方框消失事件,不加这个方框会消失
helper.onSelectOver = function () { }
document.addEventListener('pointerdown', function (event) {
for (const item of selectionBox.collection) {
// item.material.emissive.set(0x000000);
}
selectionBox.startPoint.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1, 0.5);
});
document.addEventListener('pointermove', function (event) {
if (helper.isDown) {
for (let i = 0; i < selectionBox.collection.length; i++) {
// selectionBox.collection[i].material.emissive.set(0x000000);
}
selectionBox.endPoint.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1, 0.5);
const allSelected = selectionBox.select();
for (let i = 0; i < allSelected.length; i++) {
// allSelected[i].material.emissive.set(0xffffff);
}
}
});
document.addEventListener('pointerup', function (event) {
selectionBox.endPoint.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1, 0.5);
const allSelected = selectionBox.select();
for (let i = 0; i < allSelected.length; i++) {
// allSelected[i].material.emissive.set(0xffffff);
}
});
注:
1、背景要先建一个大平面,否则点不出来。
2、按删除键可以撤销上一步操作。
3、按enter回车键可以确定图形。
let lineArray = []
addEventListener('click', onDocumentMouseDown, false);
function onDocumentMouseDown(event) {
event.preventDefault();
var vector = new THREE.Vector3();//三维坐标对象
vector.set(
(event.clientX / window.innerWidth) * 2 - 1,
- (event.clientY / window.innerHeight) * 2 + 1,
0.5);
vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
var selected = intersects[0];//取第一个物体
let x = selected.point.x
let y = selected.point.y
let z = selected.point.z
console.log(x, y, z);
lineArray.push({ x, y, z })
initLine(lineArray)
initLineToPlane(lineArray)
addPlan(x, y, z)
}
}
const lineGroup = new THREE.Group()
function initLine(lineArray) {
const geometry = new THREE.BufferGeometry()
const pointsArray = new Array()
for (let i = 0; i < lineArray.length; i++) {
const x = lineArray[i].x
const y = lineArray[i].y
const z = lineArray[i].z
pointsArray.push(new THREE.Vector3(x, y, z))
}
geometry.setFromPoints(pointsArray)
const material = new THREE.LineBasicMaterial({
color: 0xddd
});
var line = new THREE.Line(geometry, material);
line.position.z = 0.2
lineGroup.add(line)
scene.add(lineGroup);
}
function initLineToPlane(lineArray) {
scene.getObjectByName("lineToPlane") && scene.remove(scene.getObjectByName("lineToPlane"))
const heartShape = new THREE.Shape();
heartShape.moveTo(lineArray[0].x, lineArray[0].y);
for (let i = 1; i < lineArray.length; i++) {
heartShape.lineTo(lineArray[i].x, lineArray[i].y)
}
const geometry = new THREE.ShapeGeometry(heartShape);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const mesh = new THREE.Mesh(geometry, material);
mesh.position.z = 0.1
mesh.name = "lineToPlane"
scene.add(mesh);
}
const planeGroup = new THREE.Group()
function addPlan(x, y, z) {
const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.MeshBasicMaterial({ color: 0xddd, side: THREE.DoubleSide });
const plane = new THREE.Mesh(geometry, material);
plane.position.set(x, y, z + 0.3)
plane.name = "点" + lineArray.length
planeGroup.add(plane)
scene.add(planeGroup);
}
$("body").keydown(function (event) {
if (event.keyCode == 13) {
alert('你按下了Enter');
planeGroup.children.length = 0
lineGroup.children.length = 0
scene.remove(planeGroup)
scene.remove(lineGroup)
lineArray = []
}
if (event.keyCode == 8) {
planeGroup.children.length = 0
lineGroup.children.length = 0
scene.remove(planeGroup)
scene.remove(lineGroup)
if (lineArray.length) {
lineArray.length -= 1
lineArray.length && initLine(lineArray)
lineArray.length && initLineToPlane(lineArray)
lineArray.forEach((item) => {
addPlan(item.x, item.y, item.z)
})
}
}
});
2D平面展示有两种, 一种是这个项目里的鼠标触碰直升机的提示牌, 时刻与摄像头在同一角度的2D平面; 另一种是只在一个方向上可见的2D平面
1、首先是第一种, 多角度的2D平面. 我们需要用到CSS2DRenderer对其进行渲染, 即创建一个DOM, 将其赋给CSS2DRenderer, 下面代码没有设置坐标, 我是放在鼠标移动事件里设置的
var planeInfo = document.createElement('div')
planeInfo.className = 'the-modal'
planeInfo.innerHTML = '治电护航直升机' +
'ZZES 007 '
planeInfo.classList.add('hide')
infoModal = new THREE.CSS2DObject(planeInfo)
//鼠标移入的时候
//infoModal.element.classList.remove('hide')
scene.add(infoModal)
2、第二种, 固定角度2D平面. 原理是, 创建一个矩形Mesh, 然后创建一个canvas内容, 作为其贴图.
var tipsGeo2 = new THREE.PlaneBufferGeometry(3, 1, 1, 1)
var tips2 = new THREE.Mesh(tipsGeo2, createFont('测试测试', {bgColor: '#E6A23C', fontColor: '#FFFFFF'}))
tips2.position.set(9, 1.5, -3)
tips2.rotation.y = 3.15
scene.add(tips2)
/**
* text 文字
* options.fontColor 文字颜色
* options.bgColor 背景颜色
*/
function createFont (text, options={bgColor: '', fontColor: ''}) {
var canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 128;
var c = canvas.getContext('2d');
// 矩形区域填充背景
c.fillStyle = options.bgColor || '#ff0000';
c.fillRect(0, 0, 512, 128);
c.beginPath();
// 文字
c.beginPath();
c.translate(256,64);
c.fillStyle = options.fontColor || '#ffffff'; //文本填充颜色
c.font = 'bold 36px 微软雅黑'; //字体样式设置
c.textBaseline = 'middle'; //文本与fillText定义的纵坐标
c.textAlign = 'center'; //文本居中(以fillText定义的横坐标)
c.fillText(text, 0, 0);
var texture = new THREE.CanvasTexture(canvas);
var textMaterial = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
// side: THREE.DoubleSide,
transparent: true,
opacity: 0.9
});
textMaterial.map.needsUpdate = true
return textMaterial
}
第一种实现代码
import { CSS2DRenderer, CSS2DObject } from "../jsm/renderers/CSS2DRenderer.js";
let camera, scene, renderer, clock, controls, labelRenderer, infoModal;
/* 控制器 */
function initControls() {
controls = new OrbitControls(camera, labelRenderer.domElement);
controls.dampingFactor = true;
// controls.minPolarAngle = 0.5;
// controls.maxPolarAngle = 1.2;
}
/* 渲染内容 */
function initContent() {
var planeInfo = document.createElement("div");
planeInfo.className = "the-modal";
planeInfo.innerHTML = "111111111111111111111111";
infoModal = new CSS2DObject(planeInfo);
infoModal.position.set(0, 0, 0);
scene.add(infoModal);
}
function init2DRenderer() {
labelRenderer = new CSS2DRenderer();
labelRenderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.domElement.style.position = "absolute";
labelRenderer.domElement.style.top = 0;
// labelRenderer.domElement.style.pointerEvents = "none";//controls无法操作,用这个来控制能否操作
container.appendChild(labelRenderer.domElement);
}
function render() {
renderer.render(scene, camera);
labelRenderer.render(scene, camera);
controls.update();
}
function init() {
initScene();
initCamera();
initRender();
init2DRenderer();
initLight();
initControls();
initContent();
window.addEventListener("resize", onWindowResize, false);
}
/* 窗口变动触发 */
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
labelRenderer.setSize(window.innerWidth, window.innerHeight);
}
参考网络,个人收藏学习