Skip to content

场景创建与管理

关于多场景

  • 建议使用单个 Engine 实例和多个 Scene 实例
    • (需要处理不同场景之间的状态传递和数据共享)
  • 使用多个 Engine 实例(不推荐)
    • 占用更多资源
  • 使用场景栈(注意内存泄漏,无法满足复杂需求)
  • 场景管理器 (Scene Manager)
    • 需要设计和实现场景管理器框架,增加了开发工作量

场景创建

js
const sceneA = new BABYLON.Scene(engine); 
const sceneB = new BABYLON.Scene(engine); 

场景切换

js
engine.activeScene = sceneB; 
///
engine.setActiveScene(sceneB); 

场景图与层次结构

  • 场景图 是组织 3D 对象的重要工具
  • 父子关系
    js
    const parent = BABYLON.MeshBuilder.CreateBox("parent", {}, scene);
    const child = BABYLON.MeshBuilder.CreateSphere("child", {}, scene);
    child.parent = parent;
    
    // 调用父对象的 addChild(child) 方法,将子对象添加到父对象下
    parent.addChild(child);
    
    // 调用父对象的 addChild(child) 方法,将子对象添加到父对象下。
    child.setParent(parent);
  • 变换继承
    js
    parent.position = new BABYLON.Vector3(1, 0, 0);
    child.position = new BABYLON.Vector3(0, 1, 0);
    // child 的实际位置为 (1, 1, 0)
  • 访问子对象
    js
    parent.children; // [child]
  • 遍历场景图
    js
    const traverse = (node: BABYLON.Node) => {
        console.log(node.name);
        node.children.forEach(child => traverse(child));
    };
    
    traverse(scene);

动态更新

render loop 是 BabylonJS 应用的核心循环,它负责渲染场景并调用 scene 的 update 方法。

  • scene.render(): 除了渲染场景,还会调用 scene.onBeforeRenderObservable 和 scene.onAfterRenderObservable 事件。
  • scene.onBeforeRenderObservable: 在渲染之前触发,可以在这里执行对象的更新逻辑。
  • scene.onAfterRenderObservable: 在渲染之后触发,可以在这里执行清理工作或后续处理。
js
scene.onBeforeRenderObservable.add(() => {
    // 更新对象状态,例如位置、旋转、动画等
    sphere.position.y = sphere.position.y += 0.01;
});

状态机更新

根据不同的状态执行不同的更新逻辑

js
enum State {
    IDLE,
    MOVING,
    JUMPING
}
 
let currentState: State = State.IDLE;
 
scene.onBeforeRenderObservable.add(() => {
    switch (currentState) {
        case State.IDLE:
            // 执行空闲状态逻辑
            break;
        case State.MOVING:
            // 执行移动状态逻辑
            break;
        case State.JUMPING:
            // 执行跳跃状态逻辑
            break;
    }
});

时间轴更新

根据时间或帧数来控制对象的动画和行为。

js
const timeline = new BABYLON.Animation("timeline", "position.y", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_NONE);
 
const keys = [
    { frame: 0, value: 0 },
    { frame: 60, value: 5 },
    { frame: 120, value: 0 }
];
 
timeline.setKeys(keys);
 
box.animations.push(timeline);
 
scene.onBeforeRenderObservable.add(() => {
    // 更新时间轴进度
});

冻结机制 (Freezing) 减少计算开销

被冻结时,worldMatrix(世界矩阵)被缓存起来,不再需要每帧重新计算。

对于静态对象(建筑物、地形等)它们的位置、旋转和缩放不会改变

js
// 创建球体
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere",{ diameter: 2, segments: 32 },scene);
// 解冻对象
mesh.freezeWorldMatrix(); 
// 冻结对象
mesh.thawWorldMatrix(); 

资源复用

资源管理器 (Resource Manager) AssetsManager 类可以用来管理资源的加载和复用。

js
const assetsManager = new BABYLON.AssetsManager(scene);
 
