基础入门
# 前言
当今3D变得越来越流行,并且在视频游戏以外的领域也广泛使用,其中之一就是网站。
近几年来3D网页已经成为一种趋势,这主要归功于现代浏览器和移动设备都能支持3D。
如果你是希望制作含有3D的网页或app的前端开发人员,有些概念需要简单了解一下。
OpenGL
OpenGL是一个应用程序编程接口(api),其作用是能够使应用程序与显卡交互,并在显示屏上渲染东西。WebGL OpenGL以某种方式编写的应用程序连接着图形和显卡,但web浏览器并不能运行OpenGL。2011年3月,WebGL首次发布,它是轻量级的OpenGL,也被称作OpenGL ES,可以在浏览器上运行。
webgl只是一些用来连接浏览器和显卡的JavaScript函数,这些函数可以在网页上显示2d和3d的内容。Shader
webgl是浏览器和显卡之间的媒介,那么shader就是我们给显卡发送的指令,它们本质上是被嵌入应用程序代码的一些小的程序。
有两种shader: 顶点着色器vertex shader 和 片元着色器fagment shader
顶点着色器的作用是简单的指定一个或多个组成网格的点的位置。
片元着色器负责每个像素点的颜色
- 3D库
threejs,babylonjs,以及其他库都充当着实际的程序和webgl api之间的中间层,大大的简化了代码复杂度。
这些库的api不重要,重要的是要知道api是怎样在幕后运行的
# 创建项目
新建文件夹,在terminal中输入 npm create vite@latest
给project命名
使用vanilla js
使用JavaScript
打开刚刚新建的项目文件夹,npm install, npm run dev启动服务器
安装babylonjs npm install -D @babylonjs/core
删除掉不需要的文件并清空其他文件,src下只保留空的main.js和style.css,再次运行服务
# babylonjs必须的4个基本元素
- 画布,是一个HTML容器
- Engine,它是babylonjs的应用程序大脑,将用户输入的逻辑转换为生动的3D图形
- Scene,场景是呈现3D的空间
- Camera,相机的作用是在场景中显示某个空间
# 初次实践
首先添加canvas元素
在main.js文件中,导入babylonjs,并且给canvas分配一个变量
import * as BABYLON from '@babylonjs/core'
const canvas = document.getElementById('renderCanvas')
2
3
- 接下来创建Babylonjs Engine的示例,并将画布传递过去
const engine = new BABYLON.Engine(canvas)
- 创建createScene函数,这个函数将要实现99%的功能,在这个函数中,我们会返回一个场景实例,然后调用这个函数给实例分配一个变量
const createScene = function() {
const scene = new BABYLON.Scene(engine)
return scene
}
const scene = createScene()
2
3
4
5
6
7
- 然后运行渲染循环
engine.runRenderLoop(function() {
scene.render()
})
2
3
- 让场景填充整个屏幕
html, body {
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
#canvasRender {
width: 100%;
height: 100%;
touch-action: none;
}
2
3
4
5
6
7
8
9
10
11
12
13
- 快速添加一个摄像头和一个盒子
const createScene = function() {
const scene = new BABYLON.Scene(engine)
scene.createDefaultCameraOrLight(true, false, true)
const box = new BABYLON.MeshBuilder.CreateBox()
return scene
}
2
3
4
5
6
7
8
现在可以再场景中得到一个大盒子,但存在一个问题,如果我调整了窗口的大小,整个场景中的盒子形状就会变形
- 监听window的resize事件,在回调函数中调用engine的resize方法
window.addEventListener('resize',function() {
engine.resize()
})
2
3
现在window的大小发生变化,盒子的形状不会变了
场景中的对象通常叫做 Mesh,它可以是一个盒子,一个球体,一个复杂的形状,甚至更复杂的比如使用blender之类的软件创建的模型
Babylonjs提供了一组内置网格
我们可以通过向构造函数中传入参数来改变网格,这个参数在文档 (opens new window)中自己查看
const ground = new BABYLON.MeshBuilder.CreateGround('', {
height: 10,
width: 10,
subdivisions: 30
})
ground.material = new BABYLON.StandardMaterial()
ground.material.wireframe = true
2
3
4
5
6
7
8
这段代码可以创建一个分了30段的地面,并切用线框模式渲染
高度图是一张灰度图,越白的地方越高,越黑的地方越低,现在我们用高度图创建一个地形
const groundFromHM = new BABYLON.MeshBuilder.CreateGroundFromHeightMap('','/Terrain_heightmap.png',{
height:10,
width: 10,
subdivisions: 50
})
groundFromHM.material = new BABYLON.StandardMaterial()
groundFromHM.material.wireframe = true
2
3
4
5
6
7
8
# 添加文本
创建的文本原点在文本中心。
添加文本需要使用ExtrudePolygon功能,该功能需要有Earcut库。虽然Earcut脚本在Playground中已经预加载,但是在自己的项目中需要添加对该脚本的引用。
首先安装earcut
npm install -S earcut
然后在main.js中引用
import earcut from 'earcut'
window.earcut = earcut
2
3
还需要在facetype.js (opens new window)上将字体文件转换为json
const fontData = await (await fetch("/FZZongYi-M05S_Regular.json")).json();
const text = BABYLON.MeshBuilder.CreateText(
"myText",
"hello",
fontData,
{
size: 16,
resolution: 64,
depth: 1,
},
scene
);
2
3
4
5
6
7
8
9
10
11
12
属性 | 值 | 默认值 |
---|---|---|
size | 每个字母的大小 | 50 |
rosolution | 每根曲线使用的点数 | 8 |
depth | 沿Y轴挤出的深度 | 1.0 |
sideOrientation | 面方向 | DOUBLESIDE |
# 相机
babylonjs有多种相机,但是作为初学者一般只会用两种相机:
- universal camera
通用相机,通常用于第一人称射击游戏,可以使用鼠标选择方向,然后使用方向键移动 - arc rotate camrea
圆弧旋转相机,由旋转角度,目标和半径定义
添加默认灯光,然后添加Universal Camera,它有三个参数,第一个是名字,第二个是初始位置,第三个是scene, 然后允许用户控制
scene.createDefaultLight()
const camera = new BABYLON.UniversalCamera('camera', new BABYLON.Vector3(0, 5, -10), scene)
camera.attachControl(true)
2
3
可以看到现在我们可以通过箭头来控制前进后退,我们可以用鼠标做到相同的事,只需加上一行代码
camera.inputs.addMouseWheel()
我们也可以给相机一个初始的观察对象
camera.setTarget(BABYLON.Vector3.Zero())
接下来我们创建arc rotate camera,构造函数有6个参数,name,α,β ,radius,target,scene
α是绕y轴的旋转,β是绕z轴的旋转
const camera = new BABYLON.ArcRotateCamera('camera',0,0,10,new BABYLON.Vector3(0,0,0),scene)
camera.attachControl(true)
// 设置相机初始位置
camera.setPosition(new BABYLON.Vector3(0,0,-20))
// 限制β旋转
camera.lowerBetaLimit = Math.PI / 4
camera.upperBetaLimit = Math.PI / 2
// 限制半径
camera.lowerRadiusLimit = 20
camera.upperRadiusLimit = 50
2
3
4
5
6
7
8
9
10
# Mesh
一个Mesh由两部分组成,一组表示其形状的点和线,还有覆盖这些线和点的材质
让我们来改变一个小球的颜色
const sphere = new BABYLON.MeshBuilder.CreateSphere('mySphere', {
segements: 50,
diameter : 0.3,
diameterY: 0.4
}, scene)
const sphereMaterial = new BABYLON.StandardMaterial()
sphere.material = sphereMaterial
// 改变小球颜色
sphereMaterial.diffuseColor = new BABYLON.Color3(0, 1, 0)
// 改变高光颜色
sphereMaterial.specularColor = new BABYLON.Color3(1, 0, 0)
// 自发光颜色
sphereMaterial.emissiveColor = new BABYLON.Color3(0, 0, 1)
// 环境光颜色,需要设置scene的环境光
sphereMaterial.ambientColor = new BABYLON.Color3(0, 1, 1)
scene.ambientColor = new BABYLON.Color3(0, 1, 0.5)
// 透明度
sphereMaterial.alpha = 0.2
// 线框模式
sphereMaterial.wireframe = true
// 漫反射贴图
sphereMaterial.diffuseTexture = new BABYLON.Texture('/wood_texture.jpg')
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
diffuseColor和specularColor都会受光线影响,如果完全没有光线就是黑色,有光线就遵循光的三原色的叠加
接下来给地板贴图
const ground = new BABYLON.MeshBuilder.CreateGround('',{
height: 5,
width:5,
subdivisions:5,
subdivisionsX: 10
})
const groundCatMat = new BABYLON.StandardMaterial()
ground.material = groundCatMat
groundCatMat.diffuseTexture = new BABYLON.Texture('/bg.jpg')
// 水平位移
groundCatMat.diffuseTexture.uOffset = 1.4
// 垂直位移
groundCatMat.diffuseTexture.vOffset = 1.4
// 水平缩放
groundCatMat.diffuseTexture.uScale = 5
// 垂直缩放
groundCatMat.diffuseTexture.vScale = 5
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 改变位置
box.position.x = 1;
box.position = new BABYLON.Vector3(-1, 0.5, 0)
// 改变旋转
box.rotation = Math.PI / 4
box.rotation = new BABYLON.Vector3(0, 0, Math.PI / 6)
// 改变缩放
box.scaling.y = 2
box.scaling = new BABYLON.Vector3(2, 0.5, 1)
2
3
4
5
6
7
8
9
10
11
# 调试Gizmo
Gizmo是可以附加到节点的对象
Gizmo由UtilityLayerRenderer显示,不会破坏现有的场景状态,工具层与场景独立
const utilLayer = new BABYLON.UtilityLayerRenderer(scene)
const positionGizmo = new BABYLON.PositionGizmo(utilLayer)
positionGizmo.attachedMesh = ground
const rotationGizmo = new BABYLON.RotationGizmo(utilLayer)
rotationGizmo.attachedMesh = ground
const scaleGizmo = new BABYLON.ScaleGizmo(utilLayer)
scaleGizmo.attachedMesh = ground
2
3
4
5
6
7
8
9
10
# 动画
在每一帧之前都执行,下面的代码相当于每秒将box旋转0.01弧度60次
scene.registerBeforeRender(funciton() {
box.rotation.x += 0.01
})
2
3
还有另一种动画的方式
BABYLON.Animation.CreateAndStartAnimation(
'xScaleAnimation', // 名字
ground, // 动画对象
'scaling.x', // 动画属性
30, // 每秒多少帧
120, // 总共多少帧
0, // 起始值
2, // 结束值
BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT, // 循环方式
new BABYLON.CircleEase // 缓动函数
)
2
3
4
5
6
7
8
9
10
11
下面是更好控制动画的一种方案
const animation = new BABYLON.Animation(
'yRatAnimation',
'rotation.y',
30,
BABYLON.Animation.ANIMATIONTYPE_FLOAT,
BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE
)
const animationKeys = []
animationKeys.push({
frame: 0,
value: 0
})
animationKeys.push({
frame: 60,
value: 2 * Math.PI
})
animation.setKeys(animationKeys)
ground.animations = []
ground.animations.push(animation)
scene.beginAnimation(ground, 0, 60, true)
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
# Light
- PointLight 点光
const light = new BABYLON.PointLight(
'pointLight',
new BABYLON.Vector3(0, 1, 0),
scene
)
2
3
4
5
Babylonjs为光源提供了一个小Gizmo,可以帮助直观地显示它们的位置
const utilLayer = new BABYLON.UtilityLayerRenderer(scene)
const lightGizmo = new BABYLON.LightGizmo(utilLayer)
lightGizmo.light = light
2
3
- SpotLight, 聚光灯
const light = new BABYLON.SpotLight(
'pointLight', // 名字
new BABYLON.Vector3(0, 1, 0), // 位置
new BABYLON.Vector3(0, -1, 0), // 方向
Math.PI / 3, // 角度
2, // 衰减
scene // 场景
)
2
3
4
5
6
7
8
- directionalLight 平行光
const light = new BABYLON.DirectionalLight(
'directionalLight',
new BABYLON.Vector3(-2, -3, 0),
scene
)
// 改变光强度
light.intensity = 0.5
2
3
4
5
6
7
- hemispheric light 半球光
const light = new BABYLON.HemisphereicLight(
'hemisohericLight',
new BABYLON.Vector3(-5, -5, 0),
scene
)
light.groundColor = new BABYLON.Color3(0, 1, 0)
light.diffuse = new BABYLON.Color3(0, 0, 1)
light.specular = new BABYLON.Color3(0, 1, 0)
2
3
4
5
6
7
8
- 投影
const ground = new BABYLON.MeshBuilder.CreateGround('',{
height: 5,
width:5,
subdivisions:5,
subdivisionsX: 10
})
const sphere = new BABYLON.MeshBuilder.CreateSphere('mySphere', {
segments: 50,
diameter: 0.3
})
sphere.position = new BABYLON.Vector3(1, 1.5, 0)
const light = new BABYLON.DirectionalLight(
'directionalLight',
new BABYLON.Vector3(-2, -3, 0),
scene
)
const shadowGenerator = new BABYLON.ShadowGenerator(1024, light)
shadowGenerator.addShadowCaster(sphere)
ground.receiveShadows = true
shadowGenerator.setDarkness(0.5)
shadowGenerator.useBlurCloseExponentialShadowMap = true
shadowGenerator.useKernelBlur = true
shadowGenerator.blurKernel = 64
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
# Fog
- 无雾
- 线性模式
scene.fogMode = BABYLON.Scene.FOGMODE_LINEAR
scene.fogStart = 10
scene.fogEnd = 60
2
3
- 指数模式
scene.fogMode = BABYLON.Scene.FOGMODE_EXP2
scene.fogDensity = 0.2 // 雾的浓度
scene.fogColor = new BABYLON.Color3(0, 1, 1)
2
3
# 光标选择
scene.onPointerDown = function castRay() {
const hit = scene.pick(scene.pointerX, scene.pointerY)
if(hit.pickedMesh && hit.pickedMesh.name == 'mySphere') {
hit.pickedMesh.material = new BABYLON.StandardMaterial()
hit.pickedMesh.material.diffuseColor = BABYLON.Color3.Red()
}
}
2
3
4
5
6
7
8
# 加载模型
babylonjs可以加载不同格式的模型
- .babylon
- .obj
- .glb
首先安装依赖
npm install -D @babylonjs/loaders
然后导入刚刚安装的模块
import '@babylon/loaders/glTF'
BABYLON.SceneLoader.ImportMesh(
'',
'/',
'Cow.gltf',
scene,
function(meshes, particalSystems, skeletons, animationGroups) {
const model = meshes[0]
model.scaling = new BABYLON.Vector3(0.25, 0.25, 0.25)
animationGroups[5].play(true)
}
)
BABYLON.ImportMeshAsync("/ground1.glb", scene).then(() => {
const importedAnimGroups = result.animationGroups
importedAnimGroups[0].play(true)
}, (e) => {
console.log("oh no! something is off", e);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 播放音乐
const bgMusic = new BABYLON.Sound('mySound', '/DivKid.mp3', scene, null, {
loop:true,
autoplay: true
})
2
3
4
# imspector检查器
这是babylonjs中最好的功能之一,首先我们需要安装检查器模块,
npm install -D @babylonjs/inspector
然后引入Inspector
import {Inspector} from '@babylonjs/inspector'
然后在代码的最末尾加上
Inspector.Show(scene, {})
babylon的魅力不仅仅在于代码本身,还有它提供的工具,比如说Playground,SandBox