Three.js基础入门介绍——【毕业季】Three.js动态相册

前言
岁月匆匆,又是一年毕业季,这次做个动态相册展示图片,放些有意思的内容,一起回忆下校园生活吧。

预期效果

相册展示和点选切换,利用相机旋转和移动来实现一个点击切图平滑过渡的效果。

实现流程

基本流程

1、搭建场景
2、放置图片
3、鼠标事件
4、相机运动

工程文件
工程文件结构如下图:
static:存放静态资源文件
three.js-master:为官网下载的代码包,包含所有需要用到的资源包,链接:https://github.com/mrdoob/three.js/archive/master.zip
在这里插入图片描述
index.html:页面代码

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>My first three.js app</title><style>body {margin: 0;}</style>
</head><body><script type="importmap">{"imports": {"three": "./three.js-master/build/three.module.js"}}</script><script type="module">// 下文JS代码位置// ...</script>
</body></html>

搭建场景
需要导入的内容和提前声明的变量,包含后续不同function中可能用到的场景、相机、渲染器、控制器等要素。

import * as THREE from "three";
import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";
import { TWEEN } from "./three.js-master/examples/jsm/libs/tween.module.min.js";let scene, camera, renderer, controls; //场景、相机、渲染器、控制器
let pointLight, ambientLight; //光源
let curve = null, rate = 0; // 照片点位
let imgArr = []; //照片url
let imgCut = 50; //照片剪影大小
let rotateImg = true; // 是否继续轮转照片,其实是相机在转
const imgGroup = new THREE.Group(); //照片对象组
const textureLoader = new THREE.TextureLoader(); // 纹理加载器
const pointer = new THREE.Vector2(); //点击坐标
const raycaster = new THREE.Raycaster(); //射线
const threeEl = document.getElementById('container'); //元素获取

场景和部分数据初始化,其中图片这里直接是使用的网络资源图,可以使用本地文件或者自己搭建的服务环境文件。

function init() {imgArr = ["https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg95.699pic.com%2Fphoto%2F40142%2F4204.gif_wh860.gif&refer=http%3A%2F%2Fimg95.699pic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659077726&t=ea5e3abb8b838546ae9377321744f875","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F019b795b2cc0dba80121bbec95a340.jpg%402o.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076627&t=be25ec64df151d68c5dd5296a1d68fcf","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fp4.itc.cn%2Fimages01%2F20200707%2Fcc1bec607b1949549f374f3e0e68bc2d.jpeg&refer=http%3A%2F%2Fp4.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659072591&t=34a56c58c383f15a5118d81859cad417","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F019b795b2cc0dba80121bbec95a340.jpg%402o.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=aae752e553bbdeda27a4fa14b242e4e8","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fp.qpic.cn%2Fdnfbbspic%2F0%2Fdnfbbs_dnfbbs_dnf_gamebbs_qq_com_forum_202007_05_084137qjj5sjd9pqm9mprr.jpg%2F0&refer=http%3A%2F%2Fp.qpic.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=bd12aa3f0d060cc19880158e9ef7b16f","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.keaidian.com%2Fuploads%2Fallimg%2F190713%2F13174657_15.jpg&refer=http%3A%2F%2Fwww.keaidian.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=47296d32109bbffce6844b557a109d24","https://img2.baidu.com/it/u=3487630334,1818100496&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_source%2F29%2F09%2F31%2F290931cc8b21f13ff0ca273ff8e4865e.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=5c77914a690c1b53de57c7bf5692487a",]changeImg(threeEl, imgArr[0]);scene = new THREE.Scene();camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);renderer = new THREE.WebGLRenderer();// 添加相机并设置在原点camera.position.set(0, 0, 0);camera.lookAt(new THREE.Vector3(1, 0, 0));// 添加一个点光源pointLight = new THREE.PointLight(0xffffff);scene.add(pointLight);// 添加一个环境光ambientLight = new THREE.AmbientLight(0xffffff);scene.add(ambientLight);// 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.// 添加坐标系到场景中// const axes = new THREE.AxesHelper(20);// scene.add(axes);// 创建渲染器对象renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(window.innerWidth, window.innerHeight);//设置渲染区域尺寸threeEl.appendChild(renderer.domElement); //body元素中插入canvas对象render// 监听鼠标pointerListing();// 初始化控制器controls = new OrbitControls(camera, renderer.domElement);//创建控件对象controls.enabled = false;
}