const textureTask = assetsManager.addTextureTask("task1", "path/to/texture.png");
const meshTask = assetsManager.addMeshTask("task2", "", "path/to/model/", "LittlestTokyo.glb");
 
assetsManager.onFinish = () => {
    // 所有资源加载完成
};
 
assetsManager.load();
 
textureTask.onSuccess = (task) => {
    const texture = task.texture;
    // 复用 texture,例如应用到多个材质中
};
 
meshTask.onSuccess = (task) => {
    const meshes = task.loadedMeshes;
    // 复用 meshes,例如添加到场景中或存储到对象池中
};

优点:

  • 集中管理: 所有资源加载任务都集中在 AssetsManager 中,便于管理。
  • 事件驱动: 可以监听资源加载完成、错误等事件。
  • 并发加载: 可以同时加载多个资源,提高加载效率。

缺点:

  • 复杂性: 对于非常简单的应用,使用 AssetsManager 可能有些过于复杂。
  • 学习曲线: 需要学习如何使用 AssetsManager。

对象池 (Object Pooling)

js
class ObjectPool {
    private pool: BABYLON.Mesh[] = [];
    private create: () => BABYLON.Mesh;
    private maxSize: number;
 
    constructor(create: () => BABYLON.Mesh, maxSize: number = 10) {
        this.create = create;
        this.maxSize = maxSize;
    }
 
    getObject() {
        if (this.pool.length > 0) {
            return this.pool.pop();
        } else {
            return this.create();
        }
    }
 
    releaseObject(mesh: BABYLON.Mesh) {
        if (this.pool.length < this.maxSize) {
            this.pool.push(mesh);
        } else {
            mesh.dispose();
        }
    }
}
 
// 使用示例
const pool = new ObjectPool(() => {
    return BABYLON.MeshBuilder.CreateBox("pooledBox", {}, scene);
}, 20);
 
const mesh1 = pool.getObject();
mesh1.position = new BABYLON.Vector3(1, 0, 0);
scene.addMesh(mesh1);
 
const mesh2 = pool.getObject();
mesh2.position = new BABYLON.Vector3(2, 0, 0);
scene.addMesh(mesh2);
 
// 释放对象
pool.releaseObject(mesh1);

优点:

  • 性能优化: 避免频繁创建和销毁对象,减少垃圾回收开销。
  • 内存管理: 控制对象池的大小,防止内存泄漏。

缺点:

  • 管理复杂性: 需要手动管理对象的获取和释放。
  • 状态管理: 需要注意对象的状态,例如位置、旋转、缩放等。

共享资源 (Shared Resources) 多个场景共享的资源,可以将其存储在公共的位置

示例: 使用单例模式管理共享资源

js
class SharedResources {
    public static instance: SharedResources;
    public textures: Map<string, BABYLON.Texture> = new Map();
    public meshes: Map<string, BABYLON.Mesh> = new Map();
 
    private constructor() {}
 
    static getInstance() {
        if (!SharedResources.instance) {
            SharedResources.instance = new SharedResources();
        }
        return SharedResources.instance;
    }
 
    getTexture(name: string, url: string) {
        if (!this.textures.has(name)) {
            this.textures.set(name, new BABYLON.Texture(url, scene));
        }
        return this.textures.get(name);
    }
 
    getMesh(name: string, url: string) {
        if (!this.meshes.has(name)) {
            BABYLON.SceneLoader.Load(
                "",
                url,
                scene,
                (loadedScene) => {
                    const mesh = loadedScene.meshes[0];
                    this.meshes.set(name, mesh);
                },
                (error) => {
                    console.error(error);
                }
            );
        }
        return this.meshes.get(name);
    }
}
 
// 使用示例
const sharedResources = SharedResources.getInstance();
 
const texture = sharedResources.getTexture("myTexture", "path/to/texture.png");
const mesh = sharedResources.getMesh("myMesh", "path/to/model.glb");

