赞
踩
开门上threejs官网
首先两者都是用于创建 3D 图形和交互性的技术,但在使用,生态和性能上有所区别:
(1)使用上,webgl更底层更抽象,它可直接访问图形硬件,编写底层图形渲染代码(如,着色器,矩阵变换,顶点缓冲区,光源等);threejs更易使用,它建于webgl之上,专注创建3d内容而不是处理底层的图形编程。
(2)生态上,webgl可查阅的文档资源较少;threejs有官网案例,开源项目还有辅助工具(如GUI,Tween,相机控件)生态更庞大些。
(3)性能上,如果对性能有严格要求,可直接使用webgl更精细的控制图片渲染流程,需要投入更多专业知识和时间;threejs对一些复杂场景做了一些优化和抽象,通常能提供足够的性能。
总之,webgl 更适合那些对图形编程有深入了解和对性能有更高要求的开发者,threejs易学易使用,专注3d内容但没有深入了解图形编程的人,根据需求和技术各取所需。
创建场景scene → 创建一个相机camera,设置相机位置 → 创建一个渲染器canvas,尺寸 → 创建几何体geometry,添加材质material → 添加灯光light → 把要展示的添加到场景里并渲染
复习3d三要素:视点(相机/眼睛),目标(几何体),上方向
(官网基本场景案例)
以下是常用光源、材质、几何体属性合集
const itemType = { SpotLight: ['color', 'intensity', 'distance', 'angle', 'exponent'], // 聚光灯 AmbientLight: ['color'], // 环境光 PointLight: ['color', 'intensity', 'distance'], // 点光源 DirectionalLight: ['color', 'intensity'], // 平行光 HemisphereLight: ['skyColor', 'groundColor', 'intensity'], // 半球光 MeshBasicMaterial: ['color', 'opacity', 'transparent', 'wireframe', 'visible'], // 基础,显示简单颜色。 MeshDepthMaterial: ['wireframe', 'cameraNear', 'cameraFar'], // 深度,指与相机距离越远越暗 MeshNormalMaterial: ['opacity', 'transparent', 'wireframe', 'visible', 'side'], // 法向量,把法向量映射到RGB颜色的材质 MeshLambertMaterial: ['opacity', 'transparent', 'wireframe', 'visible', 'side', 'ambient', 'emissive', 'color'], // 郎伯,良好暗淡效果,没有镜面高光 MeshPhongMaterial: ['opacity', 'transparent', 'wireframe', 'visible', 'side', 'ambient', 'emissive', 'color', 'specular', 'shininess'], //phong,有镜面高光 ShaderMaterial: ['red', 'alpha'], // 着色器,可自定义应用所有光照场景 LineBasicMaterial: ['color'], // 实线 LineDashedMaterial: ['dashSize', 'gapSize'], // 虚线 PlaneGeometry: ['width', 'height', 'widthSegments', 'heightSegments'], // 平面 PlaneBufferGeometry: ['width', 'height', 'widthSegments', 'heightSegments'], // 缓冲,顶点数据索引缓存,有效减少向 GPU 传输 CircleGeometry: ['radius', 'segments', 'thetaStart', 'thetaLength'], // 圆 BoxGeometry: ['width', 'height', 'depth', 'widthSegments', 'heightSegments', 'depthSegments'], // 矩形 SphereGeometry: ['radius', 'widthSegments', 'heightSegments', 'phiStart', 'phiLength', 'thetaStart', 'thetaLength'], // 球 CylinderGeometry: ['radiusTop', 'radiusBottom', 'height', 'radialSegments', 'heightSegments', 'openEnded'], // 圆柱 TorusGeometry: ['radius', 'tube', 'radialSegments', 'tubularSegments', 'arc'], // 圆环 TorusKnotGeometry: ['radius', 'tube', 'radialSegments', 'tubularSegments', 'p', 'q', 'heightScale'], // 扭结 PolyhedronGeometry: ['radius', 'detail'], // 多面体 TetrahedronGeometry: ['radius', 'detail'], // 四面体 OctahedronGeometry: ['radius', 'detail'], // 八面体 IcosahedronGeometry: ['radius', 'detail'], //二十面体 TextGeometry: ['size', 'bevelThickness', 'bevelSize', 'bevelEnabled', 'bevelSegments', 'curveSegments', 'steps'], // 文字
设置对光有反应的材质 → 开启几何体阴影 → 使用平面接收 → 开启灯光阴影
(1)不是所有材质都对光有反应
(2)不是所有光都产生阴影
(1)对光有反应的材质:郎伯材质MeshLambertMaterial,MeshPhongMaterial,MeshStandardMaterial,MeshPhysicalMaterial
(2)可产生明确阴影的光:点光源SpotLight,平行光Directionallight
// 几何开启阴影
cube.castShadow = true;
// 使用平面接收阴影
plane.receiveShadow = true;
// 设置灯光开启阴影
spotLight.castShadow = true;
renderer.shadowMapEnabled = true;
spotLight.shadowMapWidth = 4096;//使阴影更清晰
需要添加相关文件加载器xxxLoader
const loader = new THREE.OBJMTLLoader() loader.load('../assets/models/city.obj', '../assets/models/city.mtl', (mesh) => { scene.add(mesh); }); import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js'; const fbxLoader = new THREE.FBXLoader(); fbxLoader.load('urlxxx.fbx', (object) => { // 这里object是group对象 // 遍历场景中的所有几何体数据 object.traverse((child) => { if (child.isMesh) { // 对模型数据进行后期二次处理 // scene.add(child); } }); }
补充:组对象Group、层级对象
(1)创建var group = new THREE.Group();
(2)添加group.add(mesh1);
(3)查看子对象group.children
(4)删除group.remove(mesh1);
(5)遍历group.traverse((child)=>{});
辅助工具,在动画中为属性赋值对应的变量
//需要控制的属性 const controls = { color: '', // 是否要组合成立方体 width: '', }; let geo; const gui = new dat.GUI(); for (const key in controls) { if(key=='color'){ gui.addColor(controls, 'color',key).onChange((value) => { controls.color = value; }); }else{ gui.add(controls, key).onChange(() => { // 更新几何体属性,width等尺寸需要如下操作 //1. 先删除 scene.remove(geo); //2. 再添加 const geo = new THREE.Mesh(new THREE.BoxGeometry(10, 10, 10, 10, 10, 10), new THREE.MeshNormalMaterial()) scene.add(geo); }); } }
优点:
- 支持多种动画类型:Tween.js 可以创建各种类型的动画效果,包括数字变化、颜色渐变、位置移动、缩放变换等。你可以对不同的属性进行动画化,实现更丰富多样的效果。Tween.js 还提供了丰富的缓动函数(easing functions),可帮助你实现自定义的动画变化曲线。
- 时间控制和事件回调:Tween.js 允许你控制动画的开始、暂停、恢复和停止。你可以根据需要随时进行时间控制,以适应交互或其他场景的需求。此外,Tween.js 还支持在动画达到特定时间点或完成时触发回调函数,以便执行进一步的操作或处理事件。
new TWEEN.Tween(cube.rotation).to({ x: cube.rotation.x + 2, y: cube.rotation.y + 2, z: cube.rotation.z + 2, }, 2000).start().repeat(Infinity); //动画渲染/循环 function animate() { // cube.rotation.x += 0.01; // cube.rotation.y += 0.01; TWEEN.update(); // 渲染 renderer.render(scene, camera); requestAnimationFrame(animate);//浏览器下次重绘之前执行回调 } animate();
// 相机(透视)
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 100000);
camera.position.set(1000, 500, 100);
scene.add(camera);
// 添加相机控件-轨迹
const controls = new OrbitControls(camera, canvas);
// 是否有惯性
controls.enableDamping = true;
// 是否可以缩放
// controls.enableZoom = true;
controls.enableZoom = false; // 采用鼠标滚轮
注意: 采用鼠标滚轮,一定把相机控件的缩放关闭controls.enableZoom = false;
思路主要以下:鼠标桌标计算、坐标转换unproject、统一化normalize、改变相机中心点
addWheel() { const body = document.body; body.onmoussewheel = (event) => { const value = 30; //获取鼠标坐标位置 const x = (event.clientX / window.innerWidth) * 2 - 1; const y = -(event.clientY / window.innerHeight) * 2 + 1; // 获取屏幕坐标 const vector = new THREE.Vector3(x, y, 0.5); // 将屏幕坐标转换为three.js场景坐标(鼠标点击位坐标置转三维坐标) vector.unproject(this.camera); // 获取缩放的坐标信息 vector.sub(this.camera.position).normalize(); if (event.wheelDelta > 0) { //针对相机做处理 this.camera.position.x += vector.x * value; this.camera.position.y += vector.y * value; this.camera.position.z += vector.z * value; controls.target.x += vector.x * value; controls.target.y += vector.y * value; controls.target.z += vector.z * value; } else { this.camera.position.x -= vector.x * value; this.camera.position.y -= vector.y * value; this.camera.position.z -= vector.z * value; controls.target.x -= vector.x * value; controls.target.y -= vector.y * value; controls.target.z -= vector.z * value; } } }
scene.fog = new THREE.Fog(0xffffff, 1, 50);
RenderPass二次处理 → OutlinePass配置辉光属性 → EffectComposer组合器组合以上通道 → 渲染组合render
//辉光效果 // 创建了一个渲染通道,这个通道会渲染场景,不会渲染到屏幕上 const renderScene = new RenderPass(scene, camera);//对图像做二次处理 // 分辨率 场景 相机 当前选中的物体(需要添加辉光效果) const outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera, [cube1, cube2]) outlinePass.renderToScreen = true; // 渲染到屏幕上 outlinePass.edgeStrength = 3; // 尺寸 outlinePass.edgeGlow = 2; // 发光的强度 outlinePass.edgeThickness = 2; // 光晕粗细 outlinePass.pulsePeriod = 1;// 闪烁的速度,值越小闪烁越快 outlinePass.visibleEdgeColor.set('yellow'); // 创建一个组合器对象,添加处理通道 const bloom = new EffectComposer(renderer) bloom.setSize(window.innerWidth, window.innerHeight) bloom.addPass(renderScene) bloom.addPass(outlinePass) //动画渲染/循环 function animate() { renderer.render(scene, camera); bloom.render(); requestAnimationFrame(animate);//浏览器下次重绘之前执行回调 } animate();
创建虚拟场景盒子skybox → 设置几何体cube材质 → cubeCamera获取cube材质renderTarget → 给反光几何添加上反光材质envMap为renderTarget
// 添加轨道控件 const controls = new THREE.OrbitControls(camera) // 环境纹理,虚拟反光效果 // 创建虚拟的场景 const imgs = [ './assets/img/sky/right.jpg', './assets/img/sky/left.jpg', './assets/img/sky/top.jpg', './assets/img/sky/bottom.jpg', './assets/img/sky/front.jpg', './assets/img/sky/back.jpg', ] const mats = []; for (let i = 0; i < imgs.length; i++) { mats.push(new THREE.MeshBasicMaterial({ //bumpmap凹凸,normalmap法向 map: THREE.ImageUtils.loadTexture(imgs[i]), side: THREE.DoubleSide,// 环境贴图需要设置,默认frontside前外面,backside后内面,doubleside两面 })) } // 虚拟环境盒子 const skybox = new THREE.Mesh(new THREE.BoxGeometry(100, 100, 100), new THREE.MeshFaceMaterial(mats)) scene.add(skybox) // 创建一个球体 和一个立方体 const sphereGeometry = new THREE.SphereGeometry(4, 15, 15); const cubeGeometry = new THREE.BoxGeometry(5, 5, 5); // 立方体贴图是和环境一致, 球体是跟随当前环境 const cubeMaterial = new THREE.MeshBasicMaterial({ envMap: THREE.ImageUtils.loadTextureCube(imgs)//使用和天空盒子一样材质 }) // 通过立方体相机来实现 const cubeCamera = new THREE.CubeCamera(0.1, 2000, 256); scene.add(cubeCamera); const sphereMaterial = new THREE.MeshBasicMaterial({ envMap: cubeCamera.renderTarget, }) const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); const cube = new THREE.Mesh(cubeGeometry, cubeMaterial); sphere.position.x = 5; cube.position.x = -5; scene.add(sphere) scene.add(cube) const clock = new THREE.Clock(); //动画渲染/循环 function animate() { cube.rotation.x += 0.01; cube.rotation.y += 0.01; controls.update(clock.getDelta());//动画添加相机更新 // 渲染 renderer.render(scene, camera); requestAnimationFrame(animate);//浏览器下次重绘之前执行回调 cubeCamera.updateCubeMap(renderer, scene);//反光 } animate();
mix(value1, value2, factor)
value1:第一个输入值。
value2:第二个输入值。
factor:插值因子,控制两个输入值之间的混合比例。取值范围为0到1,其中0表示完全使用 value1,1表示完全使用 value2。
名称 | 描述 |
---|---|
vertexShader | 定义顶点着色器(gl_Position,gl_PointSize) |
fragmentShader | 定义片元着色器(gl_FragColor,gl_FragCoord) |
uniforms | 所有顶点都具有相同的值的变量 |
attributes | 只在顶点着色器中,只能声明全局变量 |
varying | 从顶点着色器向片元着色器传递数据 |
transparent | true,使得着色器支持透明 |
depthTest | THREE.DoubleSide,解决建筑物展示部分问题 |
side | true,可被建筑物遮挡隐藏 |
- - - | - - - |
const material = new THREE.ShaderMaterial({ uniforms: { u_city_color: { // 得需要一个模型颜色 最底部显示的颜色 value: new THREE.Color('#1B3045') }, u_head_color: { // 要有一个头部颜色 最顶部显示的颜色 value: new THREE.Color('#ffffff') }, u_size: { value: 100, }, }, vertexShader: ` varying vec3 v_position; void main() { v_position = position; gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(v_position, 1.0); } `, fragmentShader: ` varying vec3 v_position; uniform vec3 u_city_color; uniform vec3 u_head_color; uniform float u_size; void main() { vec3 base_color = u_city_color; base_color = mix(base_color, u_head_color, v_position.z / u_size); gl_FragColor = vec4(base_color, 1.0); } `, })
补充实现渐变效果有哪些:
(1)css3线性渐变样式
div {background: linear-gradient(45deg, #ff0000, #00ff00);}
(2)渐变图片做贴图
lerp(value1, value2, factor)
(参数同mix函数) // 通过起始点和终止点来计算中心位置
const center = target.clone().lerp(source, 0.5);
// 设置中心位置的高度
center.y += options.height;
贝塞尔曲线:是一种平滑曲线,由控制点定义。二次由1个控制点,三次由2个控制点,常用于平滑路径动画、形状变形。
// 起点到终点的距离,这里是粒子数量
const len = parseInt(source.distanceTo(target));
// 获取粒子
const points = curve.getPoints(len);
const positions = [];//粒子坐标集合
const aPositions = [];//粒子索引集合
points.forEach((item, index) => {
positions.push(item.x, item.y, item.z)
aPositions.push(index)
})
const geometry = new THREE.BufferGeometry();//空几何图形
geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3))//第二参数表示获取数据数量
geometry.setAttribute('a_position', new THREE.Float32BufferAttribute(aPositions, 1))
补充:Geometry和BufferGeometry使用区别
它们都是用于描述和存储3D对象的数据结构。
(1)Geometry,使用js对象来存储几何数据,适用简单模型,优点易创建,缺点就是js对象存储额外消耗内存和cpu。
(2)BufferGeometry,使用TypedArray更底层方式存储几何数据,适用于处理大量顶点数据的复杂场景,优点少内存,缺点相对难创建。
geometry.vertices可获取顶点数据
const material = new THREE.ShaderMaterial({ uniforms: { u_color: { value: new THREE.Color(options.color) }, u_range: { value: options.range }, u_size: { value: options.size }, //粒子数量 u_total: { value: len, }, u_time: this.time, }, vertexShader: ` attribute float a_position; uniform float u_time; uniform float u_size; uniform float u_range; uniform float u_total; varying float v_opacity; void main() { float size = u_size; float total_number = u_total * mod(u_time, 1.0); if (total_number > a_position && total_number < a_position + u_range) { // 拖尾效果,超出范围的大小为0 float index = (a_position + u_range - total_number) / u_range; size *= index; v_opacity = 1.0; } else { v_opacity = 0.0; } gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); gl_PointSize = size / 10.0; } `, fragmentShader: ` uniform vec3 u_color; varying float v_opacity; void main() { gl_FragColor = vec4(u_color, v_opacity); } `, transparent: true, // 使得着色器支持透明度 });
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。