放置图片
用到了基础网格材质(MeshBasicMaterial)和贴图,用贴图加载器(TextureLoader)将图片贴到放置的平面缓冲几何体(PlaneGeometry)上,再在外层增加一个有一定透明度的立方缓冲几何体(BoxGeometry),让它看上去就像是一个相框。

基础网格材质(MeshBasicMaterial)
一个以简单着色(平面或线框)方式来绘制几何体的材质,这种材质不受光照的影响。

构造函数(Constructor)
MeshBasicMaterial( parameters : Object )
parameters - (可选)用于定义材质外观的对象,具有一个或多个属性。材质的任何属性都可以从此处传入(包括从Material继承的任何属性)。
属性color例外,其可以作为十六进制字符串传递,默认情况下为 0xffffff(白色),内部调用Color.set(color)。

TextureLoader
加载texture的一个类。 内部使用ImageLoader来加载文件。

构造函数
TextureLoader( manager : LoadingManager )
manager — 加载器使用的loadingManager,默认值为THREE.DefaultLoadingManager.

// 图片贴到对应位置
function placeImg() {imgGroup.name = 'imgGroup';// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.// Create a closed wavey loopcurve = new THREE.CatmullRomCurve3([new THREE.Vector3(75, 0, 0),new THREE.Vector3(0, 0, 75),new THREE.Vector3(-75, 0, 0),new THREE.Vector3(0, 0, -75)]);// centripetal、chordal和catmullromcurve.curveType = "catmullrom";curve.closed = true;//设置是否闭环curve.tension = 1; //设置线的张力,0为无弧度折线// // 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper// const points = curve.getPoints(50);// const geometry = new THREE.BufferGeometry().setFromPoints(points);// const material = new THREE.LineBasicMaterial({ color: 0x000000 });// // Create the final object to add to the scene// const curveObject = new THREE.Line(geometry, material);// scene.add(curveObject);const imgNum = imgArr.length;rate = imgNum <= 0 ? 0 : (1 / imgNum);imgArr.forEach((item, index) => {const imgPosition = curve.getPointAt(rate * index);// 材质对象Materialconst material = new THREE.MeshBasicMaterial({side: THREE.DoubleSide,opacity: 0.8,transparent: true,name: "material" + index,map: textureLoader.load(item)});const mesh = new THREE.Mesh(new THREE.PlaneGeometry(imgCut, imgCut), material);mesh.position.set(imgPosition.x, imgPosition.y, imgPosition.z);mesh.rotation.y = -Math.PI / 2;mesh.lookAt(scene.position) //设置朝向mesh.name = item;// imgGroup.add(mesh);scene.add(mesh);// 加一个框const boxMaterial = new THREE.MeshBasicMaterial({opacity: 0.1,transparent: true,color: 0x0081cc});const boxMesh = new THREE.Mesh(new THREE.BoxGeometry(imgCut * 1.1, imgCut * 1.1, imgCut * 0.1), boxMaterial);boxMesh.position.set(imgPosition.x, imgPosition.y, imgPosition.z);boxMesh.rotation.y = -Math.PI / 2;boxMesh.lookAt(scene.position) //设置朝向boxMesh.name = item;imgGroup.add(boxMesh);})scene.add(imgGroup);}

鼠标事件
当鼠标放到相框上,贴有照片的平面几何体外层立方几何体的透明度改变,达到选中效果,选中贴图剪影时页面背景改变。

 // 背景替换
function changeImg(element, url) {element.style.backgroundImage = `url(${url})`;// element.style.backgroundSize = '100%'; 
}
// 添加鼠标监听
function pointerListing() {threeEl.addEventListener("pointerdown", onPointerDown);threeEl.addEventListener("pointermove", onPointerMove);
}
function onPointerMove(event) {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1);// console.log(scene);raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects([...imgGroup.children],false);// 有照片就换背景if (intersects.length > 0) {const intersect = intersects[0];intersect.object.material.opacity = 0.5;}else{imgGroup.children.forEach(item => {item.material.opacity = 0.1;});}
}
function onPointerDown(event) {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1);// console.log(scene);raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects([...imgGroup.children],false);// 有照片就换背景if (intersects.length > 0) {const intersect = intersects[0];// console.log(intersect.object);// 背景替换changeImg(threeEl, intersect.object.name);// 相机移动rotateImg = false;// 照片旋转停下来const changePosition = new THREE.Vector3(intersect.object.position.x*1.5,intersect.object.position.y*1.5,intersect.object.position.z*1.5);animateCamera(camera.position, changePosition);} else {// 相机回原点const o = new THREE.Vector3(0,0,0);animateCamera(camera.position, o);rotateImg = true;}
};