优点:

  • 集中管理: 所有共享资源都集中在 SharedResources 类中,便于管理。
  • 复用性: 多个场景可以共享相同的资源,避免重复加载。

缺点:

  • 全局状态: 使用全局变量或单例模式可能会导致代码耦合度增加。
  • 线程安全: 需要注意多线程环境下的资源访问。

场景过渡与动画

淡入淡出 (Fade In/Out)

js
// 创建淡入淡出覆盖层
const createFadeOverlay = (scene: BABYLON.Scene) => {
    const advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("FadeUI", true, scene);
    const fadeRectangle = new BABYLON.GUI.Rectangle();
    fadeRectangle.width = 1;
    fadeRectangle.height = 1;
    fadeRectangle.color = "black";
    fadeRectangle.alpha = 0; // 初始透明
    fadeRectangle.horizontalAlignment = BABYLON.GUI.HorizontalAlignment.Center;
    fadeRectangle.verticalAlignment = BABYLON.GUI.VerticalAlignment.Center;
    advancedTexture.addControl(fadeRectangle);
    return fadeRectangle;
};
 
// 淡入函数
const fadeIn = (fadeRectangle: BABYLON.GUI.Rectangle, duration: number = 1000) => {
    return new Promise<void>((resolve) => {
        BABYLON.Tools.TransitionTo(
            "alpha",
            fadeRectangle,
            1,
            duration / 1000,
            () => {
                resolve();
            }
        );
    });
};
 
// 淡出函数
const fadeOut = (fadeRectangle: BABYLON.GUI.Rectangle, duration: number = 1000) => {
    return new Promise<void>((resolve) => {
        BABYLON.Tools.TransitionTo(
            "alpha",
            fadeRectangle,
            0,
            duration / 1000,
            () => {
                resolve();
            }
        );
    });
};
 
// 使用示例
const sceneManager = new SceneManager(engine);
const fadeRectangle = createFadeOverlay(scene);
 
// 切换到新场景并执行淡出淡入过渡
const switchSceneWithFade = async (newSceneName: string) => {
    // 执行淡出
    await fadeOut(fadeRectangle, 1000);
    
    // 切换场景
    sceneManager.switchScene(newSceneName);
    
    // 执行淡入
    await fadeIn(fadeRectangle, 1000);
};
 
// 监听用户输入,例如按键切换场景
window.addEventListener("keydown", (event) => {
    if (event.key === "1") {
        switchSceneWithFade("Scene1");
    } else if (event.key === "2") {
        switchSceneWithFade("Scene2");
    }
});

滑动 (Slide)

js
// 滑动过渡函数
const switchSceneWithSlide = async (newSceneName: string, direction: "left" | "right" | "up" | "down", distance: number = 10, duration: number = 1000) => {
    // 计算目标位置
    const originalPosition = camera.position.clone();
    let targetPosition: BABYLON.Vector3;
 
    switch (direction) {
        case "left":
            targetPosition = new BABYLON.Vector3(originalPosition.x - distance, originalPosition.y, originalPosition.z);
            break;
        case "right":
            targetPosition = new BABYLON.Vector3(originalPosition.x + distance, originalPosition.y, originalPosition.z);
            break;
        case "up":
            targetPosition = new BABYLON.Vector3(originalPosition.x, originalPosition.y + distance, originalPosition.z);
            break;
        case "down":
            targetPosition = new BABYLON.Vector3(originalPosition.x, originalPosition.y - distance, originalPosition.z);
            break;
    }
 
    // 创建滑动动画
    const animation = new BABYLON.Animation("slideAnimation", "position", 60, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_NONE);
    const keys = [
        { frame: 0, value: originalPosition },
        { frame: duration / 16.666, value: targetPosition }
    ];
    animation.setKeys(keys);
 
    camera.animations.push(animation);
 
    // 等待动画完成
    await new Promise((resolve) => {
        scene.onAfterRenderObservable.addOnce(() => {
            resolve();
        });
    });
 
    // 切换场景
    sceneManager.switchScene(newSceneName);
};

