Sawtooth optimization problem



Picture 1 is on pg demo and Picture 2 is my local vite build project.
Looking at the details, Picture 2 has obvious jagged edges, not clear enough.
These are all displayed on my phone.



I’m sure the code on both sides is almost the same, except for the dependency package version
The installation package on pg comes from cdn
This is my local version

    "@babylonjs/core": "^7.1.0",
    "@babylonjs/gui": "^7.1.0",
    "@babylonjs/havok": "^1.3.3",
    "@babylonjs/inspector": "^7.1.0",
    "@babylonjs/loaders": "^7.1.0",
    "@babylonjs/materials": "^7.1.0",
    "@babylonjs/post-processes": "^7.1.0",
// engine.setHardwareScalingLevel(1 / window.devicePixelRatio)
// engine.adaptToDeviceRatio = true

I tried to solve it by anti-aliasing, but this setting is very performance-consuming

Do you have a repro in the playground ?

Sorry, I believe this is no problem in the pg environment. It only appears in my local environment.
Moreover, open this service page on your mobile phone

step1,git clone (
setp2, npm install & npm run dev
step3, Make sure your computer and your iPhone are on the same LAN
step4, Open the page on your phone
You will find that the page opened in this way looks blurry and not high-definition enough.

Compare it to the example on pg,,Or you can download this case on pg and open the comparison screen on your mobile phone in the same way.
Either way, it’s normal, except that the one created with vite is blurry

import '@babylonjs/core/Debug/debugLayer'
import '@babylonjs/inspector'
import { Engine, Scene, AxesViewer, Vector3, HavokPlugin, ArcRotateCamera, Tools, Color3, SceneLoader, Color4 } from '@babylonjs/core'
// import HavokPhysics from '@babylonjs/havok'
// import MainScene from './game/main-scene'

const canvas = document.getElementById('renderCanvas') as HTMLCanvasElement

const startRenderLoop = function (engine: Engine, canvas: HTMLCanvasElement) {
  engine.runRenderLoop(function () {
    if (sceneToRender && sceneToRender.activeCamera) {

let engine: Engine
let scene: Scene
let sceneToRender: Scene
const createDefaultEngine = function () {
  return new Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: false })

class Playground {
  static CreateScene(engine: Engine, canvas: HTMLCanvasElement) {
    const scene = new Scene(engine)

    // new MainScene(scene, canvas, engine)
    scene.clearColor = new Color4(0.31, 0.48, 0.64)

    //add an arcRotateCamera to the scene
    var camera = new ArcRotateCamera('camera', Tools.ToRadians(125), Tools.ToRadians(70), 25, new Vector3(0, 3, 0), scene)
    camera.lowerRadiusLimit = 10
    camera.upperRadiusLimit = 40

    camera.attachControl(canvas, true)

    const pirateFortImport = SceneLoader.ImportMesh('', './model/', 'pirate-fort.glb', scene, (meshes) => {
      meshes[0].name = 'pirateFort'
      scene.getMeshByName('sea')!.material!.needDepthPrePass = true
      scene.getLightByName('Sun')!.intensity = 12

    return scene

const createScene = function () {
  return Playground.CreateScene(engine, engine.getRenderingCanvas()!)

const initFunction = async function () {
  const asyncEngineCreation = async function () {
    try {
      return createDefaultEngine()
    } catch (e) {
      console.log('the available createEngine function failed. Creating the default engine instead')
      return createDefaultEngine()

  engine = await asyncEngineCreation()
  if (!engine) throw 'engine should not be null.'
  startRenderLoop(engine, canvas)
  scene = createScene()

initFunction().then(() => {
  sceneToRender = scene

const fps = () => {
  const dom = document.getElementById('display-fps')
  if (dom) {
    dom.innerHTML = `${engine.getFps().toFixed()} fps`
  } else {
    const div = document.createElement('div') = 'display-fps'
    div.innerHTML = '0'

window.addEventListener('keydown', ev => {
  // Shift+Ctrl+Alt+I
  if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) {
    if (scene.debugLayer.isVisible()) {
    } else {

// resize window
window.addEventListener('resize', () => {

window.onbeforeunload = () => {

The code is only useful for the app.ts file. You don’t need to look at other files.

you should try to add antialias true in the engine creation options

1 Like

This optionsis not added to pg, but the effect is quite good. And I added this options, the fps will automatically drop from 60 to less than 30

// engine.setHardwareScalingLevel(1 / window.devicePixelRatio)
// engine.adaptToDeviceRatio = true

That’s the right way to do it, and it’s the same reason that your app needs multi-fold graphs. Unfortunately, rendering the scene at 1:1 resolution can be a bit stressful for phones.
You can try to optimize the scene frame time by freezing the mesh material, etc.
This article can be used as a reference:Why is "Mesh Selection" time so high and how to reduce it? - #5 by xiehangyun

Thank you both @sebavan and @xiehangyun ,I tried to find out where the problem was and found that it was caused by Vite. Will the same thing happen if I try webpack again?

It’s not clear to me what you’re talking about the problem.

If it is a scaling problem with the canvas, it is because the logical pixels of the mobile phone are different from the physical pixels, so the width and height of the canvas CSS are based on the logical pixels, so the width and height of the canvas must be adjusted to align the physical pixels to get a clear image. For: 1 / window.devicePixelRatio.

If you’re saying that VITE is causing your framerate to be much lower than expected, it’s probably because the babylonjs object is being listened to by vue For: this.scene = scene. In this case, you can use the Pereformance snapshot to see the call stack for each frame, and if there are stack calls from vue, you have to find all the code to mount any object under babylonjs to vue to work with.
Because there are parent-child references in babylonjs instance objects, any one object can cause all objects in the entire scene to be traversed and listened to by vue, which will incur a huge CPU performance overhead every frame.