Appearance
道路流光
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="roadFlowingLight" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
DirectionalLight,
PlaneGeometry,
MeshLambertMaterial,
Mesh,
Vector2,
Vector3,
LineCurve3,
BufferGeometry,
Float32BufferAttribute,
ShaderMaterial,
Points,
Shape,
Path,
ShapeGeometry,
MeshPhongMaterial,
DoubleSide,
Texture,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const ratio = ref<any>({ value: 0 })
const requestID = ref<any>()
let next = 0
const isRunning = ref(false)
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const vertexShader = `
// 接收js传入的attribute值,会经过线性插值
attribute float current;
// 接收js传入的uniform值
uniform float uSize;
uniform float uTime;
uniform float uRange;
uniform float uTotal;
uniform float uSpeed;
// 向片元着色器传值颜色和透明度
varying float vOpacity;
void main () {
float size = uSize;
// 根据时间确定当前飞线的位置, 以结束点为准
float currentEnd = mod(uTime * uSpeed, uTotal);
// 判断当前像素点是否在飞线范围内,如果在范围内设置尺寸和透明度
if (current < currentEnd && current > currentEnd - uRange) {
// 设置渐变的尺寸,头大尾小
float sizePct = (uRange - (currentEnd - current)) / uRange;
// size *= sizePct;
vOpacity = clamp(1.0 * sizePct, 0.2, 1.0);
} else if (current < currentEnd - uRange){
vOpacity = 0.05;
} else {
vOpacity = 0.05;
}
// 将颜色传递给片元着色器
// 设置点的大小
gl_PointSize = size * 0.4;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
const fragmentShader = `
precision mediump float;
// 接收顶点着色器传入的值
varying float vOpacity;
uniform vec3 uColor;
void main () {
// 设置颜色
gl_FragColor = vec4(uColor, vOpacity);
}
`
// 道路的点数据
const pointArr = [
// 外圈
300, -300, 0, 300, 300, 0,
300, 300, 0, -300, 300, 0,
-300, 300, 0, -300, -300, 0,
-300, -300, 0, 300, -300, 0,
// 内圈
200, -200, 0, 200, 200, 0,
200, 200, 0, -200, 200, 0,
-200, 200, 0, -200, -200, 0,
-200, -200, 0, 200, -200, 0
]
// 流光配置数据
const flyConf = {
range: 100, // 飞线长度
color: '#fe7', // 颜色
speed: 80, // 速度
size: 14 // 飞线点点的大小
}
const pointsArr1: any = []
const pointsArr2: any = []
const initScene = () => {
const ele = document.getElementById('roadFlowingLight') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 1000)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
const createLight = () => {
// 环境光
const light = new AmbientLight(0xadadad) // soft white light
scene.add(light)
// 平行光源
const directionalLight: any = new DirectionalLight(0xffffff, 1)
directionalLight.position.set(1000, 1000, 0)
scene.add(directionalLight)
}
// 创建地板
const createGround = () => {
const planeGeo = new PlaneGeometry(800, 800) // width = 1, height = 1, widthSegments(宽度分段) = 1, heightSegments(高度分段) = 1
const planeMaterial: any = new MeshLambertMaterial({
color: new Color('#efe')
})
const planeMesh: any = new Mesh(planeGeo, planeMaterial)
planeMesh.rotation.x = -Math.PI / 2
scene.add(planeMesh)
}
// 创建流光
const createLightLine = () => {
for (let i = 0; i < pointArr.length; i += 6) {
if (i < 24) {
pointsArr1.push(new Vector2(pointArr[i], pointArr[i + 1]))
} else {
pointsArr2.push(new Vector2(pointArr[i], pointArr[i + 1]))
}
let start = new Vector3(
pointArr[i],
pointArr[i + 1],
pointArr[i + 2]
)
let end = new Vector3(
pointArr[i + 3],
pointArr[i + 4],
pointArr[i + 5]
)
const curve = new LineCurve3(start, end)
const number = start.distanceTo(end)
const points = curve?.getPoints(number)
const positions: any = []
const current: any = []
points.forEach((item: any, index) => {
current.push(index)
positions.push(item.x, item.y, item.z)
})
const flyGeo = new BufferGeometry()
flyGeo.setAttribute(
'position',
new Float32BufferAttribute(positions, 3)
)
flyGeo.setAttribute(
'current',
new Float32BufferAttribute(current, 1)
)
const flyMaterial: any = new ShaderMaterial({
transparent: true,
depthWrite: false,
depthTest: false,
// blending: THREE.AdditiveBlending,
uniforms: {
uSize: {
// 点的大小
value: flyConf.size
},
uTime: ratio.value, // 时间
uColor: {
// 颜色
value: new Color(flyConf.color)
},
uRange: {
// 飞线长度
value: flyConf.range
},
uTotal: {
// 轨迹总长度,(点的总个数)
value: number
},
uSpeed: {
// 飞行速度
value: flyConf.speed
}
},
vertexShader,
fragmentShader
})
// 创建并添加到场景中
const flyPoints = new Points(flyGeo, flyMaterial)
scene.add(flyPoints)
}
}
// 内圈外圈之间的颜色
const createBetweenBackground = () => {
const shape = new Shape(pointsArr1)
const holePath = new Path(pointsArr2)
shape.holes.push(holePath)
const geometry1 = new ShapeGeometry(shape)
const material1: any = new MeshPhongMaterial({
color: new Color('#5fc2ef'),
side: DoubleSide
})
const mesh1 = new Mesh(geometry1, material1)
scene.add(mesh1)
}
const runAnimate = () => {
next += 0.12
ratio.value.value = next
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
createLight()
createGround()
createLightLine()
createBetweenBackground()
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
ratio.value.value = 0
next = 0
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
<style scoped>
</style>
雷达 -1
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="radar1" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
Mesh,
DoubleSide,
Texture,
PlaneGeometry,
ShaderMaterial,
Clock
} from 'three'
const requestID = ref<any>()
const addTime = ref<any>({ value: 0 })
const isRunning = ref(false)
let clock: any = new Clock()
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const vertexShader = `
varying vec2 vUv;
varying vec3 v_position;
void main() {
vUv = uv;
v_position = position;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
`
const fragmentShader = `
varying vec2 vUv;
uniform vec3 uColor;
uniform float uTime;
varying vec3 v_position;
float sdCircle(vec2 p, float r) {
return length(p) - r;
}
float sdBox(in vec2 p, in vec2 b) {
vec2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}
float opSubtraction(float d1, float d2) {
return max(-d1, d2);
}
// 旋转函数
vec2 rotate(vec2 uv, float rotation, vec2 mid) {
return vec2(
cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x ,
cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
);
}
// 等边三角形
float sdEquilateralTriangle(in vec2 p, in float r) {
const float k = sqrt(3.0);
p.x = abs(p.x) - r;
p.y = p.y + r / k;
if (p.x + k * p.y > 0.0) p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
p.x -= clamp(p.x, -2.0 * r, 0.0);
return -length(p) * sign(p.y);
}
// 设置三角形
float mixSjx(float progress, vec2 uv, float rotateNumber) {
float sjxd = sdEquilateralTriangle(rotate(uv, rotateNumber, vec2(0.5)) - vec2(0.5, (progress * 0.05) + 0.03), 0.006);
float sjxc = smoothstep(0.0007, 0.0007, sjxd);
return sjxc;
}
void main() {
vec2 uv = vUv;
float txUtime = 2.2;
float dis = length(v_position - vec3(0));
// 画缺角的圆
if (dis < (txUtime + 0.02) && dis > txUtime) {
gl_FragColor = vec4(vec3(0.74, 0.95, 1.00), 1.0);
}
if (uv.x > 0.48 && uv.x < 0.52) {
gl_FragColor = vec4(0.0);
}
if (uv.y > 0.48 && uv.y < 0.52) {
gl_FragColor = vec4(0.0);
}
if (uv.x + uv.y > 1.0 && uv.x + uv.y < 1.03) {
gl_FragColor = vec4(0.0);
}
if (uv.x - uv.y > 0.05 && uv.x - uv.y < 0.08) {
gl_FragColor = vec4(0.0);
}
// 画可缩小的圆
float progress = abs(sin(uTime)) ;
float d1 = sdCircle(uv - vec2(0.5), 0.36);
float d2 = sdBox(uv - vec2(0.5), vec2(0.4, ((progress + 1.4) * 0.5) * 0.2));
float d3 = sdCircle(uv - vec2(0.5), 0.35);
float d4 = opSubtraction(d1, d3);
float d5 = opSubtraction(d2, d4);
float c = smoothstep(0.007, 0.007, d5);
gl_FragColor = mix(vec4(vec3(0.87, 0.98, 1.00), 1.0) ,gl_FragColor, c);
// 画中心的 雷达
vec2 rotateVuv = rotate(vUv , uTime ,vec2(0.5));
float opacity = 1.0 - step(0.276, distance(uv, vec2(0.5)));
float st = 1.0 - step(0.01, mod(dis, 0.456));
float angle = atan(rotateVuv.x - 0.5, rotateVuv.y - 0.5);
float strength = (angle + 3.14) / 6.28 * 5.0;
if (st == 1.0) {
gl_FragColor = mix(vec4(0.0), vec4(vec3(0.87, 0.98, 1.00), 1.0), opacity);
} else {
vec4 atanColor = mix(vec4(vec3(0.35, 0.76, 0.83), 1.0), vec4(1.0, 1.0, 1.0, 0.0), opacity * strength);
vec4 color = mix(gl_FragColor, atanColor, opacity);
gl_FragColor = color;
}
// 画十字架
if (uv.x + uv.y >0.998 && uv.x + uv.y < 0.99999) {
gl_FragColor = mix(gl_FragColor, vec4(vec3(0.953, 0.969, 0.89), 1.0), opacity);
}
if (uv.x - uv.y > 0.0 && uv.x - uv.y < 0.002) {
gl_FragColor = mix(gl_FragColor, vec4(vec3(0.953, 0.969, 0.89), 1.0), opacity);
}
// 画三角形
float colors[4];
colors[0] = 0.0;
colors[1] = 1.57;
colors[2] = -1.57;
colors[3] = 3.14;
for (int i = 0; i < 4; ++i) {
gl_FragColor = mix(vec4(vec3(1.0, 1.0, 0.0), 1.0), gl_FragColor, mixSjx(progress, uv, colors[i]));
}
}
`
const initScene = () => {
const ele = document.getElementById('radar1') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 50)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
// 光
const createLight = () => {
const ambient = new AmbientLight(0x444444)
scene.add(ambient)
}
const createPlane = () => {
const geometry = new PlaneGeometry(200, 200) // width = 1, height = 1, widthSegments(宽度分段) = 1, heightSegments(高度分段) = 1
const material = new ShaderMaterial({
uniforms: {
uTime: addTime.value,
},
side: DoubleSide,
transparent: true,
vertexShader: vertexShader,
fragmentShader: fragmentShader
})
const mesh = new Mesh(geometry, material)
scene.add(mesh)
mesh.rotation.z = Math.PI / 2
return mesh
}
createPlane()
createLight()
const runAnimate = () => {
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
addTime.value.value = clock.getElapsedTime()
}
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
addTime.value.value = 0
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
clock = null
})
</script>
雷达 -2
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="radar2" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
Mesh,
DoubleSide,
Texture,
PlaneGeometry,
ShaderMaterial,
} from 'three'
const requestID = ref<any>()
const isRunning = ref(false)
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const vertexShader = `
varying vec3 vp;
void main() {
vp = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
const fragmentShader = `
varying vec3 vp;
uniform vec3 u_color;
uniform float u_pi;
uniform float u_radius;
uniform float u_rotation_step;
float getLength(float x, float y) {
return sqrt((x - 0.0) * (x - 0.0) + (y - 0.0) * (y - 0.0));
}
void main() {
// 旋转
float angOffset = u_rotation_step * 0.05;
float cosAng = cos(angOffset);
float sinAng = sin(angOffset);
mat2 modelMatrix = mat2(
cosAng,sinAng,
-sinAng,cosAng
);
vec2 point = modelMatrix * vp.xy;
// ang=[-π,π]
// atan(y,x)用于将XY坐标,返回弧度
float ang = atan(point.y, point.x);
// (u_pi - π) 或者 (u_pi - - π) 取值 0 ~ 2π
float radians = u_pi - ang;
float opacity = radians / (u_pi * 8.0);
// float opacity = 1.0;
// 隐藏某些部分
if (abs(radians) > 1.0) {
opacity = 0.0;
}
// 距离
float uLength = getLength(point.x, point.y);
if (uLength > u_radius) {
opacity = 0.0;
}
gl_FragColor = vec4(u_color, opacity);
}
`
const initScene = () => {
const ele = document.getElementById('radar2') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 50)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
// 光
const createLight = () => {
const ambient = new AmbientLight(0x444444)
scene.add(ambient)
}
const createPlane = () => {
const radarGeom = new PlaneGeometry(100, 100, 100, 100) // width = 1, height = 1, widthSegments(宽度分段) = 1, heightSegments(高度分段) = 1
const radarMat = new ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: DoubleSide,
uniforms: {
u_color: { value: new Color('#f00') },
u_radius: { value: 50.0 },
u_rotation_step: { value: 0.0 },
u_pi: { value: 3.14 }
},
transparent: true,
depthWrite: false,
})
const radar = new Mesh(radarGeom, radarMat)
scene.add(radar)
return radar
}
createLight()
const radar = createPlane()
const runAnimate = () => {
radar.material.uniforms.u_rotation_step.value += 0.5
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
雷达 -3
把多个geo合并成一个并添加shader
点击运行
<template>
<div>
<div>把多个geo合并成一个并添加shader</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="radar3" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
Mesh,
DoubleSide,
Texture,
PlaneGeometry,
ShaderMaterial,
BufferGeometry,
BufferAttribute,
BoxGeometry,
MeshStandardMaterial,
} from 'three'
const requestID = ref<any>()
const isRunning = ref(false)
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
function mergeAttributes(attributes) {
let TypedArray
let itemSize
let normalized
let gpuType = -1
let arrayLength = 0
for (let i = 0; i < attributes.length; ++i) {
const attribute = attributes[i]
if (TypedArray === undefined) TypedArray = attribute.array.constructor
if (TypedArray !== attribute.array.constructor) {
console.error(
'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes.'
)
return null
}
if (itemSize === undefined) itemSize = attribute.itemSize
if (itemSize !== attribute.itemSize) {
console.error(
'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes.'
)
return null
}
if (normalized === undefined) normalized = attribute.normalized
if (normalized !== attribute.normalized) {
console.error(
'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes.'
)
return null
}
if (gpuType === -1) gpuType = attribute.gpuType
if (gpuType !== attribute.gpuType) {
console.error(
'THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes.'
)
return null
}
arrayLength += attribute.count * itemSize
}
const array = new TypedArray(arrayLength)
const result: any = new BufferAttribute(array, itemSize, normalized)
let offset = 0
for (let i = 0; i < attributes.length; ++i) {
const attribute = attributes[i]
if (attribute.isInterleavedBufferAttribute) {
const tupleOffset = offset / itemSize
for (let j = 0, l = attribute.count; j < l; j++) {
for (let c = 0; c < itemSize; c++) {
const value = attribute.getComponent(j, c)
result.setComponent(j + tupleOffset, c, value)
}
}
} else {
array.set(attribute.array, offset)
}
offset += attribute.count * itemSize
}
if (gpuType !== undefined) {
result.gpuType = gpuType
}
return result
}
function mergeGeometries(geometries, useGroups = false) {
const isIndexed = geometries[0].index !== null
const attributesUsed = new Set(Object.keys(geometries[0].attributes))
const morphAttributesUsed = new Set(Object.keys(geometries[0].morphAttributes))
const attributes = {}
const morphAttributes: any = {}
const morphTargetsRelative = geometries[0].morphTargetsRelative
const mergedGeometry = new BufferGeometry()
let offset = 0
for (let i = 0; i < geometries.length; ++i) {
const geometry = geometries[i]
let attributesCount = 0
// ensure that all geometries are indexed, or none
if (isIndexed !== (geometry.index !== null)) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them.'
)
return null
}
// gather attributes, exit early if they're different
for (const name in geometry.attributes) {
if (!attributesUsed.has(name)) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. All geometries must have compatible attributes; make sure "' + name +
'" attribute exists among all geometries, or in none of them.')
return null
}
if (attributes[name] === undefined) attributes[name] = []
attributes[name].push(geometry.attributes[name])
attributesCount++
}
// ensure geometries have the same number of attributes
if (attributesCount !== attributesUsed.size) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. Make sure all geometries have the same number of attributes.')
return null
}
// gather morph attributes, exit early if they're different
if (morphTargetsRelative !== geometry.morphTargetsRelative) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. .morphTargetsRelative must be consistent throughout all geometries.')
return null
}
for (const name in geometry.morphAttributes) {
if (!morphAttributesUsed.has(name)) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. .morphAttributes must be consistent throughout all geometries.')
return null
}
if (morphAttributes[name] === undefined) morphAttributes[name] = []
morphAttributes[name].push(geometry.morphAttributes[name])
}
if (useGroups) {
let count
if (isIndexed) {
count = geometry.index.count
} else if (geometry.attributes.position !== undefined) {
count = geometry.attributes.position.count
} else {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index ' + i +
'. The geometry must have either an index or a position attribute')
return null
}
mergedGeometry.addGroup(offset, count, i)
offset += count
}
}
// merge indices
if (isIndexed) {
let indexOffset = 0
const mergedIndex: any = []
for (let i = 0; i < geometries.length; ++i) {
const index = geometries[i].index
for (let j = 0; j < index.count; ++j) {
mergedIndex.push(index.getX(j) + indexOffset)
}
indexOffset += geometries[i].attributes.position.count
}
mergedGeometry.setIndex(mergedIndex)
}
// merge attributes
for (const name in attributes) {
const mergedAttribute = mergeAttributes(attributes[name])
if (!mergedAttribute) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' + name +
' attribute.')
return null
}
mergedGeometry.setAttribute(name, mergedAttribute)
}
// merge morph attributes
for (const name in morphAttributes) {
const numMorphTargets = morphAttributes[name][0].length
if (numMorphTargets === 0) break
mergedGeometry.morphAttributes = mergedGeometry.morphAttributes || {}
mergedGeometry.morphAttributes[name] = []
for (let i = 0; i < numMorphTargets; ++i) {
const morphAttributesToMerge: any = []
for (let j = 0; j < morphAttributes[name].length; ++j) {
morphAttributesToMerge.push(morphAttributes[name][j][i])
}
const mergedMorphAttribute = mergeAttributes(morphAttributesToMerge)
if (!mergedMorphAttribute) {
console.error('THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the ' +
name + ' morphAttribute.')
return null
}
mergedGeometry.morphAttributes[name].push(mergedMorphAttribute)
}
}
return mergedGeometry
}
const vertexShader = `
varying vec3 vp;
void main() {
vp = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
const fragmentShader = `
varying vec3 vp;
uniform vec3 u_color;
uniform float u_pi;
uniform float u_radius;
uniform float u_rotation_step;
float getLength(float x, float y) {
return sqrt((x - 0.0) * (x - 0.0) + (y - 0.0) * (y - 0.0));
}
void main() {
// 旋转
float angOffset = u_rotation_step * 0.05;
float cosAng = cos(angOffset);
float sinAng = sin(angOffset);
mat2 modelMatrix = mat2(
cosAng,sinAng,
-sinAng,cosAng
);
vec2 point = modelMatrix * vp.xy;
// ang=[-π,π]
// atan(y,x)用于将XY坐标,返回弧度
float ang = atan(point.y, point.x);
// (u_pi - π) 或者 (u_pi - - π) 取值 0 ~ 2π
float radians = u_pi - ang;
float opacity = radians / (u_pi * 1.0);
// float opacity = 1.0;
// 隐藏某些部分
if (abs(radians) > 1.0) {
opacity = 0.0;
}
// 距离
float uLength = getLength(point.x, point.y);
if (uLength > u_radius) {
opacity = 0.0;
}
gl_FragColor = vec4(u_color, opacity);
}
`
const initScene = () => {
const ele = document.getElementById('radar3') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 50)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
// 光
const createLight = () => {
const ambient = new AmbientLight(0x444444)
scene.add(ambient)
}
function getRandomInt(min, max) {
// 包括 min,不包括 max
min = Math.ceil(min); // 如果 min 不是整数,向上取整
max = Math.floor(max); // 如果 max 不是整数,向下取整
return Math.floor(Math.random() * (max - min + 1)) + min; // 含 min,不含 max
}
const createPlane = () => {
const radarGeom = new PlaneGeometry(100, 100, 1, 1)
const mat = new MeshStandardMaterial({
color: '#f00',
side: DoubleSide
})
const plane = new Mesh(radarGeom, mat)
scene.add(plane)
}
const createMergeGeo = () => {
const radarMat = new ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side: DoubleSide,
uniforms: {
u_color: { value: new Color('#0f0') },
u_radius: { value: 50.0 },
u_rotation_step: { value: 0.0 },
u_pi: { value: 3.14 }
},
transparent: true,
depthWrite: false,
})
const mergeGeoArr: any = []
for (let i = 0; i < 3; i++) {
const boxGeometry = new BoxGeometry(10, 10, 10)
const x = getRandomInt(0, 40)
const y = getRandomInt(0, 40)
boxGeometry.translate(x, y, 0)
mergeGeoArr.push(boxGeometry)
}
const radarGeom = new PlaneGeometry(100, 100, 1, 1)
const mergeGeo: any = mergeGeometries([...mergeGeoArr, radarGeom])
const radar = new Mesh(mergeGeo, radarMat)
scene.add(radar)
return radar
}
createLight()
createPlane()
const radar = createMergeGeo()
const runAnimate = () => {
radar.material.uniforms.u_rotation_step.value += 0.5
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
高度渐变色
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="highGradientColor" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Scene,
PerspectiveCamera,
WebGLRenderer,
Color,
AmbientLight,
DirectionalLight,
PlaneGeometry,
Mesh,
ShaderMaterial,
DoubleSide,
Clock,
Texture
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const addTime = ref<any>({ value: 0 })
const requestID = ref<any>()
const isRunning = ref(false)
let clock: any = new Clock()
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const vertexShaderReplacements = `
precision highp float;
uniform float uTime;
uniform float uInterval;
varying vec3 fPosition;
varying float vInterval;
varying float vOpacity;
void main() {
vec4 pos = modelViewMatrix * vec4(position, 1.0);
fPosition = (modelMatrix * vec4(position, 1.0)).xyz;
vInterval = uInterval;
vOpacity = sin(uTime);
gl_Position = projectionMatrix * pos;
}
`
const fragmentShaderReplacements = `
precision highp float;
varying vec3 fPosition;
varying float vInterval;
varying float vOpacity;
void d_color() {
float dataY = fPosition.y;
float dataI = vInterval;
vec3 color = vec3(0.0, 0.0, 0.0);
if (dataY <= -dataI) {
// 蓝色-蓝绿
// 0,0,1 -> 0,1,1
color = vec3(0.0, 0.0, 1.0);
} else if (dataY > -dataI && dataY <= 0.0) {
float g = 1.0 - (-dataY / dataI);
color = vec3(0.0, g, 1.0);
} else if (dataY > 0.0 && dataY <= dataI) {
// 蓝绿-绿
// 0,1,1 -> 0,1,0
float g = 1.0 - dataY / dataI;
color = vec3(0.0, 1.0, g);
} else if (dataY > dataI && dataY <= 2.0 * dataI) {
// 绿-浅绿
// 0,1,0 -> 0.5,1,0
float r = 0.5 * ((dataY - dataI) / dataI);
color = vec3(r, 1.0, 0.0);
} else if (dataY > 2.0 * dataI && dataY <= 3.0 * dataI) {
// 浅绿-黄
// 0.5,1,0 -> 1,1,0
float r = 0.5 + ((dataY - 2.0 * dataI) / dataI) * 0.5;
color = vec3(r, 1.0, 0.0);
} else if (dataY > 3.0 * dataI && dataY <= 4.0 * dataI) {
// 黄-土黄
// 1,1,0 -> 1,0.76,0
float g = 1.0 - ((dataY - 3.0 * dataI) / dataI) * (1.0 - 0.76);
color = vec3(1.0, g, 0.0);
} else if (dataY > 4.0 * dataI && dataY <= 5.0 * dataI) {
// 土黄-橙
// 1,0.76,0 -> 1,0.58,0
float g = 0.76 - ((dataY - 4.0 * dataI) / dataI) * (0.76 - 0.58);
color = vec3(1.0, g, 0.0);
} else if (dataY > 5.0 * dataI && dataY <= 6.0 * dataI) {
// 橙-红
// 1,0.58,0 -> 1,0,0
float g = 0.58 - ((dataY - 5.0 * dataI) / dataI) * 0.58;
color = vec3(1.0, g, 0.0);
} else {
// 红
// 1.0,0.0,0.0
color = vec3(1.0, 0.0, 0.0);
}
gl_FragColor = vec4(color, vOpacity);
}
void main() {
d_color();
}
`
const initScene = () => {
const ele = document.getElementById('highGradientColor') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 1000)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
}
// 光
const createLight = () => {
//环境光
const ambient = new AmbientLight(0x444444)
scene.add(ambient)
// 平行光
const directionalLight = new DirectionalLight(0xffffff)
// 平行光配置
directionalLight.position.set(-40, 60, -10)
directionalLight.castShadow = true
directionalLight.shadow.camera.near = 20
directionalLight.shadow.camera.far = 200
directionalLight.shadow.camera.left = -50
directionalLight.shadow.camera.right = 50
directionalLight.shadow.camera.top = 50
directionalLight.shadow.camera.bottom = -50
// 距离和强度
directionalLight.intensity = 0.5
// 设置阴影的分辨率
directionalLight.shadow.mapSize.width = 1024
directionalLight.shadow.mapSize.height = 1024
scene.add(directionalLight)
}
// 创建平面
const createPlane = () => {
const geometry = new PlaneGeometry(30, 200) // width = 1, height = 1, widthSegments(宽度分段) = 1, heightSegments(高度分段) = 1
const material = new ShaderMaterial({
// wireframe: true,
side: DoubleSide,
uniforms: {
uInterval: {
value: 25.0
},
uTime: addTime.value,
},
vertexShader: vertexShaderReplacements,
fragmentShader: fragmentShaderReplacements
})
const plane = new Mesh(geometry, material)
// plane.position.set(0, 100, 0)
scene.add(plane)
}
const runAnimate = () => {
addTime.value.value = clock.getElapsedTime()
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
createLight()
createPlane()
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
addTime.value.value = 0
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
clock = null
})
</script>
<style scoped>
</style>
扩散波纹
点击运行
<template>
<div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="diffusionRipple" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
BoxGeometry,
Mesh,
ShaderMaterial,
DoubleSide,
Texture,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
const requestID = ref<any>()
const isRunning = ref(false)
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const vertexShader = `
varying vec3 vp;
void main() {
vp = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
const fragmentShader = `
varying vec3 vp;
uniform vec3 u_color;
uniform vec3 u_tcolor;
uniform float u_r;
uniform float u_length;
uniform float u_max;
float getLength(float x, float y) {
return sqrt((x - 0.0) * (x - 0.0) + (y - 0.0) * (y - 0.0));
}
void main() {
float uOpacity = 0.3;
vec3 vColor = u_color;
float uLength = getLength(vp.x, vp.z);
if (uLength <= u_r && uLength > u_r - u_length) {
float opacity = sin((u_r - uLength) / uLength);
uOpacity = opacity;
if (vp.y < 0.0) {
vColor = u_color * opacity;
} else {
vColor = u_tcolor;
}
}
gl_FragColor = vec4(vColor, uOpacity);
}
`
const initScene = () => {
const ele = document.getElementById('diffusionRipple') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(0, 0, 1000)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
const createLight = () => {
const light = new AmbientLight(0xadadad)
scene.add(light)
}
const createBox = () => {
const geometry = new BoxGeometry(300, 300, 300)
const material = new ShaderMaterial({
uniforms: {
u_color: { value: new Color('#5588aa') },
u_tcolor: { value: new Color('#f55c1a') },
u_r: { value: 0.25 },
u_length: { value: 20 },
},
side: DoubleSide,
transparent: true,
vertexShader: vertexShader,
fragmentShader: fragmentShader
})
const mesh = new Mesh(geometry, material)
scene.add(mesh)
return mesh
}
createLight()
const box = createBox()
const material = box.material
const runAnimate = () => {
material.uniforms.u_r.value += 1
if (material.uniforms.u_r.value >= 300) {
material.uniforms.u_r.value = 20
}
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>
模拟建筑
把多个geo合并成一个并添加shader
点击运行
<template>
<div>
<div>把多个geo合并成一个并添加shader</div>
<div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
<div v-if="isRunning" id="radar2" class="stage"></div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import {
Scene,
WebGLRenderer,
PerspectiveCamera,
Color,
AmbientLight,
Mesh,
Texture,
ShaderMaterial,
BoxGeometry,
BufferAttribute,
EdgesGeometry,
LineBasicMaterial,
LineSegments
} from 'three'
const requestID = ref<any>()
const isRunning = ref(false)
let sceneResources
const onTrigger = async () => {
if (!isRunning.value) {
isRunning.value = true
await nextTick()
sceneResources = await initScene()
} else {
isRunning.value = false
destroy()
}
}
const initScene = () => {
const ele = document.getElementById('radar2') as HTMLElement
const width = Number(window.getComputedStyle(ele).width.split('px')[0])
const height = Number(window.getComputedStyle(ele).height.split('px')[0])
const scene = new Scene()
const camera: any = new PerspectiveCamera(75, width / height, 0.1, 3000)
camera.position.set(80, 80, 80)
scene.add(camera)
const renderer: any = new WebGLRenderer({
antialias: true,
alpha: true
})
renderer.setSize(width, height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor(new Color('#32373E'), 1)
ele.appendChild(renderer.domElement)
// 添加 OrbitControls
const createOrbitControls = () => {
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.dampingFactor = 0.25
controls.enableZoom = true
return controls
}
// 光
const createLight = () => {
const ambient = new AmbientLight(0x444444)
scene.add(ambient)
}
const buildingSweepingLightShader = {
uniforms: {
boxH: {
type: 'f',
value: -10.0
}
},
vertexShader: `
attribute vec3 color;
varying vec3 vColor;
varying float v_pz;
void main() {
v_pz = position.y;
vColor = color;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float boxH;
varying vec3 vColor;
varying float v_pz;
float plot(float pct) {
return smoothstep(pct - 8.0, pct, v_pz) - smoothstep(pct, pct + 0.02, v_pz);
}
void main() {
float f1 = plot(boxH);
vec4 b1 = mix(vec4(0.8, 0.1, 0.1, 0.1), vec4(f1, f1, f1, 1.0), 0.1);
gl_FragColor = mix(vec4(vColor, 1.0), b1, b1);
gl_FragColor = vec4(vec3(gl_FragColor), 0.9);
}
`
}
const createBuildings = () => {
const buildMat = new ShaderMaterial({
uniforms: buildingSweepingLightShader.uniforms,
vertexShader: buildingSweepingLightShader.vertexShader,
fragmentShader: buildingSweepingLightShader.fragmentShader,
})
buildMat.needsUpdate = true
for (let i = 0; i < 10; i++) {
const height = Math.random() * 10 + 2
const width = 3
const cubeGeom = new BoxGeometry(width, height, width)
cubeGeom.setAttribute('color', new BufferAttribute(new Float32Array(24 * 3), 3))
const colors = cubeGeom.attributes.color
let r = Math.random() * 0.2
let g = Math.random() * 0.2
let b = Math.random() * 0.2
// 设置立方体的6个面的24个顶点的颜色
for (let i = 0; i < 24; i++) {
colors.setXYZ(r, g, b, 0.8)
}
// 重置立方体顶部的四边形的4个顶点的颜色
const k = 2
colors.setXYZ(k * 4 + 0, .0, g, 1.0)
colors.setXYZ(k * 4 + 1, .0, g, 1.0)
colors.setXYZ(k * 4 + 2, .0, g, 1.0)
colors.setXYZ(k * 4 + 3, .0, g, 1.0)
const cube = new Mesh(cubeGeom, buildMat)
cube.position.set(Math.random() * 100 - 50, height / 2, Math.random() * 100 - 50)
scene.add(cube)
// 绘制边框线
const lineGeom = new EdgesGeometry(cubeGeom)
const lineMaterial = new LineBasicMaterial({
color: 0x0F8BF5,
linewidth: 10,
linecap: 'round',
linejoin: 'round'
})
const line = new LineSegments(lineGeom, lineMaterial)
line.scale.copy(cube.scale)
line.rotation.copy(cube.rotation)
line.position.copy(cube.position)
scene.add(line)
}
}
createLight()
createBuildings()
const runAnimate = () => {
buildingSweepingLightShader.uniforms.boxH.value += 0.1
if (buildingSweepingLightShader.uniforms.boxH.value > 10) {
buildingSweepingLightShader.uniforms.boxH.value = -10.0
}
requestID.value = requestAnimationFrame(runAnimate)
renderer.render(scene, camera)
}
runAnimate()
const controls = createOrbitControls()
return {
renderer,
scene,
controls,
}
}
const destroy = () => {
if (sceneResources) {
sceneResources.scene.clear()
sceneResources.scene.traverse((child) => {
if (child.geometry) child.geometry?.dispose()
if (child.material) {
if (child.material.map) child.material.map?.dispose()
child.material?.dispose()
}
})
if (sceneResources.scene.background) {
if (sceneResources.scene.background instanceof Texture) {
sceneResources.scene.background?.dispose()
}
}
sceneResources.renderer?.dispose()
sceneResources.renderer.forceContextLoss()
sceneResources.controls?.dispose()
cancelAnimationFrame(requestID.value)
sceneResources = null
}
}
onMounted(async() => {
await nextTick()
})
onUnmounted(() => {
destroy()
})
</script>