diff --git a/src/components/ThreePerfView.vue b/src/components/ThreePerfView.vue
index ca787fb..5ce9141 100644
--- a/src/components/ThreePerfView.vue
+++ b/src/components/ThreePerfView.vue
@@ -7,7 +7,8 @@
物体数:{{ restate.objects }}
顶点数:{{ restate.vertices }}
- 三角形数:{{ restate.faces }}
+ 三角形:{{ restate.faces }}
+ 标签:{{ restate.viewLabelCount }}
@@ -179,26 +180,58 @@ function test2() {
refreshCount()
}
+function isLabelInView(label, frustum, cameraPosition, maxDistance = 65) {
+ const pos = new THREE.Vector3()
+ label.getWorldPosition(pos)
+
+ // 正交相机的视锥体范围
+ if (frustum.containsPoint(pos)) {
+ // 检查标签是否在相机位置的最大距离内
+ if (shouldShowLabel(label)) {
+ return true
+ }
+ }
+ return false
+}
+
+const labels: Text[] = []
+
+function getLabelPixelSize(fontSize, cameraZoom) {
+ const pixelRatio = renderer.getPixelRatio()
+ const referenceZoom = 1
+ const referenceFontSize = 0.2
+ const referencePixelSize = 16 // fontSize=0.2, zoom=1 时显示为 16px
+
+ const scale = (fontSize / referenceFontSize) * (cameraZoom / referenceZoom)
+ return referencePixelSize * scale * pixelRatio
+}
+
+function shouldShowLabel(label, minPixelSize = 700) {
+ const pixelSize = getLabelPixelSize(label.fontSize, camera.zoom)
+ console.log(`pixel size: ${pixelSize}`)
+ return pixelSize >= minPixelSize
+}
+
/**
- * BufferGeometry + Label
+ * InstanceMesh(Point) + BufferGeometry + Label
*/
function test3() {
- cleanupThree()
- const xcount = 100
+ cleanupThree() // 清空画布
+ const xcount = 300
const zcount = 100
const dist = 1.25
const spacing = dist
const y = 0.1
-
const noShaderMaterial = new THREE.MeshBasicMaterial({
color: 0xFFFF99,
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
})
- const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25) // 设置大小
+ const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25)
+ // 使用 InstancedMesh 网格优化
const instancedMesh = new THREE.InstancedMesh(planeGeometry, noShaderMaterial, zcount * xcount)
instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
const dummy = new THREE.Object3D()
@@ -212,7 +245,7 @@ function test3() {
const pz = z * dist
dummy.position.set(px, y, pz)
- dummy.rotation.set(-Math.PI / 2, 0, 0) // 假设你想让平面朝上,相当于面对相机视角的一部分
+ dummy.rotation.set(-Math.PI / 2, 0, 0)
dummy.updateMatrix()
instancedMesh.setMatrixAt(z * xcount + x, dummy.matrix)
@@ -222,9 +255,9 @@ function test3() {
scene.add(instancedMesh)
const positions = []
- const labels = []
+ labels.length = 0
- function createTextLabel(text, position) {
+ function createTextLabel(text, position): Text {
const label = new Text()
label.text = text
label.font = SimSunTTF
@@ -243,7 +276,8 @@ function test3() {
label.position.set(position.x, position.y + 0.3, position.z - 0.2)
label.quaternion.copy(camera.quaternion)
- label.sync()
+ label.visible = false
+ // label.sync()
return label
}
@@ -290,8 +324,9 @@ function test3() {
lineSegments.name = 'grid-lines'
scene.add(lineSegments)
- // labels.forEach(label => scene.add(label))
+ labels.forEach(label => scene.add(label))
+ // 统计总数量
refreshCount()
}
@@ -299,6 +334,7 @@ function test3() {
const restate = reactive({
targetColor: '#ff0000',
loadScale: 1,
+ viewLabelCount: 0,
mode: 'translate',
objects: 0,
vertices: 0,
@@ -399,16 +435,52 @@ function initThree() {
statsControls.dom.style.left = 'auto'
viewerDom.appendChild(statsControls.dom)
- renderer.setAnimationLoop(renderView)
+ renderer.setAnimationLoop(animate)
renderer.setClearColor(0x000000, 0)
// animate()
}
// 动画循环
+let frameCount = 0
+
function animate() {
- animationFrameId = requestAnimationFrame(animate)
+ // animationFrameId = requestAnimationFrame(animate)
renderView()
+
+ if (frameCount++ % 60 === 0) { // 每 60 帧更新一次文本
+ const frustum = new THREE.Frustum()
+ const cameraCopy = camera.clone()
+
+ // 必须更新相机的世界矩阵
+ cameraCopy.updateMatrixWorld()
+
+ // 构造投影矩阵
+ const projScreenMatrix = new THREE.Matrix4().multiplyMatrices(
+ cameraCopy.projectionMatrix,
+ cameraCopy.matrixWorldInverse
+ )
+
+ // 设置视锥体
+ frustum.setFromProjectionMatrix(projScreenMatrix)
+
+ let viewLabelCount = 0
+ labels.forEach((label: Text) => {
+ // label.quaternion.copy(camera.quaternion) // billboard 效果保持朝向相机
+ const isvis = isLabelInView(label, frustum, camera.position)
+ if (isvis) {
+ viewLabelCount++
+ }
+ if (isvis && label.visible === false) {
+ label.visible = true
+ label.sync()
+ } else if (!isvis && label.visible === true) {
+ label.visible = false
+ label.sync()
+ }
+ })
+ restate.viewLabelCount = viewLabelCount
+ }
}
function handleResize(entries) {