NoteZ_技术博客 NoteZ_技术博客
🏠 首页
  • 📚 Web技术
  • 📋 Npm笔记
  • 📑 Markdown
  • 📄 Git笔记
  • 📝 Nginx文档
  • 📓 Linux文档
  • 📖 技术文档
  • 📜 其他文档
  • 🧊 NodeJs
  • 🎡 Express
  • 🔥 Rust
  • 🎉 Koa2
  • 🍃 MongoDB
  • 🐬 MySql
  • 🥦 Oracle
  • 🍁 Python
  • 🍄 JavaScript
  • 🌰 CSS
  • 🧄 HTML
  • 🥑 Canvas
  • 🌽 Nuxt
  • 🍆 React
  • 🥜 Vue
  • 🧅 TypeScript
  • 🌶️ AI
  • 📘 分类
  • 📗 标签
  • 📙 归档
⚜️ 在线编辑 (opens new window)
  • 📁 站点收藏
  • 📦 前端组件库
  • 📊 数据可视化
  • 🌈 开源插件
  • 🎗️ 关于我
  • 🔗 友情链接
GitHub (opens new window)

NoteZ_技术博客

前端界的小学生
🏠 首页
  • 📚 Web技术
  • 📋 Npm笔记
  • 📑 Markdown
  • 📄 Git笔记
  • 📝 Nginx文档
  • 📓 Linux文档
  • 📖 技术文档
  • 📜 其他文档
  • 🧊 NodeJs
  • 🎡 Express
  • 🔥 Rust
  • 🎉 Koa2
  • 🍃 MongoDB
  • 🐬 MySql
  • 🥦 Oracle
  • 🍁 Python
  • 🍄 JavaScript
  • 🌰 CSS
  • 🧄 HTML
  • 🥑 Canvas
  • 🌽 Nuxt
  • 🍆 React
  • 🥜 Vue
  • 🧅 TypeScript
  • 🌶️ AI
  • 📘 分类
  • 📗 标签
  • 📙 归档
⚜️ 在线编辑 (opens new window)
  • 📁 站点收藏
  • 📦 前端组件库
  • 📊 数据可视化
  • 🌈 开源插件
  • 🎗️ 关于我
  • 🔗 友情链接
GitHub (opens new window)
  • Web技术

  • Git笔记

  • Linux文档

  • Markdown

  • Nginx文档

  • Npm笔记

  • 技术文档

  • 其他文档

    • 解决 bash_wget 未找到命令的解决办法
    • CentOS7 常见问题应对,如何升级make和gcc版本
    • CodeMirror代码编辑器实现自定义提示功能增强版(支持搜索、调用接口查询提示内容)
    • CSS 滚动条样式
    • d3 svg 基本图形绘制
    • d3.js中update,enter,exit的概念
    • dat.gui 基本使用方法
    • echarts 使用案例(demo)
    • jsplumb 中文基础教程
    • Linux服务器(centos7)使用LibreOffice将Word转换PDF文档出现中文乱码或方格解决方法
    • npm install 报错 npm ERR code UNABLE_TO_VERIFY_LEAF_SIGNATURE npm ERR errno UNABLE_TO_VERIFY_LEA 解决
    • Npm 清除缓存
    • Npm设置淘宝镜像
    • NPS内网穿透安装方法
    • ThreeJs 基础入门
      • 3D 场景前置知识
      • 相机
      • 灯光
      • Mesh
      • 几何图形
      • 加载外部模型
      • 粒子
      • 场景交互
      • 动画
    • unable to verify the first certificate 原因及解决方法
    • vue 安装node-sass报错解决方案(缺少python2.7支持)
    • windows下安装 stable-diffusion-webui 步骤
    • yarn的安装与使用
    • 关于微信支付 WeixinJSBridge.invoke 、 wx.chooseWXPay使用方法
    • 内网穿透的几款工具汇总
    • 前端使用 swd-deploy 自动化部署项目到服务器
    • 常用工具集(utils.js)
    • 开源项目大杂烩
    • 微信小程序-APP生命周期与运行机制总结
    • 微信小程序踩坑之布局适配单位(rpx、px、vw、vh)
    • 服务器常用的状态码
    • 解决google浏览器翻译无法使用的问题
    • 解决使用 Gitalk 登录授权报错的问题
    • 解决在使用 stable-diffusion-webui 时,安装 gfpgan 失败的方案(windows下的操作)
    • 通过 js 进行 shapefile 文件解析渲染方法
    • 部署脚本 deploy.sh
    • Tauri打包慢或者报错问题解决方法
    • Ubuntu和Nginx搭配Certbot配置SSL证书https访问网站
    • Centos下yum无法正常使用
    • Linux 系统下通过 Let‘s Encrypt 生成免费 https 证书的步骤
    • Mongo 风格的查询对象映射到 SQL 查询的 Node.js 库 json-sql
    • CentOS7安装与卸载anaconda3基础步骤
  • 前端开发
  • 其他文档
NoteZ
2019-07-19
目录

ThreeJs 基础入门

Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影、材质等各种对象。使用它可以让我们更加直观的了解 webgl 的世界。

# 3D 场景前置知识

  • 场景(Scene):是物体、光源等元素的容器,可以配合 chrome 插件使用,抛出 window.scene即可实时调整 obj 的信息和材质信息。
  • 相机(Camera):场景中的相机,代替人眼去观察,场景中只能添加一个,一般常用的是透视相机(PerspectiveCamera)
  • 物体对象(Mesh):包括二维物体(点、线、面)、三维物体,模型等等
  • 光源(Light):场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等
  • 渲染器(Renderer):场景的渲染方式,如webGL\canvas2D\Css3D。
  • 控制器(Control): 可通过键盘、鼠标控制相机的移动

# 相机

Three.js中我们常用的有两种类型的相机:正交(orthographic)相机、透视(perspective)相机。一般情况下为了模拟人眼我们都是使用透视相机; 正交镜头的特点是,物品的渲染尺寸与它距离镜头的远近无关。也就是说在场景中移动一个物体,其大小不会变化。正交镜头适合2D游戏。 透视镜头则是模拟人眼的视觉特点,距离远的物体显得更小。透视镜头通常更适合3D渲染。

THREE.PerspectiveCamera(fov,aspect,near,far)

参数 描述
fov 视野角度,从镜头可以看到的场景的部分。通常3D游戏的FOV取值在60-90度之间较好的默认值为60
aspect 渲染区域的纵横比。较好的默认值为window.innerWidth/window.innerHeight
near 最近离镜头的距离
far 远离镜头的距离

透视相机示意图:

1662343643099-84.png

创建摄像机以后还要对其进行移动、然后对准物体积聚的场景中心位置,分别是设置其 position和调用 lookAt 方法,参数均是一个 xyz向量(new THREE.Vector3(x,y,z))

::: info camera.position:控制相机在整个3D环境中的位置(取值为3维坐标对象-THREE.Vector3(x,y,z)) camera.lookAt:控制相机的焦点位置,决定相机的朝向(取值为3维坐标对象-THREE.Vector3(x,y,z)) :::

# 灯光

在Three.js中光源是必须的,如果一个场景你不设置灯光那么世界将会是一片漆黑。Three.js内置了多种光源以满足特定场景的需要。大家可以根据自己的项目需要来选择何种灯光

光源分类

光源 说明
AmbientLight 环境光,其颜色均匀的应用到场景及其所有对象上,这种光源为场景添加全局的环境光。这种光没有特定的方向,不会产生阴影。通常不会把AmbientLight作为唯一的光源,而是和SpotLight、DirectionalLight等光源结合使用,从而达到柔化阴影、添加全局色调的效果。指定颜色时要相对保守,例如#0c0c0c。设置太亮的颜色会导致整个画面过度饱和,什么都看不清。
PointLight 3D空间中的一个点光源,向所有方向发出光线
SpotLight 产生圆锥形光柱的聚光灯,台灯、天花板射灯通常都属于这类光源,这种光源的使用场景最多,特别是在你需要阴影效果的时候。
DirectionalLight 也就无限光,光线是平行的。典型的例子是日光,用于模拟遥远的,类似太阳那样的光源。该光源与SpotLight的主要区别是,它不会随着距离而变暗,所有被照耀的地方获得相同的光照强度。
HemisphereLight 特殊光源,用于创建户外自然的光线效果, 此光源模拟物体表面反光效果、微弱发光的天空,模拟穹顶(半球)的微弱发光效果,让户外场景更加逼真。使用DirectionalLight + AmbientLight可以在某种程度上来模拟户外光线,但是不够真实,因为无法体现大气层的散射效果、地面或物体的反射效果
AreaLight 面光源,指定一个发光的区域
LensFlare 不是光源,用于给光源添加镜头光晕效果

