This could be a issue with Fresh and Deno but I thought I’ll ask here first since I’m using only ESM packages and they’re supposed to work without issues with Deno.
I’m importing the Babylon ESM packages through esm.sh, but I’m getting errors with @babylonjs/materials
and @babylonjs/loaders
(I’ve imported the glTF loader).
babylon-fresh.zip (13.3 KB)
Relevant import URLs in deno.json
:
{
"babylon": "https://esm.sh/@babylonjs/core@6.21.2",
"babylon/loaders/": "https://esm.sh/@babylonjs/loaders@6.21.2/",
"babylon/materials": "https://esm.sh/@babylonjs/materials@6.21.2"
}
Main file is components\babylon\BabylonApp.ts
.
BabylonApp.ts
import "babylon/loaders/glTF";
import { GradientMaterial, SkyMaterial } from "babylon/materials";
import {
BoundingBox,
Camera,
Color3,
Color4,
Constants,
DefaultRenderingPipeline,
DirectionalLight,
Engine,
FreeCamera,
GizmoManager,
ImageProcessingConfiguration,
MeshBuilder,
PBRMaterial,
Quaternion,
ReflectionProbe,
RenderTargetTexture,
Scene,
SceneLoader,
ScenePerformancePriority,
ShadowGenerator,
SSAO2RenderingPipeline,
SSRRenderingPipeline,
StandardMaterial,
Vector3,
} from "babylon";
export default class BabylonApp {
engine: Engine;
scene: Scene;
canvas: HTMLCanvasElement;
camera: FreeCamera;
gizmoManager: GizmoManager;
constructor(canvas: HTMLCanvasElement) {
this.engine = new Engine(canvas);
this.canvas = canvas;
this.scene = new Scene(this.engine);
this._setPerformancePriority("intermediate");
this.camera = this._createController();
this.gizmoManager = this._createGizmoManager();
this._createEnvironment();
this._setSnapshotMode("standard");
}
_createController() {
if (!this.scene) {
throw new Error("No scene");
}
const camera = new FreeCamera(
"Camera",
new Vector3(1.5, 2.5, -15),
this.scene,
);
camera.setTarget(Vector3.Zero());
camera.attachControl(this.canvas, true);
camera.applyGravity = false;
camera.checkCollisions = false;
camera.ellipsoid = new Vector3(1, 1, 1);
camera.minZ = 0.45;
camera.angularSensibility = 4000;
camera.speed = 0.25;
addEventListener("keydown", (event) => {
if (event.key === "Shift") {
camera.speed = 0.5;
}
});
addEventListener("keyup", (event) => {
if (event.key === "Shift") {
camera.speed = 0.25;
}
});
camera.keysUp.push(87);
camera.keysLeft.push(65);
camera.keysDown.push(83);
camera.keysRight.push(68);
camera.keysUpward.push(69);
camera.keysDownward.push(81);
this.scene.onPointerDown = (evt) => {
if (!this.engine) {
throw new Error("No engine");
}
if (evt.button === 2) {
this.engine.enterPointerlock();
}
};
this.scene.onPointerUp = (evt) => {
if (!this.engine) {
throw new Error("No engine");
}
if (evt.button === 2) {
this.engine.exitPointerlock();
}
};
return camera;
}
_createGizmoManager() {
if (!this.scene) {
throw new Error("No scene");
}
const gizmoManager = new GizmoManager(this.scene);
gizmoManager.clearGizmoOnEmptyPointerEvent = true;
gizmoManager.positionGizmoEnabled = true;
gizmoManager.rotationGizmoEnabled = false;
gizmoManager.scaleGizmoEnabled = false;
return gizmoManager;
}
async _createEnvironment(): Promise<void> {
if (!this.scene) {
throw new Error("No scene");
}
if (!this.camera) {
throw new Error("No camera");
}
if (!this.gizmoManager) {
throw new Error("No gizmo manager");
}
this.scene.shadowsEnabled = true;
this.scene.imageProcessingConfiguration.toneMappingEnabled = true;
this.scene.imageProcessingConfiguration.toneMappingType =
ImageProcessingConfiguration.TONEMAPPING_ACES;
this.scene.clearColor = new Color4(1, 1, 1, 1);
this.scene.ambientColor = new Color3(0.6, 0.6, 0.6);
this._setupPostProcessEffects(this.camera);
const skySun = new DirectionalLight(
"skySun",
new Vector3(0, 0, 0),
this.scene,
);
skySun.direction = new Vector3(-0.95, -0.28, 0);
skySun.intensity = 2;
skySun.shadowEnabled = true;
skySun.autoCalcShadowZBounds = true;
const sunShadowGenerator = new ShadowGenerator(1024, skySun);
sunShadowGenerator.setDarkness(0);
sunShadowGenerator.filter =
ShadowGenerator.FILTER_BLURCLOSEEXPONENTIALSHADOWMAP;
sunShadowGenerator.transparencyShadow = true;
this._setupSkybox(this.scene, skySun);
const { meshes: kenneyPlayground } = await SceneLoader
.ImportMeshAsync(
"",
"https://raw.githubusercontent.com/fazil47/assets/master/3d/environments/",
"KenneyPlayground.glb",
this.scene,
);
kenneyPlayground.forEach((mesh) => {
mesh.isPickable = true;
mesh.checkCollisions = true;
mesh.receiveShadows = true;
if (mesh.material) {
if (
mesh.material instanceof PBRMaterial ||
mesh.material instanceof StandardMaterial
) {
mesh.material.ambientColor = new Color3(1, 1, 1);
mesh.material.backFaceCulling = true;
}
}
sunShadowGenerator.addShadowCaster(mesh);
});
if (!this.gizmoManager.attachableMeshes) {
this.gizmoManager.attachableMeshes = kenneyPlayground.slice(1);
} else {
this.gizmoManager.attachableMeshes.push(...kenneyPlayground.slice(1));
}
const { meshes: bmw } = await SceneLoader.ImportMeshAsync(
"",
"https://raw.githubusercontent.com/fazil47/assets/master/3d/vehicles/",
"bmw_m4_2021.glb",
this.scene,
);
const bmwBoundingBox = new BoundingBox(
new Vector3(0, 0, 0),
new Vector3(0, 0, 0),
);
bmw.forEach((mesh) => {
mesh.isPickable = true;
mesh.receiveShadows = true;
sunShadowGenerator.addShadowCaster(mesh);
if (mesh.material) {
if (
mesh.material instanceof PBRMaterial ||
mesh.material instanceof StandardMaterial
) {
mesh.material.ambientColor = new Color3(1, 1, 1);
mesh.material.backFaceCulling = true;
}
}
bmwBoundingBox.reConstruct(
Vector3.Minimize(
bmwBoundingBox.minimumWorld,
mesh.getBoundingInfo().boundingBox.minimumWorld,
),
Vector3.Maximize(
bmwBoundingBox.maximumWorld,
mesh.getBoundingInfo().boundingBox.maximumWorld,
),
);
});
bmw[0].position.y += 0.09;
bmw[0].rotationQuaternion = Quaternion.RotationAxis(
Vector3.Up(),
Math.PI / 6,
);
const bmwBoundingBoxMesh = MeshBuilder.CreateBox(
"bmwBoundingBox",
{
width: bmwBoundingBox.maximumWorld.x - bmwBoundingBox.minimumWorld.x,
height: bmwBoundingBox.maximumWorld.y - bmwBoundingBox.minimumWorld.y,
depth: bmwBoundingBox.maximumWorld.z - bmwBoundingBox.minimumWorld.z,
},
this.scene,
);
bmw[0].parent = bmwBoundingBoxMesh;
this.gizmoManager.attachableMeshes.push(bmwBoundingBoxMesh);
bmwBoundingBoxMesh.isPickable = true;
bmwBoundingBoxMesh.isVisible = false;
this._resetSnapshot();
}
_setupSkybox(scene: Scene, skySun: DirectionalLight) {
const skyboxMaterial = new SkyMaterial("skyboxMaterial", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.useSunPosition = true;
skyboxMaterial.sunPosition = skySun.direction.scale(-1);
const quaternionDelta = 0.02;
addEventListener("keydown", (event) => {
if (skySun.direction.y <= 0) {
if (event.key === "1") {
this._rotateSun(skySun, skyboxMaterial, quaternionDelta);
} else if (event.key === "2") {
this._rotateSun(skySun, skyboxMaterial, -quaternionDelta);
}
} else {
skySun.direction.y = 0;
}
});
skyboxMaterial.luminance = 0.4;
skyboxMaterial.turbidity = 10;
skyboxMaterial.rayleigh = 4;
skyboxMaterial.mieCoefficient = 0.005;
skyboxMaterial.mieDirectionalG = 0.98;
skyboxMaterial.cameraOffset.y = 200;
skyboxMaterial.disableDepthWrite = false;
const skybox = MeshBuilder.CreateBox(
"skyBox",
{ size: 1000.0 },
scene,
);
skybox.material = skyboxMaterial;
skybox.infiniteDistance = true;
skybox.isPickable = false;
const groundboxMaterial = new GradientMaterial(
"groundboxMaterial",
scene,
);
groundboxMaterial.topColor = new Color3(1, 1, 1);
groundboxMaterial.topColorAlpha = 0;
groundboxMaterial.bottomColor = new Color3(0.67, 0.56, 0.45);
groundboxMaterial.offset = 0.5;
groundboxMaterial.smoothness = 1;
groundboxMaterial.scale = 0.1;
groundboxMaterial.backFaceCulling = false;
groundboxMaterial.disableDepthWrite = true;
groundboxMaterial.freeze();
const groundbox = MeshBuilder.CreateSphere("groundbox", {
diameter: 500,
}, scene);
groundbox.layerMask = 0x10000000;
groundbox.position.y = 0;
groundbox.infiniteDistance = true;
groundbox.material = groundboxMaterial;
groundbox.isPickable = false;
const reflectionProbe = new ReflectionProbe(
"ref",
64,
scene,
false,
);
reflectionProbe.renderList?.push(skybox);
reflectionProbe.renderList?.push(groundbox);
reflectionProbe.refreshRate =
RenderTargetTexture.REFRESHRATE_RENDER_ONEVERYTWOFRAMES;
scene.environmentTexture = reflectionProbe.cubeTexture;
scene.environmentIntensity = 2;
}
_setupPostProcessEffects(camera: Camera) {
if (!this.scene) {
throw new Error("No scene");
}
const defaultPipeline = new DefaultRenderingPipeline(
"default",
false,
this.scene,
[camera],
);
defaultPipeline.fxaaEnabled = true;
defaultPipeline.glowLayerEnabled = true;
if (SSAO2RenderingPipeline.IsSupported) {
const ssao = new SSAO2RenderingPipeline(
"ssao",
this.scene,
0.5,
[camera],
);
ssao.totalStrength = 1.2;
ssao.base = 0;
ssao.radius = 1.0;
ssao.epsilon = 0.02;
ssao.samples = 16;
ssao.maxZ = 250;
ssao.minZAspect = 0.5;
ssao.expensiveBlur = true;
ssao.bilateralSamples = 16;
ssao.bilateralSoften = 1;
ssao.bilateralTolerance = 1;
}
{
const ssr = new SSRRenderingPipeline(
"ssr",
this.scene,
[camera],
false,
Constants.TEXTURETYPE_UNSIGNED_BYTE,
);
ssr.thickness = 0.1;
ssr.selfCollisionNumSkip = 2;
ssr.enableAutomaticThicknessComputation = false;
ssr.blurDispersionStrength = 0.02;
ssr.roughnessFactor = 0.05;
ssr.enableSmoothReflections = true;
ssr.step = 20;
ssr.maxSteps = 100;
ssr.maxDistance = 1000;
ssr.blurDownsample = 1;
ssr.ssrDownsample = 1;
ssr.isEnabled = false;
}
}
_rotateSun(
skySun: DirectionalLight,
skyMaterial: SkyMaterial,
angle: number,
) {
skySun.direction.applyRotationQuaternionInPlace(
Quaternion.RotationAxis(Vector3.Forward(), angle),
);
skyMaterial.sunPosition = skySun.direction.scale(-1);
const sunColor = this._getSunColor(Math.abs(skySun.direction.y));
skySun.diffuse = sunColor;
this.scene!.ambientColor = sunColor;
}
_getSunColor(sunElevation: number) {
const sunriseSunsetColor = new Color3(1, 0.65, 0);
const daySkyColor = new Color3(0.6, 0.6, 0.6);
const dimWhiteSkyColor = new Color3(0.4, 0.4, 0.4);
const sunriseSunsetThreshold = 0.2;
if (sunElevation <= sunriseSunsetThreshold) {
const t = sunElevation / sunriseSunsetThreshold;
const interpolatedColor = this._interpolateColors(
dimWhiteSkyColor,
sunriseSunsetColor,
daySkyColor,
t,
);
return interpolatedColor;
} else {
return daySkyColor;
}
}
_interpolateColors(
color1: Color3,
color2: Color3,
color3: Color3,
t: number,
) {
const interpolatedColor12 = Color3.Lerp(color1, color2, t);
const interpolatedColor23 = Color3.Lerp(color2, color3, t);
const finalInterpolatedColor = Color3.Lerp(
interpolatedColor12,
interpolatedColor23,
t,
);
return finalInterpolatedColor;
}
_setPerformancePriority(
priority: "compatible" | "intermediate" | "aggressive",
) {
if (!this.scene) {
throw new Error("No scene");
}
switch (priority) {
case "aggressive":
this.scene.performancePriority = ScenePerformancePriority.Aggressive;
break;
case "intermediate":
this.scene.performancePriority = ScenePerformancePriority.Intermediate;
break;
case "compatible":
default:
this.scene.performancePriority =
ScenePerformancePriority.BackwardCompatible;
}
}
_setSnapshotMode(mode: "disabled" | "standard" | "fast") {
if (!this.scene) {
throw new Error("No scene");
}
this.scene.executeWhenReady(() => {
if (!this.engine) {
throw new Error("No engine");
}
switch (mode) {
case "disabled":
this.engine.snapshotRendering = false;
break;
case "standard":
this.engine.snapshotRenderingMode =
Constants.SNAPSHOTRENDERING_STANDARD;
this.engine.snapshotRendering = true;
break;
case "fast":
this.engine.snapshotRenderingMode = Constants.SNAPSHOTRENDERING_FAST;
this.engine.snapshotRendering = true;
break;
}
});
}
_resetSnapshot() {
if (!this.scene) {
throw new Error("No scene");
}
this.scene.executeWhenReady(() => {
if (!this.engine) {
throw new Error("No engine");
}
this.engine.snapshotRenderingReset();
});
}
}