Skip to content

房子

fps: 0
点击运行
<template>
  <div>
    <div class="flex space-between">
      <div>fps: {{ fps }}</div>
      <div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
    </div>
    <canvas v-if="isRunning" id="textureHouse" class="stage"></canvas>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import 'babylonjs-loaders'
import {
  Engine,
  Scene,
  ArcRotateCamera,
  Vector3,
  Vector4,
  Color3,
  HemisphericLight,
  MeshBuilder,
  Mesh,
  StandardMaterial,
  Texture
} from 'babylonjs'

let sceneResources

const fps = ref(0)
const isRunning = ref(false)

const semiHouseWidth = 2
const cubeHouseWidth = 1

const onTrigger = async () => {
  if (!isRunning.value) {
    isRunning.value = true
    await nextTick()
    sceneResources = await initScene()
  } else {
    isRunning.value = false
    destroy()
  }
}

const initScene = async () => {
  const ele = document.getElementById("textureHouse") as any

  ele.addEventListener('wheel', function(event) {
    // 根据需要处理滚动
    // 例如,可以修改相机的半径或角度
    event.preventDefault() // 阻止默认滚动行为
  })

  const engine: any = new Engine(ele, true, {
    preserveDrawingBuffer: true,
    stencil: true,
    disableWebGL2Support: false
  })

  const scene = new Scene(engine)
  scene.useRightHandedSystem = true

  const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
  camera.upperBetaLimit = Math.PI / 2.2
  camera.wheelPrecision = 30
  camera.panningSensibility = 200
  camera.attachControl(ele, true)
  camera.setPosition(new Vector3(20, 20, 20))

  const createLight = () => {
    const light = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
    return light
  }

  const createGround = () => {
    const groundMat = new StandardMaterial('ground')
    groundMat.diffuseColor = new Color3(0.2, 0.5, 0.6)
    const ground = MeshBuilder.CreateGround('ground', {
      width: 20,
      height: 20
    })
    ground.material = groundMat
  }

  const createBox = (width) => {
    const boxMat = new StandardMaterial('box')
    if (width === semiHouseWidth) {
      boxMat.diffuseTexture = new Texture('/images/semiHouse.png')
    } else {
      boxMat.diffuseTexture = new Texture('/images/cubeHouse.png')
    }

    const faceUV: any = []
    if (width === semiHouseWidth) {
      // 后面
      faceUV[0] = new Vector4(0.6, 0.0, 1.0, 1.0)
      // 前面
      faceUV[1] = new Vector4(0.0, 0.0, 0.4, 1.0)
      // 右面
      faceUV[2] = new Vector4(0.4, 0, 0.6, 1.0)
      // 左面
      faceUV[3] = new Vector4(0.4, 0, 0.6, 1.0)
    } else {
      // 后面
      faceUV[0] = new Vector4(0.5, 0.0, 0.75, 1.0)
      // 前面
      faceUV[1] = new Vector4(0.0, 0.0, 0.25, 1.0)
      // 右面
      faceUV[2] = new Vector4(0.25, 0, 0.5, 1.0)
      // 左面
      faceUV[3] = new Vector4(0.75, 0, 1.0, 1.0)
    }

    const box = MeshBuilder.CreateBox('box', {
      width: width,
      faceUV: faceUV,
      wrap: true
    })
    box.material = boxMat
    box.position.y = 0.5
    return box
  }

  const createRoof = (width) => {
    const roofMat = new StandardMaterial('roof')
    roofMat.diffuseTexture = new Texture('/images/roof.jpg')
    const roof = MeshBuilder.CreateCylinder('roof', {
      diameter: 1.3,
      height: 1.2,
      tessellation: 3
    })
    roof.material = roofMat
    roof.scaling.x = 0.75
    roof.scaling.y = width
    roof.rotation.z = Math.PI / 2
    roof.position.y = 1.22
    return roof
  }

  const mergeHouse = (width) => {
    const box = createBox(width)
    const roof = createRoof(width)
    return Mesh.MergeMeshes([box, roof], true, false, undefined, false, true)
  }

  const creteHouse = () => {
    const semiHouse = mergeHouse(semiHouseWidth)
    const cubeHouse = mergeHouse(cubeHouseWidth)

    // 每项都是一个数组[房屋类型,旋转,x,z]
    const places: any = []
    
    places.push([1, -Math.PI / 16, -6.8, 2.5])
    places.push([1, 15 * Math.PI / 16, -4.1, -1])
    places.push([1, 5 * Math.PI / 4, 0, -1])
    places.push([1, Math.PI + Math.PI / 2.5, 0.5, -3])
    places.push([1, Math.PI + Math.PI / 2.25, 0.75, -7])
    places.push([1, Math.PI / 1.95, 4.5, -3])
    places.push([1, Math.PI / 1.9, 4.75, -7])
    places.push([1, -Math.PI / 3, 6, 4])

    places.push([2, -Math.PI / 3, 5.25, 2])
    places.push([2, 15 * Math.PI / 16, -2.1, -0.5])
    places.push([2, Math.PI / 1.9, 4.75, -1])
    places.push([2, Math.PI + Math.PI / 2.1, 0.75, -5])
    places.push([2, -Math.PI / 16, -4.5, 3])
    places.push([2, -Math.PI / 16, -1.5, 4])
    places.push([2, -Math.PI / 3, 1.5, 6])
    places.push([2, 15 * Math.PI / 16, -6.4, -1.5])
    places.push([2, Math.PI / 1.9, 4.75, -5])

    const houses: any = []
    for (let i = 0; i < places.length; i++) {
      if (places[i][0] === 1) {
        houses[i] = cubeHouse?.createInstance('house' + i)
      } else {
        houses[i] = semiHouse?.createInstance('house' + i)
      }
      houses[i].rotation.y = places[i][1]
      houses[i].position.x = places[i][2]
      houses[i].position.z = places[i][3]
    }
  }


  const runAnimate = () => {
    engine.runRenderLoop(function() {
      if (scene && scene.activeCamera) {
        scene.render()

        fps.value = engine.getFps().toFixed(2)
      }
    })
  }

  createLight()
  createGround()
  creteHouse()
  runAnimate()

  return {
    scene,
    engine, 
  }
}

const destroy = () => {
  if (sceneResources) {
    sceneResources.engine.stopRenderLoop() 
    sceneResources.engine.dispose()
    sceneResources.scene.dispose()
    sceneResources = null
  }
}

onMounted(async() => {
  await nextTick()
})

onUnmounted(() => {
  destroy()
})
</script>

renderTargetTexture-1

fps: 0
点击运行
<template>
  <div>
    <div class="flex space-between">
      <div>fps: {{ fps }}</div>
      <div @click="onTrigger" class="pointer">点击{{ !isRunning ? '运行' : '关闭' }}</div>
    </div>
    <canvas v-if="isRunning" id="renderTargetTexture1" class="stage"></canvas>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, nextTick, onUnmounted } from 'vue'
import 'babylonjs-loaders'
import {
  Engine,
  Scene,
  ArcRotateCamera,
  Vector3,
  Color4,
  Color3,
  DirectionalLight,
  HemisphericLight,
  MeshBuilder,
  RenderTargetTexture,
  StandardMaterial,
  Animation
} from 'babylonjs'
import {
  AdvancedDynamicTexture,
  StackPanel,
  Control,
  TextBlock,
} from 'babylonjs-gui'

let sceneResources

const fps = ref(0)
const isRunning = ref(false)

const onTrigger = async () => {
  if (!isRunning.value) {
    isRunning.value = true
    await nextTick()
    sceneResources = await initScene()
  } else {
    isRunning.value = false
    destroy()
  }
}

const initScene = async () => {
  const ele = document.getElementById("renderTargetTexture1") as any

  ele.addEventListener('wheel', function(event) {
    // 根据需要处理滚动
    // 例如,可以修改相机的半径或角度
    event.preventDefault() // 阻止默认滚动行为
  })

  const engine: any = new Engine(ele, true, {
    preserveDrawingBuffer: true,
    stencil: true,
    disableWebGL2Support: false
  })

  const scene = new Scene(engine)
  scene.useRightHandedSystem = true

  const camera = new ArcRotateCamera('camera', -Math.PI / 1.5, Math.PI / 2.2, 15, new Vector3(0, 0, 0), scene)
  camera.upperBetaLimit = Math.PI / 2.2
  camera.wheelPrecision = 30
  camera.panningSensibility = 200
  camera.attachControl(ele, true)
  camera.setPosition(new Vector3(0, 0, 20))

  const createLight = () => {
    const light1 = new DirectionalLight('dir01', new Vector3(0, 0, -1), scene)
    const light2 = new HemisphericLight('light',new Vector3(1, 1, 0), scene)
    return [light1, light2]
  }

  const createAxis = () => {
    const axisX = MeshBuilder.CreateLines(
      'axisX', {
        colors: [new Color4(1, 0, 0, 1), new Color4(1, 0, 0, 1)],
        points: [new Vector3(0, 0, 0), new Vector3(80, 0, 0)]
      },
      scene
    )

    const axisY = MeshBuilder.CreateLines(
      'axisY', {
        colors: [new Color4(0, 1, 0, 1),  new Color4(0, 1, 0, 1)  ],
        points: [new Vector3(0, 0, 0), new Vector3(0, 80, 0) ]
      },
      scene
    )

    const axisZ = MeshBuilder.CreateLines(
      'axisZ', {
        colors: [new Color4(0, 0, 1, 1), new Color4(0, 0, 1, 1)],
        points: [new Vector3(0, 0, 0), new Vector3(0, 0, 80)]
      },
      scene
    )

    return [axisX, axisY, axisZ]
  }

  const createGui = async () => {
    const adt = AdvancedDynamicTexture.CreateFullscreenUI('UI')

    const xBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
    xBox.position = new Vector3(80, 0, 0)
    const xPanel = new StackPanel()
    xPanel.width = '20px'
    xPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
    xPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
    const x = new TextBlock()
    x.text = 'X'
    x.height = '30px'
    x.color = 'red'
    adt.addControl(xPanel)
    xPanel.addControl(x)
    xPanel.linkWithMesh(xBox)

    const yBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
    yBox.position = new Vector3(0, 80, 0)
    const yPanel = new StackPanel()
    yPanel.width = '20px'
    yPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
    yPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
    const y = new TextBlock()
    y.text = 'Y'
    y.height = '30px'
    y.color = 'green'
    adt.addControl(yPanel)
    yPanel.addControl(y)
    yPanel.linkWithMesh(yBox)

    const zBox = MeshBuilder.CreateBox('x', { size: 1 }, scene)
    zBox.position = new Vector3(0, 0, 80)
    const zPanel = new StackPanel()
    zPanel.width = '20px'
    zPanel.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_RIGHT
    zPanel.verticalAlignment = Control.VERTICAL_ALIGNMENT_BOTTOM
    const z = new TextBlock()
    z.text = 'Z'
    z.height = '30px'
    z.color = 'blue'
    adt.addControl(zPanel)
    zPanel.addControl(z)
    zPanel.linkWithMesh(zBox)
  }

  const createGround = () => {
    const groundMat = new StandardMaterial('ground')
    groundMat.diffuseColor = new Color3(0.2, 0.5, 0.6)
    const ground = MeshBuilder.CreateGround('ground', {
      width: 20,
      height: 20
    })
    ground.material = groundMat
  }

  const runAnimation = (mesh) => {
    let animation
    const keys: any = []
    keys.push({
      frame: 0,
      value: new Vector3(0, 0, 0)
    })
    keys.push({
      frame: 45,
      value: new Vector3(2, 0, 0)
    })
    keys.push({
      frame: 90,
      value: new Vector3(0, 0, 0)
    })
    animation = new Animation(
      'boxAnimation',
      'position',
      60, // 帧率
      Animation.ANIMATIONTYPE_VECTOR3,
      Animation.ANIMATIONLOOPMODE_CYCLE
    )
    mesh.animations.push(animation)
    animation.setKeys(keys)
    scene.beginAnimation(mesh, 0, 90, true)
  }

  const createTexture = () => {
    const sphere = MeshBuilder.CreateSphere('sphere', { diameter: 4 }, scene)
    const mat = new StandardMaterial('mat', scene)
    mat.emissiveColor = new Color3(0.2, 0.3, 0.4)
    sphere.material = mat
    runAnimation(sphere)
    
    // 调整相机视角,确保球体在纹理中是圆形
    const rtCamera = new ArcRotateCamera('rtCamera', 0, 0, 10, Vector3.Zero(), scene)
    rtCamera.setTarget(Vector3.Zero())
    rtCamera.setPosition(new Vector3(0, 0, 20))

    const renderTargetTexture = new RenderTargetTexture('renderTargetTexture', { width: 256, height: 256 }, scene) as any
    renderTargetTexture.renderList.push(sphere)
    renderTargetTexture.camera = rtCamera
    scene.customRenderTargets.push(renderTargetTexture)

    const m = new StandardMaterial('m', scene)
    m.diffuseTexture = renderTargetTexture
    m.backFaceCulling = false // 确保双面渲染

    const plane = MeshBuilder.CreatePlane('plane', { size: 4 }, scene)
    plane.material = m
    plane.position = new Vector3(0, 4, 0)
  }


  const runAnimate = () => {
    engine.runRenderLoop(function() {
      if (scene && scene.activeCamera) {
        scene.render()

        fps.value = engine.getFps().toFixed(2)
      }
    })
  }

  createAxis()
  createGui()
  createLight()
  createGround()
  createTexture()
  runAnimate()

  return {
    scene,
    engine, 
  }
}

const destroy = () => {
  if (sceneResources) {
    sceneResources.engine.stopRenderLoop() 
    sceneResources.engine.dispose()
    sceneResources.scene.dispose()
    sceneResources = null
  }
}

onMounted(async() => {
  await nextTick()
})

onUnmounted(() => {
  destroy()
})
</script>