mrdoob / three.js

JavaScript 3D Library.
https://threejs.org/
MIT License
102.34k stars 35.35k forks source link

Points rendering glitch in iOS Safari #27470

Closed trusktr closed 9 months ago

trusktr commented 9 months ago

Description

iOS Safari not rendering Points properly. I have a feeling this is a bug in Safari.

Reproduction steps

  1. Open the below CodePen example in iOS Safari (Note, the asset it loads is 37 MB, in case it is taking some time)
  2. It looks like this:

    Click to view screenshot ![IMG_0009](https://github.com/mrdoob/three.js/assets/297678/266b09c6-6526-48b5-accb-f29b9d37eb8c)
  3. Open the same example in Android Chrome
  4. It looks like this:

    Click to view screenshot ![Screenshot_20231229-181354](https://github.com/mrdoob/three.js/assets/297678/a405b827-d1e3-49bc-8fd6-aaf34df86434)

Code

<!--
  In iOS Safari, at time of writing, the scene is all glitched out, like in this screenshot:

  https://twitter.com/jeffscottward/status/1740775461246914865
-->

<base href="https://docs.lume.io"><script src="./importmap.js"></script>

<loading-icon id="loading"></loading-icon>

<script>
    import('lume/dist/examples/LoadingIcon.js')
</script>

<div info="" align="center">A scene scanned with a Velodyne laser radar scanner (lidar),<br>focused on a Ford Shelby GT350.</div>

<style>
  html,
  body {
    height: 100%;
    margin: 0;
    background: #222;
    --color: 228, 20, 255 /*vibrant pink*/;
    color: rgb(var(--color)); font-family: sans-serif;
  }
  canvas {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    touch-action: none;
  }
  loading-icon {
    --loading-icon-color: var(--color);
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 10px; height: 10px;
  }
  [info] {
    position: absolute; top: 0; left: 0; width: 100%;
    box-sizing: border-box; padding: 10px;
    z-index: 1;
  }
</style>

<script type="module">  
    import * as THREE from 'three'
    import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader.js';
    import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

    const threeScene = new THREE.Scene()
    const renderer = new THREE.WebGLRenderer()
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(window.devicePixelRatio)
    renderer.setClearAlpha(0)

    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000)
    camera.position.z = 2000
    threeScene.add(camera)
    const controls = new OrbitControls(camera, renderer.domElement)
    controls.enableDamping = true

    const ambientLight = new THREE.AmbientLight("white", 1.2)
    threeScene.add(ambientLight)

    const pointLight = new THREE.PointLight("deeppink", 1500000)
    pointLight.position.set(200, 200, 200)
    threeScene.add(pointLight)

    const box = new THREE.Mesh(new THREE.BoxGeometry(200, 200, 200), new THREE.MeshPhongMaterial({color: 'red'}))
    box.material = THREE.DoubleSide
    threeScene.add(box)

    let points = null

    function resize() {
        camera.aspect = window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()
        renderer.setSize(window.innerWidth, window.innerHeight)
    }
    window.onresize = resize
    resize()

    requestAnimationFrame(function loop(t) {
        pointLight.position.set(500 * Math.sin(t * 0.001), 500 * Math.cos(t * 0.001), pointLight.position.z)
        controls.update();
        renderer.render(threeScene, camera)

        requestAnimationFrame(loop)
    })

    const loads = []

    loads.push(new Promise(resolve => {
        const loader = new PLYLoader()

        loader.load('/examples/velodyne-lidar-scan/shelby-scene.ply', (geometry) => {
            const material = new THREE.MeshPhysicalMaterial({ color: 'royalblue' })
            // points = new THREE.Mesh(geometry, material) // no glitch
            points = new THREE.Points(geometry, material) // iOS Safari glitches

            // Do this, or else the point light will not do anything at all.
            // Three.js is not just a walk in the park!
            geometry.computeVertexNormals();

            // Replicate the original positioning
            points.rotation.set(THREE.MathUtils.degToRad(-90), 0, 0)
            const pos = [0, 0, 60]
            points.position.set(...pos)
            points.scale.set(50, 50, 50)

            threeScene.add(points)

            resolve()
        })
    }))

    loads.push(new Promise(resolve => {
        const loader = new GLTFLoader()

        loader.load('/examples/velodyne-lidar-scan/puck.gltf', (gltf) => {
            threeScene.add(gltf.scene)
            resolve()
        })
    }))

    Promise.all(loads).then(() => {
        document.body.append(renderer.domElement)
        loading.remove()
    })
</script>

Live example

Screenshots

See reproduction

Version

r158 (at time of writing, see https://docs.lume.io/modules/three/package.json)

Device

Mobile

Browser

Safari

OS

iOS

gkjohnson commented 9 months ago
const material = new THREE.MeshPhysicalMaterial({ color: 'royalblue' })
points = new THREE.Points(geometry, material) // iOS Safari glitches

MeshPhysicalMaterial is not designed to be used with Points - you must use "PointsMaterial".

trusktr commented 9 months ago

Ah, indeed. I was going for that shimmering effect. I can fallback to PointsMaterial on iOS.

trusktr commented 9 months ago

I see this is marked as "Help (please use the forum)". Should people post in the forum first, then if something is actually a bug post it here?

Just curious because it wasn't obvious if the above was a bug or not. I figured switching to gl.POINTS would effectively not make any difference (merely change the fragments on screen), and it worked great everywhere except specifically iOS Safari.

mrdoob commented 9 months ago

Should people post in the forum first, then if something is actually a bug post it here?

Yes

RenaudRohlinger commented 9 months ago

The issue arise when not specifying gl_PointSize in the vertex shader on some GPUs @trusktr. If you wanted to be consistant with your visual on iOS you could also use onBeforeCompile and specify gl_PointSize with the same code as in points.glsl.js.