旋转 (Rotation)

js
// 旋转过渡函数
const switchSceneWithRotation = async (newSceneName: string, axis: "x" | "y" | "z" = "y", angle: number = Math.PI / 2, duration: number = 1000) => {
    // 计算目标旋转
    const originalRotation = camera.rotation.clone();
    let targetRotation: BABYLON.Vector3;
 
    switch (axis) {
        case "x":
            targetRotation = new BABYLON.Vector3(originalRotation.x + angle, originalRotation.y, originalRotation.z);
            break;
        case "y":
            targetRotation = new BABYLON.Vector3(originalRotation.x, originalRotation.y + angle, originalRotation.z);
            break;
        case "z":
            targetRotation = new BABYLON.Vector3(originalRotation.x, originalRotation.y, originalRotation.z + angle);
            break;
    }
 
    // 创建旋转动画
    const animation = new BABYLON.Animation("rotationAnimation", "rotation", 60, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_NONE);
    const keys = [
        { frame: 0, value: originalRotation },
        { frame: duration / 16.666, value: targetRotation }
    ];
    animation.setKeys(keys);
 
    camera.animations.push(animation);
 
    // 等待动画完成
    await new Promise((resolve) => {
        scene.onAfterRenderObservable.addOnce(() => {
            resolve();
        });
    });
 
    // 切换场景
    sceneManager.switchScene(newSceneName);
};

缩放 (Scaling)

js
// 缩放过渡函数
const switchSceneWithScale = async (newSceneName: string, scale: number = 2, duration: number = 1000) => {
    // 计算目标缩放
    const originalScale = camera.zoom;
    const targetScale = originalScale * scale;
 
    // 创建缩放动画
    const animation = new BABYLON.Animation("scaleAnimation", "zoom", 60, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_NONE);
    const keys = [
        { frame: 0, value: originalScale },
        { frame: duration / 16.666, value: targetScale }
    ];
    animation.setKeys(keys);
 
    camera.animations.push(animation);
 
    // 等待动画完成
    await new Promise((resolve) => {
        scene.onAfterRenderObservable.addOnce(() => {
            resolve();
        });
    });
 
    // 切换场景
    sceneManager.switchScene(newSceneName);
};

组合过渡 (Combined Transitions)

js
// 淡入淡出 + 滑动过渡函数
const switchSceneWithFadeAndSlide = async (newSceneName: string, direction: "left" | "right" | "up" | "down", distance: number = 10, duration: number = 1000) => {
    // 执行淡出
    await fadeOut(fadeRectangle, duration / 2);
    
    // 执行滑动
    await switchSceneWithSlide(newSceneName, direction, distance, duration / 2);
    
    // 执行淡入
    await fadeIn(fadeRectangle, duration / 2);
};
 
// 淡入淡出 + 旋转过渡函数
const switchSceneWithFadeAndRotation = async (newSceneName: string, axis: "x" | "y" | "z" = "y", angle: number = Math.PI / 2, duration: number = 1000) => {
    // 执行淡出
    await fadeOut(fadeRectangle, duration / 2);
    
    // 执行旋转
    await switchSceneWithRotation(newSceneName, axis, angle, duration / 2);
    
    // 执行淡入
    await fadeIn(fadeRectangle, duration / 2);
};

相机系统与优化

1. ArcRotateCamera(弧旋转相机)

特点:

  • 旋转: 允许用户通过鼠标或触摸旋转视角。
  • 缩放: 支持缩放功能,例如鼠标滚轮或捏合手势。
  • 平移: 可以通过键盘或触摸手势进行平移。

适用场景:

  • D 模型查看器: 适用于需要用户自由旋转和查看 3D 模型的场景。
  • 游戏: 适用于第三人称视角的游戏,例如角色扮演游戏、动作游戏等。
  • 建筑可视化: 适用于建筑和室内设计的可视化展示。