相机运动
相机本身在旋转,相对的图片看起来就像是在滚动播放,当点击某个图片时,相机移动到对应对象更外层,点击空白再回到原点,达到平滑切换的效果。

function onPointerDown(event) {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1);// console.log(scene);raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects([...imgGroup.children],false);// 有照片就换背景if (intersects.length > 0) {const intersect = intersects[0];// console.log(intersect.object);// 背景替换changeImg(threeEl, intersect.object.name);// 相机移动rotateImg = false;// 照片旋转停下来const changePosition = new THREE.Vector3(intersect.object.position.x*1.5,intersect.object.position.y*1.5,intersect.object.position.z*1.5);animateCamera(camera.position, changePosition);} else {// 相机回原点const o = new THREE.Vector3(0,0,0);animateCamera(camera.position, o);rotateImg = true;}
};
// current1 相机当前的位置
// target1 相机的目标位置
// current2 当前的controls的target
// target2 新的controls的target
function animateCamera(current1, target1,callBack) {var tween = new TWEEN.Tween({x1: current1.x, // 相机当前位置xy1: current1.y, // 相机当前位置yz1: current1.z, // 相机当前位置z// x2: current2.x, // 控制当前的中心点x// y2: current2.y, // 控制当前的中心点y// z2: current2.z // 控制当前的中心点z});tween.to({x1: target1.x, // 新的相机位置xy1: target1.y, // 新的相机位置yz1: target1.z, // 新的相机位置z// x2: target2.x, // 新的控制中心点位置x// y2: target2.y, // 新的控制中心点位置x// z2: target2.z // 新的控制中心点位置x}, 1000);tween.onUpdate(function (object) {camera.position.x = object.x1;camera.position.y = object.y1;camera.position.z = object.z1;// controls.target.x = object.x2;// controls.target.y = object.y2;// controls.target.z = object.z2;// controls.update();});tween.onComplete(function () {callBack && callBack()});tween.easing(TWEEN.Easing.Cubic.InOut);tween.start();
}

完整代码

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>My first three.js app</title><style>body {margin: 0px;}#container {/* background: #000000 url("https://seopic.699pic.com/photo/50041/6756.jpg_wh1200.jpg") no-repeat; */background: #000000;background-size: 100vw 100vh;overflow: hidden;width: 100vw;height: 100vh;}</style>
</head><body><div id="container"></div><script type="importmap">{"imports": {"three": "./three.js-master/build/three.module.js"}}</script><script type="module">import * as THREE from "three";import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";import { TWEEN } from "./three.js-master/examples/jsm/libs/tween.module.min.js";let scene, camera, renderer, controls; //场景、相机、渲染器、控制器let pointLight, ambientLight; //光源let curve = null, rate = 0; // 照片点位let imgArr = []; //照片urllet imgCut = 50; //照片剪影大小let rotateImg = true; // 是否继续轮转照片,其实是相机在转const imgGroup = new THREE.Group(); //照片对象组const textureLoader = new THREE.TextureLoader(); // 纹理加载器const pointer = new THREE.Vector2(); //点击坐标const raycaster = new THREE.Raycaster(); //射线const threeEl = document.getElementById('container'); //元素获取function init() {imgArr = ["https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg95.699pic.com%2Fphoto%2F40142%2F4204.gif_wh860.gif&refer=http%3A%2F%2Fimg95.699pic.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659077726&t=ea5e3abb8b838546ae9377321744f875","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F019b795b2cc0dba80121bbec95a340.jpg%402o.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076627&t=be25ec64df151d68c5dd5296a1d68fcf","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fp4.itc.cn%2Fimages01%2F20200707%2Fcc1bec607b1949549f374f3e0e68bc2d.jpeg&refer=http%3A%2F%2Fp4.itc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659072591&t=34a56c58c383f15a5118d81859cad417","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F019b795b2cc0dba80121bbec95a340.jpg%402o.jpg&refer=http%3A%2F%2Fimg.zcool.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=aae752e553bbdeda27a4fa14b242e4e8","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fp.qpic.cn%2Fdnfbbspic%2F0%2Fdnfbbs_dnfbbs_dnf_gamebbs_qq_com_forum_202007_05_084137qjj5sjd9pqm9mprr.jpg%2F0&refer=http%3A%2F%2Fp.qpic.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=bd12aa3f0d060cc19880158e9ef7b16f","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.keaidian.com%2Fuploads%2Fallimg%2F190713%2F13174657_15.jpg&refer=http%3A%2F%2Fwww.keaidian.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=47296d32109bbffce6844b557a109d24","https://img2.baidu.com/it/u=3487630334,1818100496&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500","https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic_source%2F29%2F09%2F31%2F290931cc8b21f13ff0ca273ff8e4865e.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659076255&t=5c77914a690c1b53de57c7bf5692487a",]changeImg(threeEl, imgArr[0]);scene = new THREE.Scene();camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);renderer = new THREE.WebGLRenderer();// 添加相机并设置在原点camera.position.set(0, 0, 0);camera.lookAt(new THREE.Vector3(1, 0, 0));// 添加一个点光源pointLight = new THREE.PointLight(0xffffff);scene.add(pointLight);// 添加一个环境光ambientLight = new THREE.AmbientLight(0xffffff);scene.add(ambientLight);// 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.// 添加坐标系到场景中// const axes = new THREE.AxesHelper(20);// scene.add(axes);// 创建渲染器对象renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });renderer.setSize(window.innerWidth, window.innerHeight);//设置渲染区域尺寸threeEl.appendChild(renderer.domElement); //body元素中插入canvas对象render// 监听鼠标pointerListing();// 初始化控制器controls = new OrbitControls(camera, renderer.domElement);//创建控件对象controls.enabled = false;}// 背景替换function changeImg(element, url) {element.style.backgroundImage = `url(${url})`;// element.style.backgroundSize = '100%'; }// 添加鼠标监听function pointerListing() {threeEl.addEventListener("pointerdown", onPointerDown);threeEl.addEventListener("pointermove", onPointerMove);}function onPointerMove(event) {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1);// console.log(scene);raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects([...imgGroup.children],false);// 有照片就换背景if (intersects.length > 0) {const intersect = intersects[0];intersect.object.material.opacity = 0.5;}else{imgGroup.children.forEach(item => {item.material.opacity = 0.1;});}}function onPointerDown(event) {// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)pointer.set((event.clientX / window.innerWidth) * 2 - 1,-(event.clientY / window.innerHeight) * 2 + 1);// console.log(scene);raycaster.setFromCamera(pointer, camera);const intersects = raycaster.intersectObjects([...imgGroup.children],false);// 有照片就换背景if (intersects.length > 0) {const intersect = intersects[0];// console.log(intersect.object);// 背景替换changeImg(threeEl, intersect.object.name);// 相机移动rotateImg = false;// 照片旋转停下来const changePosition = new THREE.Vector3(intersect.object.position.x*1.5,intersect.object.position.y*1.5,intersect.object.position.z*1.5);animateCamera(camera.position, changePosition);} else {// 相机回原点const o = new THREE.Vector3(0,0,0);animateCamera(camera.position, o);rotateImg = true;}};// current1 相机当前的位置// target1 相机的目标位置// current2 当前的controls的target// target2 新的controls的targetfunction animateCamera(current1, target1,callBack) {var tween = new TWEEN.Tween({x1: current1.x, // 相机当前位置xy1: current1.y, // 相机当前位置yz1: current1.z, // 相机当前位置z// x2: current2.x, // 控制当前的中心点x// y2: current2.y, // 控制当前的中心点y// z2: current2.z // 控制当前的中心点z});tween.to({x1: target1.x, // 新的相机位置xy1: target1.y, // 新的相机位置yz1: target1.z, // 新的相机位置z// x2: target2.x, // 新的控制中心点位置x// y2: target2.y, // 新的控制中心点位置x// z2: target2.z // 新的控制中心点位置x}, 1000);tween.onUpdate(function (object) {camera.position.x = object.x1;camera.position.y = object.y1;camera.position.z = object.z1;// controls.target.x = object.x2;// controls.target.y = object.y2;// controls.target.z = object.z2;// controls.update();});tween.onComplete(function () {callBack && callBack()});tween.easing(TWEEN.Easing.Cubic.InOut);tween.start();}// 图片贴到对应位置function placeImg() {imgGroup.name = 'imgGroup';// 红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.// Create a closed wavey loopcurve = new THREE.CatmullRomCurve3([new THREE.Vector3(75, 0, 0),new THREE.Vector3(0, 0, 75),new THREE.Vector3(-75, 0, 0),new THREE.Vector3(0, 0, -75)]);// centripetal、chordal和catmullromcurve.curveType = "catmullrom";curve.closed = true;//设置是否闭环curve.tension = 1; //设置线的张力,0为无弧度折线// // 为曲线添加材质在场景中显示出来,不添加到场景显示也不会影响运动轨迹,相当于一个Helper// const points = curve.getPoints(50);// const geometry = new THREE.BufferGeometry().setFromPoints(points);// const material = new THREE.LineBasicMaterial({ color: 0x000000 });// // Create the final object to add to the scene// const curveObject = new THREE.Line(geometry, material);// scene.add(curveObject);const imgNum = imgArr.length;rate = imgNum <= 0 ? 0 : (1 / imgNum);imgArr.forEach((item, index) => {const imgPosition = curve.getPointAt(rate * index);// 材质对象Materialconst material = new THREE.MeshBasicMaterial({side: THREE.DoubleSide,opacity: 0.8,transparent: true,name: "material" + index,map: textureLoader.load(item)});const mesh = new THREE.Mesh(new THREE.PlaneGeometry(imgCut, imgCut), material);mesh.position.set(imgPosition.x, imgPosition.y, imgPosition.z);mesh.rotation.y = -Math.PI / 2;mesh.lookAt(scene.position) //设置朝向mesh.name = item;// imgGroup.add(mesh);scene.add(mesh);// 加一个框const boxMaterial = new THREE.MeshBasicMaterial({opacity: 0.1,transparent: true,color: 0x0081cc});const boxMesh = new THREE.Mesh(new THREE.BoxGeometry(imgCut * 1.1, imgCut * 1.1, imgCut * 0.1), boxMaterial);boxMesh.position.set(imgPosition.x, imgPosition.y, imgPosition.z);boxMesh.rotation.y = -Math.PI / 2;boxMesh.lookAt(scene.position) //设置朝向boxMesh.name = item;imgGroup.add(boxMesh);})scene.add(imgGroup);}//执行渲染操作   指定场景、相机作为参数function render() {renderer.render(scene, camera);//执行渲染操作if (rotateImg) {camera.rotateY(0.001);//每次绕y轴旋转0.001弧度}TWEEN.update();// newmesh.rotateY(0.01);//每次绕y轴旋转0.01弧度requestAnimationFrame(render);//请求再次执行渲染函数render}function initWindow() {let onResize = function () {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);};window.addEventListener("resize", onResize, false);};init();placeImg();initWindow();render();</script></body></html>

实现效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/282256.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【python】python汽车效能数据集—回归建模(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

【学习】软件测试行业未来的发展趋势预测

近年来&#xff0c;随着中国数字经济的蓬勃发展&#xff0c;软件测试行业也迎来了新的春天。从早期的手工测试到自动化测试&#xff0c;再到持续集成和持续交付&#xff0c;中国的软件测试行业经历了快速的发展和变革。各行各业均对软件测试提出了更高的要求&#xff0c;尤其在…

将数据转换成xml格式的文档并下载

现在有一个实体类对象的集合&#xff0c;需要将它们转换为xml文档&#xff0c;xml文档就是标签集合的嵌套&#xff0c;例如一个学生类&#xff0c;有姓名、年龄等&#xff0c;需要转换成一下效果&#xff1a; <student><age>14</age><name>张三</na…

【Java】Oracle发布Java22最新版本

甲骨文&#xff08;ORACLE&#xff09;已经于2023年3月19日正式发布了最新版本的JDK&#xff0c;版本号&#xff1a;22 根据官方声明&#xff0c;Java 22 (Oracle JDK 22) 在性能、稳定性和安全性方面进行了数千种改进&#xff0c;包括对Java 语言、其API 和性能&#xff0c;以…

docker 哲学 - 网络桥接器、容器网络接口 、容器间的通信方式

1、解释 docker0 veth eth 2、vethXX 和 ethXX 是肯定一一对应吗 比如 eth1 对应 veth1 3、如果 A容器使用 默认创建方式 。定义他内部网络为 eth0&#xff0c;容器B使用 --network 连上 已创建的网络 172.89.2.1 。此时假设 B的 ip是 172.89.2.2 &#xff0c;容器网络接口是 e…

Godot 学习笔记(4):一切以场景为中心

文章目录 前言场景搭建新建子场景最简单的按钮事件 手动控制场景手动加载场景添加多个场景对象更快速的获取脚本对象 删除多个场景对象脚本命名的问题 总结 前言 Godot的场景是C#与Godot最后的中间连接。我们解决了场景的加载&#xff0c;我们基本可以保证C#和godot之间的彻底…

C++初阶:vector相关练习

目录 1. 只出现一次的数2. 杨辉三角3. 删除有序数组中的重复项4. 只出现一次的数II5. 只出现一次的数III6. 数组中出现次数超过一半的数7. 电话号码的字母组合&#xff08;多叉树遍历&#xff09; 1. 只出现一次的数 题目信息&#xff1a; 题目链接&#xff1a; 只出现一次的数…

工程信号的去噪和(分类、回归和时序)预测

&#x1f680;【信号去噪及预测论文代码指导】&#x1f680; 还为小论文没有思路烦恼么&#xff1f;本人专注于最前沿的信号处理与预测技术——基于信号模态分解的去噪算法和深度学习的信号&#xff08;回归、时序和分类&#xff09;预测算法&#xff0c;致力于为您提供最精确、…

ruoyi-nbcio-plus基于vue3的flowable增加开始节点的表单绑定修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

java.lang.String final

关于String不可变的问题&#xff1a;从毕业面试到现在&#xff0c;一个群里讨论的东西&#xff0c;反正码农面试啥都有&#xff0c;这也是我不咋喜欢面试代码&#xff0c;因为对于我而言&#xff0c;我并不喜欢这些面试。知道或不知道基本没啥含氧量&#xff0c;就是看看源代码…

蓝桥杯刷题(十三)

1.煤球数目 代码 cnt ans 0 start 1 a [] while cnt<100:ansstartstart 1t ansstartcnt1a.append(ans) print(sum(a))2.奖券数目 代码 def f(x)->bool:while x:if x%104:return Falsex//10return True ans 0 for i in range(10000,100000):if f(i):ans1 print(a…

二叉搜索树(二叉排序树)(含力扣相关题及题解)

文章目录 二叉搜索树&#xff08;二叉排序树&#xff09;1、二叉搜索树概念2、二叉搜索树的操作2.1、二叉搜索树的查找2.2、二叉搜索树的插入2.2、二叉树的删除 3、二叉搜索树的实现&#xff08;含递归版本&#xff09;4、二叉搜索树的应用4.1、K模型4.2、KV模型 5、二叉搜索树…

C语言例:设 int x; 则表达式 (x=4*5,x*5),x+25 的值

代码如下&#xff1a; #include<stdio.h> int main(void) {int x,m;m ((x4*5,x*5),x25);printf("(x4*5,x*5),x25 %d\n",m);//x4*520//x*5100//x2545return 0; } 结果如下&#xff1a;

拌合楼管理系统开发(十) 不谈技术只谈管理之大宗物资虚假贸易

前言:不谈技术只谈管理 大宗物资往往都是虚假贸易的重灾区,多年前规模就是面子的口号下,一大批国央企挖空心思做大规模,开展了一大批虚假贸易,同时为了面上的合规性,往往会有三方甚至更多方进入到整个链条中,钱货在这个链条中流转,甚至有些就是钱在流转,如果整个链条有一个环节…

电视盒子哪款好?数码小编分享电视盒子品牌排行榜

电视盒子是我们使用最多的数码产品自已&#xff0c;在挑选电视盒子这块超多朋友踩过雷&#xff0c;广告多&#xff0c;频繁卡顿&#xff0c;收费节目多&#xff0c;究竟电视盒子哪款好&#xff1f;本期小编要分享的就是目前最值得入手的电视盒子品牌排行榜&#xff0c;想买电视…

数据结构中单向链表(无头)的学习

一.数据结构 1.定义 一组用来保存一种或者多种特定关系的数据的集合&#xff08;组织和存储数据&#xff09; 程序的设计&#xff1a;将现实中大量而复杂的问题以特定的数据类型和特定的存储结构存储在内存中&#xff0c; 并在此基础上实现某个特定的功能的操…

SpringBoot-03 | SpringBoot自动配置

SpringBoot-03 | SpringBoot自动配置 原理分析代码示例源码剖析SpringBootConfiguration&#xff1a;组合注解&#xff0c;标记当前类为配置类ComponentScanEnableAutoConfigurationImport加载spring.factoriesrun初始化加载spring.factoriesspring.factories中的钩子类 网上盗…

58、服务攻防——应用协议设备KibanaZabbix远控向日葵VNCTeamViwer

文章目录 vnc默认端口&#xff1a;5900 or 5902&#xff0c;hydra支持vnc破解。VNC有三种模式&#xff1a;使用vnc密码、windows密码、无密码。 teamviewer、向日葵都是使用之前爆过漏洞进行测试。 zabbix&#xff1a;监控系统&#xff0c;蓝队部署平台。zabbix页面如下&#…

四川宏博蓬达法律咨询有限公司守护您的法律安全

在法治社会中&#xff0c;法律咨询服务的需求日益增长&#xff0c;而四川宏博蓬达法律咨询有限公司正是这一领域中一颗璀璨的明星。公司以其专业的法律服务和严谨的工作态度&#xff0c;为广大客户提供了安全、高效、全面的法律支持&#xff0c;赢得了社会各界的广泛赞誉。 一、…

高德地图——轨迹回放和电子围栏

功能点 地图的初始化显示电子围栏&#xff08;先初始化在调接口显示电子围栏&#xff09;显示定位显示轨迹轨迹回放 &#xff08;回放速度无法控制是因为高德地图的版本问题&#xff0c;不要设置版本&#xff0c;使用默认的即可生效&#xff09;获取当前城市及天气情况设置地图…