关于光源的详细 API 大家可以参考 threejs 官网,很详细,demo 也很完整 传送门 (opens new window)。

# Mesh

在计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。当线段数量越多,长度就越短,当达到你无法察觉这是线段时,一条平滑的弧线就出现了。 计算机的三维模型也是类似的。只不过线段变成了平面,普遍用三角形组成的网格来描述。我们把这种模型称之为 Mesh 模型。 在 threeJs 的世界中,材质(Material)+几何体(Geometry)就是一个 mesh。设置其name属性可以通过scene.getObjectByName(name)获取该物体对象;Geometry就好像是骨架,材质则类似于皮肤,对于材质和几何体的分类。见下表格:

材质分类

材质 说明
MeshBasicMaterial 基本的材质,显示为简单的颜色或者显示为线框。不考虑光线的影响
MeshDepthMaterial 使用简单的颜色,但是颜色深度和距离相机的远近有关
MeshNormalMaterial 基于面Geometry的法线(normals)数组来给面着色
MeshFacematerial 容器,允许为Geometry的每一个面指定一个材质
MeshLambertMaterial 考虑光线的影响,哑光材质
MeshPhongMaterial 考虑光线的影响,光泽材质
ShaderMaterial 允许使用自己的着色器来控制顶点如何被放置、像素如何被着色
LineBasicMaterial 用于THREE.Line对象,创建彩色线条
LineDashMaterial 用于THREE.Line对象,创建虚线条
RawShaderMaterial 仅和THREE.BufferedGeometry联用,优化静态Geometry(顶点、面不变)的渲染
SpriteCanvasMaterial 在针对单独的点进行渲染时用到
SpriteMaterial 在针对单独的点进行渲染时用到
PointCloudMaterial 在针对单独的点进行渲染时用到

# 几何图形

2D

图形 说明
矩形 THREE.PlaneGeometry 外观上是一个矩形new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
THREE.CircleGeometry 传送门 (opens new window) 外观上是一个圆形或者扇形 new THREE.CircleGeometry(3, 12);new THREE.CircleGeometry(3, 12, 0, Math.PI);第三个参数和第四个分别是起始角度和结束角度,默认0-2*PI
THREE.RingGeometry 传送门 (opens new window) 外观上是一个圆环或者扇环 new THREE.RingGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength)
THREE.ShapeGeometry 传送门 (opens new window) 该形状允许你创建自定义的二维图形,其操作方式类似于SVG/Canvas中的画布

3D

图形 说明
THREE.BoxGeometry 传送门 (opens new window) 这是一个具有长宽高的盒子 BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
THREE.SphereGeometry 传送门 (opens new window) 这是一个三维球体/不完整球体 SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)
THREE. CylinderGeometry 传送门 (opens new window) 可以绘制圆柱、圆筒、圆锥或者截锥 new THREE.CylinderGeometry(radiusTop,radiusBottom,height,radialSegments,heightSegments,openEnded)

# 加载外部模型

一般来讲我们的场景中不可能都是一些奇奇怪怪的形状,或多或少项目中都会用到一些外部的模型资源,不如动物啊,装饰物啊什么的,再加上一些动画,这样整个场景更加显得生动,那么 threejs 中我们可以通过哪些方式来加载外部的模型资源呢?

加载外部模型,是通过Three.js加载器(Loader)实现的。加载器把文本/二进制的模型文件转化为Three.js对象结构。 每个加载器理解某种特定的文件格式。

注意

需要注意的是,由于贴图的尺寸必须是(2的幂数)X (2的幂数),如:1024X512,所以为了防止贴图变形,平面的宽度比例需要与贴图的比例一致。

支持的格式

格式 说明
JSON Three.js自定义的、基于JSON的格式。可以声明式的定义一个Geometry或者Scene.利用该格式,你可以方便的重用复杂的Geometry或Scene
OBJ / MTL OBJ是Wavefront开发的一种简单3D格式,此格式被广泛的支持,用于定义Geometry,MTL用于配合OBJ,它指定OBJ使用的材质
Collada(dae) 基于XML的格式,被大量3D应用程序、渲染引擎支持
STL STereoLithography的简写,在快速原型领域被广泛使用。3D打印模型通常使用该格式定义Three.js提供了STLExporter.js,使用它可以把Three.js模型导出为STL格式
CTM openCTM定义的格式,以紧凑的格式存储基于三角形的Mesh
VTK Visualization Toolkit定义的格式,用于声明顶点和面。此格式有二进制/ASCII两种变体,Three.js仅支持ASCII变体
AWD 3D场景的二进制格式,主要被away3d引擎使用,Three.js不支持AWD压缩格式
Assimp 开放资产导入库(Open asset import library)是导入多种3D模型的标准方式。使用该Loader你可以导入多种多样的3D模型格式
VRML 虚拟现实建模语言(Virtual Reality Modeling Language)是一种基于文本的格式,现已经被X3D格式取代尽管Three.js不直接支持X3D,但是后者很容易被转换为其它格式
Babylon 游戏引擎Babylon的私有格式
PLY 常用于存储来自3D扫描仪的信息

在项目一开始尝试是使用 dae 文件,后面发现 json 文件更加方便一点,所以最终使用的是 jsonloader 导入 json 文件。json文件可以通过 blender 或者3DsMax 导出,他们都有各自的 export json的插件。在软件中处理好模型贴图和动画以后,导出 json 文件和相应的贴图文件给到前端即可。

var jsonLoader = new THREE.JSONLoader();
 jsonLoader.load('model.json', function (geometry, materials) {
    materials.forEach(function (mat) {
         //这里面可以设置材质的各种信息
        mat.skinning = true;
        mat.color = new THREE.Color("rgb(233,203,113)"); //模型颜色
        mat.emissive = new THREE.Color("rgb(110,110,110)");//自发光颜色
    });
    var model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
    model.name = “model name”;
    scene.add(model);
    //下面是播放模型中的动画内容
    var sceneAnimationClip = model.geometry.animations[0]
    var mixer = new THREE.AnimationMixer(model);
    mixers.push(mixer);
    var sceneAnimation = mixer.clipAction(sceneAnimationClip);
    sceneAnimation.play();

});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

同理其他类型的文件也可以使用相应的 loader 导入文件,控制其材质信息和动画播放,具体的可以查看官网的 demo。

# 粒子

THREE.Sprite

在WebGlRenderer渲染器中使用THREE.Sprite创建的粒子可以直接添加到scene中。创建出来的精灵总是面向镜头的。即不会有倾斜变形之类透视变化,只有近大远小的变化。

比如一个纹理为花瓣的粒子示例:

//花瓣的贴图
var textureList = [
        __uri("../../img/flower-1.png"),
        __uri("../../img/flower-2.png"),
        __uri("../../img/flower-3.png"),
        __uri("../../img/flower-4.png"),
        __uri("../../img/flower-5.png"),
        __uri("../../img/flower-6.png"),
        __uri("../../img/flower-7.png"),
        __uri("../../img/flower-8.png"),
        __uri("../../img/flower-9.png"),
        __uri("../../img/flower-10.png")]
var particles = []; //存储生成的粒子
//粒子从Z轴产生区间在-20到20
for (var zpos = -20; zpos < 20; zpos += 0.5) {
    var texturerain = textLoader.load(textureList[Math.floor(Math.random() * 10)])
    var material = new THREE.SpriteMaterial({
            transparent: true,
            opacity: util.getRandomInt(0.7, 1),
            map: texturerain
        }
    );
    //生成粒子
    particle = new THREE.Sprite(material);
    particle.name = "particle"
    //随即产生x轴,y轴
    particle.position.x = Math.random() * 100 
    particle.position.y = Math.random() * 100;
    //设置z轴
    particle.position.z = zpos;
    //将产生的粒子添加到场景
    scene.add(particle);
    //将粒子位置的值保存到数组
    particles.push(particle);
}

//移动粒子的函数
function updateParticles() {
  //遍历每个粒子
  for (var i = 0; i < particles.length; i++) {
      particle = particles[i];
      //设置粒子向前移动的速度依赖于鼠标在平面Y轴上的距离
      particle.position.y -= i / particles.length / 50;
      particle.position.x -= i / particles.length / 80;
      if (particle.position.y < -7) { //溢出视野以后设置回原位置
          particle.position.x = Math.random() * 100 - 50;
          particle.position.y = 7;
      }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 场景交互

Three.js中并没有直接提供“点击”功能,一开始使用的时候我也觉得一脸懵逼,后来才发现我们可以基于THREE.Raycaster来判断鼠标当前对应到哪个物体,用来进行碰撞检测.

//核心代码
var clickObjects = []; //存储哪些 obj 需要交互
var _raycaster = new THREE.Raycaster();//射线拾取器
var raycAsix = new THREE.Vector2();//屏幕点击点二维坐标
var container = null;

function onMouseMove(event) {
    event.preventDefault();
    container = document.getElementById("Canvas1");
    raycAsix.x = ( (event.pageX - $(container).offset().left) / container.offsetWidth ) * 2 - 1;
    raycAsix.y = -( (event.pageY - $(container).offset().top) / container.offsetHeight ) * 2 + 1;
    _raycaster.setFromCamera(raycAsix, Camera);
    var intersects = _raycaster.intersectObjects(clickObjects);//获取射线上与存储的可被点击物体的集合的交集,集合的第一个物体为距离相机最近的物体,最后一个则为离相机最远的。
    if (intersects.length > 0) {
        document.body.style.cursor = 'pointer';
        console.log(intersects[0].object.name) //打印导入模型时设置的model name
    } else {
        document.body.style.cursor = 'default';
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

其他的交互比如点击事件都是基于此。

# 动画

场景中如果我们添加了各种 mesh 和模型并给他加入了一些 tweend动画会发现他并不会运动,因为你的场景并没有实时渲染,所以要让场景真的动起来,我们需要用到requestAnimationFrame;关于它的详细使用请大家自行 google,核心代码如下

var requestAnimationFrame = window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame;
 function animate() {
    var delta = clock.getDelta();
    if (mixers.length > 0) {
        for (var i = 0; i < mixers.length; i++) {
            mixers[i].update(delta);
        }
    }
    //Renderer即我们实例化的 webglRender 对象;
    updateParticles()
    Renderer.clear();
    Renderer.render(scene, Camera);
    requestAnimationFrame(animate);
    //如果有使用 Tween做一些补间动画,也需要在此调用 TWEEN.update();
    TWEEN.update();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

另外我们如果想自己 K 动画也是可以的,不过我觉得应该没有人这么无聊

jsonLoader.load('../resource/hudie.json', function ( geometry, materials ) {
    materials.forEach(function (mat){
        mat.skinning = true;
        mat.color =  new THREE.Color("rgb(0,255, 0)");
        mat.emissive =new THREE.Color("rgb(255, 0, 255)");
    });
    var tracks = [];
    //NumberKeyframeTrack(name,times,values),依次去K模型的位置,缩放和旋转
    tracks.push( new THREE.NumberKeyframeTrack( '.position', [ 0, 1, 2 ], [ -15,-5,-7,  0, 0, 0, 5,0.3,-9 ] ) );
    tracks.push( new THREE.NumberKeyframeTrack( '.scale', [ 0, 1, 2 ], [  0.04, 0.04, 0.04,  0.04, 0.04, 0.04,
     0.04, 0.04, 0.04] ) );
    tracks.push( new THREE.NumberKeyframeTrack( '.rotation', [ 0, 1, 2 ], [  0, 0.2, 0,  0, 0, 0, 0, 0.2, 0.2 ] ) );
    var  model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
    model.name="蝴蝶";
     var clip = new THREE.AnimationClip( 'Action', -1, tracks );
     var mixer = new THREE.AnimationMixer( model );
     mixers.push(mixer);
     mixer.clipAction( clip ).play();
     scene.add( model );
} );
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ThreeJs
上次更新: 2024/01/30, 00:35:17
NPS内网穿透安装方法
unable to verify the first certificate 原因及解决方法

← NPS内网穿透安装方法 unable to verify the first certificate 原因及解决方法→

最近更新
01
Gitea数据备份与还原
03-10
02
Linux 中使用 rsync 同步文件目录教程
03-10
03
Linux 使用 rsync 互相传输同步文件的简单步骤
03-08
更多文章>
Theme by Vdoing | Copyright © 2019-2025 NoteZ,All rights reserved | 冀ICP备2021027292号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式