js
const camera = new BABYLON.ArcRotateCamera("arcCamera", Math.PI / 2, Math.PI / 4, 10, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);

2. UniversalCamera(通用相机)

特点:

  • 多功能: 结合了 FreeCamera 和 TouchCamera 的功能,支持鼠标、触摸和键盘输入。
  • 第一人称视角: 适用于第一人称视角的应用,例如第一人称射击游戏、虚拟现实等。

适用场景:

  • b第一人称游戏: 适用于需要用户以第一人称视角探索 3D 世界的游戏。
  • b虚拟现实: 适用于虚拟现实应用,提供沉浸式的体验。
  • b模拟器: 适用于驾驶模拟器、飞行模拟器等应用。
js
const camera = new BABYLON.UniversalCamera("universalCamera", new BABYLON.Vector3(0, 1, -5), scene);
camera.attachControl(canvas, true);

3. FreeCamera(自由相机)

特点:

  • 自由移动: 允许用户自由移动相机的位置和方向。
  • 键盘控制: 主要通过键盘控制相机的移动,例如 WASD 键。

适用场景:

  • 飞行模拟: 适用于需要用户自由飞行和探索的场景。
  • 3D 平台游戏: 适用于需要用户跳跃和移动的平台游戏。
js
const camera = new BABYLON.FreeCamera("freeCamera", new BABYLON.Vector3(0, 1, -5), scene);
camera.attachControl(canvas, true);

4. FollowCamera(跟随相机)

特点:

  • 目标跟踪: 相机始终跟随一个目标对象,例如玩家角色。
  • 平滑过渡: 相机移动具有平滑的过渡效果。

适用场景:

  • 第三人称游戏: 适用于需要相机始终跟随玩家角色的第三人称游戏。
  • 运动游戏: 适用于需要跟踪运动对象的游戏,例如赛车游戏、足球游戏等。
js
const camera = new BABYLON.FollowCamera("followCamera", new BABYLON.Vector3(0, 10, -20), scene);
camera.radius = 30; // 相机与目标的距离
camera.heightOffset = 10; // 相机与目标的高度差
camera.rotationOffset = Math.PI / 4; // 相机与目标的水平旋转角度
camera.attachControl(canvas, true);
 
// 设置目标对象
const target = BABYLON.MeshBuilder.CreateBox("target", {}, scene);
camera.lockedTarget = target;

5. AnaglyphCamera(红蓝 3D 相机)

特点:

  • 立体视觉: 通过红蓝滤镜实现立体视觉效果。
  • 3D 效果: 适用于需要 3D 效果的场景,例如 3D 电影、虚拟现实等。

适用场景:

  • 3D 电影: 适用于需要立体视觉效果的 3D 电影。
  • 虚拟现实: 适用于简单的虚拟现实应用。
js
const camera = new BABYLON.AnaglyphCamera("anaglyphCamera", 0.033, 0.033, 0.5, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);

相机优化

1. 视锥体裁剪 (Frustum Culling)

概念: 视锥体裁剪是一种剔除不可见对象的技术。相机有一个视锥体,只有位于视锥体内的对象才会被渲染。

实现:

BabylonJS 默认情况下会进行视锥体裁剪。 手动优化: 可以通过调整相机的 fov (视场角)、aspectRatio (宽高比)、near 和 far (近裁剪面和远裁剪面) 来优化视锥体。

js
const camera = new BABYLON.ArcRotateCamera("arcCamera", Math.PI / 2, Math.PI / 4, 10, BABYLON.Vector3.Zero(), scene);
camera.fov = Math.PI / 3; // 调整视场角
camera.minZ = 0.1; // 设置近裁剪面
camera.maxZ = 100; // 设置远裁剪面

2. 多视口渲染 (Multiple Viewports)

