3d字体
const scene = new THREE.Scene()
const textureLoader = new THREE.TextureLoader()
const matcapTexture = textureLoader.load('textures/matcaps/8.png')
// 字体加载器
const fontLoader = new FontLoader()
fontLoader.load(
// 字体文件转json:https://gero3.github.io/facetype.js/
'/fonts/helvetiker_regular.typeface.json',
(font) =>
{
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture })
// 文本
// 通过减少 curveSegments 和 bevelSegments 属性来保持几何体尽可能低的多边形
const textGeometry = new TextGeometry(
'Hello Three.js',
{
font: font,
size: 0.5,
height: 0.2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
bevelOffset: 0,
bevelSegments: 5
}
)
// 文本几何体移到坐标原点
textGeometry.computeBoundingBox() // 计算几何体的最大最小坐标值
textGeometry.translate(
- (textGeometry.boundingBox.max.x - 0.02) * 0.5, // 减掉 bevelSize
- (textGeometry.boundingBox.max.y - 0.02) * 0.5, // 减掉 bevelSize
- (textGeometry.boundingBox.max.z - 0.03) * 0.5 // 减掉 bevelThickness
)
// 上面移动到原点的代码等同于:
// textGeometry.center()
const text = new THREE.Mesh(textGeometry, material)
scene.add(text)
// 甜甜圈
const donutGeometry = new THREE.TorusGeometry(0.3, 0.2, 32, 64)
for(let i = 0; i < 100; i++)
{
const donut = new THREE.Mesh(donutGeometry, material)
donut.position.x = (Math.random() - 0.5) * 10
donut.position.y = (Math.random() - 0.5) * 10
donut.position.z = (Math.random() - 0.5) * 10
donut.rotation.x = Math.random() * Math.PI
donut.rotation.y = Math.random() * Math.PI
const scale = Math.random()
donut.scale.set(scale, scale, scale)
scene.add(donut)
}
}
)
灯光
// 环境光
const ambientLight = new THREE.AmbientLight()
ambientLight.color = new THREE.Color(0xffffff)
ambientLight.intensity = 0.5
scene.add(ambientLight)
// 平行光 DirectionalLight( color : Color, intensity : Float )
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.3)
directionalLight.position.set(1, 0.25, 0)
scene.add(directionalLight)
// 半球光 光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。半球光不能投射阴影。
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.3)
scene.add(hemisphereLight)
// 点光源
const pointLight = new THREE.PointLight(0xff9000, 0.5, 10, 2)
pointLight.position.set(1, - 0.5, 1)
scene.add(pointLight)
// 平面光光源 光源从一个矩形平面上均匀地发射光线。这种光源可以用来模拟像明亮的窗户或者条状灯光光源。
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 2, 1, 1)
rectAreaLight.position.set(- 1.5, 0, 1.5)
rectAreaLight.lookAt(new THREE.Vector3())
scene.add(rectAreaLight)
// 聚光灯(SpotLight) 光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。
const spotLight = new THREE.SpotLight(0x78ff00, 0.5, 10, Math.PI * 0.1, 0.25, 1)
spotLight.position.set(0, 2, 3)
scene.add(spotLight)
spotLight.target.position.x = - 0.75
scene.add(spotLight.target)
// 光源辅助器可以帮助定位和定向光源 辅助器的第二参数是控制其大小
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.2)
scene.add(hemisphereLightHelper)
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.2)
scene.add(directionalLightHelper)
const pointLightHelper = new THREE.PointLightHelper(pointLight, 0.2)
scene.add(pointLightHelper)
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
scene.add(spotLightHelper)
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)
阴影 shadow
// 阴影的显示,首先需要开启阴影功能
renderer.shadowMap.enabled = true
// ...
// 几何体需要开启蒙上阴影
sphere.castShadow = true
// ...
// 地面需要开启接收阴影
plane.receiveShadow = true
/** 添加直射光 **/
// 灯光要开启蒙上阴影,来存储shadowMap
directionalLight.castShadow = true
// 灯光的shadowMap
console.log(directionalLight.shadow)
// shadowMap的默认大小是512*512,修改其贴图尺寸可以让阴影更清晰
directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024
// Three.js使用相机来进行阴影贴图渲染。这些相机与我们已经使用的相机具有相同的属性。
// 这意味着我们必须定义近和远。它不会真正提高阴影的质量,但它可能会修复看不到阴影或阴影突然被裁剪的错误。
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
scene.add(directionalLightCameraHelper)
// 设置阴影贴图的相机的near和far属性来保证,场景内物体的阴影都被计算
directionalLight.shadow.camera.near = 1
directionalLight.shadow.camera.far = 6
// 控制相机每侧可以看到的距离,值越小,阴影越精确。但如果它太小,阴影就会被裁剪掉
directionalLight.shadow.camera.top = 2
directionalLight.shadow.camera.right = 2
directionalLight.shadow.camera.bottom = - 2
directionalLight.shadow.camera.left = - 2
// radius 属性控制阴影模糊
directionalLight.shadow.radius = 10
// THREE.BasicShadowMap 性能很好,但质量很差
// THREE.PCFShadowMap 性能较差,但边缘更光滑
// THREE.PCFSoftShadowMap 性能较差,但边缘更柔软
// THREE.VSMShadowMap 更低的性能,更多的约束,可能会产生意想不到的结果
renderer.shadowMap.type = THREE.PCFSoftShadowMap
/** 添加聚光灯 **/
const spotLight = new THREE.SpotLight(0xffffff, 0.4, 10, Math.PI * 0.3)
spotLight.castShadow = true
spotLight.position.set(0, 2, 2)
scene.add(spotLight)
scene.add(spotLight.target)
const spotLightCameraHelper = new THREE.CameraHelper(spotLight.shadow.camera)
scene.add(spotLightCameraHelper)
// 降低环境光和直射光强度
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.4)
// 更改贴图尺寸,保证质量
spotLight.shadow.mapSize.width = 1024
spotLight.shadow.mapSize.height = 1024
// field of view 视场角度 和 远近
spotLight.shadow.camera.fov = 30
spotLight.shadow.camera.near = 1
spotLight.shadow.camera.far = 6
// 隐藏灯光helper
spotLightCameraHelper.visible = false
/** 增加点光源 **/
const pointLight = new THREE.PointLight(0xffffff, 0.3)
pointLight.castShadow = true
pointLight.position.set(- 1, 1, 0)
scene.add(pointLight)
// ...
使用blender等三维软件烘焙生成的阴影贴图
// 关闭光源
directionalLight.castShadow = false
spotLight.castShadow = false
pointLight.castShadow = false
renderer.shadowMap.enabled = false
// 加载一张烘焙的贴图,/static/textures/bakedShadow.jpg
const textureLoader = new THREE.TextureLoader()
const bakedShadow = textureLoader.load('/textures/bakedShadow.jpg')
// 当然,如果球体或灯光移动,阴影并不会随着移动
const plane = new THREE.Mesh(
new THREE.PlaneGeometry(5, 5),
new THREE.MeshBasicMaterial({
map: bakedShadow
})
)
粒子 particle
粒子的一个简单示例
const particlesGeometry = new THREE.BufferGeometry()
const particlesMaterial = new THREE.PointsMaterial({
color: '#ff88cc', // 控制粒子颜色
size: 0.02, // 控制粒子尺寸
sizeAttenuation: true // 指定远处粒子的尺寸是否应小于近距离粒子
})
const count = 500
const positions = new Float32Array(count * 3)
const colors = new Float32Array(count * 3)
for(let i = 0; i < count * 3; i++)
{
positions[i] = (Math.random() - 0.5) * 10
colors[i] = Math.random()
}
particlesGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
// 需要注意 PointsMaterial 的 color 属性也会影响几何所设置的颜色,若不需要受影响,需去除color属性
particlesGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
// 设置粒子纹理贴图
const textureLoader = new THREE.TextureLoader()
const particleTexture = textureLoader.load('/textures/particles/2.png')
// 使用透明激活透明度
particlesMaterial.transparent = true
// 值得注意的是,材质的color属性也会影响贴图的颜色
// particlesMaterial.map = particleTexture
// 使用 alphaMap 属性上的纹理而不是贴图
particlesMaterial.alphaMap = particleTexture
// 粒子的绘制顺序与它们创建的顺序相同,WebGL并不知道哪个粒子在另一个粒子的前面
// alphaTest是一个介于0和1之间的值,使WebGL能够根据像素的透明度知道何时不渲染该像素。
// 默认情况下,该值为0,这意味着像素无论如何都会被渲染。如果我们使用一个较小的值,例如0.001,则当alpha为0时,像素将不会被渲染
// particlesMaterial.alphaTest = 0.001
// 绘制时,WebGL会测试正在绘制的内容是否比已绘制的更接近,这称为 depthTest,设置 false,粒子不再区分前后
// articlesMaterial.depthTest = false
// WebGL会测试正在绘制的内容是否比已经绘制的更接近。所绘制内容的深度存储在我们所说的深度缓冲区中。
// 我们可以告诉WebGL不要在该深度缓冲区中写入粒子,而不是不测试粒子是否比该深度缓冲区中的粒子更接近
particlesMaterial.depthWrite = false
// 通过更改blending属性,我们可以告诉WebGL不仅要绘制像素,还要将该像素的颜色添加到已绘制的像素的颜色中
particlesMaterial.blending = THREE.AdditiveBlending
const particles = new THREE.Points(particlesGeometry, particlesMaterial)
scene.add(particles)
// 关于three的时钟
const clock = new THREE.Clock()
const tick = () =>
{
// ...
// 轨道控制的缓动需要每帧调用
controls.update()
// 获取当前运行时间
const elapsedTime = clock.getElapsedTime()
for(let i = 0; i < count; i++)
{
let i3 = i * 3
// 实现波浪的效果,让每个粒子的y根据其x坐标来进行设置,并且加入了时间参数
const x = particlesGeometry.attributes.position.array[i3]
particlesGeometry.attributes.position.array[i3 + 1] = Math.sin(elapsedTime + x)
}
particlesGeometry.attributes.position.needsUpdate = true
// 渲染画面
renderer.render(scene, camera)
// 每一帧重新调用
window.requestAnimationFrame(tick)
// ...
}
星系的产生
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as dat from 'lil-gui'
const gui = new dat.GUI()
const canvas = document.querySelector('canvas.webgl')
const scene = new THREE.Scene()
/**
* 星系参数
*/
const params = {
count: 50000, // 控制粒子数量
size: 2, // 粒子大小
radius: 5, // 星云半径
branches: 3, // 星云分支数
spin: 0.7, // 扭转幅度
randomness: 0.1, // 粒子离散程度
randomnessPower: 6, // 距离中心的离散幅度(数值越大,距离中心远的离散程度越大)
insideColor: '#ba33cc', // 中心颜色
outsideColor: '#4c6fd6', // 外边缘颜色
colorVariety: 1, // 颜色复杂度
colorOffset: 1, // 色值位移
}
let gemotry = null
let material = null
let points = null
function color(color, radius, colorVariety = 2, colorOffset = 1) {
const a = 0.5;
const b = 0.5;
const c = { r: 0.333, g: 0.667, b: 0.739 }
return a + b * Math.sin(Math.PI * colorVariety * (c[color] + radius + colorOffset))
}
const generateGalaxy = () => {
// 每次重新构建,清除之前产生的粒子
if (points) {
gemotry.dispose()
material.dispose()
scene.remove(points)
}
let positions = new Float32Array(params.count * 3);
let colors = new Float32Array(params.count * 3);
// const inside = new THREE.Color(params.initColor)
// const outside = new THREE.Color(params.finalColor)
gemotry = new THREE.BufferGeometry()
for (var i = 0; i < params.count; i++) {
var i3 = i * 3
// 使粒子均匀分布在最大长度为radius的线上
const radius = params.radius * Math.random()
// 根据分支数计算出每个分支的基础角度
var branchAngle = (i % params.branches) / params.branches * Math.PI * 2
// 粒子的旋转角度,根据与中心的距离来判断,让直线旋转起来
var spinAngle = params.spin * radius
// 粒子的离散程度,根据与中心的距离远近,越远离散程度越大
const randomX = Math.pow(Math.random(), params.randomnessPower) * (Math.random() < 0.5 ? -1 : 1) * radius * params.randomness
const randomY = Math.pow(Math.random(), params.randomnessPower) * (Math.random() < 0.5 ? -1 : 1) * radius * params.randomness
const randomZ = Math.pow(Math.random(), params.randomnessPower) * (Math.random() < 0.5 ? -1 : 1) * radius * params.randomness
positions[i3] = Math.cos(branchAngle + spinAngle) * radius + randomX
positions[i3 + 1] = randomY
positions[i3 + 2] = Math.sin(branchAngle + spinAngle) * radius + randomZ
// let mixedColor = inside.clone();
// mixedColor.lerp(outside, radius / params.radius)
// colors[i3 ] = mixedColor.r
// colors[i3 + 1] = mixedColor.g
// colors[i3 + 2] = mixedColor.b
// 生成随机颜色
colors[i3] = color('r', radius / params.radius, params.colorVariety, params.colorOffset)
colors[i3 + 1] = color('g', radius / params.radius, params.colorVariety, params.colorOffset)
colors[i3 + 2] = color('b', radius / params.radius, params.colorVariety, params.colorOffset)
}
gemotry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
gemotry.setAttribute('color', new THREE.BufferAttribute(colors, 3))
material = new THREE.PointsMaterial({
size: params.size,
sizeAttenuation: false,
depthWrite: false,
blending: THREE.AdditiveBlending,
vertexColors: true
})
points = new THREE.Points(gemotry, material)
scene.add(points)
}
gui.add(params, 'count').min(100).max(1000000).step(100).onFinishChange(generateGalaxy)
gui.add(params, 'size').min(0.001).max(0.1).step(0.001).onFinishChange(generateGalaxy)
gui.add(params, 'radius').min(0.01).max(20).step(0.01).onFinishChange(generateGalaxy)
gui.add(params, 'branches').min(2).max(20).step(1).onFinishChange(generateGalaxy)
gui.add(params, 'spin').min(- 5).max(5).step(0.001).onFinishChange(generateGalaxy)
gui.add(params, 'randomness').min(0).max(2).step(0.001).onFinishChange(generateGalaxy)
gui.add(params, 'randomnessPower').min(1).max(10).step(0.001).onFinishChange(generateGalaxy)
// gui.addColor(params, 'insideColor').onFinishChange(generateGalaxy)
// gui.addColor(params, 'outsideColor').onFinishChange(generateGalaxy)
gui.add(params, 'colorVariety').min(1).max(10).step(0.1).onFinishChange(generateGalaxy)
gui.add(params, 'colorOffset').min(1).max(10).step(0.1).onFinishChange(generateGalaxy)
generateGalaxy()
/**
* Sizes
*/
const sizes = {
width: window.innerWidth,
height: window.innerHeight
}
window.addEventListener('resize', () => {
// Update sizes
sizes.width = window.innerWidth
sizes.height = window.innerHeight
// Update camera
camera.aspect = sizes.width / sizes.height
camera.updateProjectionMatrix()
// Update renderer
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
})
/**
* Camera
*/
// Base camera
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100)
camera.position.x = 3
camera.position.y = 3
camera.position.z = 3
scene.add(camera)
// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
/**
* Renderer
*/
const renderer = new THREE.WebGLRenderer({
canvas: canvas
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
/**
* Animate
*/
const clock = new THREE.Clock()
const tick = () => {
const elapsedTime = clock.getElapsedTime()
// Update controls
controls.update()
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
tick()