上一节我们基本环境已经搭建好了,并且目标已经确认,这节我们继续开发
MyCanvas
WebGLRenderer关联Canvas
我们通过传入的canvas来拿到DOM元素
const webGLRendererConstructorParameters = computed<WebGLRendererParameters>(
() => ({
canvas: unrefElement(canvas),
})
);
把WebGLRenderer和Canvas关联上
const renderer = shallowRef<any>(
new WebGLRenderer(webGLRendererConstructorParameters.value)
);
循环更新
我们创建一个循环拥有更新每帧
import { useRenderLoop } from "./useRenderLoop";
// 返回三个方法供我们使用,pause暂停、resume恢复、onLoop循环
const { pause, resume, onLoop } = useRenderLoop();
循环我们使用 useRafFn
参考useRafFn我们先在useRenderLoop.ts里定义好输出内容
import type { EventHookOn,Fn } from "@vueuse/core";
import { Clock } from 'three'
export interface RenderLoop {
delta: number
elapsed: number
clock: Clock
}
export interface UseRenderLoopReturn {
onLoop: EventHookOn<RenderLoop>;
pause: Fn;
resume: Fn;
}
export const useRenderLoop = (): UseRenderLoopReturn => ({
onLoop: onLoop.on,
pause,
resume,
});
对上面内容进行一下说明,为什么是这样,我们核心的做法就是通过createEventHook创建钩子,然后循环通过useRafFn循环触发并传入相关参数,跟我们以前 animation 其实是类似的
接下来我们书写一下,先创建事件钩子
// 创建事件钩子,触发trigger,on监听
const onLoop = createEventHook<RenderLoop>();
创建循环
// 创建时钟
const clock = new Clock();
let delta = 0;
let elapsed = 0;
// useRafFn 每秒都执行,pause 暂停,resume 继续
const { pause, resume } = useRafFn(
() => {
onLoop.trigger({ delta, elapsed, clock });
},
{ immediate: false }
);
完整代码
import type { EventHookOn, Fn } from "@vueuse/core";
import { Clock } from "three";
import { createEventHook, useRafFn } from "@vueuse/core";
export interface RenderLoop {
delta: number;
elapsed: number;
clock: Clock;
}
export interface UseRenderLoopReturn {
onLoop: EventHookOn<RenderLoop>;
pause: Fn;
resume: Fn;
}
// 创建事件钩子,触发trigger,on监听
const onLoop = createEventHook<RenderLoop>();
// 创建时钟
const clock = new Clock();
let delta = 0;
let elapsed = 0;
// useRafFn 每秒都执行,pause 暂停,resume 继续
const { pause, resume } = useRafFn(
() => {
onLoop.trigger({ delta, elapsed, clock });
},
{ immediate: false }
);
export const useRenderLoop = (): UseRenderLoopReturn => ({
onLoop: onLoop.on,
pause,
resume,
});
在 useMyContextProvider.ts 验证下是否生效
onLoop(() => {
console.log("rendering");
});
resume();
打开控制台,可以看到已经生效了
在循环中我们需要实现下面内容
观察我们上面写的内容,我们已经有了 renderer、scene,目前我们还缺少 camera,虽然相机是通过标签传递进来的,但是我们可以做一个默认的相机,先来完事我们的内容,然后再处理标签传递过来的内容
这块内容需要我们构思一下:我们新增返回camera、registerCamera,当我们没有传递标签的时候,就registerCamera注册一下我们默认的
// src/components/MyCanvas.vue
const { camera, registerCamera } = context.value;
const addDefaultCamera = () => {
const camera = new PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(3, 3, 3);
camera.lookAt(0, 0, 0);
registerCamera(camera);
};
// 注册默认相机
if (!camera.value) {
addDefaultCamera();
}
创建 useCamera.ts 来处理上述内容
import { ref, computed } from "vue";
import { Camera } from "three";
export const useCamera = () => {
const cameras = ref<Camera[]>([]);
const camera = computed<Camera | undefined>(() => cameras.value[0]);
const registerCamera = (newCamera: Camera) => {
cameras.value.push(newCamera);
};
return {
camera,
cameras,
registerCamera,
};
};
说明一下上面代码:当我们 registerCamera 我们相机的时候,cameras 长度发生变化,camera 是响应式的,然后在上面我们设置的循环中就可以使用到了
然后我们在 useMyContextProvider.ts 中添加我们的相机
import { useCamera } from "./useCamera";
const { camera, registerCamera } = useCamera();
在我们循环中就可以实现下面这个内容了
onLoop(() => {
if (camera.value) {
renderer.value.render(scene, camera.value);
}
});
再上面我们的camera 有ts类型报错,我们修复一下
// src/components/myInterface.ts
import type { ComputedRef, MaybeRef } from "vue";
import type { Scene } from "three";
import { Camera } from "three";
export interface MyContext {
scene: Scene;
canvas: MaybeRef<HTMLCanvasElement>;
camera: ComputedRef<Camera | undefined>
registerCamera: (camera: Camera) => void;
}
现在观察我们的页面
看着和最开始不太一样了,最开始是白色,现在是黑色了,如果还不确定,我们可以修改一下背景色看看是否生效了
renderer.value.setClearColor("red"); // 设置背景色,这里是红色。
下面是完整代码
// src/components/useMyContextProvider.ts
import { computed, shallowRef } from "vue";
import { MyContext } from "./myInterface";
import type { WebGLRendererParameters, Scene } from "three";
import { unrefElement } from "@vueuse/core";
import { WebGLRenderer } from "three";
import { useRenderLoop } from "./useRenderLoop";
import type { MaybeRef } from "vue";
import { useCamera } from "./useCamera";
export function useMyContextProvider({
canvas,
scene,
}: {
canvas: MaybeRef<HTMLCanvasElement>;
scene: Scene;
}): MyContext {
const webGLRendererConstructorParameters = computed<WebGLRendererParameters>(
() => ({
canvas: unrefElement(canvas),
})
);
const renderer = shallowRef<any>(
new WebGLRenderer(webGLRendererConstructorParameters.value)
);
renderer.value.setClearColor("red"); // 设置背景色,这里是红色。
const { camera, registerCamera } = useCamera();
const { pause, resume, onLoop } = useRenderLoop();
onLoop(() => {
if (camera.value) {
renderer.value.render(scene, camera.value);
}
});
resume();
return {
canvas,
scene,
camera,
registerCamera,
};
}
发现我们的效果生效了,说明已经成功了
接下来就是传入形状标签,生成对应的形状,相机可以放在最后了,因为我们已经实现了一个默认相机