概念: 在一个画布上渲染多个视口,每个视口使用不同的相机。

适用场景:

分屏游戏: 例如双人游戏,每个玩家有自己的视角。

监控场景: 例如在虚拟城市中显示不同区域的视角。

js
// 创建第一个视口
const camera1 = new BABYLON.ArcRotateCamera("camera1", Math.PI / 2, Math.PI / 4, 10, BABYLON.Vector3.Zero(), scene);
camera1.attachControl(canvas, true);
 
// 创建第二个视口
const camera2 = new BABYLON.ArcRotateCamera("camera2", Math.PI / 2, Math.PI / 4, 10, BABYLON.Vector3.Zero(), scene);
camera2.attachControl(canvas, true);
 
// 设置视口大小
const viewport1 = {
    x: 0,
    y: 0,
    width: 0.5,
    height: 1
};
const viewport2 = {
    x: 0.5,
    y: 0,
    width: 0.5,
    height: 1
};
 
// 渲染两个视口
engine.runRenderLoop(() => {
    scene.render(camera1, viewport1);
    scene.render(camera2, viewport2);
});

3. 相机碰撞检测 (Camera Collision Detection) 防止相机穿过物体或穿过场景边界。

BabylonJS 提供了 collision 和 physics 模块,可以用来实现相机碰撞检测。

js
camera.checkCollisions = true;
camera.applyGravity = true;
scene.collisionsEnabled = true;
 
// 创建地面并启用碰撞
const ground = BABYLON.MeshBuilder.CreateGround("ground", { width: 100, height: 100 }, scene);
ground.checkCollisions = true;

4. 相机动画 (Camera Animation)

使用 BabylonJS 的动画系统:

js
const animCam = new BABYLON.Animation("cameraAnimation", "position", 60, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_NONE);
const keys = [
    { frame: 0, value: new BABYLON.Vector3(0, 1, -10) },
    { frame: 60, value: new BABYLON.Vector3(0, 1, -20) }
];
animCam.setKeys(keys);
camera.animations.push(animCam);

使用 Path3D 和 FollowCamera 实现路径跟踪:

js
const points = [
    new BABYLON.Vector3(0, 1, -10),
    new BABYLON.Vector3(10, 1, -10),
    new BABYLON.Vector3(10, 1, -20),
    new BABYLON.Vector3(0, 1, -20)
];
const path = new BABYLON.Path3D(points);
const followCamera = new BABYLON.FollowCamera("followCamera", points[0], scene);
followCamera.attachControl(canvas, true);
followCamera.lockedTarget = mesh;
followCamera.speed = 2;
followCamera.radius = 5;
followCamera.heightOffset = 2;

性能优化

  • 减少相机数量: 尽量使用较少的相机,避免不必要的渲染开销。
  • 优化相机参数: 调整相机的 fov, near 和 far 值,以减少渲染对象的数量。
  • 使用 LOD (Level of Detail) 技术: 根据相机与对象的距离,切换不同细节级别的模型。
  • 实例化: 对于大量相同的对象,使用实例化技术来提高渲染效率。

相机类型概览

BabylonJS 提供了多种相机类型,每种相机都有其独特的功能和适用场景。以下是一些常见的相机类型:

1.ArcRotateCamera(弧旋转相机)

2.UniversalCamera(通用相机)

3.FreeCamera(自由相机)

4.FollowCamera(跟随相机)

5.AnaglyphCamera(红蓝 3D 相机)

6.DeviceOrientationCamera(设备方向相机)

7.VirtualJoysticksCamera(虚拟摇杆相机)

8.VRDeviceOrientationCamera(VR 设备方向相机)

光源

1. HemisphericLight(半球光):环境光

js
const hemisphericLight = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
hemisphericLight.intensity = 0.5; // 调整光照强度
hemisphericLight.diffuse = new BABYLON.Color3(1, 1, 1); // 设置光照颜色

2. DirectionalLight(方向光)

