用VUE3构建,你的3D世界

2,531 阅读3分钟

最终效果

image.png 效果链接

安装 sandi-ui

pnpm install sandi-ui

在vue3的项目中使用

import { createApp } from 'vue'
import App from './App.vue'
import sandiUI from "sand-ui"
const app = createApp(App)
app.use(sandiUI)
app.mount('#app')

开始制作你的3D世界吧

创建一个渲染器

我们需要一个渲染器,将三维空间渲染出来,即我们看到的画面,渲染器一定要设置 长度和宽度, 这里 设置backgroundColor属性,默认可以不设置,注意目前还是空白的 因为我们,没有设置场景和摄像机

    <SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
    </SDWebglRenderer>

创建一个场景

场景就是我们的三维空间,我们可以在里面放任意的物体

    <SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
        <SDScene>
        </SDScene>
    </SDWebglRenderer>

创建一个摄像机

摄像机是铁三角之一,另外两个,当然是场景和渲染器,这三个是缺一不可的。此时,我们,可以看到渲染器的设置的背景色,摄像机可以放在我们放在场景中哟

    <SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
        <SDScene>
        <SDPerspectiveCamera/>
        </SDScene>
    </SDWebglRenderer>

此时应该是这样的,这个颜色是摄像机的背景色

image.png

创建一个平面

这里使用SDMesh组件和SDPlaneGeometry组件,创建一个地面.

    <SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDScene>
            <SDPerspectiveCamera   :positionZ="1" />
            <SDMesh>
                <SDPlaneGeometry />
                <SDMeshBasicMaterial>
                </SDMeshBasicMaterial>
            </SDMesh>
        </SDScene>
    </SDWebglRenderer>

效果

image.png

变成地面

我们调整了相机的位置,并且,对平面沿X轴转90度,

    <SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDScene>
            <SDPerspectiveCamera   :positionZ="2" :positionY="0.5"  />
            <SDMesh :rotationX="-Math.PI / 2">
                <SDPlaneGeometry />
            </SDMesh>
        </SDScene>
    </SDWebglRenderer>

image.png

给地面加上材质

这里使用SDMeshBasicMaterial,加载我们材质,可以看到它变成白色了

    <SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDScene>
            <SDPerspectiveCamera   :positionZ="2" :positionY="0.5"  />
            <SDMesh :rotationX="-Math.PI / 2">
                <SDPlaneGeometry />
                <SDMeshBasicMaterial>
                      <SDTextureLoader url="/img/crate.gif" type="map" />
                </SDMeshBasicMaterial>
            </SDMesh>
        </SDScene>
    </SDWebglRenderer>

image.png

给地面加上贴图

这里使用SDTextureLoader,加载贴图,这样就有一个地面了。 image.png

在地面上跑起来吧

在跑起来之前我们先把地面变大,我们使用 v-for 来铺一个更大的地面

    <SDWebglRenderer :width="862" :height="400" :backgroundColor="0x1f63d1">
        <SDPerspectiveCamera />
        <SDScene>
                <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
               <SDBoxGeometry :width="20" :height="20" :depth="20" />
               <SDMeshBasicMaterial>
                </SDMeshBasicMaterial>
            </SDMesh>
        </SDScene>
    </SDWebglRenderer>

image.png 但是我们只能看到一部分 所以我们使用 SDGroup 设置位置移动到中间位置,PS:SDGroup 互相嵌套哟

    <SDWebglRenderer :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <SDGroup :positionX="-5" :positionZ="-5">
                <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
                    :positionZ="index % 10">
                    <SDPlaneGeometry />
                    <SDMeshBasicMaterial>
                        <SDTextureLoader url="/img/crate.gif" type="map" />
                    </SDMeshBasicMaterial>
                </SDMesh>
            </SDGroup>
        </SDScene>
    </SDWebglRenderer>

image.png 好了,只需要添加一个 SDPointerLockControls 组件,我们就可以肆意的在场景里跑起来了。提前告诉你esc 显示鼠标

<script setup lang="ts">
import { ref } from "vue";
const lock = ref(false);
</script>
    <SDWebglRenderer @click="()=>{lock=true}" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <SDGroup :positionX="-5" :positionZ="-5">
                <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
                    :positionZ="index % 10">
                    <SDPlaneGeometry />
                    <SDMeshBasicMaterial>
                        <SDTextureLoader url="/img/crate.gif" type="map" />
                    </SDMeshBasicMaterial>
                </SDMesh>
            </SDGroup>
        </SDScene>
    </SDWebglRenderer>

