5 changed files with 733 additions and 1 deletions
|
After Width: | Height: | Size: 346 KiB |
@ -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> |
||||
@ -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> |
||||
Loading…
Reference in new issue