npm install --save three
import * as THREE from 'three'
scene场景,camera相机,renderer渲染器
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(75,800/800,0.1,700)
PerspectiveCamera:
参数一:视野角度,无论在什么时候,你所能再显示器上看到的场景的范围,单位是角度。
参数二:长宽比,一个物体的宽除以她的高
参数三:近截面和远截面,当某些部分比摄像机的远截面或者近截面近的时候,该部分将不会被渲染到场景中。
当您使用纹理时,使用更高分辨率的纹理可以减少锯齿
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize( 800, 800 );
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
BoxGeometry(x轴上的宽度,y轴上的高度,z轴上的深度) 默认为1
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
表示基于以三角形为polygon mesh(多边形网格)的物体的类。
同时也作为其他类的基类 Mesh( geometry :
BufferGeometry, material : Material ) geometry ——
(可选)BufferGeometry的实例,默认值是一个新的BufferGeometry。 material ——
(可选)一个Material,或是一个包含有Material的数组,默认是一个新的MeshBasicMaterial。
mesh = new THREE.Mesh( geometry, material );
//元素中插入canvas对象container.derer.domElement);
某些设备以及浏览器直到现在仍然不支持WebGL。
以下的方法可以帮助你检测当前用户所使用的环境是否支持WebGL,如果不支持,将会向用户提示一条信息。
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
if ( WebGL.isWebGLAvailable() ) {this.animate();
} else {const warning = WebGLErrorMessage();ElementById( 'container' ).appendChild( warning );
}
animate() {requestAnimationFrame( this.animate );ation.x += 0.sh.rotation.y += 0.der( this.scene, this.camera );}
// 看的方向
this.camera.lookAt(0,0,0)
//添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(3)
this.scene.add( axesHelper );
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//添加轨道控制器
ls = new OrbitControls(this.derer.domElement)
//设置带阻尼的惯性
ableDamping = true
//设置阻尼系数,惯性的大小
ls.dampingFactor = 0.05
//设置自动旋转
ls.autoRotate = true
//更新
ls.update()
运行效果:
完整代码:
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshcontrols:null, //轨道控制器}},methods:{init(){let container = ElementById('container');//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,1000/1000,0.1,700)//创建渲染器derer = new THREE.WebGLRenderer();derer.setSize(window.innerWidth, window.innerHeight);// 让物体渲染的没有楼梯纹derer.setPixelRatio(window.devicePixelRatio) //创建一个立方体const geometry = new THREE.BoxGeometry( 1, 1, 1 );//我们需要给它一个MeshBasicMaterial材质,来让它有绿色颜色const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );//需要一个 Mesh(网格sh = new THREE.Mesh( geometry, material );// 添加物体到网格this.scene.add( sh );// 设置相机位置this.camera.position.z = 5; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//设置自动旋转ls.autoRotate = true//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( 'container' ).appendChild( warning );}},//旋转起来animate() {ls.update()requestAnimationFrame( this.animate );ation.x += 0.sh.rotation.y += 0.der( this.scene, this.camera );}}
}
</script>
//子元素材质绿色
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
//父元素材质红色
const material2 = new THREE.MeshBasicMaterial( { color: "red" } );
sh2 = new THREE.Mesh( geometry, material2 );
sh = new THREE.Mesh( geometry, material );//父元素添加子元素
sh2.sh)
//设置父元素位置
sh2.position.set(-3,0,0)
//设置子元素位置
sh.position.set(3,0,0)
效果:
sh.position.distanceTo(this.camera.position)
//添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(3)
this.scene.add( axesHelper );
sh.scale.set(2,2,2)
ation.x = Math.PI / 4
der("YXZ") //旋转顺序
ation.x = Math.PI / 4
ation.y = Math.PI / 4
ation.Z = Math.PI / 4
// 看的方向
this.camera.lookAt(0,0,0)
const group = new THREE.Group()
//
this.scene.add(group)
物体的局部缩放。默认值是Vector3( 1, 1, 1 )。父元素被放大了,子元素也根着进行放大。
物体的局部旋转,以弧度来表示,欧拉角秒是一个旋转变换,通过指定轴顺序和各个轴上的指定旋转角度来旋转一个物体,对Euler实例进行遍历将按相应的顺序生成她的分量(x,y,z,order)。
也属于局部旋转,跟父元素有关联。会叠加父元素的旋转
Euler(0,1,1,“YXZ”)
x:用弧度表示x轴旋转的量,默认是0
y:用弧度表示y轴旋转的量,默认是0
z:用弧度表示z轴旋转的量,默认是0
order:表示旋转顺序的字符串,默认为’XYZ’(必须是大写)
//子物体放大两倍sh.scale.set(2,2,2)//物体绕着X轴旋转45°ation.x = Math.PI / 4
Threejs为我们提供了强大的动画系统接口API,通过这些接口,我们可以很轻松的实现物体的移动、旋转、缩放、颜色变化、透明度变化等各种效果。
npm install --save gasp@3.5.1
引入
import gsap from "gsap"
使用:
(sh2.position,{duration:1,delay:1,x:3})(sh2.position,{duration:1,delay:3,x:0})
animate() {ls.update()// 每一帧请求调用requestAnimationFrame( this.animate );// ation.x += 0.01;// ation.y += 0.der( this.scene, this.camera );}
Camera类就是我们所说的抽象类。你不应该直接使用它,但你可以继承它来访问公共属性和方法。以下一些类继承自Camera类。
正交相机(OrthographicCamera)使用正交投影进行渲染。在正交投影中,物体的大小不会随着距离的增加而减小,这意味着所有物体在渲染时保持相同的尺寸,不受距离的影响。这种相机在制作
2D 游戏和 CAD 工具等应用中非常有用。
属性参数:
- left 渲染空间的左边界。
- right 渲染空间的右边界。
- top 渲染空间的上边界。
- bottom 渲染空间的下边界。
- near near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1。
- far far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值2000。
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
//透视相机案例
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = 800; //宽度
const height = 500; //高度
// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
透视相机(PerspectiveCamera)使用透视投影进行渲染。在透视投影中,物体的大小会随着距离的增加而减小,这使得远离相机的物体看起来更小,符合现实世界中的透视效果。这种相机在制作 3D 游戏和仿真应用中非常常见。
属性参数:
- fov 相机视锥体竖直方向视野角度,垂直视角。
- aspect 相机视锥体水平方向和竖直方向长度比,一般设置为Canvas画布宽高比width / height。
- near 相机视锥体近裁截面相对相机距离。
- far 相机视锥体远裁截面相对相机距离,far-near构成了视锥体高度方向。
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);// 正投影相机案例
const width = window.innerWidth; //canvas画布宽度
const height = window.innerHeight; //canvas画布高度
const k = width / height; //canvas画布宽高比
const s = 600;//控制left, right, top, bottom范围大小
const camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 8000);
ElementById('x').addEventListener('click', function () {camera.position.set(500, 0, 0); //x轴方向观察camera.lookAt(0, 0, 0); //重新计算相机视线方向
})
// 通过UI按钮改变相机观察角度
ElementById('y').addEventListener('click', function () {camera.position.set(0, 500, 0); //y轴方向观察camera.lookAt(0, 0, 0); //重新计算相机视线方向
})
// 通过UI按钮改变相机观察角度
ElementById('z').addEventListener('click', function () {camera.position.set(0, 0, 500); //z轴方向观察camera.lookAt(0, 0, 0); //重新计算相机视线方向
})
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//添加轨道控制器
ls = new OrbitControls(this.derer.domElement)
//添加阻尼带有惯性
ableDamping = true
//设置阻尼系数
ls.dampingFactor = 0.05
geometry.setAttribute("position", new THREE.BufferAttribute(pointsArray, 3));
实例代码:
const geometry = new THREE.BufferGeometry();
const tempArr = [];
for (let i = 0; i < 120; i++) {const x = Math.random() * 2 - 1;const y = Math.random() * 2 - 1;const z = Math.random() * 2 - 1;tempArr.push(x, y, z);
}
const pointsArray = new Float32Array([...tempArr]);
//创建顶点属性
geometry.setAttribute("position", new THREE.BufferAttribute(pointsArray, 3));const material = new THREE.MeshBasicMaterial({color: 0x00ff00,wireframe: true,transparent: true, //开启透明// opacity: 0.5, //设置透明度side: THREE.DoubleSide, //两面可见
});
//需要一个 Mesh(网格)
sh = new THREE.Mesh(geometry, material);
// 添加物体到网格
this.scene.sh);
材质+Geometry = Mesh,材质就像物体的皮肤,决定了几何体的外表,材质可以定义一个几何体的看起来是木头还是金属,还是透明的,各种颜色的,然后添加到场景中进行渲染。
属性 | 描述 |
---|---|
id | 标识符 |
uuid | 唯一通用识别码 |
name | 自定义材质名称 |
opacity(不透明度) | 在0.0 - 1.0的范围内的浮点数,表明材质的透明度。值0.0表示完全透明,1.0表示完全不透明。如果材质的transparent属性未设置为true,则材质将保持完全不透明,此值仅影响其颜色。 默认值为1.0。 |
transparent(是否透明) | 定义此材质是否透明。这对渲染有影响,因为透明对象需要特殊处理,并在非透明对象之后渲染。设置为true时,通过设置材质的opacity属性来控制材质透明的程度。默认值为false。 |
overdraw(过度描绘) | 当使用THREE.CanvasRender时,多边形会被渲染的稍微大些,当使用这个渲染器渲染的物体有间隙时,可以设置为true。 |
visible(是否可见) | 此材质是否可见。默认为true。 |
side(侧面) | 定义将要渲染哪一面 - 正面,背面或两者。 默认为THREE.FrontSide。其他选项有THREE.BackSide和THREE.DoubleSide。 |
needsUpdate(是否更新) | 指定需要重新编译材质。实例化新材质时,此属性自动设置为true。 |
colorWrite(是否输出颜色) | 为false时,具有该材质的物体不会被真正绘制到场景。该物体不可见,其它物体被遮挡的部分也不可见。 |
flatShading(平面着色) | 定义材质是否使用平面着色进行渲染。默认值为false。 |
lights(光照) | 材质是否受到光照的影响。默认为true。 |
premultipliedAlpha(预计算Alpha混合) | 是否预乘alpha(透明度)值,默认false |
dithering(抖动) | 是否启用颜色抖动模式。该模式可以一定程度减少颜色不均匀问题,默认false |
shadowSide(投影面) | 定义投影的面。设置时,可以是THREE.FrontSide, THREE.BackSide, 或Materials。默认值为 null。如果为null, 则面投射阴影确定如下:THREE.FrontSide 背面THREE.BackSide 前面THREE.DoubleSide 双面 |
vertexColors(顶点颜色) | 可以为物体的每一个顶点指定特有颜色。是否使用顶点着色。默认值为THREE.NoColors。 其他选项有HREE.VertexColors 和 THREE.FaceColors。 |
fog(雾) | 材质是否受雾影响。默认为true。 |
名称 | 描述 |
---|---|
blending(融合) | 决定物体材质如何与背景融合,一般融合模式为THREE.NormalBlending,这种模式下只显示材质的上层。 |
blendSrc(融合源) | 混合源。默认值为THREE.SrcAlphaFactor。 |
blendSrcAlpha(融合源透明度) | blendSrc的透明度。 默认值为 null。 |
blendDst(融合目标) | 定义了融合时使用何种背景(目标),默认为THREE.OneMinusSrcAlphaFactor,其含义是目标也使用源的alpha通道进行融合,只是使用的值为1(源的aloha通道值)。 |
blendDstAlpha(融合目标透明度) | 为blendDst的指定透明度,默认为null。 |
blendEquation(融合公式) | 使用混合时所采用的混合方程式。默认值为使它们相加(AddEquation),也可以创建自定义融合模式。 |
名称 | 描述 |
---|---|
depthTest | 是否在渲染此材质时启用深度测试。默认为 true。 |
depthWrite | 渲染此材质是否对深度缓冲区有任何影响。默认为true。在绘制2D叠加时,将多个事物分层在一起而不创建z-index时,禁用深度写入会很有用。 |
depthFunc | 使用何种深度函数。默认为LessEqualDepth。 |
polygonOffset | 是否使用多边形偏移。默认值为false。 |
polygonOffsetFactor、polygonOffsetUnits | 设置多边形偏移系数。默认值为0。 |
alphaTest | 设置运行alphaTest时要使用的alpha值。如果不透明度低于此值,则不会渲染材质。默认值为0。 |
precision | 重写此材质渲染器的默认精度。可以是"highp", “mediump” 或 “lowp”。默认值为null。 |
当窗口缩小的时候会出现如下效果:
自适应代码,监听窗口变化
window.addEventListener("resize",()=>{console.log("我改变了")//重置渲染器宽高比derer.setSize(window.innerWidth,window.innerHeight)//重置相机宽高比this.camera.aspect = window.innerWidth/window.innerHeight//更新相机投影矩阵this.camera.updateProjectionMatrix()// 更新renderer der(this.scene, this.camera); })
全屏显示,添加一个按钮带有全屏显示
//全屏
allView(){questFullscreen()},//退出全屏backAllView(){itFullscreen()},
if (document.fullscreenEnabled) {// 当前浏览器支持全屏模式
} else {// 当前浏览器不支持全屏模式
}
var fullscreenElement = document.fullscreenElement || FullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
document.addEventListener('fullscreenchange', function() {if (document.fullscreenElement) {console.log('进入全屏模式');} else {console.log('退出全屏模式');}
});
document.addEventListener('fullscreenerror', function() {console.log('进入全屏模式失败');
});
// 双击进入全屏和退出全屏window.addEventListener("dblclick", () => {derer.domElementconsole.log("aaa")const fullscreenElement =document.fullscreenElement || document.webkitFullScreenElement;let container = ElementById("container");if (!fullscreenElement) {if (questFullscreen) {questFullscreen();} else if (container.webkitRequestFullscreen) {container.webkitRequestFullscreen();}} else {itFullscreen){itFullscreen()}else if(document.webkitExitFullscreen){itFullscreen()}}});
在使用threejs默认的GridHelper时, 只可以设置网格的整体大小和份数,不可以设置单个格子的大小, 网上没有找到这方面的解决方案,决定自己根据官方代码进行修改。
属性参数
GridHelper( size : number, divisions : Number, colorCenterLine : Color, colorGrid : Color )
//添加网格地面
idHeloer = new THREE.GridHelper(10,10,0xff0000,0xff0000)
this.scene.idHeloer)
为了能够快速的搭建three.js的交互,three.js社区就出现了lil-gui,语法简介,上手快,主要作用获取一个对象和该对象上的属性名,并根据属性的类型自动生成一个界面组件来操作该属性,使用lil-gui,可以通过界面组件来控制场景中的物体,提高调试效率。
方便编写代码时对相机,灯光等对象的参数进行实时调节,使用lil-GUI库,可以快速创建控制三维场景的UI交互界面,threejs三维空间很多参数都需要通过GUI的方式调试出来。
导入GUI
//导入GUI
import { GUI } from 'three/examples/jsm/dule.min.js';
//实例化一个gui对象
const gui = new GUI()
xxx.add()方法用于向GUI中添加要控制的对象及其属性
// Object.add(控制对象,对象具体属性,属性参数最小值,属性参数最大值)folder.add(vector3, 'x', -1000, 100, 0.01)folder.add(vector3, 'y', -1000, 100, 0.1)folder.add(vector3, 'z', -1000, 100, 0.01)folder.open()
示例:
Change(function(val){console.log(val);})
当加入add后,该表物体位置会触发Change的回调。
Object.step()方法可以设置每次改变属性值间隔是多少。
folder.add(vector3, 'z', -1000, 100).step(0.1)
Object.addColor()方法生成颜色值改变的交互界面,它接收两个参数,一个是控制对象,一个是颜色属性。
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );gui.addColor(params, 'color').onChange( function() { lor.set( lor ); } );
Object.name()方法让gui界面显示的中文名称。
gui.addColor(params, 'color').name("颜色").onChange( function() { lor.set( lor ); } );
folder.add(vector3, 'x', -1000, 100, 0.1).step(0.1).name("x轴")
folder.add(vector3, 'y', -1000, 100, 0.1).step(0.1).name("y轴")
folder.add(vector3, 'z', -1000, 100, 0.1).step(0.1).name("z轴")
Object.addFolder()创建一个分组,我们可以将同一对象的属性通过.addFolder()方法创建在同一个分组中。
//创建颜色和位置分组const groupPositoinGui = gui.addFolder("位置") const groupColorGui = gui.addFolder("颜色")groupPositoinGui.add(vector3, 'x', -1000, 100, 0.1).step(0.1).name("x轴")groupPositoinGui.add(vector3, 'y', -1000, 100, 0.1).step(0.1).name("y轴")groupPositoinGui.add(vector3, 'z', -1000, 100, 0.1).step(0.1).name("z轴")groupColorGui.addColor(params, 'color').name("颜色").onChange( function() { lor.set( lor ); } );
默认情况下,GUI创建的所有菜单都是打开的,我们可以通过.close()方法控制其关闭,通过.open()方法控制其打开。
var geometry = new THREE.BufferGeometry();
// 创建顶点数据
const vertices = new Float32Array([-1.0,-1.0,0.0,1.0,-1.0,0.0,1.0,1.0,0.0,1.0,1.0,0,-1.0,1.0,0,-1.0,-1.0,0
])
3个为一组,表示一个顶点的xyz坐标
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
//创建材质
const material = new THREE.MeshBasicMaterial({color: 0xffff00, //材质颜色// side:THREE.DoubleSide, //是否正反面都可看wireframe:true,//线框})
sh = new THREE.Mesh( geometry, material );
// 添加物体到网格
this.scene.add( sh );
5. 共用顶点使用并绘制索引,两个点面重合公用一个线
//使用索引绘制
const vertices = new Float32Array([-1.0,-1.0,0.0,1.0,-1.0,0.0,1.0,1.0,0.0,-1.0,1.0,0
])
//创建顶点属性
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
//创建索引
const indice = new Uint16Array([0,1,2,2,3,0])
//设置索引
geometry.setIndex(new THREE.BufferAttribute(indice,1))
//创建材质
const material = new THREE.MeshBasicMaterial({color: 0xffff00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看wireframe:true,//线框})
//创建索引
const indice = new Uint16Array([0,1,2,2,3,0])
//设置索引
geometry.setIndex(new THREE.BufferAttribute(indice,1))
//设置两个顶点组,形成两个素材
geometry.addGroup(0,3,0)
geometry.addGroup(3,3,1)
//创建材质
const material = new THREE.MeshBasicMaterial({color: 0xffff00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material2 = new THREE.MeshBasicMaterial({color: 0xff00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})
sh = new THREE.Mesh( geometry, [material,material2] );
// 添加物体到网格
this.scene.add( sh );
其中/设置两个顶点组,形成两个素材 geometry.addGroup(0,3,0)
,geometry.addGroup(3,3,1)。 sh = new THREE.Mesh( geometry,
[material,material2] );中应使用参数标识第一和第二个素材。
const cubeGemoetry = new THREE.BoxGeometry( 1, 1, 1 );//创建材质const material0 = new THREE.MeshBasicMaterial({color: 0x00ff00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material1 = new THREE.MeshBasicMaterial({color: 0xff0000, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material2 = new THREE.MeshBasicMaterial({color: 0x00ffff, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material3 = new THREE.MeshBasicMaterial({color: 0x000f00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material4 = new THREE.MeshBasicMaterial({color: 0xf0ff00, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})const material5 = new THREE.MeshBasicMaterial({color: 0xff00ff, //材质颜色side:THREE.DoubleSide, //是否正反面都可看})sh = new THREE.Mesh( cubeGemoetry, [material0,material1,material2,material3,material4,material5] );// 添加物体到网格this.scene.add( sh );
let planeGeometry = new THREE.PlaneGeometry(1,1)
2. 加载纹理加载器
let textureLoader = new THREE.TextureLoader()
//加载纹理let texture = textureLoader.load(require("../../public/map.png"))// 加载ao贴图let aoMap = textureLoader.load(require("../../public/aomap.jpg"))
素材:
map.png纹理
roughness.png:粗糙度贴图
置换贴图 作位移使用displacementMap.png
aomap.png该纹理的红色通道用作环境遮挡贴图。默认值为null。aoMap需要第二组UV
normal.png 法线贴图
metalness金属贴图
,alpha贴图是一张灰度纹理,用于控制整个表面的不透明度
//设置平面材质
let planeMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff,map:texture, //贴图transparent:true, //允许透明度aoMap:aoMap,//设置ao贴图
});
gui.add(planeMaterial,"aoMapIntensity").min(0).max(10).name("ao强度")
this.planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );
this.scene.add( this.planeMesh );
效果:
texture.offset.set(0.5, 0.5, 0)
2. 旋转属性:纹理将围绕中心点旋转多少度,单位为弧度(rad),正值为逆时针旋转,默认值问为 0
ation = Math.PI / 6
由于没有设置中心点导致旋转后的问题
3. 设置旋转中心点:对应纹理的中心,默认为 (0, 0)
set(0.5, 0.5)
4. 纹理的重复:repeat 让纹理在物体上重复
// 设置纹理的重复(x 轴方向重复2次,y 轴方向重复3次)
peat.set(2, 3)
// 设置纹理重复的模式(重复到无穷大)
texture.wrapS = THREE.MirroredRepeatWrapping
texture.wrapT = THREE.RepeatWrapping
纹理贴图的时候,很重要的一步就是纹理采样
采样就是如上图中所示,根据片元的纹理坐标,到纹理图中提取对应位置颜色的过程
一张小纹理贴到一个大空间(例如16X16的纹理映射到32X32的像素空间),相当于纹理拉大
透视关系下近处的片元 上述情况下一个片元会覆盖不到一个纹理像素(图中红圈
- THREE.NearestFilter 最近点采样
- THREE.LinearFilter 线性采样 默认值
NearestFilter 最近点采样方法:返回与指定纹理坐标(在曼哈顿距离之内)最接近的纹理像素的值。如图纹理的S,T坐标范围是0-1,纹理本身是由一个个离散的像素组成的,将每个纹理像素看成一个小方块,则每个像素都占一定的纹理坐标。
根据片元的纹理坐标,计算出落在哪个像素中(小方块),最近点采样就直接取此像素的颜色值为采样值
const colorTexture = textureLoader.load(require("/public/checkerboard-8x8.png"))
colorTexture.magFilter = THREE.NearestFilter
不加colorTexture.magFilter = THREE.NearestFilter
加上加colorTexture.magFilter = THREE.NearestFilter之后
LinearFilter 线性采样方法:返回距离指定的纹理坐标最近的四个纹理元素的加权平均值,线性采样会考虑纹理坐标点附近的4个纹理像素值,一般根据面积比例加权计算出最终采样结果,因为对像素做了加权平均,所以过度比较平滑
一张大纹理贴到一个小空间(例如32X32的纹理映射到16X16的像素空间),相当于纹理缩小
透视关系下远处的片元 上述情况下一个片元会覆盖多个纹理像素
NearestFilter、LinearFilter上面已经介绍过了, 其他四种属性就是增加了mipmap相关操作,后面会介绍mipmap
NearestMipmapNearestFilter选择与被纹理化像素的尺寸最匹配的mipmap, 并以NearestFilter为标准来生成纹理值。
NearestMipmapLinearFilter选择与被纹理化像素的尺寸最接近的两个mipmap, 并以NearestFilter为标准来从每个mipmap中生成纹理值。最终的纹理值是这两个值的加权平均值。
LinearMipmapNearestFilter选择与被纹理化像素的尺寸最匹配的mipmap, 并以LinearFilter(最靠近像素中心的四个纹理元素的加权平均值)为标准来生成纹理值。
LinearMipmapLinearFilter是默认值,它选择与被纹理化像素的尺寸最接近的两个mipmap, LinearFilter为标准来从每个mipmap中生成纹理值。最终的纹理值是这两个值的加权平均值。
checkerboard-1024x1024.png
例子,直写了一个单一的例子,其他的可以自行设置
const colorTexture = textureLoader.load(require("/public/checkerboard-1024x1024.png"))
colorTexture.minFilter = THREE.LinearMipmapNearestFilter
纹理优化指的是如何在最大化减小计算机资源的情况下,获得最好的视觉效果,和一切优化手段类似,最终我们要回到资源本身,即始终选择满足需求的情况下,更小的资源或将资源压缩到足够小。除此之外,还需要注意由于 mipmapping 技术会二分的切割纹理,因此我们应该始终保障纹理的宽高是「偶数」!
Three.js 会使用一种名为 mipmapping 的纹理优化技术,它通过预先生成一系列不同大小的纹理贴图(也称为 mipmap),来减少在渲染过程中的计算和内存消耗。
在使用 mipmapping 技术时,WebGL 将纹理图像分解成一系列递减的尺寸,从原始尺寸开始,每次缩小到原来的一半,直到缩小到一个像素。这些不同大小的纹理贴图被存储在 GPU 内存中,随着渲染距离的变远,GPU 会自动选择更小的贴图来显示,从而减少了纹理贴图在远处的像素数提高性能。
使用 mipmapping 技术可以减少因纹理采样而导致的失真和锯齿,提高纹理质量。同时,由于更小的纹理贴图需要更少的内存,因此也可以减少内存占用。
而 mipmapping 技术对于开发者的意义在于,当纹理图像的分辨率大于或小于模型的分辨率时,Three.js 让开发者能够通过 API 选择合适的过滤算法以取得计算速度与渲染效果之间的平衡。
通过纹理上的 minFilter 属性可以配置纹理的缩小过滤器,以应对纹理图像分辨率大于物体分辨率,需要缩小时的效果,有如下可选值:
THREE.LinearMipmapLinearFilter(默认):使用 MipMap 和线性插值,效果比较平滑,但是计算速度较慢;
THREE.NearestFilter:使用最近邻插值,这种插值方式会产生明显的马赛克效果,但是计算速度比较快;
THREE.LinearFilter:使用线性插值,效果比较平滑,但是计算速度比较慢;
THREE.NearestMipmapNearestFilter:使用最近邻插值和 MipMap,这种插值方式会产生明显的马赛克效果,但是计算速度较快;
THREE.NearestMipmapLinearFilter:使用 MipMap 和最近邻插值,这种插值方式会产生明显的马赛克效果,但是计算速度较快;
THREE.LinearMipmapNearestFilter:使用线性插值和 MipMap,效果比较平滑,但是计算速度较慢;
例如:
colorTexture.minFilter = THREE.LinearMipmapNearestFilter
您可以通过 magFilter 属性配置放大过滤器,它的使用场景刚好和缩小过滤器相反,并且可选值也少的多,只有两个:
THREE.LinearFilter(默认):使用线性插值,效果比较平滑,但是计算速度比较慢;
THREE.NearestFilter:使用最近邻插值,这种插值方式会产生明显的马赛克效果,但是计算速度比较快。
.aoMap 该纹理的红色通道用作环境遮挡贴图。默认值为 null。aoMap 需要第二组 UV,UV:纹理坐标通常具有U和V两个坐标轴,因此称之为UV坐标。U代表横向坐标上的分布、V代表纵向坐标上的分布。
其实oa贴图就是让物体更具有立体感,加深三维感官,我就随便找了张图替代oa贴图
// 加载ao贴图let aoMap = textureLoader.load(require("../../public/ao.jpg"))
//设置平面材质
let planeMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff,map:texture, //贴图transparent:true, //允许透明度aoMap:aoMap,//设置ao贴图});gui.add(planeMaterial,"aoMapIntensity").min(0).max(10).name("ao强度")
如果有井盖更深的oa图 立体感就会实现,井盖的凹凸之类的效果。
//透明度贴图
let alphaMap = textureLoader.load(require("../../public/displacementMap.png"))
let planeMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff,map:texture, //贴图transparent:true, //允许透明度aoMap:aoMap,//设置ao贴图alphaMap:alphaMap//透明度贴图
});
处于一个半透明状态
2. 添加光照贴图
贴图网址:/
//环境贴图我是直接three的仓库中获取的
.js/blob/master/examples/textures/equirectangular/venice_sunset_1k.hdr
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
let rgbeLoader = new RGBELoader()
rgbeLoader.loadAsync("../venice_sunset_1k.hdr").then((texture) => {//设置球形贴图texture.mapping = THREE.EquirectangularReflectionMapping;//将加载的材质texture设置给背景和环境this.scene.background = texture;vironment = texture;
});
效果:
Three.js中可以通过使用CubeTexture进行环境贴图,CubeTexture需要将6张图片(正面、反面、上下左右)包装成一个立方体纹理。
//素材:
.js/tree/master/examples/textures/cube/pisa
// 设置cube纹理加载器
const cubeTextureLoader = new THREE.CubeTextureLoader(); // 立方体纹理加载器
const envMapTexture = cubeTextureLoader.load([ // 设置环境贴图"../px.png","../nx.png","../py.png","../ny.png","../pz.png","../nz.png",
]);
let planeGeometry = new THREE.SphereGeometry(1, 32, 32);
let planeMaterial = new THREE.MeshStandardMaterial( { metalness: 0.7, // 金属度roughness: 0.1, // 粗糙度envMap: envMapTexture, // 环境贴图
});//给场景添加背景
this.scene.background = envMapTexture;
效果:
colors.png
heightmap.png
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//加载纹理加载器
let textureLoader = new THREE.TextureLoader()//加载纹理
let texture = textureLoader.load(require("../../public/Planks033A_1K-JPG_Color.jpg"))
// 加载ao贴图
let aoMap = textureLoader.load(require("../../public/Planks033A_1K-JPG_AmbientOcclusion.jpg"))
//透明度贴图
let alphaMap = textureLoader.load(require("../../public/Planks033A_1K-JPG_Displacement.jpg"))
// 光照贴图
let lightMap = textureLoader.load(require("../../public/colors.png"))
// 高光贴图
let specularMap = textureLoader.load(require("../../public/heightmap.png"))
//rebeLoader贴图,加载hdr贴图
let rgbeLoader = new RGBELoader() rgbeLoader.loadAsync("../venice_sunset_1k.hdr").then((texture) => {//设置球形贴图texture.mapping = THREE.EquirectangularReflectionMapping;//将加载的材质texture设置给背景和环境this.scene.background = texture;vironment = texture;}); // 创建球体
let planeGeometry = new THREE.SphereGeometry(1, 32, 32);//设置球体材质let planeMaterial = new THREE.MeshStandardMaterial( { metalness: 0.7, // 金属度roughness: 0.1, // 粗糙度map:texture, //贴图transparent:true, //允许透明度aoMap:aoMap,//设置ao贴图lightMap:lightMap, //光照贴图specularMap:specularMap // 设置高光贴图// alphaMap:alphaMap//透明度贴图});
this.planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );
this.scene.add( this.planeMesh );
前两张完整代码:【如何设置加载RGPRload贴图,如何设置透明度贴图,如何设置光照贴图,高光贴图,如何设置纹理贴图,如何加载oa贴图,如何加载加载hdr贴图,如何创建一个球体,材质,】
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{init(){const gui = new GUI()let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建平面//加载纹理加载器let textureLoader = new THREE.TextureLoader()// 设置cube纹理加载器// const cubeTextureLoader = new THREE.CubeTextureLoader(); // 立方体纹理加载器// const envMapTexture = cubeTextureLoader.load([ // 设置环境贴图// "../px.png",// "../nx.png",// "../py.png",// "../ny.png",// "../pz.png",// "../nz.png",// ]);//加载纹理let texture = textureLoader.load(require("../../public/Planks033A_1K-JPG_Color.jpg"))// 加载ao贴图let aoMap = textureLoader.load(require("../../public/Planks033A_1K-JPG_AmbientOcclusion.jpg"))//透明度贴图let alphaMap = textureLoader.load(require("../../public/Planks033A_1K-JPG_Displacement.jpg"))// 光照贴图let lightMap = textureLoader.load(require("../../public/colors.png"))// 高光贴图let specularMap = textureLoader.load(require("../../public/heightmap.png"))//rebeLoader贴图,加载hdr贴图let rgbeLoader = new RGBELoader() rgbeLoader.loadAsync("../venice_sunset_1k.hdr").then((texture) => {//设置球形贴图texture.mapping = THREE.EquirectangularReflectionMapping;//将加载的材质texture设置给背景和环境this.scene.background = texture;vironment = texture;}); // 创建球体 let planeGeometry = new THREE.SphereGeometry(1, 32, 32);//设置球体材质let planeMaterial = new THREE.MeshStandardMaterial( { metalness: 0.7, // 金属度roughness: 0.1, // 粗糙度map:texture, //贴图transparent:true, //允许透明度aoMap:aoMap,//设置ao贴图lightMap:lightMap,specularMap:specularMap // 设置高光贴图// alphaMap:alphaMap//透明度贴图});// this.scene.background = envMapTexture;// gui.add(planeMaterial,"aoMapIntensity").min(0).max(10).name("ao强度")this.planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );this.scene.add( this.planeMesh );//创建渲染器derer = new THREE.WebGLRenderer();//渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); //创建一个立方体const geometry = new THREE.BoxGeometry( 1, 1, 1 );//我们需要给它一个MeshBasicMaterial材质,来让它有绿色颜色const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );sh = new THREE.Mesh( geometry, material );//设置子元素位置 sh.position.set(0,0,0)// 添加物体到网格// this.scene.add( sh );// 设置相机位置this.camera.position.z = 5; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//设置自动旋转//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()requestAnimationFrame( this.animate );// ation.x += 0.01;// ation.y += 0.der( this.scene, this.camera );}}
}
</script>
在Three.js中,fog类是用于创建线性雾的效果,雾效果常用于模拟真实世界中视觉深度递减的效果,也可以用于创建某些艺术效果,当物体距离观察者越远,雾就越密,物体的颜色就越接近雾的颜色。
雾通常是基于离摄像机的距离褪色至某种特定颜色的方式。 在three.js中有两种设置雾的对象:
.Fog() 定义了线性雾。简单来说就是雾的密度是随着距离线性增大的。.color 雾的颜色。.near 应用雾的最小距离。任何物体比 near 近不会受到影响。.far 应用雾的最大距离。任何物体比 far 远则完全是雾的颜色。
.FogExp2(color,density) 定义了指数雾。在相机附近提供清晰的视野,且距离相机越远,雾的浓度随着指数增长越快。color代表雾的颜色density代表雾的增涨速度
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器derer = new THREE.WebGLRenderer();//渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); //创建一个立方体const boxGeometry = new THREE.BoxGeometry( 1,1,100 );//我们需要给它一个MeshBasicMaterial材质,来让它有绿色颜色const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 });//添加到场景中sh = new THREE.Mesh( boxGeometry, material );this.scene.add( sh );//创建场景雾this.scene.fog = new THREE.Fog(0x999999,0.1,30)//创建场景指数雾// this.scene.fog = new THREE.FogExp2(0x999999,0.1)this.scene.background = new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 5; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()requestAnimationFrame( this.animate );// ation.x += 0.01;// ation.y += 0.der( this.scene, this.camera );}}
}
</script>
指数雾:
线性雾:
用于载入glTF2.0资源的加载器。
glTF(gl传输格式)是一种开放格式的规范,用于高效的传输,加载3D内容,该类文件以JSON(.gltf)格式或二进制格式提供,外部文件存储贴图(.jps,.png)和额外的二进制数据(.bin)。一个glft组件可以传输一个活多个场景,包括网格,材质,贴图,股价,变形目标,动画,灯光及其摄影。
//gltf素材地址,直接下载使用
.js/blob/master/examples/models/gltf/SheenChair.glb
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//实例化gltf加载器const gltgLoader = new GLTFLoader()gltgLoader.load(//模型路径"../SheenChair.glb",//加载完成后的回调函数(gltf)=>{console.log(gltf)this.scene.add( gltf.scene );})this.scene.background=new THREE.Color(0x999999)
当前所看到的是纯黑色的,如果想要其颜色显示出来,要么设置环境贴图,或者设置光线,就会有四面八方的光照射进来,颜色就会亮起来。
其中HDE贴图上章节已经给出相关资源下载地址
//添加环境贴图
let rgbeLoader = new RGBELoader()
rgbeLoader.loadAsync("../venice_sunset_1k.hdr").then((texture) => {//设置球形贴图texture.mapping = THREE.EquirectangularReflectionMapping;//将加载的材质texture设置给背景和环境this.scene.background = texture;vironment = texture;
});
GLTF加载器(GLTFLoader)所有代码
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器derer = new THREE.WebGLRenderer();//渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); //实例化gltf加载器const gltgLoader = new GLTFLoader()gltgLoader.load(//模型路径"../SheenChair.glb", //加载完成后的回调函数(gltf)=>{console.log(gltf)this.scene.add( gltf.scene );})let rgbeLoader = new RGBELoader()rgbeLoader.loadAsync("../venice_sunset_1k.hdr").then((texture) => {//设置球形贴图texture.mapping = THREE.EquirectangularReflectionMapping;//将加载的材质texture设置给背景和环境this.scene.background = texture;vironment = texture;});//加载纹理this.scene.background=new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 5; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()requestAnimationFrame( this.animate );// ation.x += 0.01;// ation.y += 0.der( this.scene, this.camera );}}
}
</script>
将draco文件复制到public静态资源中。
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
const gltgLoader = new GLTFLoader()
gltgLoader.load(//模型路径"../SheenChair.glb", //加载完成后的回调函数(gltf)=>{this.scene.add( gltf.scene );}
)
// 实例化加载器draco
const dracoLoader = new DRACOLoader()
//设置文件路径
dracoLoader.setDecoderPath("../draco/")
//设置把gltf加载器draco解码器
gltgLoader.setDRACOLoader(dracoLoader)
//执行渲染函数
der()render(){ls.update()requestAnimationFrame( der );der( this.scene, this.camera );
}
这个类用于进行raycasting(光线投射)。 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。
new THREE.Raycaster(origin, direction, near, far)
origin:光线投射的原点,Vector3类型。
direction -射线的方向,Vector3类型。
near -投射近点,不能为负值,应该小于far,其默认值为0 。
far -投射远点,不能小于near,其默认值为无穷大。
创建的光线投射对象有一个intersectObject()方法用来获取射线交叉的对象,使用方法如下
const raycaster = new THREE.Raycaster(origin, direction, near, far)
const arr= raycaster.intersectObjects(object, recursive,optionalTarget)
raycaster.intersectObjects()参数
- object-要检查的是否与射线相交的对象,Object3D类型。
- recursive-是否检查所有后代,可选默认为false,Boolean类型。
- optionalTarget-可选参数,放置结果的目标数组。Array类型。若使用这个参数返回检查结果则在每次调用之前必须清空这个数组。
distance -射线投射原点和相交部分之间的距离。
point -相交部分的坐标。
face -相交的面。
faceIndex -相交的面的索引。
object -相交的物体。
uv -相交部分的点的UV坐标。
创建三个球体,通过光线投射技术,光线射到那个球体,那个球体的颜色就会改变
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2(1, 1)
window.addEventListener("click",(e)=>{//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2mouse.x = (e.clientX/window.innerWidth)*2-1mouse.y = -(e.clientY/window.innerHeight)*2+1// 通过摄像机和鼠标的位置,更新涉嫌raycaster.setFromCamera(mouse,this.camera)//计算物体和射线的焦点能不能碰到物体const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])if(intersects.length>0){intersects[0].lor.lor16())}
})
归一化坐标,是一个二维坐标,仅有X/Y两个维度,且X和Y的取值范围均为[-1, 1],坐标原点位于three.js所创建的canvas的中心处。
归一化坐标公式:
// 则有公式如下:
mouse.x = ((event.clientX - BoundingClientRect().left) / BoundingClientRect().width) * 2 - 1;
mouse.y = - ((event.clientY - BoundingClientRect().top) / BoundingClientRect().height) * 2 + 1;
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
export default {name: 'HomeView',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{//随机生成十六进制颜色color16(){//十六进制颜色随机var r = Math.floor(Math.random()*256);var g = Math.floor(Math.random()*256);var b = Math.floor(Math.random()*256);var color = '#'String(16)String(16)String(16);return color;},init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器derer = new THREE.WebGLRenderer();//渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); // 创建三个球const sphere1 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0x00ff00}))sphere1.position.x = -3this.scene.add(sphere1)const sphere2 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0xff0000}))sphere2.position.x = 0this.scene.add(sphere2)const sphere3 = new THREE.Mesh(new THREE.SphereGeometry(1,32,32),new THREE.MeshBasicMaterial({color:0x0000ff})) sphere3.position.x = 3this.scene.add(sphere3)//创建射线const raycaster = new THREE.Raycaster()//用一个二维向量保存鼠标点击画布上的位置const mouse = new THREE.Vector2(1, 1) window.addEventListener("click",(e)=>{//设置鼠标向量的x,y值,将XY轴归一化,X从-1到1,Y为从-1到1,所以除以2mouse.x = (e.clientX/window.innerWidth)*2-1mouse.y = -(e.clientY/window.innerHeight)*2+1console.log(mouse.x,mouse.y)// 通过摄像机和鼠标的位置,更新涉嫌raycaster.setFromCamera(mouse,this.camera)//计算物体和射线的焦点能不能碰到物体const intersects = raycaster.intersectObjects([sphere1,sphere2,sphere3])console.log("intersects",intersects)if(intersects.length>0){intersects[0].lor.lor16())}})this.scene.background=new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 15; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()requestAnimationFrame( this.animate );// ation.x += 0.01;// ation.y += 0.der( this.scene, this.camera );}}
}
</script>
效果以附上图
Tween.js是一个可以产生平滑动画效果的js库,其官方地址为:.js
当然threejs包中自带的包含tween.js
地址:node_moduls>three>examples>jsm>libs&dule.js
tween补间动画,是一个概念,允许你以平滑的方式更改对象的属性,你只需要告述它那些属性要更改,当补间动画结束运行时他们应该具有哪些最终值,补间引擎将负责计算从七十点到结束点的值。
.to()方法
控制补间的运动形式及方向 .to() , 当tween启动时,Tween.js将读取当前属性值并 应用相对值来找出新的最终值
.start(time) 方法
补间动画启动的方法, .start 方法接受一个参数 time , 如果加入这个参数,那么补间不会立即开始直到特定时刻才会开始
.stop()方法
关闭补间动画 .stop() , 关闭这个正在执行的补间动画
.repeat()方法
使用该方法可以使动画重复执行,它接受一个参数 , 描述需要重复多少次
.delay()方法
延迟执行动画的方法 .delay() , 接受一个参数用于控制延迟的具体时间,表示延迟多少时间后才开始执行动画
.pause()方法
暂停动画.pause() , 暂停当前补间运动,与resume方法配合使用
.resume()方法
恢复动画 .resume() , 恢复这个已经被暂停的补间运动
.yoyo() 方法
控制补间重复的模式 .yoyo() , 这个功能只有在使用 repeat 时才有效果 ,该动画像悠悠球一样来回运动 , 而不是重新开始
.update()方法
更新补间动画 TWEEN.update() , 动态更新补间运动一般配合 questAnimationFrame 使用
.chain()方法
链式补间动画,当我们顺序排列不同的补间动画时,比如我们在上一个补间结束的时候立即启动另外一个补间动画,使用 .chain() 方法来做。
//tweenB动画在tweenA动画完成后执行
tweenA.chain(tweenB);
在一些情况下,可能需要将多个补间链接到另一个补间,以使它们(链接的补间)同时开始动画:
tweenA.chain(tweenB,tweenC);
注意:调用 tweenA.chain(tweenB) 实际上修改了tweenA,所以tweenA总是在tweenA完成时启动。 chain
的返回值只是tweenA,不是一个新的tween。
.getAll()方法
获取所有的补间组 All()
.removeAll()方法
删除所有的补间组 veAll()
.add()方法
新增补间 TWEEN.add(tween) ,添加一个特定的补间 var tween=new TWEEN.Tween()
.remove()方法
删除补间 ve(tween),删除一个特定的补间var tween=new TWEEN.Tween()
.Group()方法
新增一个补间组,var Group=TWEEN.Group() , new TWEEN.Tween({ x: 1 }, Group) ,
将已经配置好的补间动画进行分组 , TWEEN.update()和veAll() , 不会影响到已经分好组的补间动画
.onStart()补间动画开始时执行,只执行一次,new TWEEN.Tween().onStart((obj)=>{}) , 补间开始时执行,只执行一次, 当使用 repeat() 重复补间时,不会重复运行 ,onStart((obj)=>{}) obj 补间对象作为第一个参数传入
.onStop() 停止补间动画时执行
new TWEEN.Tween().onStop((obj)=>{}) , 当通过 onStop() 显式停止补间时执行,但在正常完成时并且在停止任何可能的链补间之前执行补间,onStop((obj)=>{}) obj 补间对象作为第一个参数传入
.onUpdate() 每次更新时执行
new TWEEN.Tween().onUpdate((obj)=>{}) , 每次补间更新时执行,返回实际更新后的值, onUpdate((obj)=>{}) obj 补间对象作为第一个参数传入
.onComplete() 补间动画完成时执行
new TWEEN.Tween().onComplete((obj)=>{}) , 当补间正常完成(即不停止)时执行 , onComplete((obj)=>{}) obj 补间对象作为第一个参数传入
.onRepeat() 重复补间动画时执行
new TWEEN.Tween().onRepeat((obj)=>{}) , 当补间动画完成,即将进行重复动画的时候执行 , onComplete((obj)=>{}) `obj 补间对象作为第一个参数传入
tween.js为我们封装好了常用的缓动动画,如线性,二次,三次,四次,五次,正弦,指数,圆形,弹性,下落和弹跳等缓动函数, 以及对应的缓动类型:In (先慢后快) ;Out (先快后慢) 和 InOut (前半段加速,后半段减速)
常见的缓动动画如下
以上每个效果都分三个缓动类型,分别是:
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
const sphere1 = new THREE.Mesh(
new THREE.CylinderGeometry(1, 1, 1, 64),new THREE.MeshBasicMaterial( {color: 0xffff00} )
)
sphere1.position.set(0, 10, 0)
sphere1.position.x = 0
sphere1.position.y = 0
sphere1.position.z = 0
sphere1.scale.set(-1, -1, 1)
ation.z = -Math.PI/2
this.scene.add(sphere1)
const tween = new TWEEN.Tween(sphere1.position)
const tweenXZ = new TWEEN.ation)
const tween2 = new TWEEN.Tween(sphere1.position)
const tween3 = new TWEEN.Tween(sphere1.position)
const tween4 = new TWEEN.Tween(sphere1.position)
//移动
({x:4},300).onUpdate(()=>{})
//旋转
({y:-Math.PI/2},150).onUpdate(()=>{})
//移动
({y:-4},300).onUpdate(()=>{})
//移动
({x:0},300).onUpdate(()=>{})
//移动
({y:0},300).onUpdate(()=>{})
// 一边移动一边旋转,动画在tween动画完成后执行tween2,并带有旋转效果
tween.chain(tween2,tweenXZ)
tween2.chain(tween3,tweenXZ)
tween3.chain(tween4,tweenXZ)
tween4.chain(tween,tweenXZ)
tween.easing(TWEEN.Easing.Quadratic.Inout)
tween.start()
animate() {ls.update()TWEEN.update()requestAnimationFrame( this.animate );der( this.scene, this.camera );}
完整代码:
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
export default {name: 'Three9',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{//随机生成十六进制颜色color16(){//十六进制颜色随机var r = Math.floor(Math.random()*256);var g = Math.floor(Math.random()*256);var b = Math.floor(Math.random()*256);var color = '#'String(16)String(16)String(16);return color;},init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)//创建渲染器derer = new THREE.WebGLRenderer();//渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); // 创建三个球const sphere1 = new THREE.Mesh(new THREE.CylinderGeometry(1, 1, 1, 64),new THREE.MeshBasicMaterial( {color: 0xffff00} ))sphere1.position.set(0, 10, 0)sphere1.position.x = 0 sphere1.position.y = 0 sphere1.position.z = 0sphere1.scale.set(-1, -1, 1) ation.z = -Math.PI/2 this.scene.add(sphere1)const tween = new TWEEN.Tween(sphere1.position) const tweenXZ = new TWEEN.ation)const tween2 = new TWEEN.Tween(sphere1.position)const tween3 = new TWEEN.Tween(sphere1.position)const tween4 = new TWEEN.Tween(sphere1.position)//移动({x:4},300).onUpdate(()=>{})//旋转({y:-Math.PI/2},150).onUpdate(()=>{})//移动({y:-4},300).onUpdate(()=>{})//移动({x:0},300).onUpdate(()=>{})//移动({y:0},300).onUpdate(()=>{})// 一边移动一边旋转,动画在tween动画完成后执行tween2,并带有旋转效果tween.chain(tween2,tweenXZ)tween2.chain(tween3,tweenXZ)tween3.chain(tween4,tweenXZ) tween4.chain(tween,tweenXZ)//动画运行速度曲线tween.easing(TWEEN.Easing.Quadratic.Inout) tween.start()this.scene.background=new THREE.Color(0x999999)// 设置相机位置this.camera.position.z = 15; this.camera.position.y =2; this.camera.position.x = 2; // 看的方向 this.camera.lookAt(0,0,0)//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05//元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()TWEEN.update()requestAnimationFrame( this.animate );der( this.scene, this.camera );}}
}
</script>
//创建一个场景
this.scene = new THREE.Scene()
let container = document.body;
//创建一个场景
this.scene = new THREE.Scene()
//透视摄像机
this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)
// 设置相机位置
this.camera.position.z = 30;
this.camera.position.y =2;
this.camera.position.x = 2;
// 看的方向
this.camera.lookAt(0,2,2)
//创建渲染器
derer = new THREE.WebGLRenderer();
// 渲染器尺寸
derer.setSize( window.innerWidth, window.innerHeight );
//创建一个球体
const sphereGeometry = new THREE.SphereGeometry(1, 20, 20)
//设置材质
const material = new THREE.MeshStandardMaterial()
const material1 = new THREE.MeshBasicMaterial()
const sphere = new THREE.Mesh(sphereGeometry, material1)
//球投射阴影,打开球体的投射阴影
sphere.castShadow = true
// 将球添加到场景中
this.scene.add(sphere)
//创建一个平面
const planeGeometry = new THREE.PlaneGeometry(15,15)
//设置平面材质,要求平面材质必须要可接收阴影的投射
const planeMesh = new THREE.Mesh(planeGeometry,material)
//平面位置位于球体的下方
planeMesh.position.set(0,-1,0)
//旋转平面位置
ation.x = -Math.PI / 2
// 开启平面接收阴影
iveShadow = true
//将平面添加到场景中
this.scene.add(planeMesh)
// 环境光均匀的照亮场景中的所有物体
const light = new THREE.AmbientLight( 0xffffff, 0.9 );
// 环境光添加到场景中
this.scene.add( light );
// 平行光,从一个平行光位置position到target位置
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.95 );
//设置平行光的位置
directionalLight.position.set(10, 3, 10)
//开启光照投射阴影
directionalLight.castShadow = true
//将光照添加到场景中
this.scene.add( directionalLight );
//场景背景图
this.scene.background=new THREE.Color(0x999999)
//开启场景中的阴影贴图
abled = true
//添加世界坐标辅助器
const axesHelper = new THREE.AxesHelper(3)
this.scene.add( axesHelper );
//添加轨道控制器
ls = new OrbitControls(this.derer.domElement)
//添加阻尼带有惯性
ableDamping = true
//设置阻尼系数
ls.dampingFactor = 0.05
//元素中插入canvas对象
container.derer.domElement);
if ( WebGL.isWebGLAvailable() ) {this.animate();
} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );
}
效果:
directionalLight.shadow.radius = 20;
2. 设置阴影贴图的分辨率
directionalLight.shadow.mapSize.set(4096, 4096)
3. 设置平行光投射相机的属性
directionalLight.ar = 0.5;
directionalLight.shadow.camera.far = 500;
directionalLight.p = 5;
directionalLight.shadow.camera.bottom = -5;
directionalLight.shadow.camera.left = -5;
directionalLight.shadow.camera.right = 5;
使用GUI控制left,near,x,y,z,修改后一定要调用updateProjectionMatrix方法进行相机投影的更新。
const gui = new GUI()
gui.add(directionalLight.position, 'x').min(0).max(30).step(1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()})
gui.add(directionalLight.position, 'y').min(0).max(30).step(1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()})
gui.add(directionalLight.position, 'z').min(0).max(30).step(1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()})
gui.add(directionalLight.shadow.camera, 'left').min(0).max(1).step(0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()})
gui.add(directionalLight.shadow.camera, 'near').min(0).max(10).step(0.1).onChange(()=>{directionalLight.shadow.camera.updateProjectionMatrix()})
光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。
//创建一个立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1 )
//设置材质
const boxMaterial = new THREE.MeshStandardMaterial({color: 0x00ff00})
//实例化立方体
const sphere = new THREE.Mesh(boxGeometry, boxMaterial)
//球投射阴影,打开球体的投射阴影
sphere.castShadow = true
// 将球添加到场景中
this.scene.add(sphere)
//创建一个平面
const planeGeometry = new THREE.PlaneGeometry(50,50)
//平面材质
const planeMaterial = new THREE.MeshStandardMaterial({color: 0xf0fff0})
//设置平面材质,要求平面材质必须要可接收阴影的投射
const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial)
//平面位置位于球体的下方
planeMesh.position.set(0,-1,0)
//旋转平面位置
ation.x = -Math.PI / 2
// 开启平面接收阴影
iveShadow = true
//将平面添加到场景中
this.scene.add(planeMesh)
// 环境光均匀的照亮场景中的所有物体
const light = new THREE.AmbientLight( 0xffffff, 0.5 );
// 环境光添加到场景中
this.scene.add( light );
//聚光灯
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(5, 5, 5); // 设置聚光灯位置
spotLight.castShadow = true; // 设置聚光灯投射阴影
spotLight.intensity = 2; // 设置聚光灯强度
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
//开启光照投射阴影
spotLight.castShadow = true
// 设置阴影贴图模糊度
spotLight.shadow.radius = 20;
// 设置阴影贴图的分辨率
spotLight.shadow.mapSize.set(512, 512);
spotLight.target = sphere; // 设置聚光灯的目标为立方体 会自动对准目标
spotLight.angle = Math.PI / 6; // 设置聚光灯的角度
spotLight.distance = 0; // 设置聚光灯的距离
spotLight.penumbra = 0; // 设置聚光灯的边缘
spotLight.decay = 0; // 设置聚光灯的衰减
this.scene.add(spotLight);
const gui = new GUI()
gui.add(sphere.position, "x").min(-5).max(5).step(0.1);
gui.add(spotLight, "angle").min(0).max(Math.PI / 2).step(0.01);
gui.add(spotLight, "distance").min(0).max(10).step(0.01);
gui.add(spotLight, "penumbra").min(0).max(1).step(0.01);
gui.add(spotLight, "decay").min(0).max(5).step(0.01);
效果如下:
完整代码:
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
export default {name: 'Three10',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,}},methods:{init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)// 设置相机位置this.camera.position.z = 10; this.camera.position.y =0; this.camera.position.x = 0; // 看的方向 this.camera.lookAt(0,0,0)//创建渲染器derer = new THREE.WebGLRenderer();// 渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); //创建一个立方体const boxGeometry = new THREE.BoxGeometry(1, 1, 1 )//设置材质const boxMaterial = new THREE.MeshStandardMaterial({color: 0x00ff00})//实例化立方体const sphere = new THREE.Mesh(boxGeometry, boxMaterial)//球投射阴影,打开球体的投射阴影sphere.castShadow = true// 将球添加到场景中this.scene.add(sphere)//创建一个平面const planeGeometry = new THREE.PlaneGeometry(50,50)//平面材质const planeMaterial = new THREE.MeshStandardMaterial({color: 0xf0fff0})//设置平面材质,要求平面材质必须要可接收阴影的投射const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial)//平面位置位于球体的下方planeMesh.position.set(0,-1,0)//旋转平面位置 ation.x = -Math.PI / 2// 开启平面接收阴影iveShadow = true//将平面添加到场景中this.scene.add(planeMesh)// 环境光均匀的照亮场景中的所有物体const light = new THREE.AmbientLight( 0xffffff, 0.5 );// 环境光添加到场景中this.scene.add( light );// 平行光,从一个平行光位置position到target位置const spotLight = new THREE.SpotLight(0xffffff, 1);spotLight.position.set(5, 5, 5); // 设置聚光灯位置spotLight.castShadow = true; // 设置聚光灯投射阴影spotLight.intensity = 2; // 设置聚光灯强度spotLight.shadow.mapSize.width = 1024;spotLight.shadow.mapSize.height = 1024;//设置平行光的位置// directionalLight.position.set(5,5,5)//开启光照投射阴影spotLight.castShadow = true// 设置阴影贴图模糊度spotLight.shadow.radius = 20;// 设置阴影贴图的分辨率spotLight.shadow.mapSize.set(512, 512);spotLight.target = sphere; // 设置聚光灯的目标为立方体 会自动对准目标spotLight.angle = Math.PI / 6; // 设置聚光灯的角度spotLight.distance = 0; // 设置聚光灯的距离spotLight.penumbra = 0; // 设置聚光灯的边缘spotLight.decay = 0; // 设置聚光灯的衰减//设置阴影模糊度// spotLight.shadow.radius = 20;//设置阴影贴图的分辨率// spotLight.shadow.mapSize.set(4096, 4096)// spotLight.ar = 500;// spotLight.shadow.camera.far = 4000;// spotLight.shadow.camera.fov = 30;//将光照添加到场景中this.scene.add(spotLight); const gui = new GUI()gui.add(sphere.position, "x").min(-5).max(5).step(0.1);gui.add(spotLight, "angle").min(0).max(Math.PI / 2).step(0.01);gui.add(spotLight, "distance").min(0).max(10).step(0.01);gui.add(spotLight, "penumbra").min(0).max(1).step(0.01);gui.add(spotLight, "decay").min(0).max(5).step(0.01);//场景背景图this.scene.background=new THREE.Color(0x999999)//开启场景中的阴影贴图abled = derer.physicallyCorrectLights = true; // 设置渲染器的物理正确性//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05 //元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()TWEEN.update()requestAnimationFrame( this.animate );der( this.scene, this.camera );}}
}
</script>
从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光,该光源可以投射阴影
,就像生活中的白炽灯,光线沿着发光核心向外发散,同一平面的不同位置与点光源光线入射角是不同的,点光源照射下,同一个平面不同区域是呈现出不同的明暗效果。
和环境光不同,环境光不需要设置光源位置,而点光源需要设置位置属性.position,光源位置不同,物体表面被照亮的面不同,远近不同因为衰减明暗程度不同。
你可以把案例源码中点光源位置从(400, 200, 300)位置改变到(-400, -200, -300),你会发现网格模型被照亮的位置从前面变到了后面,这很正常,光源只能照亮面对着光源的面,背对着光源的无法照射到,颜色会比较暗。
//点光源
var point = new THREE.PointLight(0xffffff);
//设置点光源位置,改变光源的位置
point.position.set(400, 200, 300);
scene.add(point);
效果:代码在下方给出
//创建一个立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1 )
//设置材质
const boxMaterial = new THREE.MeshStandardMaterial()
//实例化立方体
const sphere = new THREE.Mesh(boxGeometry, boxMaterial)
//投射阴影,打开物体的投射阴影
sphere.castShadow = true
// 将物体加到场景中
this.scene.add(sphere)
//创建一个平面
const planeGeometry = new THREE.PlaneGeometry(50,50)
//平面材质
const planeMaterial = new THREE.MeshStandardMaterial({color: 0xf0fff0})
//设置平面材质,要求平面材质必须要可接收阴影的投射
const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial)
//平面位置位于球体的下方
planeMesh.position.set(0,-1,0)
//旋转平面位置
ation.x = -Math.PI / 2
// 开启平面接收阴影
iveShadow = true
//将平面添加到场景中
this.scene.add(planeMesh)
//创建一个灯
const boxGeometry1 = new THREE.BoxGeometry(0.3, 0.3, 0.3 )
//设置材质
const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0xffffff})
//实例化立方体
this.sphere1 = new THREE.Mesh(boxGeometry1, boxMaterial1)
// 将球添加到场景中
this.scene.add(this.sphere1)
//灯位于物体的上方
this.sphere1.position.set(2,4,2)
// 环境光均匀的照亮场景中的所有物体
const light = new THREE.AmbientLight( 0xffffff, 0.5 );
// 环境光添加到场景中
this.scene.add( light );
// 点光源,从一个平行光位置position到target位置
const pointLight = new THREE.PointLight(0xffffff, 1);
// pointLight.position.set(2, 2, 2); // 设置光源位置
pointLight.castShadow = true; // 设置光源投射阴影
pointLight.shadow.mapSize.set(1024,1024)
//将点光源添加到上面创建的小灯中
this.sphere1.add(pointLight)
//设置时钟this.clockTime = new THREE.Clock()this.animate()
animate() {ls.update()const clock = new THREE.Clock();let time = ElapsedTime()this.sphere1.position.x = Math.sin(time)*3this.sphere1.position.z = s(time)*3this.sphere1.position.y = 2+Math.sign(time)*0.5TWEEN.update()requestAnimationFrame( this.animate );der( this.scene, this.camera );}
完整代码:
<template><div id="container"></div>
</template><script>
import * as THREE from 'three'
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import {DRACOLoader} from "three/examples/jsm/loaders/DRACOLoader.js"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
export default {name: 'Three11',components: {},mounted(){this.init()},data(){return {camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshmesh2:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,clockTime:null,sphere1:null,}},methods:{init(){let container = document.body;//创建一个场景this.scene = new THREE.Scene()//透视摄像机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,700)// 设置相机位置this.camera.position.z = 10; this.camera.position.y =0; this.camera.position.x = 0; // 看的方向 this.camera.lookAt(0,0,0)//创建渲染器derer = new THREE.WebGLRenderer();// 渲染器尺寸derer.setSize( window.innerWidth, window.innerHeight ); //创建一个立方体const boxGeometry = new THREE.BoxGeometry(1, 1, 1 )//设置材质const boxMaterial = new THREE.MeshStandardMaterial()//实例化立方体const sphere = new THREE.Mesh(boxGeometry, boxMaterial)//球投射阴影,打开球体的投射阴影sphere.castShadow = true// 将球添加到场景中this.scene.add(sphere)//创建一个灯const boxGeometry1 = new THREE.BoxGeometry(0.3, 0.3, 0.3 )//设置材质const boxMaterial1 = new THREE.MeshBasicMaterial({color: 0xffffff})//实例化立方体this.sphere1 = new THREE.Mesh(boxGeometry1, boxMaterial1)// 将球添加到场景中this.scene.add(this.sphere1)//平面位置位于球体的下方this.sphere1.position.set(2,4,2)//创建一个平面const planeGeometry = new THREE.PlaneGeometry(50,50)//平面材质const planeMaterial = new THREE.MeshStandardMaterial({color: 0xf0fff0})//设置平面材质,要求平面材质必须要可接收阴影的投射const planeMesh = new THREE.Mesh(planeGeometry,planeMaterial)//平面位置位于球体的下方planeMesh.position.set(0,-1,0)//旋转平面位置 ation.x = -Math.PI / 2// 开启平面接收阴影iveShadow = true//将平面添加到场景中this.scene.add(planeMesh)// 环境光均匀的照亮场景中的所有物体const light = new THREE.AmbientLight( 0xffffff, 0.5 );// 环境光添加到场景中this.scene.add( light );// 点光源,从一个平行光位置position到target位置const pointLight = new THREE.PointLight(0xffffff, 1);pointLight.castShadow = true; // 设置点光源投射阴影pointLight.shadow.mapSize.set(1024,1024)this.sphere1.add(pointLight)//设置时钟this.clockTime = new THREE.Clock()//开启光照投射阴影pointLight.castShadow = true// 设置阴影贴图模糊度pointLight.shadow.radius = 20;// 设置阴影贴图的分辨率pointLight.shadow.mapSize.set(512, 512);pointLight.target = sphere; // 设置点光源的目标为立方体 会自动对准目标pointLight.angle = Math.PI / 6; // 设置点光源的角度pointLight.distance = 0; // 设置点光源的距离pointLight.decay = 0; // 设置点光源的衰减 const gui = new GUI()gui.add(sphere.position, "x").min(-5).max(5).step(0.1);gui.add(pointLight, "distance").min(0).max(10).step(0.001);gui.add(pointLight, "decay").min(0).max(5).step(0.01);//场景背景图this.scene.background=new THREE.Color(0x999999)//开启场景中的阴影贴图abled = derer.physicallyCorrectLights = true; // 设置渲染器的物理正确性//添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(3) this.scene.add( axesHelper );//添加轨道控制器ls = new OrbitControls(this.derer.domElement)//添加阻尼带有惯性ableDamping = true//设置阻尼系数ls.dampingFactor = 0.05 //元素中插入canvas对象container.derer.domElement); if ( WebGL.isWebGLAvailable() ) {this.animate();} else {const warning = WebGLErrorMessage();ElementById( document.body ).appendChild( warning );}},//旋转起来animate() {ls.update()const clock = new THREE.Clock();let time = ElapsedTime()this.sphere1.position.x = Math.sin(time)*3this.sphere1.position.z = s(time)*3this.sphere1.position.y = 2+Math.sign(time)*0.5TWEEN.update()requestAnimationFrame( this.animate );der( this.scene, this.camera );}}
}
</script>
效果:
<template><div><div class="home"><div class="canvas-container" ref="canvasDom"></div><div class="home-content"><h2>车身颜色</h2><div class="select"><divclass="select-item"v-for="(item, index) in colors":key="index"@click="selectColor(index)"><divclass="select-item-color":style="{'backgroundColor': item}"></div></div></div><h2>贴膜材质</h2><div class="select"><divclass="select-item"v-for="(item, index) in materials":key="index"@click="selectMaterial(index)"><div class="select-item-text">{{ item.name }}</div></div></div></div></div></div>
</template><script>
// 引入three.js导入
import * as THREE from 'three'
// 补间动画库导入
import gsap from "gsap"
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
// 导入水面
import { Water } from "three/examples/jsm/objects/Water"
//导入天空
import { Sky } from 'three/examples/jsm/objects/Sky';
export default {name: 'Three12',components: {},mounted(){this.init()},data(){return {colors:["green", "blue", "orange", "gray", "red", "purple","#FFD700"],materials:[{ name: "磨砂", value: 1 },{ name: "冰晶", value: 0 }],camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshlight:null, //平行光源light2:null, //平行光源light3:null, //平行光源light4:null, //平行光源light5:null, //平行光源light6:null, //平行光源wheels:[], //轮毂carBody:null, //车身forntCar:null, //前脸hoodCar:null, //引擎盖glassCar:null, //挡风玻璃water:null, //水面gridHeloer:null, //网格地面 carBodyMaterial:new THREE.MeshPhysicalMaterial({color:0xff0000,metalness:1,//金属度roughness:0.1, //粗糙程度}),//创建材质wheelsMaterial:new THREE.MeshPhysicalMaterial({color:0xff0000,metalness:0.5,//金属度roughness:0.5, //粗糙程度clearcoat:1, //清晰度,光滑透亮clearcoatRoughness:0, //coat层的粗糙度}),//创建材质forntCarMaterial:new THREE.MeshPhysicalMaterial({color:0xff0000,metalness:0.5,//金属度roughness:0.5, //粗糙程度}),//创建材质hoodCarMaterial:new THREE.MeshPhysicalMaterial({color:0xff0000,metalness:0.5,//金属度roughness:0.5, //粗糙程度clearcoat:1, //清晰度,光滑透亮clearcoatRoughness:0, //coat层的粗糙度}),//创建材质glassCarMaterial:new THREE.MeshPhysicalMaterial({color: 0xffffff, metalness: 0.25, roughness: 0, transmission: 1.0}),//创建材质carModel:null, // 阴影gltfLoader:null,dracoLoader:null,controls:null, //轨道控制器rgbeLoacer:null,}},methods:{//选择颜色selectColor(index){lor.lors[index]);lor.lors[index]);lor.lors[index]);lor.lors[index]);},selectMaterial(index){this.carBodyMaterial.clearcoatRoughness = this.materials[index].value;this.forntCarMaterial.clearcoatRoughness = this.materials[index].value;this.hoodCarMaterial.clearcoatRoughness = this.materials[index].value;this.wheelsMaterial.clearcoatRoughness = this.materials[index].value;},init(){//创建一个场景this.scene = new THREE.Scene()//初始化相机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)//设置相机位置this.camera.position.set(0,2,6)//设置相机的宽高比this.camera.aspect = window.innerWidth/window.innerHeight//更新投影矩阵,设置后调用.updateProjectionMatrix()来使得改变生效this.camera.updateProjectionMatrix()//初始化渲染器derer = new THREE.WebGLRenderer({//设置抗锯齿antialias: true,toneMapping:THREE.ACESFilmicToneMapping}) // derer.outputEncoding = THREE.Mapping = THREE.MappingExposure = 0.abled = derer.physicallyCorrectLights = true;//设置渲染器derer.setSize(window.innerWidth,window.innerHeight)//将渲染元素画布最佳到body中去// document.body.derer.domElement)// this.scene.background = new THREE.Color("#ccc")//初始化控制器ls = new OrbitControls(this.derer.domElement)// //添加阻尼带有惯性ableDamping = true// //设置阻尼系数ls.dampingFactor = 0.05 this.scene.background = new THREE.Color("#ccc");// vironment = new THREE.Color("#ccc")this.scene.fog = new THREE.Fog( 0x333333, 10, 15 );vironment = new RGBELoader().load( 'venice_sunset_1k.hdr' ); vironment.mapping = THREE.EquirectangularReflectionMapping;this.scene.fog = new THREE.Fog( 0x333333, 10, 15 );// 实例化加载器dracothis.dracoLoader = new DRACOLoader()//初始化GLTF模式加载、this.gltfLoader = new GLTFLoader()//设置解压缩文件路径 this.dracoLoader.setDecoderPath("draco/")//阴影纹理const shadow = new THREE.TextureLoader().load( 'ferrari_ao.png' );//设置把gltf加载器draco解码器this.gltfLoader.setDRACOLoader(this.dracoLoader)this.gltfLoader.load("model/bmw01.glb",(gltf)=>{ const model = averse((child)=>{if(child.isMesh){console.log(child.name)}// this.carModel = gltf.scene.children[ 0 ]; // // shadow// const mesh = new THREE.Mesh(// new THREE.PlaneGeometry( 0.655 * 4, 1.3 * 4 ),// new THREE.MeshBasicMaterial( {// map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true// } )// );// ation.x = - Math.PI / 2;// derOrder = 2;// this.carModel.add( mesh );// // 车轮// this.scene.add( this.carModel );if(child.isMesh&&child.name.includes("轮毂")){this.wheels.push(child)this.wheels.material = this.wheelsMaterial}//车身if(child.isMesh&&child.name.includes("Mesh002")){this.carBody = childthis.carBody.material = this.carBodyMaterial}//前脸if(child.isMesh&&child.name.includes("前脸")){this.forntCar= childthis.forntCar.material = this.forntCarMaterial}// 引擎盖 hoodCar:null, //引擎盖if(child.isMesh&&child.name.includes("引擎盖_1")){this.hoodCar = childthis.hoodCar.material = this.hoodCarMaterial}//挡风玻璃if(child.isMesh&&child.name.includes("挡风玻璃")){this.glassCar = childthis.glassCar.material = this.glassCarMaterial}})this.scene.add( gltf.scene );}) //添第一灯光 上下左右前后都打光this.light = new THREE.DirectionalLight(0xffffff,1)this.light.position.set(0,0,10)this.scene.add(this.light)//第二栈灯this.light2 = new THREE.DirectionalLight(0xffffff,1)this.light2.position.set(0,0,-10)this.scene.add(this.light2)//第三栈灯this.light3 = new THREE.DirectionalLight(0xffffff,1)this.light3.position.set(-10,0,0)this.scene.add(this.light3)//第四栈灯this.light4 = new THREE.DirectionalLight(0xffffff,1)this.light4.position.set(10,0,0)this.scene.add(this.light4)//第五栈灯this.light5 = new THREE.DirectionalLight(0xffffff,1)this.light5.position.set(0,10,0)this.scene.add(this.light5)//第六栈灯this.light6 = new THREE.DirectionalLight(0xffffff,1)this.light6.position.set(5,-10,0)this.scene.add(this.light6)//添加点光源const pointLight = new THREE.PointLight(0xffffff,30)pointLight.position.set(0.5,2.3,0)pointLight.castShadow = truethis.scene.add(pointLight)//实例化一个gui对象// const gui = new GUI()// gui.add(this.camera.position,'x').max(100).min(-10).name('x的位置').step(1)// gui.add(this.camera.position,'y').max(100).min(-10).name('y的位置').step(1)// gui.add(this.camera.position,'z').max(100).min(-10).name('z的位置').step(1)//添加网格地面idHeloer = new THREE.GridHelper( 20, 40, 0xffffff, 0xffffff )idHeloer.material.opacity = 0.idHeloer.material.depthWrite = ansparent = true;this.scene.idHeloer)// document.body.derer.domElement)console.log("canvasDom",this.$refs.canvasDom)this.$refs.canvasDom.derer.domElement);der()},//渲染函数 “”render(){const time = - w() / 1000;for ( let i = 0; i < this.wheels.length; i ++ ) {// this.wheels[ i ].rotation.z = time * Math.PI * 2;}ls && ls.update()idHeloer.position.z = - ( time ) % 1;der);der(this.scene, this.camera );},}
}
</script>
<style>
*{margin:0px;padding:0px;
}
canvas{width: 100%;height: 100%;position: fixed;left: 0;top: 0;
}.home-content {position: fixed;top: 0;right: 20px;
}.select-item-color {width: 50px;height: 50px;border: 1px solid #ccc;margin: 10px;display: inline-block;cursor: pointer;border-radius: 10px;
}
.select {display: flex;
}
</style>
效果:
<template><div id="scenes" style="position: fixed;left: 0;top: 0;z-index: 10;pointer-events: none;transition: all 1s;":style="{transform: `translate3d(0, ${-this.sceneIndex.value * 100}vh, 0)`,}"><div v-for="item in this.scenesArr" style="width: 100vw; height: 100vh"><h1 style="padding: 100px 50px; padding-left: -500; font-size: 50px; color: #fff">{{ }}</h1></div></div>
</template><script>
// 引入three.js导入
import * as THREE from 'three'
// 补间动画库导入
import gsap from "gsap"
// webGL兼容
import WebGL from 'three/examples/jsm/capabilities/WebGL.js';
import { GUI } from 'three/examples/jsm/dule.min.js';
// 轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
//导入RGBRload加载器
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"
//导入场景模型加载器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"
//导入模型解压器
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader"
//导入Tween动画组件库
import * as TWEEN from "three/examples/jsm/dule.js"
// 导入水面
import { Water } from "three/examples/jsm/objects/Water"
//导入天空
import { Sky } from 'three/examples/jsm/objects/Sky';
export default {name: 'Three11',components: {},mounted(){// 监听鼠标滚轮事件window.addEventListener("wheel",(e)=>{if(this.isAnimate) returnthis.isAnimate = trueif(e.deltaY>0){this.sceneIndex.value++if(this.sceneIndex.value>this.scenesArr.length-1){this.sceneIndex.value = 0}}console.log("this.sceneIndex.value",this.sceneIndex.value)this.scenesArr[this.sceneIndex.value].callback()setTimeout(() => {this.isAnimate = false;}, 1000);},false)this.init()},data(){return {// 滚轮的防抖节流isAnimate:false,sceneIndex:{value:0},scenesArr:[{text:"日子过得很慢",callback:()=>{//执行换位函数anslateCamera(new THREE.Vector3(100, 100, 100),new THREE.Vector3(5, 3, 19) )}},{text:"生活过得很烂",callback:()=>{//执行换位函数anslateCamera( new THREE.Vector3(5,3,19),new THREE.Vector3(0,0,0) )}},{text:"除了想你",callback:()=>{//执行换位函数anslateCamera(new THREE.Vector3(10,3,0), new THREE.Vector3(5,2,0))}},{text:"其他我什么都做不好",callback:()=>{//执行换位函数anslateCamera(new THREE.Vector3(62,13,22), new THREE.Vector3(0,0,0))}},{text:"我爱你玉玉子",callback:()=>{//执行换位函数anslateCamera(new THREE.Vector3(38,24,25), new THREE.Vector3(5,2,0))}}],timeLine1:gsap.timeline(),timeline2:gsap.timeline(),camera: null, //相机对象scene: null, //场景对象renderer: null, //渲染器对象mesh: null, //网格模型对象Meshlight:null, //平行光源water:null, //水面hdrLoader:null,mesh2:null,gltfLoader:null,dracoLoader:null,controls:null, //轨道控制器material2:null, //父元素planeMesh:null, //平面rgbeLoacer:null,clockTime:null,sphere1:null,}},methods:{init(){// let container = document.body;//创建一个场景this.scene = new THREE.Scene()//初始化相机this.camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000)//设置相机位置this.camera.position.set(100,100,100)//设置相机的宽高比this.camera.aspect = window.innerWidth/window.innerHeight//更新投影矩阵,设置后调用.updateProjectionMatrix()来使得改变生效this.camera.updateProjectionMatrix()//初始化渲染器derer = new THREE.WebGLRenderer({//设置抗锯齿antialias: true,toneMapping:THREE.ACESFilmicToneMapping}) derer.outputEncoding = THREE.Mapping = THREE.MappingExposure = 0.abled = derer.physicallyCorrectLights = true;//设置渲染器derer.setSize(window.innerWidth,window.innerHeight)//将渲染元素画布最佳到body中去document.body.derer.domElement)// 设置水面效果//初始化控制器ls = new OrbitControls(this.derer.domElement)// //添加阻尼带有惯性ableDamping = true// //设置阻尼系数ls.dampingFactor = 0.05 //加载环境纹理// beLoacer = new RGBELoader()// beLoacer.load("textures/sky.hdr",(textures)=>{// textures.mapping = THREE.EquirectangularReflectionMapping// this.scene.background = textures// // 设置场景中没有纹理物体的默认纹理// vironment = textures// }) // 实例化加载器dracothis.dracoLoader = new DRACOLoader()//初始化GLTF模式加载this.gltfLoader = new GLTFLoader()//设置文件路径this.dracoLoader.setDecoderPath("draco/")//设置把gltf加载器draco解码器this.gltfLoader.setDRACOLoader(this.dracoLoader)this.gltfLoader.load("model/scene.glb",(gltf)=>{ const model = averse((child)=>{if(child.name=='Plane'){child.visible = false}if (child.isMesh) {child.castShadow = iveShadow = true;}console.log("child",child.name)})this.scene.add( gltf.scene );}) //添加一个水面const waterGeometry = new THREE.CircleGeometry(300,23)//创建睡眠实例this.water = new Water(waterGeometry,{textureWidth: 512,textureHeight: 512,// 纹理图片waterNormals: new THREE.TextureLoader().load("textures/waternormals.jpg",function (texture) {texture.wrapS = texture.wrapT = THREE.RepeatWrapping;}, ),sunDirection: new THREE.Vector3(),sunColor: 0xffffff,waterColor: 0x001e0f,distortionScale: 3.7,fog: this.scene.fog !== undefined});ation.x = - Math.PI / 2;this.scene.add( this.water );const sky = new Sky();sky.scale.setScalar( 10000 );this.scene.add( sky );const skyUniforms = sky.material.uniforms;const sun = new THREE.Vector3();const sceneEnv = new THREE.Scene(); const parameters = {elevation: 5,azimuth: 180};const pmremGenerator = new THREE.PMREMGenerator( derer );let renderTarget;skyUniforms[ 'turbidity' ].value = 10;skyUniforms[ 'rayleigh' ].value = 2;skyUniforms[ 'mieCoefficient' ].value = 0.005;skyUniforms[ 'mieDirectionalG' ].value = 0.8;const phi = THREE.MathUtils.degToRad( 90 - parameters.elevation );const theta = THREE.MathUtils.degToRad( parameters.azimuth );sun.setFromSphericalCoords( 1, phi, theta );sky.material.uniforms[ 'sunPosition' ].py( sun );this.water.material.uniforms[ 'sunDirection' ].py( sun ).normalize();if ( renderTarget !== undefined ){renderTarget.dispose();}sceneEnv.add( sky );renderTarget = pmremGenerator.fromScene( sceneEnv );this.scene.add( sky );vironment = ure;//创建点光源组const pointLightGroup = new THREE.Group()pointLightGroup.position.set(-8, 2.5, -1.5);let pointLightArr = []let radius = 5;//循环渲染点光源for (let i = 0; i < 3; i++) {// 创建球体当灯泡const sphereGeometry = new THREE.SphereGeometry(0.2, 32, 32);const sphereMaterial = new THREE.MeshStandardMaterial({color: 0xffffff,emissive: 0xffffff,emissiveIntensity: 10,});const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);pointLightArr.push(sphere);sphere.position.set(radius * s((i * 2 * Math.PI) / 3),s((i * 2 * Math.PI) / 3),radius * Math.sin((i * 2 * Math.PI) / 3));let pointLight = new THREE.PointLight(0xffffff, 50);sphere.add(pointLight);pointLightGroup.add(sphere);}this.scene.add(pointLightGroup);// 使用补间函数,从0到2π,使灯泡旋转let options = {angle: 0,};(options, {angle: Math.PI * 2,duration: 10,repeat: -1,ease: "linear",onUpdate: () => {ation.y = options.angle;pointLightArr.forEach((item, index) => {item.position.set(radius * s((index * 2 * Math.PI) / 3),s((index * 2 * Math.PI) / 3 + options.angle * 5),radius * Math.sin((index * 2 * Math.PI) / 3));});},});//添加光源this.light = new THREE.DirectionalLight(0xffffff,1)this.light.position.set(0,50,0)this.scene.add(this.light)//添加点光源const pointLight = new THREE.PointLight(0xffffff,30)pointLight.position.set(0.5,2.3,0)pointLight.castShadow = truethis.scene.add(pointLight)//实例化一个gui对象const gui = new GUI()gui.add(this.camera.position,'x').max(100).min(-10).name('x的位置').step(1)gui.add(this.camera.position,'y').max(100).min(-10).name('y的位置').step(1)gui.add(this.camera.position,'z').max(100).min(-10).name('z的位置').step(der()},//渲染函数 “”render(){ls.update()const time = w() * 0.001;requestAnimationFrame( der );this.water.material.uniforms[ 'time' ].value += 1.0 / 60.der( this.scene, this.camera );},// 定义相机移动函数translateCamera(position, target){(this.camera.position,{x:position.x,y:position.y,z:position.z,duration:1, //持续时间ease:"power2.inOut" //控制动画期间的变化率,默认"power1.out"})// (ls.target,{// x:target.x,// y:target.y,// z:target.z,// duration:1, //持续时间// ease:"power2.inOut" //控制动画期间的变化率,默认"power1.out"// })},}
}
</script>
<style>
*{margin: 0;padding: 0;
}
canvas{width: 100%;height: 100%;position: fixed;left: 0;top: 0;
}
</style>
本文发布于:2024-01-29 08:45:21,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170648912314086.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |