如果要说WebGL与ThreeJs肯定就要说3D了
3D的基础概念
3D分为纹理和贴图和材质
- 纹理即纹路,每个物体表面上不同的样子,比如木头的木纹状
- 贴图是图 最简单的形式是ps之类的软件做出来的一张图,这些图在3D中用来贴到物体的表面,用来表现物体的纹理
- 材质主要用来表现物体对光的交互(反射,折射等)性质的,比如金属对光的反射和毛毯对光的反射性质完全不一样,那么对3D程序来说,这样的差别就通过材质这个属性来计算出不同的颜色
日常中有哪些3D周边
数据可视化
- echarts
- Highcharts
- D3.js
跨界AR和VR
- Reacr-VR/AR
- ar.js
- renderloop
游戏开发
- Cocos2d-JS
- Egret
- CreateJS
3D开发
- Three.js
- xeolgl
- cesiumo
- babylonjs
WEBGL概念知识
显示器不认识数学家YY的数学模型 他只认识像素图
图形工程师就是把数学家YY的数学模型给转化成图片扔给显示器显示出来
要做好上面的事,学习WebGL = 学数学家Yy的数学模型 + 怎么把数学模型转化成图片
OpenGL/Dx 规定与封装了把数学模型转换成图片的过程
WebGL就是OpenGL和Javascript生的傻儿子
WebGL怎么把数学模型转换成图片?
- OpenGL中有固定渲染管线和可编程渲染管线
WebGL目前只有可编程渲染管线
- 可编程渲染管线:固定的MVP套路计算机会跑,但有时候其中某些套路不想让计算机自动处理,手动编写某个套路就是可编程渲染管线
着色器:就是帮助我们做可编程渲染管线的工具。WebGL常用的有顶点着色器,片元着色器
顶点着色器:顾名思义它能够处理订单坐标、大小等(矩阵计算后的结果),能够把数学坐标光栅化
- 片元中的每一个元素对应与缓冲区中的一个像素。光栅化其实是一种将几何图元变成二维图像的过程。该过程包含了两部分的工作,第一部分的工作:决定窗口坐标中的哪些整形栅格区域被基本图元占用;第二部分的工作:分配一个颜色值和一个深度值到各个区域。光栅化过程产生的是片元。
- 光栅化:把物体的数学描述以及物体相关的颜色信息转换为屏幕上用于对应位置的像素及用于填充色素的颜色。这个过程就是光栅化,这是一个将模拟信号转化为离散信号的过程。光栅化就是把顶点数据转换为片元的过程
- 片元着色器:能够接受光栅化数据并加以处理使其显示到屏幕上(光栅化数据包含了像素的位置,颜色等信息)
固定渲染管线:就是把一套把数学模型转成图片的套路 ---模型坐标转换(M)、视图坐标转换(V)、投影坐标转换(P)简称MVP套路
- 模型坐标转换:模型的基准点(原点)、模型的大小、模型旋转角度等
- 视图坐标转换:从哪个方向、角度观看这个模型
- 投影坐标转换:离得越近的呈现出来的图像就应该越大,越远越小
javascript程序 ==> 顶点着色器 ==> 片元着色器 ==> webGL程序
在构建3D物体时通过顶点组成三角形网格,但这些三角形网格都是矢量图形,最终在屏幕上显示时还是需要转换为像素图形,这种转化过程被称为栅格化,是计算机图形学的关键技术之一,
webGL程序和普通的javascript程序不一样,WebGL程序除了Javascript部分之外,还包含两个着色程序,分别是顶点着色器和片元着色器;在ThreeJs中描述一个3D物体,需要轮廓,材质,和纹理三个要素;那么可以简单理解为顶点着色器用来处理物体的轮廓程序,片色着色器是用来处理物体材质和纹理的程序
Three.js
threejs三大组件
- Camera 相机
- Renderers(渲染器) ----Shaders
- Scenes(屏幕)
图像的表示
Lights(光线)
- Shadows
- Geometries (几何图形)
- Materials(材质)
- Objects(图形对象)
Loaders(加载器)
- Managers
- Textures(纹理)
框架原理
Math数学库
- Interpolants (插值)
Extras(附件)
- core
- Curves
- Helpers
- Objects
- Constant(常量)
Core (核心)
- BufferAttribute
动画和声音
Animation(动画)
- Tracks
- Audio(声音)
Three.js必备三个条件(scene、camera、renderer渲染)
- 场景Scene是一个物体的容器(通俗理解装东西的),开发者可以将需要的角色放入到场景中,例如苹果,葡萄。同时角色自身也管理着其在场景中的位置
- 相机camera的作用就是面对场景,在场景中取一个合适的景,把它拍下来。(可以想象成人的眼睛)
渲染器renderer的作用就是将相机拍摄下来的图片,放到浏览器中去显示
坐标系的位置和指向
坐标系的原点在画布中心 (canvas.width/2,canvas.height/2)
通过Three.js提供的THREE.AxisHelper()辅助方法将坐标系可视化
THREE.PerspectiveCamera(fov,aspect,near,far)
- For :视场,即摄像机能看到的视野。比如,人类有接近180度的视场,而有些鸟类有接近360度的视场。但是由于计算机不能完全显示我们能够看到的场景,所以一般会选择一块较小的区域。对于游戏而言,视场的大小通常为60-90度。 推荐默认值为:50
- Aspect:指定渲染结果的横向尺寸和纵向尺寸的比值。在我们的示例中,由于使用窗口作为输出界面,所以使用的是窗口的长度比。推荐默认值:window.innerWdth/window.innerHeight
- Near: 指定从距离摄像机多近的距离开始渲染。推荐默认值: 0.1
- Far:指定摄像机从它所处的位置开始能看到多远。若过小,那么场景中的远处不会被渲染;若过大,可能会影响性能,推荐默认值:1000
Mesh
网格就是一系列的多边形组成的,三角形或者四边形,网格一般由顶点来描绘,我们看见的三维开发的模型就是由一些列的点组成的
Mesh好比一个包装工
将可视化的材质 粘合在一个数学世界里的几何体上,形成一个可添加到场景中的对象
创建的材质和几何体可以多次使用
还有Points(点集)、Line(线/虚线)等
BoxGeometry 长方体 | CircleGeometr 圆形 | ConeGeometry圆锥体 | CylinderGeometry 圆柱体 |
---|---|---|---|
DodecahedronGeometry(十二面体) | IcosahedronGeometry(二十面体) | LatheGeometry(让任意曲线绕y轴旋转生成一个形状,如花瓶) | OctahedronGeometry(八面体) |
ParametricGeometry(根据参数生成形状) | PolyhedronGeometry(多面体) | RingGeometry(环形) | ShapeGemetry(二锥形) |
SphereGeometry(球体) | TetrahedronGeometry(四面体) | TorusGeometry(圆环形) | TorusKnotGeometry(换面钮结体) |
TubeGeometry(管道) | \ | \ | \ |
Material材质
- MeshBaseMaterial(网格基础材质)是一种非常简单的材质,这种材质不会考虑光照的影响。使用这种材质网格会渲染成简单的平面多边形,并且可以显示几何体的线框
- MeshDepthMaterial(网格深度材质)使用该材质的物体的外观不是由某个材质属性决定的,而是有物体到相机的距离决定的,离相机越近越亮,离相机越远越暗。该材质的属性很少,没有设置物体颜色的属性, 如果想改变物体的颜色,就需要创建多材质的物体
- MeshNormalMaterial(网格法向材质)通过法向量来隐射RGB颜色。每个法向量不同的面赋予不同的颜色
- MeshFaceMaterial (网格面材质)可以为几何体每一个面指定不同的材质,比如一个立方体有6个面 你可以为每一个面指定一个材质
- MeshLambertMaterial(网格朗伯材质)用于创建看上去暗淡的、不光亮的表面,可以对光源产生阴影的效果
- MeshPhongMaterial(网格Phong式材质)用于创建光亮表面的材质。可以产生阴影的效果
- ShaderMaterial(着色器材质)该材质是最复杂的一种材质 可以使用自己定制的着色器
Light光源
- 环境光源没有位置概念,会将颜色应用到场景的每一个物体上,主要作用是弱化阴影 给场景添加颜色
- 点光源类似于照明弹,朝所有的方向发光,因此不产生阴影
- 聚光灯光源类似于手电筒,形成锥形的光束、随着距离的增加而变弱,可以设定生成阴影
- 方向光光源类似于太阳,从很远的地方发出的平行光束 距离越远,衰减的越多
- 想要一个自然的室外效果 除了添加环境光弱化阴影,添加聚光灯为场景增加光线,还需要使用半球光光源将天空和空气泵以及地面的散射计算进去 使得更自然更真实
- 平面光光源定义了一个发光的发光体,需要使用webgl的延迟渲染机制
- 炫光效果 在有太阳的时候使用炫光光源 会使得场景更真实
粒子
粒子一直面向摄像机(无论你旋转摄像机还是设置粒子的rotation属性)
THREE.Sprite(meterial) 可以加载图片作为粒子的纹理
THREE.Points(geometry.material) 创建纯点作为粒子
射线
用于鼠标去获取在3D世界被鼠标选中的一些物体
mouse.x = (e.clientX/window.innerWidth)*2 -1
mouse.y = (e.clientY/window.innerHeight)*2 -1
推导过程:
设A点为点击点(x1,y1)x1 = e.clientX, y1 = e.clinetY
设A点在世界坐标中的坐标值为B(x2,y2)
由于A点的坐标值的原点是以屏幕左上角为(0,0)
我们可以计算可得以屏幕为原点的B值
X2 = x1-innerWidth/2
Y2 = innerHeight/2 - y1
又由于在世界坐标的范围是[-1,1],要得到正确的B值我们必须要将坐标标准化
X2 = (x1 - innerWidth/2)/(innerWidth/2) = (x1/innerWidth)*2-1
同理可得y2 = -(y1/innerHeight)*2 + 1
一般需要用到的插件
Dat.GUI提供了可视化调参的面板,对参数调整的操作提供了极大的便利
Stats.js 帧率,每帧的渲染时间、内存占用量、用户自定义
适合开发VR的开发工具
- VR盒子 大部分的VR盒子都是通过光学透镜把手机屏幕的画面转变为VR画质,使得用户享有沉浸式的体验,这一产品的代表作为谷歌Cardboard 这个需要500元,建议买国产
- VR一体机 是指哪些具备了独立处理器的VR设备 他们不再通过手机或电脑的处理器运行,具有独立输入输出的能力,自成一体,故称一体机,推荐一款 柔宇
- VR头显 是目前最顶级的VR设备 在这个领域里有HTC Vive和Oculus Rift配合一部高配置的电脑主机,但是你想要有顶级的VR体验 花个六七千是必须得
- 使用过的: 暴风VR 零镜小白VR
Three.js智慧工厂3D项目
借助一些库实现快速开发
- 缓存离屏渲染(两层canvas)
- Lower-canvas和upper-canvas(底层绘制上层处理用户的相应)
- 射线算法优化相交的点
- 处理Retina屏,canvas.width,canvas.height放大至dpi倍
需要组件
yarn add three -D
yarn add obj2gltf -D
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin:0;
padding:0;
}
body{
background: #000;
}
</style>
</head>
<body>
<script src="./node_modules/three/build/three.js"></script>
<script src="./node_modules/three/examples/jsm/loaders/GLTFLoader.js"></script>
<script src="./node_modules/three/examples/jsm/controls/OrbitControls.js"></script>
<script src="./node_modules/three/examples/jsm/loader/DRACOLoader.js"></script>
<script src="./index.js"></script>
</body>
</html>
Index.js
/*
* @Author: yangyuguang
* @Date: 2023-01-15 11:10:55
* @LastEditors: yangyuguang
* @LastEditTime: 2023-01-15 15:48:54
* @FilePath: /tt/index.js
*/
let container,camera,scene,light,rendered,controls;
init()
function init(){
container = document.createElement('div')
document.body.appendChild(container)
// 创建眼睛
camera = new THREE.PerspectiveCamera(45,window.innerWidth/window.innerHeight,0.25,1000)
camera.position.set(0,80,200)
controls = new THREE.OrbitControls(camera,container) //轨道控制器 随着相机和容器走的
controls.target.set(0,-0.2,-0.2) //设置目标的距离
controls.update()
scene = new THREE.Scene()//容器
light = new THREE.HemisphereLight(0xbbbbfff,0x444422)//光线
scene.add(light)
light.position.set(0,1,0)//光线角度
const loader = new THREE.GLTFLoader().setPath('model/')
const _DRACOLoader = new THREE.DRACOLoader()
//利用DRACOLoader进行解压
_DRACOLoader.setDecoderPath("./javascript")
loader.setDRACOLoader(_DRACOLoader)
loader.load('model.gltf',function(gltf){
scene.add(gltf.scene)
},undefined,function(e){
gltf.scene.scale.set(0.03,0.03,0.03)//场景过大,缩小一些
gltf.scene.position.set(-80,0,0)//缩放之后,模型的位置也调整一下
scene.add(gltf.scene)
})
rendered = new THREE.WebGL1Renderer({
antialias:true
})
rendered.setPixelRatio(window.devicePixelRatio)
rendered.setSize(window.innerWidth,window.innerHeight)
container.appendChild(rendered.domElement)
// 打一些辅助线
const axesHelper = new THREE.AxesHelper(60)
scene.add(axesHelper)
const hemisphereLight = new THREE.HemisphereLight(light,60)
scene.add(hemisphereLight)
const helper = new THREE.CameraHelper(camera)
scene.add(helper)
}
function animate(){
requestAnimationFrame(animate)
rendered.render(scene,camera)
}
animate()
使用obj2gltf将obj文件转成gltf
"gltf":"obj2gltf -i ./static/model.obj -t"
然后加载的是gltf文件,但是gltf的体积还是太大,所以需要压缩,使用draco
- 把这个编译成js的文件夹下载下来,放到自己的项目中,
- 官方提供了一个APi,所以可以做一个package.json的压缩
- 先下载 yarn add gltf-pipeline -D
"scripts": {
"gltf":"obj2gltf -i ./static/model.obj -t",
"min":"gltf-pipeline -i ./static/model.gltf -d -s"
},
-d 压缩 -s是分离(它把它绘图的数据变成二进制的bin,把一些基本文件的配置变成原生的gltf),也就生成了新的gltf文件,
但是压缩归压缩,回显还是需要解压的,利用DRACOLoader进行解压
上面的是将Obj转成gltf类型的模型,那么也可以直接渲染Obj模型
HTML方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>加载OBJ和MTL文件</title>
<style>
html,
body{
width: 100%;
height: 100%;
overflow: hidden;
}
*{
margin:0;
padding:0;
}
</style>
<!-- 引入three.js三维引擎 -->
<script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/controls/OrbitControls.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/OBJLoader.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/MTLLoader.js"></script>
</head>
<body>
<script>
var scene = new THREE.Scene(),
camera,
renderer = new THREE.WebGLRenderer();
/* OBJ和材质文件mtl加载 */
var OBJLoader = new THREE.OBJLoader(); //obj加载器
var MTLLoader = new THREE.MTLLoader();//材质文件加载器
MTLLoader.load('./static/material.mtl',function(meterials){
console.log(meterials);
OBJLoader.setMaterials(meterials);
OBJLoader.load('./static/model.obj',function(obj){
/* 返回的组对象插入场景中 */
scene.add(obj)
obj.children[0].scale.set(5,5,5)//网络模型播放
})
})
createLight()
createCamera()
createRenderer()
/* 通过requestAnimationFrame()操作三维场景 */
var controls = new THREE.OrbitControls(camera,renderer.domElement); //创建控件对象
// 创建一个时钟对象Clock
var clock = new THREE.Clock()
function render(){
renderer.render(scene,camera);//执行渲染操作
requestAnimationFrame(render) //请求再次执行渲染函数render
}
render()
// 正投影相机设置
function createCamera(){
var width = window.innerWidth; //窗口设置
var height = window.innerHeight;//窗口高度
var k = width/height;//窗口宽高比
var s = 150;//三维场景显示范围控制系数,系数越大,显示的范围越大
// 创建相机对象
camera = new THREE.OrthographicCamera(-s*k,s*k,s,-s,1,1000)
camera.position.set(200,300,200) //设置相机位置
camera.lookAt(scene.position);//设置相机方向(指向的场景对象)
}
// 透视相机
function createPerspectiveCamera(){
var width = window.innerWidth;//窗口宽度
var height = window.innerHeight//窗口高度
/* 透视投影相机对象 */
camera= new THREE.PerspectiveCamera(60,width/height,1,1000)
camera.position.set(200,300,200)//设置相机位置
camera.lookAt(scene.position)//设置相机方向(指向的场景对象)
}
/* 创建渲染器对象 */
function createRenderer(){
renderer.setSize(window.innerWidth,window.innerHeight)//设置渲染区域尺寸
renderer.setClearColor(0xb9d3ff,1)//设置背景颜色
document.body.appendChild(renderer.domElement)//body元素中插入canvas对象
}
/* 创建光源 */
function createLight(){
// 点光源
var point = new THREE.PointLight(0xffffff)
point.position.set(400,200,300)//点光源位置
scene.add(point);//点光源添加到场景中
// 环境光
var ambient = new THREE.AmbientLight(0x444444)
scene.add(ambient)
}
</script>
</body>
</html>
在vue中使用
第一种方法使用three-obj-mtl-loader插件
使用npm install three-obj-mtl-loader --save
在组件中引入:import { OBJLoader, MTLLoader } from 'three-obj-mtl-loader'
使用OBJLoader和MTLLoader加载文件:
import {OBJLoader,MTLLoader} from "three-obj-mtl-loader"
// 加载obj和mtl模型
let _this = this
let mtlLoader = new MTLLoader()
mtlLoader.load('./static/material.mtl',function(materials){
materials.preload();
let objLoader = new OBJLoader()
objLoader.load('./static/model.obj',function(obj){
obj.scale.x = obj.scale.y = obj.scale.z = 100
obj.rotation.y = 500
let mesh =obj
mesh.position.y = -50
_this.scene.add(mesh)
})
})
第二种方法使用vue-3d-model组件
npm install vue-3d-model --save
引入组件:
import { ModelObj } from 'vue-3d-model'
components: {
ModelObj
},
data() {
return {
publicPath: process.env.BASE_URL
}
},
使用组件:
<model-obj
id="place"
:src="`${publicPath}model/model.obj`"
:mtl="`${publicPath}model/material.mtl`"
backgroundColor="rgb(0,0,0)"
:scale="{ x: 0.8, y: 0.8, z: 0.8 }"
>
</model-obj>
一般使用这个会存在找不到obj模块的情况,这是因为经过了webpack的处理,需要把.obj文件放到vue处理静态文件的文件夹中,vue-cli3是放在static文件夹下,但是vue-cli3及之后就需要放到public文件夹下,并且在组件中通过process.env.BASE_URL+public文件夹下的.obj文件的路径来引用
在uniapp中使用threeJS加载Obj模型
<template>
<view id="d3Container" ref="mainContent">
</view>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader'
import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
// import { ModelObj } from 'vue-3d-model'
export default {
name: 'ThreePage',
components: {
// ModelObj
},
data () {
return {
publicPath: process.env.BASE_URL,
mesh: null,
camera: null,
scene: null,
originX: 20,
originY: 0,
originZ: 20,
renderer: null,
controls: null,
animationId: null
}
},
mounted () {
this.init()
},
methods: {
destroyed () {
this.clear()
},
init () {
this.createScene() //创建场景
this.loadLoader() //加载P模型
this.createLight() //创建光源
this.createCamera() //创建相机
this.createRender() //创建渲染器
this.createControls() //创建空间对象
this.render() //渲染
},
clear () {
this.mesh = null
this.camera = null
this.scene = null
this.renderer = null
this.controls = null
cancelAnimationFrame(this.animationId)
},
createScene () {
this.scene = new THREE.Scene()
},
loadLoader () {
let MTLloader = new MTLLoader();
let loader = new OBJLoader()
MTLloader.load('/model/material.mtl', materials => {
loader.setMaterials(materials) //设置obj使用的材质贴图
loader.load('/model/model.obj', geometry => {
// 如果obj模型是由多个子模型构成的模型组合,调用.traverse来对每个子模型进行处理
geometry.traverse(function (child) {
if (child.isMesh) {
// 设置为false,不管渲染对象是否在摄像机的视椎体中,都会渲染对象
child.frustumCulled = false
// 开启投射阴影
child.castShadow = true
/* 设置通道颜色 */
child.material.emissive = child.material.color
/* 设置纹理 */
child.material.emissiveMap = child.materials.map
}
})
/* mesh:添加到场景中的对象 */
this.mesh = geometry
this.mesh.scale.set(0.5, 0.5, 0.5) //设置大小比例
this.scene.add(this.mesh)
})
})
},
/**
* @description: 创建光源
*/
createLight () {
// 环境光
let pointColor = '#fff'
/* 创建光 */
const ambientLight = new THREE.AmbientLight(0x222222, 0.35)
this.scene.add(ambientLight)
/* 创建聚光灯 */
const spotLight = new THREE.SpotLight(0xffffff)
spotLight.position.set(50, 50, 50)
// 平行光开启阴影
spotLight.castShadow = true
/* 接受投影效果 */
spotLight.receiveShadow = true
this.scene.add(spotLight)
},
// 创建相机
createCamera () {
const element = this.$refs.mainContent
const width = element.clientWidth
const height = element.clientHeight
this.cWidth = width
this.cHeight = height
/* 窗口宽高比 */
const k = width / height
this.aspect = k
this.camera = new THREE.PerspectiveCamera(35, k, 1, 10000)
/* 设置相机位置 */
this.camera.position.set(this.originX, this.originY, this.originZ)
// 如果需要改变默认的up值,需要先重置up值
this.camera.up.set(0, 0, 1)
/* 设置相机方向 */
this.camera.lookAt(
new THREE.Vector3(this.originX, this.originY, this.originZ)
)
this.scene.add(this.camera)
},
createRender () {
const element = this.$refs.mainContent
this.renderer = new THREE.WebGLRenderer({
antialias: true, //是否执行抗锯齿
alpha: true, //画布是否包含alpha(透明度)缓冲区,默认false
/* 是否保留缓冲区直到手动清除或覆盖,默认false */
preserveDrawingBuffer: true
})
/* 设置渲染区域尺寸 */
this.renderer.setSize(element.clientWidth, element.clientHeight)
/* 显示阴影 */
this.renderer.shadowMap.enabled = true
// 阴影类型
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.renderer.setClearColor(new THREE.Color(0xeeeeee)) //设置canvas的背景颜色
element.innerHTML = ''
element.appendChild(this.renderer.domElement)
},
render () {
this.animationId = requestAnimationFrame(this.render) //旋转动画
this.renderer.render(this.scene, this.camera)
// 更新控制器
this.controls.update()
},
createControls () {
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
},
onWindowResze () {
console.log('====================================')
console.log('获取屏幕尺寸')
console.log('====================================')
this.camera.aspect = this.aspect
/* 重新计算相机对象的投影矩阵 */
this.camera.updateProjectionMatrix()
}
}
}
</script>
<style lang="scss" scoped>
#d3Container {
width: 90vw;
height: 90vh;
z-index: 888;
border: 1px black solid;
}
</style>
<template>
<div ref="container" class="container"></div>
</template>
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
export default {
data () {
return {
scene: null,
camera: null,
renderer: null
}
},
methods: {
init () {
this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(
90,
document.body.clientWidth / document.body.clientHeight,
0.1,
100
)
this.camera.position.set(0, 0, 3)
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(
document.body.clientWidth,
document.body.clientHeight
)
this.$refs.container.appendChild(this.renderer.domElement)
var controls = new OrbitControls(this.camera, this.renderer.domElement)
// 创建立方体 BoxGeometry(width, height, depth)长 宽 深度
const geometry = new THREE.BoxGeometry(1, 1, 1)
// MeshBasicMaterial 一种非常简单的材质,设置材质的颜色
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
this.scene.add(cube)
this.loop()
},
loop () {
requestAnimationFrame(this.loop)
this.renderer.render(this.scene, this.camera)
}
},
mounted () {
this.init()
}
}
</script>
<style lang="scss" scoped>
.container {
width: 100vw;
height: 100vh;
}
</style>
用例
<template>
<view class="login-container">
<view id="login-three-container" ref="container"></view>
</view>
</template>
<script>
import * as THREE from 'three'
import {GUI} from "three/examples/jsm/libs/dat.gui.module"
import _ from 'lodash'
import Stats from 'three/examples/jsm/libs/stats.module'
export default {
data () {
return {
/* 容器 */
container: null,
/* 声明视口的宽度 */
width: null,
/* 声明视口的高度 */
height: null,
// 盒模型的深度
depth: 1400,
/* 场景 */
scene: null,
renderer: null,
/* 球组 */
sphere_Group: null,
/* 声明性能监控 */
stats: new Stats(),
/* 声明云流动的渲染函数1 */
renderCloudMove_first: null,
/* 声明云流动的渲染函数2 */
renderCloudMove_second: null,
/* 声明流动的云对象1(包含路径,云实例) */
cloudParameter_first: null,
/* 声明流动的云对象2 (包含路径,云实例) */
cloudParameter_second: null,
/* 声明相机在z轴的位置 */
zAxisNumber: null,
/* 声明相机 */
camera: null,
/* 声明相机目标点 THREE.Vector3三维向量 分别是x,y,z值*/
cameraTarget: new THREE.Vector3(0, 0, 0),
/* 声明球体几何 */
sphereGeometry: null,
/* 声明完整球 */
sphere: null,
/* 声明点的参数 */
parameters: null,
material: [],
/* 声明点在z轴上移动的进度 */
zprogress: null,
/* 声明同上(第二个几何点) */
zprogress_second: null,
/* 盒模型的深度 */
depth: 1400,
/* 声明粒子1的初始位置 */
particles_init_position: null,
/* 声明粒子1 */
particles_first: [],
/* 声明粒子2 */
particles_second: [],
/* 声明调试对象 */
gui:new GUI()
}
},
mounted () {
this.container = document.getElementById('login-three-container')
this.width = this.container.clientWidth
this.height = this.container.clientHeight
this.initScene()
this.initSceneBg()
this.initCamera()
this.initLight()
this.initSphereModal()
this.initSphereGroup()
this.particles_init_position = -this.zAxisNumber - depth/2
this.zprogress = this.particles_init_position
this.zprogress_second = this.particles_init_position*2
this.particles_first = this.initSceneStar(this.particles_init_position)
this.particles_second = this.initSceneStar(this.zprogress_second)
this.cloudParameter_first = this.initTubeRoute([
new THREE.Vector3(-this.width/10,0,-this.depth/2),
new THREE.Vector3(-this.width/4,this.height/8,0),
new THREE.Vector3(-this.width/4,0,this.zAxisNumber)
],400,200)
this.cloudParameter_second = this.initTubeRoute(
[
new THREE.Vector3(this.width / 8, this.height / 8, -this.depth / 2),
new THREE.Vector3(this.width / 8, this.height / 8, this.zAxisNumber)
],
200,
100
)
this.initRenderer()
this.initOrbitControls() //控制器必须放在renderer函数后面
this.animate()
},
Params(){
this.color = "#000"
this.length =10
this.size = 3
this.visible = true
this.x = 0
this.y = 0
this.z = 100
this.widthSegments = 64
this.heightSegments = 32
this.radius = 16
},
initGUI(){
const params = new this.Params()
this.gui.add(params,'x',-1500,1500).onChange(x=>{
/* 点击颜色面板,e为返回的10进制颜色 */
this.Sphere_Group.position.x = x
})
this.gui.add(params,'y',-50,1500).onChange(y=>{
this.Sphere_Group.position.y = y
})
this.gui.add(params,'z',-200,1000).onChange(z=>{
this.Sphere_Group.position.z = z
})
this.gui.add(params,'widthSegments',0,64).onChange(widthSegments=>{
this.sphereGeometry.parameters.widthSegments = widthSegments
})
this.gui.add(params,'heightSegments',0,32).onChange(heightSegments=>{
this.sphereGeometry.parameters.heightSegments = heightSegments
})
this.gui.add(params,'radius',5,30).onChange(radius=>{
this.sphereGeometry.parameters.radius = radius
this.renderer.render(this.scene,this.camera)
})
},
/* 初始化相机 */
initCamera () {
/*
方式1: 固定视野的距离,求满足完整的视野画面需要多大的视域角度
tan 正切值(直角边除以临边)
const mathTan_value = width/2/depth
视域角度 Math.atan返回的是改角的弧度值
弧度等于角度度乘于PI再除于180
角度等于弧度乘于180再除于PI
const fov_angle = (Math.atan(mathTan_value)*180)/Math.PI
创建透视相机
new THREE.PerspectiveCamera(for_angle,width/height,1,depth) //角度 长宽比 近端面 远端面
场景是一个矩形容器(坐标(0,0,0)是矩形容器的中心),相机能看到的距离是depth
camera.position.set(0,0,depth/2)
使用透视相机
参数值分别是:
fov(filed of view) - 摄像机视椎体垂直视野角度
aspect - 摄像机视椎体长宽比
near - 摄像机视锥体近端面
far --摄像机视椎体远端面
这里需要注意:透视相机鱼眼效果,如果视域越大,边缘变形越大
为了避免变形,可以将fov角度设置小一些,距离拉远一些
固定视域角度,求需要多少距离才能满足完整的视野画面
15度等于 (Math.PI / 12)
*/
const fov = 15
const distance = this.width / 2 / Math.tan(Math.PI / 12)
this.zAxisNumber = Math.floor(distance - this.depth / 2)
this.camera = new THREE.PerspectiveCamera(
fov,
this.width / this.height,
1,
30000
)
// 躺着看
this.camera.position.set(0, 0, this.zAxisNumber)
// camera.lookAt坐标中心 侧着看
this.camera.lookAt(this.cameraTarget)
},
// 初始化球体模型
initSphereModal () {
/* 创建一个光亮表面的材质效*/
let material = new THREE.MeshPhongMaterial()
/* 将纹理赋值给材质 */
material.map = new THREE.TextureLoader().load(
require('./images/earth_bg.png')
)
/* 设置材质透明度 */
meterial.blendDstAlpha = 1
// 几何体 球体 (半径 经度切片数 纬度切片数)
this.sphereGeometry = new THREE.SphereGeometry(50, 64, 32)
// 模型
this.sphere = new THREE.Mesh(this.sphereGeometry, material)
},
initSphereGroup () {
/*
THREE.Group声明组容器
可以将一系列的mesh模型组合
*/
this.Sphere_Group = new THREE.Group()
this.Sphere_Group.add(this.sphere)
this.Sphere_Group.position.x = -400
this.Sphere_Group.position.y = 200
this.Sphere_Group.position.z = -200
this.scene.add(this.Sphere_Group)
},
/* 光源 */
initLight () {
/* 添加光源 AmbientLight光源的颜色将会影响到所有物体的每一面,没有特别来源,没有阴影 */
const ambientLight = new THREE.AmbientLight(0xffffff, 1)
// 右下角点光源 (PointLight创建点光源,类似照明弹,迎面亮一点,背面暗一些,没有阴影)
const light_rightBottom = new THREE.PointLight(0x0655fd, 5, 0)
/* 设置发光位置 */
light_rightBottom.position.set(0, 100, -200)
this.scene.add(light_rightBottom)
this.scene.add(ambientLight)
},
// 渲染器
initRenderer () {
// 开启抗锯齿
// 在css中设置背景色透明显示渐变色
this.renderer = new THREE.WebGLRenderer({
antialias: true, //抗锯齿
alpha: true //alpha透明系数
})
// 定义渲染器的尺寸,在这里它会填满整个屏幕
this.renderer.setSize(this.width, this.height)
this.renderer.shadowMap.enabled = true
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
this.container.appendChild(renderer.domElement)
this.container.appendChild(this.stats.dom)
},
/* 初始化场景 */
initScene () {
this.scene = new THREE.Scene()
/* 场景中添加雾的效果,Fog参数分别代表雾的颜色,开始雾化的视线距离,刚好雾化至看不见的视线距离 */
this.scene.fog = new THREE.Fog(0x000000, 0, 10000)
},
initRenderer () {},
initSceneBg () {
/* 纹理加载器 加载纹理 */
new THREE.TextureLoader().loader(require('./images/sky.png'), texture => {
/* 创建立方体 */
const geometry = new THREE.BoxGeometry(
this.width,
this.height,
this.depth
)
/* map:为材质设置纹理贴图 side:设定在几何体的哪个面应用材质 THREE.BackSide:内面*/
const material = new THREE.MeshBasicMaterial({
map: texture,
side: THREE.BackSide
}) //创建基础为网格基础材料
const mesh = new THREE.Mesh(geometry, material)
this.scene.add(mesh)
})
},
// 初始化轨道控制器
initOrbitControls () {
const controls = new initOrbitControls(
this.camera,
this.renderer.domElement
)
// enabled设置为true是可以使用鼠标控制视角
controls.enabled = false
controls.update()
},
/* 初始化流动路径 */
initTubeRoute (route, geometryWidth, geometryHeight) {
// CatmullRomCurve3函数用来创建曲线 false:是否闭合
const curve = new THREE.CatmullRomCurve3(route, false)
/* 创建管道几何体
path:样条曲线
segments:管道的分段数
radius:管道半径
radiusSegments:管道截面圆的分段数
closed:是否收尾连接
*/
const tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 50, false)
// 创建一个简单的材质
const tubeMaterial = new THREE.MeshBasicMaterial({
opacity: 0,
transparent: true
})
const tube = new THREE.Mesh(tubeGeometry, tubeMaterial)
this.scene.add(tube)
/* 创建一个矩形 */
const clondGeometry = new THREE.PlaneGeometry(
this.geometryWidth,
this.geometryHeight
)
/* 纹理加载 */
const textureLoader = new THREE.TextureLoader()
const cloudTexture = textureLoader.load(require('./images/cloud.png'))
// https://juejin.cn/post/6956852013758054436
const clondMaterial = new THREE.MeshBasicMaterial({
map: cloudTexture, //添加纹理贴图
/* THREE.AdditiveBlending:饱和度叠加渲染 */
blending: THREE.AdditiveBlending, //blending:物体的材质如何与背景融合
depthTest: false, //是否使用像素深度
transparent: true //开启透明度
})
const cloud = new THREE.Mesh(clondGeometry, clondMaterial)
this.scene.add(cloud)
return {
clound,
curve
}
},
/* 初始化场景星星效果 */
initSceneStar (initZposition) {
/* 创建一个geometry实例 控制物体的几何形状*/
const geometry = new THREE.BufferGeometry()
const vertices = []
const pointsGeometry = []
/* 纹理加载*/
const textureLoader = new THREE.TextureLoader()
const sprite1 = textureLoader.load(require('./images/starflake1.png'))
const sprite2 = textureLoader.load(require('./images/starflake2.png'))
this.parameters = [
[[0.6, 100, 0.75], sprite1, 50],
[[0, 0, 1], sprite2, 20]
]
// 初始化500个节点
for (let i = 0; i < 500; i++) {
/*
const x = Math.random()*2*width-width
等价
THREE.MathUtils.randFloatSpread(width)
在(-width/2,width/2)之间的一个随机浮点数
*/
const x = THREE.MathUtils.randFloatSpread(width)
const y = _.radom(0, height / 2)
const z = _.radom(-this.depth / 2, this.zAxisNumber)
vertices.push(x, y, z)
}
/*给当前几何体设置属性
BufferGeometry 缓冲区类型几何体
BufferAttribute用于存储与BufferGeometry相关联的attribute(顶点位置向量,面片索引,法向量,UV坐标,以及任何自定义的attribute)
*/
geometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3)
)
/* 创建2种不同材质的节点 500*2 */
for (let i = 0; i < this.parameters.length; i++) {
const color = this.parameters[i][0]
const sprite = this.parameters[i][1]
const size = this.parameters[i][2]
/*
PointsMaterial点材质,点模型的时候需要
*/
this.materials[i] = new THREE.PointsMaterial({
size,
map: sprite, //添加纹理贴图
blending: THREE.AdditiveBlending, //物体的颜色与背景叠加
depthTest: true, //是否使用像素深度
transparent: true //是否透明
})
/*
setHSL(色相,饱和度,亮度)
*/
this.materials[i].color.setHSL(color[0], color[1], color[2])
/* 创建粒子系统对象 */
const particles = new THREE.Points(geometry, materials[i])
// 旋转
particles.rotation.x = Math.random() * 0.2 - 0.15
particles.rotation.y = Math.radom() * 0.2 - 0.15
particles.rotation.z = Math.radom() * 0.2 - 0.15
particles.position.setZ(initZposition)
pointsGeometry.push(particles)
this.scene.add(particles)
}
return pointsGeometry
},
/* 旋转星球的自转 */
renderSphereRotate () {
if (this.sphere) {
this.Sphere_Group.rotateY(0.001)
}
},
/* 渲染星星的运动 */
renderStarMove () {
const time = Date.now() * 0.00005
this.zprogress += 1
this.zprogress_second += 1
if (this.zprogress >= this.zAxisNumber + this.depth / 2) {
this.zprogress = this.particles_init_position
} else {
this.particles_first.forEach(item => {
item.position.setZ(this.zprogress)
})
}
if (this.zprogress_second >= this.zAxisNumber + this.depth / 2) {
this.zprogress_second = this.particles_init_position
} else {
this.particles_second.forEach(item => {
item.position.setZ(this.zprogress_second)
})
}
for (let i = 0; i < this.materials.length; i++) {
const color = this.parameters[i][0]
const h = ((360 * (color[0] + time)) % 360) / 360
this.meterial[i].color.setHSL(
color[0],
color[1],
parseFloat(h.toFixed(2))
)
}
},
initCloudMove (cloudParameter, speed, scaleSpeed, maxScale, startScale) {
let cloudProgress = 0
return () => {
if (startScale < maxScale) {
startScale += scaleSpeed
cloudParameter.cloud.scale.setScalar(startScale)
}
if (cloudProgress > 1) {
cloudProgress = 0
startScale = 0
} else {
cloudProgress += speed
if(cloudParameter.curve){
const point = cloudParameter.curve.getPoint(cloudProgress)
if(point&&point.x){
cloudParameter.cloud.position.set(point.x,point.y,point.z)
}
}
}
}
},
/* 动画刷新 */
animate(){
requestAnimationFrame(this.animate)
this.renderSphereRotate()
this.renderStarMove()
this.renderCloudMove_first()
this.renderCloudMove_second()
renderer.render(this.scene,this.camera)
},
}
</script>
<style lang="scss" scoped>
.login-container {
width: 100%;
height: 100vh;
position: relative;
#login-three-container {
width: 100%;
height: 100%;
}
}
</style>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。