但是有个问题,我们发现安esc后第二次点击视图没办法移动,因为props是单向的,我们组件内部状态变更,我们用回调属性unlockCallback,同步状态

    <SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" :unlockCallback="() => {lock = false}" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <SDGroup :positionX="-5" :positionZ="-5">
                <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(200)" :positionX="Math.floor(index / 10)"
                    :positionZ="index % 10">
                    <SDPlaneGeometry />
                    <SDMeshBasicMaterial>
                        <SDTextureLoader url="/img/crate.gif" type="map" />
                    </SDMeshBasicMaterial>
                </SDMesh>
            </SDGroup>
        </SDScene>
    </SDWebglRenderer>

开始添加箱子吧

这里使用SDMesh组件和SDBoxGeometry组件,创建一个箱子. 同样的 我们使用 SDMeshBasicMaterial 和 SDTextureLoader 赋予材质

    <SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <SDGroup :positionX="-5" :positionZ="-5">
                <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
                    :positionZ="index % 10">
                    <SDPlaneGeometry />
                    <SDMeshBasicMaterial>
                        <SDTextureLoader url="/img/crate.gif" type="map" />
                    </SDMeshBasicMaterial>
                </SDMesh>
            </SDGroup>
            <SDMesh>
                <SDBoxGeometry />
                <SDMeshBasicMaterial>
                    <SDTextureLoader url="/img/crate.gif" type="map" />
                </SDMeshBasicMaterial>
            </SDMesh>
        </SDScene>
    </SDWebglRenderer>

image.png

将箱子组件和地面封装起来

没错你可以,封装出自己的3D组件哟

Floor.vue
<template>
    <SDGroup>
        <SDMesh :rotationX="-Math.PI / 2" v-for="(_, index) in Array(100)" :positionX="Math.floor(index / 10)"
            :positionZ="index % 10">
            <SDPlaneGeometry />
            <SDMeshBasicMaterial>
                <SDTextureLoader url="/img/crate.gif" type="map" />
            </SDMeshBasicMaterial>
        </SDMesh>
    </SDGroup>
</template>
Box.vue
<template>
    <SDMesh>
        <SDBoxGeometry />
        <SDMeshBasicMaterial>
            <SDTextureLoader url="/img/crate.gif" type="map" />
        </SDMeshBasicMaterial>
    </SDMesh>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>

<template>
    <SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <Floor :positionX="-5" :positionZ="-5" />
            <Box />
        </SDScene>
    </SDWebglRenderer>
</template>

创建 充满箱子的世界

我们修改 Box.vue 代码 使用 v-for 创建多个箱子 我们使用 SDGroup 包裹 所有 SDMesh

Box.vue
<script setup >
const getXZ = () => Math.floor(Math.random() * 10)
const getY = () => Math.floor(Math.random() * 100)
</script>
<template>
    <SDGroup>
        <SDMesh v-for=" (_, index) in new Array(500).fill(1) " :positionX="getXZ()" :positionY="getY()"
            :positionZ="getXZ()">
            <SDBoxGeometry />
            <SDMeshBasicMaterial>
                <SDTextureLoader url="/img/crate.gif" type="map" />
            </SDMeshBasicMaterial>
        </SDMesh>
    </SDGroup>
</template>

我们一样 移动floor 到中间位置

<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>

<template>
    <SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <Floor :positionX="-5" :positionZ="-5" />
            <Box :positionX="-5" :positionZ="-5" />
        </SDScene>
    </SDWebglRenderer>
</template>

修改跳跃高度

通过设置 SDPointerLockControls 的 options 属性设置跳跃高度

<script setup lang="ts">
import { ref } from "vue";
import Box from "./components/Box.vue"
import Floor from "./components/Floor.vue"
const lock = ref(false);
</script>

<template>
    <SDWebglRenderer @click="() => { lock = true }" :width="720" :height="360" :backgroundColor="0x1f63d1">
        <SDPointerLockControls :lock="lock" :unlockCallback="() => { lock = false }"
:options="{ junpHeight: 5 }" />
        <SDScene>
            <SDPerspectiveCamera :positionZ="2" :positionY="0.5" />
            <Floor :positionX="-5" :positionZ="-5" />
            <Box :positionX="-5" :positionZ="-5" />
        </SDScene>
    </SDWebglRenderer>
</template>

源代码地址:github.com/MILIFIRE/sa…