在线演示(基于three:v0.119.1和vue:v3.0.0-rc.5)
- 场景(容器)
- 相机(取景)
- 渲染器(输出)
- 物件
- 形状/结构
- 材质
- 纹理
- 光源
// HelloWorld.ts
import { defineComponent, ref, h, onMounted } from 'vue'
import * as THREE from 'three'
export default defineComponent({
setup() {
const cvs = ref<HTMLCanvasElement>(null)
onMounted(() => {
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 5)
camera.position.z = 2
const geometry = new THREE.BoxBufferGeometry()
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)
const renderer = new THREE.WebGLRenderer({ canvas: cvs.value })
const animate = () => {
cube.rotation.x += 0.01
cube.rotation.y += 0.01
renderer.render(scene, camera)
}
renderer.setAnimationLoop(animate)
})
return () => h('canvas', { ref: cvs })
}
})
模拟人眼所看到的景象
- 构造器
const fov = 45 // 摄像机视锥体垂直视野角度,从视图的底部到顶部,以角度来表示。默认值是50
const aspect = 2 // 摄像机视锥体宽高比,通常是使用画布的宽/画布的高。默认值是1(正方形画布)
const near = 0.1 // 摄像机视锥体近截面(距离),默认值是0.1(个单位)
const far = 5 // 摄像机视锥体远截面(距离),默认值是2000(个单位)
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
- 更新相机
// 如果改变 fov/aspect/near/far ,需要重新计算投影矩阵(而相机的位置和目标会自动更新)
camera.aspect = canvas.clientWidth / canvas.clientHeight // window.innerWidth / window.innerHeight
// 更新摄像机投影矩阵。在参数被改变以后必须被调用
camera.updateProjectionMatrix()
- 将大画布拆分成多个小视口
/**
* +---+---+---+
* | A | B | C |
* +---+---+---+
* | D | E | F |
* +---+---+---+
**/
// E
const width = 1920 // 副摄像机的宽度(每个视图的宽)
const height = 1080 // 副摄像机的高度(每个视图的高)
const x = 1 * width // 副摄像机的水平偏移(视图的起点)
const y = 1 * height // 副摄像机的垂直偏移(视图的起点)
const fullWidth = w * 3 // 多视图的全宽设置(画布宽)
const fullHeight = h * 2 // 多视图的全高设置(画布高)
// 设置视图偏移量
camera.setViewOffset(fullWidth, fullHeight, x, y, width, height)
// 渲染视图
renderer.render(scene, camera)
// 清除任何由 .setViewOffset 设置的视图偏移量(使渲染恢复正常)
camera.clearViewOffset()
放置物件、灯光和摄像机的地方
- 初始化
const scene = new THREE.Scene()
// 设置背景(可以是 Color 或 Texture),默认值为null
scene.background = new THREE.Color('white')
const color = 'lightblue' // 雾的颜色(16进制整型、css风格字符串)
const near = 1 // 开始应用雾的最小距离,距离活动相机小于“near”个单位的物体不会受到雾的影响。默认值为1
const far = 2 // 计算并应用雾化停止的最大距离。距离活动相机超过“far”个单位的物体不会受到雾的影响。默认值为1000
// 影响场景中的每个物体的雾效果,默认值为null
scene.fog = new THREE.Fog(color, near, far)
- 场景中添加物体、灯光、相机
scene.add(mesh)
scene.add(light)
scene.add(camera)
// 形状-立方体
const width = 1 // X轴上面的宽度,默认值为1
const height = 1 // Y轴上面的高度,默认值为1
const depth = 1 // Z轴上面的深度,默认值为1
const widthSegments = 1 // (可选)宽度的分段数,默认值是1
const heightSegments = 1 // (可选)宽度的分段数,默认值是1
const depthSegments = 1 // (可选)宽度的分段数,默认值是1
const geometry = new THREE.BoxBufferGeometry(
width,
height,
depth,
widthSegments,
heightSegments,
depthSegments
)
// 材质-不受光照的影响
const color = 0xffffff // 材质的颜色,默认值为白色 (0xffffff)
const material = new THREE.MeshBasicMaterial({ color })
// 网格-基于三角形组成的多边形网格的物体。
// Mesh: geometry-默认值是一个新的BufferGeometry,material-默认是一个新的MeshBasicMaterial(颜色随机)
const mesh = new THREE.Mesh(geometry, material)
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
const gltfLoader = new GLTFLoader()
gltfLoader.load('/models/mountain_landscape/scene.gltf', gltf => {
scene.add(gltf.scene)
})
const loader = new THREE.TextureLoader()
const material = new THREE.MeshBasicMaterial({
map: loader.load('/textures/cube/neg-x.jpg')
})
const geometry = new THREE.BoxBufferGeometry()
const mesh = new THREE.Mesh(geometry, material)
const loader = new THREE.CubeTextureLoader()
const texture = loader
.setPath('/textures/cube/')
.load([
'pos-x.jpg',
'neg-x.jpg',
'pos-y.jpg',
'neg-y.jpg',
'pos-z.jpg',
'neg-z.jpg'
])
const scene = new THREE.Scene()
scene.background = texture
const material = new THREE.MeshBasicMaterial({
envMap: texture
})
const mesh = new THREE.Mesh(
new THREE.SphereBufferGeometry(0.4, 32, 16),
material
)
scene.add(mesh)
const geometry = new THREE.SphereBufferGeometry(500, 60, 40)
const texture = new THREE.VideoTexture(video.value)
const material = new THREE.MeshBasicMaterial({ map: texture })
const mesh = new THREE.Mesh(geometry, material)
const camera = new THREE.PerspectiveCamera(75, 2, 1, 1100)
camera.position.set(0, 10, 20)
用平行光模拟太阳光的效果
const color = 0xffffff // 用16进制表示光的颜色。 默认值为 0xffffff (白色)
const intensity = 1 // 光照的强度。默认值为1
const light = new THREE.DirectionalLight(color, intensity)
投射阴影会增加渲染次数。一种常见的解决方案是使用多个灯光,但只有一个平行光产生阴影
renderer.shadowMap.enabled = true // 开启阴影贴图
light.castShadow = true // 光源投射阴影
mesh.castShadow = true // 物体投射阴影
mesh.receiveShadow = true // 物体接收阴影
- 初始化
const canvas = document.querySelector('canvas')
const renderer = new THREE.WebGLRenderer({
canvas, // 一个供渲染器绘制其输出的canvas,和domElement属性对应。如果没有传会创建一个新canvas
alpha: false, // canvas是否包含alpha (透明度)。默认为 false
antialias: false // 是否执行抗锯齿。默认为 false
})
renderer.domElement // 一个canvas,渲染器在其上绘制输出(对应于构造器传入的canvas)
renderer.shadowMap.enabled = false // 如果设置开启,允许在场景中使用阴影贴图。默认是 false
renderer.xr.enabled = false // 该标志通知渲染器准备进行XR渲染。默认值为 false
renderer.setClearColor(0xffffff, 1.0) // 设置颜色及其透明度
- 渲染场景
const render = () => {
// 用相机(camera)渲染一个场景(scene)
renderer.render(scene, camera)
}
- 循环渲染
// 可用来代替requestAnimationFrame的内置函数。对于WebXR项目,必须使用此函数(不能使用raf)
renderer.setAnimationLoop(render)
// 停止所有正在进行的动画
renderer.setAnimationLoop(null)
- 调整输出画布的大小
const canvas = renderer.domElement
const pixelRatio = window.devicePixelRatio
const width = (canvas.clientWidth * pixelRatio) | 0
const height = (canvas.clientHeight * pixelRatio) | 0
const needResize = canvas.width !== width || canvas.height !== height
if (needResize) {
// 将输出画布的大小调整为给定的宽高度,同时考虑设备像素比率,并将视口从(0, 0)开始调整为适合的大小
// 将updateStyle设置为false,可防止对输出画布进行任何样式更改
renderer.setSize(width, height, false)
}
- 渲染一个目标缓冲
const rtWidth = 512 // 渲染目标宽度
const rtHeight = 512 // 渲染目标高度
const renderTarget = new THREE.WebGLRenderTarget(rtWidth, rtHeight, {
depthBuffer: false, // 渲染到深度缓冲区。默认是 true
stencilBuffer: false // 渲染到模板缓冲区。默认是 true
})
// 设置需要被激活的渲染目标为renderTarget
renderer.setRenderTarget(renderTarget)
// 渲染renderTarget
renderer.render(rtScene, rtCamera)
// 将画布设置成活动渲染目标(参数为空)
renderer.setRenderTarget(null)
// 渲染画布
renderer.render(scene, camera)
- 剪裁视口
// 禁用
renderer.setScissorTest(false)
// 告诉渲染器清除颜色、深度或模板缓存。此方法将颜色缓存初始化为当前颜色。默认都是true
const color = true
const depth = true
const stencil = true
renderer.clear(color, depth, stencil)
// 启用剪裁检测(只有在所定义的裁剪区域内的像素才会受之后的渲染器影响)
renderer.setScissorTest(true)
// 将剪裁区域设为(x, y)到(x + width, y + height),(x, y)是剪裁区域的左下角
renderer.setScissor(x, y, width, height)
// 将视口大小设为(x, y)到(x + width, y + height),(x, y)是视口区域的左下角
renderer.setViewport(x, y, width, height)
// 渲染场景
renderer.render(scene, camera)
EffectComposer 类管理了产生最终视觉效果的后期处理过程链。
后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上
- 初始化
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
import { BloomPass } from 'three/examples/jsm/postprocessing/BloomPass'
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass'
// 每一个Pass都有继承了一些公共属性,例如:
// enabled 是否启用该过程
// renderToScreen 是否渲染到屏幕(画布)
const bloomPass = new BloomPass(
1, // strength
25, // kernel size
4, // sigma
256 // blur render target resolution
)
// bloomPass.enabled = false // 是否使用此处理过程
const filmPass = new FilmPass(
0.35, // noise intensity
0.025, // scanline intensity
648, // scanline count
false // grayscale
)
- 过程链
// 效果合成器将创建两个渲染目标(rtA,rtB),可以添加多个过程对象
const composer = new EffectComposer(renderer)
// 首先,将原相机和场景作为第一个渲染目标
composer.addPass(new RenderPass(scene, camera))
// 添加模糊效果
composer.addPass(bloomPass)
// 绘制噪音和扫描线
composer.addPass(filmPass)
- 渲染
// 执行所有启用的后期处理过程
composer.render(deltaTime)
- 初始化
const raycaster = new THREE.Raycaster()
const coords = new THREE.Vector2()
- 设备坐标
const onMouseMove = event => {
const canvas = event.target
const { left, top, width, height } = canvas.getBoundingClientRect()
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
coords.x = ((event.clientX - left) / width) * 2 - 1
coords.y = -((event.clientY - top) / height) * 2 + 1
}
- 计算焦点
// coords 在标准化设备坐标中鼠标的二维坐标(X分量与Y分量应当在-1到1之间)
// camera 射线所来源的摄像机
raycaster.setFromCamera(coords, camera)
// scene.children 检测和射线相交的一组物体
const intersects = raycaster.intersectObjects(scene.children)
if (intersects.length) {
// 突出显示第一个对象(最近的一个)
intersects[0].object.material.emissive.setHex(0xff0000)
}
import { VRButton } from 'three/examples/jsm/webxr/VRButton'
// 初始化
const renderer = new THREE.WebGLRenderer({ canvas: cvs.value })
renderer.xr.enabled = true
vrBtn = VRButton.createButton(renderer)
document.body.appendChild(vrBtn)
// 渲染
const render = () => {
renderer.render(scene, camera)
}
renderer.setAnimationLoop(render)
// 销毁
renderer.setAnimationLoop(null)
document.body.removeChild(vrBtn)
- OrbitControls 轨道控制器
- TrackballControls 轨迹球控制器
- DragControls 拖放控制器
- DeviceOrientationControls (移动)设备方向控制器
- AxesHelper 坐标轴
- GridHelper 网格
- CameraHelper 相机
- DirectionalLightHelper 平行光
- 角度与弧度的转换
弧度 = 角度 * Math.PI / 180
- 根据距离求可见高度
tanα = ∠α 对边 / ∠α 邻边
可见高度 = Math.tan(相机角度 / 2 * Math.PI / 180) * 与相机的距离 * 2
相机距离 = 高度 / 2 / Math.tan(相机角度 / 2 * Math.PI / 180)