js
const directionalLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-1, -2, -1), scene);
directionalLight.intensity = 1.0; // 调整光照强度
directionalLight.diffuse = new BABYLON.Color3(1, 1, 1); // 设置光照颜色

3. PointLight(点光源)

js
const pointLight = new BABYLON.PointLight("pointLight", new BABYLON.Vector3(0, 10, 0), scene);
pointLight.intensity = 0.8; // 调整光照强度
pointLight.diffuse = new BABYLON.Color3(1, 1, 1); // 设置光照颜色

4. SpotLight(聚光灯)

js
const spotLight = new BABYLON.SpotLight("spotLight", new BABYLON.Vector3(0, 10, 0), new BABYLON.Vector3(0, -1, 0), Math.PI / 3, 2, scene);
spotLight.intensity = 0.7; // 调整光照强度
spotLight.diffuse = new BABYLON.Color3(1, 1, 1); // 设置光照颜色

阴影

阴影是光照的“影子”,它们为 3D 世界增添了深度、层次感和真实感。

1. 阴影生成器 (ShadowGenerator)

js
// 创建 ShadowGenerator 实例
// 第一个参数: 阴影贴图的大小(分辨率),例如 1024。
// 第二个参数: 产生阴影的光源对象。
const shadowGenerator = new BABYLON.ShadowGenerator(1024, directionalLight);

// 添加投射阴影的对象
// 第一个参数: 需要投射阴影的网格对象。
// 第二个参数(可选): 是否包含子对象。
shadowGenerator.addShadowCaster(mesh, true);

// 设置接收阴影的对象:
mesh.receiveShadows = true;

2. 阴影类型

  • 标准阴影贴图 (Standard Shadow Map):
    • 优点: 实现简单,计算成本较低。
    • 缺点: 阴影质量较低,存在锯齿和阴影模糊问题。
    • 适用场景: 简单场景或对阴影质量要求不高的应用。
  • 百分比更近过滤 (PCF, Percentage Closer Filtering):
    • 优点: 阴影质量较高,边缘平滑。
    • 缺点: 计算成本较高。
    • 适用场景: 需要更高质量的阴影,例如建筑可视化、3D 建模等。
  • 指数阴影贴图 (ESM, Exponential Shadow Map):
    • 优点: 阴影质量高,边缘平滑,计算成本适中。
    • 缺点: 可能会出现阴影漏光问题。
    • 适用场景: 需要高质量阴影且对性能有一定要求的场景。
  • 模糊指数阴影贴图 (BESM, Blurred Exponential Shadow Map):
    • 优点: 阴影质量更高,阴影边缘更加柔和。
    • 缺点: 计算成本较高。
    • 适用场景: 需要非常柔和的阴影效果,例如室内场景、角色模型等。
  • 方差阴影贴图 (VSM, Variance Shadow Map):
    • 优点: 阴影质量高,支持软阴影。
    • 缺点: 可能会出现阴影漏光问题,计算成本较高。
    • 适用场景: 需要高质量软阴影的场景,例如户外场景、开放世界等。
js
shadowGenerator.useBlurExponentialShadowMap = true;
shadowGenerator.blurScale = 2; // 调整模糊强度

3. 阴影优化

  • 降低阴影贴图的分辨率,例如从 1024 降低到 512,可以减少计算成本,但会降低阴影质量。
  • 将视锥体划分为多个区域,每个区域使用不同分辨率的阴影贴图。
  • 调整阴影偏移值,可以减少自阴影 (self-shadowing) 问题和阴影漏光问题。
    • shadowGenerator.bias = 0.001; // 调整阴影偏移值

柔和的光照: 使用 HemisphericLight 或 PointLight 来模拟柔和的自然光。

js
const hemisphericLight = new BABYLON.HemisphericLight("hemiLight", new BABYLON.Vector3(0, 1, 0), scene);
hemisphericLight.intensity = 0.3;
hemisphericLight.diffuse = new BABYLON.Color3(1, 0.8, 0.6); // 暖黄色

