Browse Source

性能测试渲染

master
修宁 6 months ago
parent
commit
3ff3b09f15
  1. BIN
      src/assets/Models/Plastic_Rough.jpg
  2. 321
      src/components/ThreePerfView.vue
  3. 390
      src/components/ThreeRoomView.vue
  4. 18
      src/editor/menus/Model3DView.ts
  5. 5
      src/router/index.ts

BIN
src/assets/Models/Plastic_Rough.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

321
src/components/ThreePerfView.vue

@ -0,0 +1,321 @@
<template>
<div class="model3d-view">
<el-space :gutter="10" class="toolbar">
<el-upload :on-change="test1"
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false">
<el-button type="primary">测试1</el-button>
</el-upload>
<div class="demo-color-block">
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span>
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span>
<span class="demonstration"> 三角形数:<el-text type="danger">{{ restate.faces }}</el-text></span>
</div>
</el-space>
<div class="main-content">
<div class="canvas-container" ref="canvasContainer" />
</div>
</div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import { getCurrentInstance, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls'
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader'
const canvasContainer = ref(null)
let resizeObserver: ResizeObserver | null = null
let scene: THREE.Scene | null = null
let renderer: THREE.WebGLRenderer | null = null
let viewerDom: HTMLElement | null = null
let camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null = null
let controls: OrbitControls | null = null
let axesHelper: THREE.AxesHelper | null = null
let gridHelper: THREE.GridHelper | null = null
let statsControls: Stats | null = null
let animationFrameId: number | null = null
let modelGroup = new THREE.Group()
const restate = reactive({
targetColor: '#ff0000',
loadScale: 1,
mode: 'translate',
objects: 0,
vertices: 0,
faces: 0
})
//
const state = {
showAxesHelper: true,
showGridHelper: true,
camera: {
position: { x: 0, y: 5, z: 10 },
rotation: { x: 0, y: 0, z: 0 }
}
}
onMounted(() => {
nextTick(() => {
initThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(viewerDom)
window['cp'] = getCurrentInstance()
})
})
onBeforeUnmount(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
cleanupThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
window['cp'] = null
})
function initThree() {
viewerDom = canvasContainer.value
if (!viewerDom) {
console.error('Viewer DOM element not found')
return
}
//
scene = new THREE.Scene()
scene.background = new THREE.Color(0xeeeeee)
//
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
})
renderer.clearDepth()
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(viewerDom.clientWidth, viewerDom.clientHeight)
viewerDom.appendChild(renderer.domElement)
//
initMode3DCamera()
// 线
axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
gridHelper = new THREE.GridHelper(1000, 1000)
scene.add(gridHelper)
//
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
directionalLight.position.set(5, 5, 5).multiplyScalar(3)
directionalLight.castShadow = true
scene.add(directionalLight)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1)
scene.add(hemisphereLight)
//
statsControls = new Stats()
statsControls.showPanel(0)
statsControls.dom.style.right = '0'
statsControls.dom.style.left = 'auto'
viewerDom.appendChild(statsControls.dom)
animate()
}
//
function animate() {
animationFrameId = requestAnimationFrame(animate)
renderView()
}
function handleResize(entries) {
for (let entry of entries) {
// entry.contentRect
console.log('Element size changed:', entry.contentRect)
const width = entry.contentRect.width
const height = entry.contentRect.height
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height
camera.updateProjectionMatrix()
} else if (camera instanceof THREE.OrthographicCamera) {
camera.left = width / -2
camera.right = width / 2
camera.top = height / 2
camera.bottom = height / -2
camera.updateProjectionMatrix()
}
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
break
}
}
function initMode3DCamera() {
if (camera) {
scene.remove(camera)
}
const viewerDom = canvasContainer.value
// ============================
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 0.1, 2000)
cameraNew.position.set(5, 5, 5)
cameraNew.lookAt(0, 0, 0)
camera = cameraNew
scene.add(camera)
const controlsNew = new OrbitControls(camera, viewerDom)
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE } //
controlsNew.enableDamping = false
controlsNew.screenSpacePanning = false //
controlsNew.minDistance = 2
controlsNew.addEventListener('change', syncCameraState)
controls = controlsNew
controls.update()
camera.updateProjectionMatrix()
syncCameraState()
}
/**
* 重新加载相机状态到全局状态
*/
function syncCameraState() {
if (camera) {
state.camera.position.x = camera.position.x
state.camera.position.y = camera.position.y
state.camera.position.z = camera.position.z
state.camera.rotation.x = camera.rotation.x
state.camera.rotation.y = camera.rotation.y
state.camera.rotation.z = camera.rotation.z
}
}
function renderView() {
statsControls?.update()
renderer?.render(scene, camera)
}
function cleaupModel() {
if (modelGroup) {
scene.remove(modelGroup)
}
tcontrols.detach()
transformEditCtl.value.detach()
}
function cleanupThree() {
//
if (scene) {
scene.traverse((obj: THREE.Mesh) => {
//
if (obj.geometry) {
obj.geometry.dispose()
}
//
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose())
} else {
obj.material.dispose()
}
}
//
if (obj.texture) {
obj.texture.dispose()
}
//
if (obj.renderTarget) {
obj.renderTarget.dispose()
}
// OrbitControls
if (obj.dispose) {
obj.dispose()
}
})
if (modelGroup) {
scene.remove(modelGroup)
}
//
scene.children = []
modelGroup = null
}
if (statsControls) {
statsControls.dom.remove()
}
if (renderer) {
renderer.dispose()
renderer.forceContextLoss()
console.log('WebGL disposed, memory:', renderer.info.memory)
renderer.domElement = null
}
}
</script>
<style scoped lang="less">
.model3d-view {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
.main-content {
display: flex;
flex: 1;
overflow: hidden;
.model3d-content {
height: 100%;
display: flex;
flex-direction: row;
}
}
.canvas-container {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

390
src/components/ThreeRoomView.vue

@ -0,0 +1,390 @@
<template>
<div class="model3d-view">
<el-space :gutter="10" class="toolbar">
<el-upload :on-change="test1"
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false">
<el-button type="primary">测试1</el-button>
</el-upload>
<div class="demo-color-block">
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span>
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span>
<span class="demonstration"> 三角形数:<el-text type="danger">{{ restate.faces }}</el-text></span>
</div>
</el-space>
<div class="main-content">
<div class="canvas-container" ref="canvasContainer" />
</div>
</div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import { renderIcon } from '@/utils/webutils.ts'
import { nextTick, onMounted, reactive, ref } from 'vue'
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js'
import Plastic_Rough_JPG from '@/assets/Models/Plastic_Rough.jpg'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module'
import { HorizontalBlurShader } from 'three/addons/shaders/HorizontalBlurShader.js'
import { VerticalBlurShader } from 'three/addons/shaders/VerticalBlurShader.js'
const restate = reactive({
targetColor: '#ff0000',
loadScale: 1,
mode: 'translate',
objects: 0,
vertices: 0,
faces: 0,
shadow: {
blur: 0.1,
darkness: 0.3,
opacity: 1
},
plane: {
color: '#ffffff',
opacity: 0
},
showWireframe: false
})
const canvasContainer = ref(null)
let resizeObserver: ResizeObserver | null = null
let scene: THREE.Scene | null = null
let renderer: THREE.WebGLRenderer | null = null
let viewerDom: HTMLElement | null = null
let camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null = null
let controls: OrbitControls | null = null
let stats: any = null
let shadowGroup: THREE.Group | null = null
let renderTarget: THREE.WebGLRenderTarget | null = null
let renderTargetBlur: THREE.WebGLRenderTarget | null = null
let plane: THREE.Mesh | null = null
let blurPlane: THREE.Mesh | null = null
let fillPlane: THREE.Mesh | null = null
let shadowCamera: THREE.OrthographicCamera | null = null
let depthMaterial: THREE.MeshDepthMaterial | null = null
let horizontalBlurMaterial: THREE.ShaderMaterial | null = null
let verticalBlurMaterial: THREE.ShaderMaterial | null = null
const CAMERA_HEIGHT = 10
const datamodel = {
useShadows: true,
groundWidth: 1000,
groundHeight: 0.1,
delayAddThings: [],
staticThings: []
}
const globalSettings = {
useRealMaterial: true,
metalMaterialCfg: {
emissive: 0,
specular: 0x6d6d6d,
metalness: 0.8,
roughness: 0.2
},
paintMaterialCfg: {
emissive: 0,
specular: 0x6d6d6d,
metalness: 0.6,
roughness: 0.8
},
platisticMaterCfg: {
emissive: 0,
specular: 0x6d6d6d,
metalness: 0.4,
roughness: 0.8
}
}
const globalRuntime = {
materialResources: Object.create(null)
}
var ground = new THREE.Mesh(
new THREE.BoxGeometry(datamodel.groundWidth, 0.3, datamodel.groundHeight, 1, 1, 1),
new THREE.MeshPhongMaterial({ color: 0xb0b0b0 })
)
ground.receiveShadow = true
onMounted(() => {
nextTick(() => {
initThree()
})
})
function test1() {
initStaticShadows(datamodel.groundWidth, datamodel.groundHeight)
}
function initThree() {
viewerDom = canvasContainer.value
if (!viewerDom) {
console.error('Viewer DOM element not found')
return
}
scene = new THREE.Scene()
scene.background = new THREE.Color(0x343a40) // 0x343a40
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
})
renderer.setSize(viewerDom.clientWidth, viewerDom.clientHeight)
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(viewerDom)
viewerDom.appendChild(renderer.domElement)
if (datamodel.useShadows) {
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap
}
const pmremGenerator = new THREE.PMREMGenerator(renderer)
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture
const materialGround = new THREE.MeshPhongMaterial({
color: 0x2b5d94,
emissive: 0,
specular: 0x909090,
shininess: 10
})
let loader = new THREE.TextureLoader()
loader.load(Plastic_Rough_JPG, function(texture) {
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
texture.repeat.set(datamodel.groundWidth, datamodel.groundHeight)
ground.material = materialGround
ground.material.normalMap = texture
ground.material.needsUpdate = true
ground.material.transparent = false
})
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3)
hemiLight.position.set(0, 15, 0)
scene.add(hemiLight)
const staticShadowLight = new THREE.DirectionalLight(0xe0e0e0, 2)
staticShadowLight.position.set(0, 10, 0)
staticShadowLight.castShadow = true
staticShadowLight.shadow.mapSize.width = 2048 // default
staticShadowLight.shadow.mapSize.height = 2048 // default
staticShadowLight.shadow.camera.top = 80
staticShadowLight.shadow.camera.bottom = -80
staticShadowLight.shadow.camera.left = -80
staticShadowLight.shadow.camera.right = 80
staticShadowLight.shadow.camera.near = 1
staticShadowLight.shadow.camera.far = 1000
staticShadowLight.shadow.intensity = 0.5
scene.add(staticShadowLight)
scene.add(ground)
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000)
camera.position.set(0, 2, 30)
controls = new OrbitControls(camera, renderer.domElement)
controls.enablePan = true
controls.enableZoom = true
controls.target.set(0, 1, 0)
controls.update()
stats = new Stats()
viewerDom.appendChild(stats.dom)
stats.dom.style.left = 'auto'
stats.dom.style.right = '0px'
renderer.setAnimationLoop(animate)
renderer.setClearColor(0x000000, 0)
renderer.setPixelRatio(window.devicePixelRatio)
}
var renderNormalShadowMap = false
function animate() {
if (renderNormalShadowMap == true) {
renderNormalShadowMap = false
const initialBackground = scene.background
scene.background = null
scene.overrideMaterial = depthMaterial
const initialClearAlpha = renderer.getClearAlpha()
renderer.setClearAlpha(0)
renderer.setRenderTarget(renderTarget)
renderer.render(scene, shadowCamera)
scene.overrideMaterial = null
blurShadow(restate.shadow.blur)
blurShadow(restate.shadow.blur * 0.4)
renderer.setRenderTarget(null)
renderer.setClearAlpha(initialClearAlpha)
scene.background = initialBackground
for (let item of datamodel.delayAddThings) {
scene.add(item)
}
shadowGroup.position.y = 0.16
for (let item of datamodel.staticThings) {
item.traverse((e) => e.isMesh && (e.castShadow = false))
item.castShadow = false
}
}
renderer.render(scene, camera)
stats.update()
}
function handleResize(entries) {
for (let entry of entries) {
const width = entry.contentRect.width
const height = entry.contentRect.height
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height
camera.updateProjectionMatrix()
} else if (camera instanceof THREE.OrthographicCamera) {
camera.left = width / -2
camera.right = width / 2
camera.top = height / 2
camera.bottom = height / -2
camera.updateProjectionMatrix()
}
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
break
}
}
function initStaticShadows(PLANE_WIDTH, PLANE_HEIGHT) {
shadowGroup = new THREE.Group()
shadowGroup.position.y = 0.16
scene.add(shadowGroup)
var ratio = PLANE_HEIGHT / PLANE_WIDTH
renderTarget = new THREE.WebGLRenderTarget(2048, 2048 * ratio)
renderTarget.texture.generateMipmaps = false
renderTargetBlur = new THREE.WebGLRenderTarget(2048, 2048 * ratio)
renderTargetBlur.texture.generateMipmaps = false
const planeGeometry = new THREE.PlaneGeometry(PLANE_WIDTH, PLANE_HEIGHT).rotateX(Math.PI / 2)
const planeMaterial = new THREE.MeshBasicMaterial({
map: renderTarget.texture,
opacity: restate.shadow.opacity,
transparent: true,
depthWrite: false
})
plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.renderOrder = 1
shadowGroup.add(plane)
plane.scale.y = -1
blurPlane = new THREE.Mesh(planeGeometry)
blurPlane.visible = false
shadowGroup.add(blurPlane)
const fillPlaneMaterial = new THREE.MeshBasicMaterial({
color: restate.plane.color,
opacity: restate.plane.opacity,
transparent: true,
depthWrite: false
})
fillPlane = new THREE.Mesh(planeGeometry, fillPlaneMaterial)
fillPlane.rotateX(Math.PI)
shadowGroup.add(fillPlane)
shadowCamera = new THREE.OrthographicCamera(-PLANE_WIDTH / 2, PLANE_WIDTH / 2, PLANE_HEIGHT / 2, -PLANE_HEIGHT / 2, 0, CAMERA_HEIGHT)
shadowCamera.rotation.x = Math.PI / 2
shadowGroup.add(shadowCamera)
depthMaterial = new THREE.MeshDepthMaterial()
depthMaterial.userData.darkness = { value: restate.shadow.darkness }
depthMaterial.onBeforeCompile = function(shader) {
shader.uniforms.darkness = depthMaterial.userData.darkness
shader.fragmentShader = /* glsl */ `
uniform float darkness;
${shader.fragmentShader.replace(
'gl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );',
'gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * darkness );'
)}
`
}
depthMaterial.depthTest = false
depthMaterial.depthWrite = false
horizontalBlurMaterial = new THREE.ShaderMaterial(HorizontalBlurShader)
horizontalBlurMaterial.depthTest = false
verticalBlurMaterial = new THREE.ShaderMaterial(VerticalBlurShader)
verticalBlurMaterial.depthTest = false
}
function blurShadow(amount) {
blurPlane.visible = true
blurPlane.material = horizontalBlurMaterial
blurPlane.material.uniforms.tDiffuse.value = renderTarget.texture
horizontalBlurMaterial.uniforms.h.value = (amount * 1) / 256
renderer.setRenderTarget(renderTargetBlur)
renderer.render(blurPlane, shadowCamera)
blurPlane.material = verticalBlurMaterial
blurPlane.material.uniforms.tDiffuse.value = renderTargetBlur.texture
verticalBlurMaterial.uniforms.v.value = (amount * 1) / 256
renderer.setRenderTarget(renderTarget)
renderer.render(blurPlane, shadowCamera)
blurPlane.visible = false
}
</script>
<style scoped lang="less">
.model3d-view {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
.main-content {
display: flex;
flex: 1;
overflow: hidden;
.model3d-content {
height: 100%;
display: flex;
flex-direction: row;
}
}
.canvas-container {
width: 100%;
height: 100%;
position: relative;
}
}
</style>

18
src/editor/menus/Model3DView.ts

@ -1,6 +1,7 @@
import { defineMenu } from '@/runtime/DefineMenu.ts' import { defineMenu } from '@/runtime/DefineMenu.ts'
import Model3DView from '@/components/Model3DView.vue' import Model3DView from '@/components/Model3DView.vue'
import FabricView from '@/components/FabricView.vue' import FabricView from '@/components/FabricView.vue'
import ThreePerfView from '@/components/ThreePerfView.vue'
export default defineMenu((menus) => { export default defineMenu((menus) => {
menus.insertChildren('tool', menus.insertChildren('tool',
@ -35,7 +36,22 @@ export default defineMenu((menus) => {
showCancelButton: false, showCancelButton: false,
showOkButton: false, showOkButton: false,
dialogClass: 'fabric-view-wrap', dialogClass: 'fabric-view-wrap',
propsData: { fabricView: true } data: { fabricView: true }
})
}
},
{
name: 'threePerfView', label: 'ThreePerf查看器', order: 3,
click: () => {
system.showDialog(ThreePerfView, {
title: 'Fabric查看器',
width: 950,
height: 400,
showClose: true,
showMax: true,
showCancelButton: false,
showOkButton: false,
dialogClass: 'model-3d-view-wrap'
}) })
} }
} }

5
src/router/index.ts

@ -15,6 +15,11 @@ const router = createRouter({
component: () => import('@/components/FabricView.vue') component: () => import('@/components/FabricView.vue')
}, },
{ {
path: '/tp',
name: 'tp',
component: () => import('@/components/ThreePerfView.vue')
},
{
path: '/editor', path: '/editor',
name: 'editor', name: 'editor',
// component: HomeView, // component: HomeView,

Loading…
Cancel
Save