柔和的阴影: 使用 BESM 或 PCF 阴影类型,并调整阴影偏移和过滤参数,以获得柔和的阴影效果。

js

shadowGenerator.useBlurExponentialShadowMap = true;
shadowGenerator.blurScale = 3; // 增加模糊强度

昏暗的光照: 使用 DirectionalLight 或 SpotLight 来模拟昏暗的光线。

js
const directionalLight = new BABYLON.DirectionalLight("dirLight", new BABYLON.Vector3(-1, -1, -1), scene);
directionalLight.intensity = 0.5;
directionalLight.diffuse = new BABYLON.Color3(0.3, 0.3, 0.5); // 冷蓝色

强烈的阴影

js
shadowGenerator.useStandardShadowMap = true;
shadowGenerator.bias = 0.0001; // 调整阴影偏移值

动态的光照: 使用 PointLight 或 SpotLight 并添加动态变化,例如闪烁、颜色变化等。

js
const pointLight = new BABYLON.PointLight("pointLight", new BABYLON.Vector3(0, 10, 0), scene);
pointLight.intensity = 1.0;
pointLight.diffuse = new BABYLON.Color3(0.5, 0.8, 1); // 蓝白色
 
// 添加闪烁动画
const animateLight = () => {
    pointLight.intensity = 0.5 + 0.5 * Math.sin(Date.now() / 500);
    requestAnimationFrame(animateLight);
};
animateLight();

光晕效果: 使用 Post-Processing 效果,例如 Bloom 或 Glow,来模拟光晕效果。

js
const postProcess = new BABYLON.PostProcess("glow", "glow", ["offset"], ["textureSampler"], 1, camera);
postProcess.onApply = (effect) => {
    effect.setFloat("offset", 0.5);
    effect.setTexture("textureSampler", scene.activeCamera.getActivePostProcess().texture);
};

生命周期

BABYLON.Engine 引擎

  • 创建
    • const engine = new BABYLON.Engine(canvas, true); // 启用抗锯齿
  • 启动渲染循环
    • engine.runRenderLoop(() => { scene.render();});
  • 渲染与更新
    • scene.onBeforeRenderObservable 更新场景
    • scene.onAfterRenderObservable 渲染场景
  • 暂停与恢复
    • engine.pauseRenderLoop() 暂停
    • engine.resumeRenderLoop() 恢复
  • 销毁与释放
    • engine.dispose();

Scene 场景

  • 创建
    • const scene = new BABYLON.Scene(engine);
  • 场景图与对象管理
  • 渲染与更新
    • scene.render() 渲染
      • scene.onBeforeRenderObservable 更新场景
      • scene.onAfterRenderObservable 渲染场景
  • 销毁与释放
    • scene.dispose();

Camera 相机

  • 创建
    • const camera = new BABYLON.ArcRotateCamera("camera1", Math.PI / 2, Math.PI / 4, 5, BABYLON.Vector3.Zero(), scene);
  • 相机控制
    • camera.attachControl(canvas, true); 将相机与画布关联,启用用户输入控制。
  • 销毁与释放
    • camera.dispose();

Light 光源

  • 创建
    • const light = new BABYLON.DirectionalLight("light1", new BABYLON.Vector3(-1, -2, -1), scene);
  • 阴影生成
    js
    const shadowGenerator = new BABylon.ShadowGenerator(1024, light);
    shadowGenerator.addShadowCaster(mesh, true);
  • 销毁与释放
    • light.dispose();

Mesh 网格

  • 创建
    • const mesh = BABYLON.MeshBuilder.CreateBox("box1", {}, scene);
  • 网格变换
  • 销毁与释放
    • mesh.dispose();

Resources 资源

  • 创建
    • const texture = new BABYLON.Texture("path/to/texture.png", scene);
  • 资源复用
  • 销毁与释放
    • texture.dispose();