Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flickering of THREE.Points based on camera position and texture coordinates, but only on Nvidia cards

Tags:

three.js

I have a problem with flickering of THREE.Points depending on their UV coordinates, as seen in the following codepen: http://codepen.io/anon/pen/qrdQeY?editors=0010

The code in the codepen is condensed down as much as possible (171 lines), but to summarize what I'm doing:

  • Rendering sprites using THREE.Points
  • BufferGeometry contains spritesheet index and position for each sprite
  • RawShaderMaterial with custom vertex and pixel shader to lookup up the UV coordinates of the sprite for the given index
  • a 128x128px spritesheet with 4x4 cells contains the sprites

Here's the code:

/// FRAGMENT SHADER ===========================================================
const fragmentShader = `
precision highp float;

uniform sampler2D spritesheet;

// number of spritesheet subdivisions both vertically and horizontally
// e.g. for a 4x4 spritesheet this number is 4
uniform float spritesheetSubdivisions;

// vParams[i].x = sprite index
// vParams[i].z = sprite alpha
varying vec3 vParams;

/**
 * Maps regular UV coordinates spanning the entire spritesheet
 * to a specific sprite within the spritesheet based on the given index,
 * which points into a spritesheel cell (depending on spritesheetSubdivisions
 * and assuming that the spritesheet is regular and square).
 */
vec2 spriteIndexToUV(float idx, vec2 uv) {
    float cols = spritesheetSubdivisions;
    float rows = spritesheetSubdivisions;

    float x = mod(idx, cols);
    float y = floor(idx / cols);

    return vec2(x / cols + uv.x / cols, 1.0 - (y / rows + (uv.y) / rows));
}

void main() {
    vec2 uv = spriteIndexToUV(vParams.x, gl_PointCoord);
    vec4 diffuse = texture2D(spritesheet, uv);

    float alpha = diffuse.a * vParams.z;
    if (alpha < 0.5) discard;

    gl_FragColor = vec4(diffuse.xyz, alpha);
}
`

// VERTEX SHADER ==============================================================
const vertexShader = `
precision highp float;

uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform float size;
uniform float scale;

attribute vec3 position;
attribute vec3 params; // x = sprite index, y = unused, z = sprite alpha
attribute vec3 color;

varying vec3 vParams;

void main() {
    vParams = params;

    vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
    gl_Position = projectionMatrix * mvPosition;
    gl_PointSize = size * ( scale / - mvPosition.z );
}
`

// THREEJS CODE ===============================================================

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);

const renderer = new THREE.WebGLRenderer({canvas: document.querySelector("#mycanvas")});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0xf0f0f0)

const pointGeometry = new THREE.BufferGeometry()
pointGeometry.addAttribute("position", new THREE.BufferAttribute(new Float32Array([
  -1.5, -1.5, 0,
  -0.5, -1.5, 0,
  0.5, -1.5, 0,
  1.5, -1.5, 0,

  -1.5, -0.5, 0,
  -0.5, -0.5, 0,
  0.5, -0.5, 0,
  1.5, -0.5, 0,

  -1.5, 0.5, 0,
  -0.5, 0.5, 0,
  0.5, 0.5, 0,
  1.5, 0.5, 0,

  -1.5, 1.5, 0,
  -0.5, 1.5, 0,
  0.5, 1.5, 0,
  1.5, 1.5, 0,
]), 3))

pointGeometry.addAttribute("params", new THREE.BufferAttribute(new Float32Array([
  0, 0, 1,    // sprite index 0 (row 0, column 0)
  1, 0, 1,    // sprite index 1 (row 0, column 1)
  2, 0, 1,    // sprite index 2 (row 0, column 2)
  3, 0, 1,    // sprite index 3 (row 0, column 4)

  4, 0, 1,    // sprite index 4 (row 1, column 0)
  5, 0, 1,    // sprite index 5 (row 1, column 1)
  6, 0, 1,    // ...
  7, 0, 1,

  8, 0, 1,
  9, 0, 1,
  10, 0, 1,
  11, 0, 1,

  12, 0, 1,
  13, 0, 1,
  14, 0, 1,
  15, 0, 1
]), 3))

const img = document.querySelector("img")
const texture = new THREE.TextureLoader().load(img.src);

const pointMaterial = new THREE.RawShaderMaterial({
  transparent: true,
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  uniforms: {
    spritesheet: {
      type: "t",
      value: texture
    },
    spritesheetSubdivisions: {
      type: "f",
      value: 4
    },
    size: {
      type: "f",
      value: 1
    },
    scale: {
      type: "f",
      value: window.innerHeight / 2
    }
  }
})

const points = new THREE.Points(pointGeometry, pointMaterial)
scene.add(points)

const render = function (timestamp) {
  requestAnimationFrame(render);


  camera.position.z = 5 + Math.sin(timestamp / 1000.0)

  renderer.render(scene, camera);
};

render();

// resize viewport
window.addEventListener( 'resize', onWindowResize, false );

function onWindowResize(){

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize( window.innerWidth, window.innerHeight );

}

If you have an Nvidia card you will see three sprites flicker while the camera is moving back and forth along the Z axis. On integrated Intel graphics chips the problem does not occur.

I'm not sure how to solve this problem. The affected uv coordinates seem kind of random. I'd be grateful for any pointers.

like image 753
Bunkerbewohner Avatar asked Nov 08 '22 01:11

Bunkerbewohner


1 Answers

The mod()/floor() calculations inside your spriteIndexToUV() function are causing problems in certain constellations (when spriteindex is a multiple of spritesheetSubdivisions).

I could fix it by tweaking the cols variable with a small epsilon:

vec2 spriteIndexToUV(float idx, vec2 uv) 
{
  float cols = spritesheetSubdivisions - 1e-6; // subtract epsilon
  float rows = spritesheetSubdivisions;

  float x = mod(idx, cols);
  float y = floor(idx / cols);

  return vec2(x / cols + uv.x / cols, 1.0 - (y / rows + (uv.y) / rows));
}

PS: That codepen stuff is really cool, didn't know that this existed :-)

edit: It might be even better/clearer to write it like this:

float cols = spritesheetSubdivisions;
float rows = spritesheetSubdivisions;

float y = floor ((idx+0.5) / cols);
float x = idx - cols * y;

That way, we keep totally clear of any critical situations in the floor operation -- plus we get rid of the mod() call.

As to why floor (idx/4) is sometimes producing 0 instead of 1 when idx should be exactly 4.0, I can only speculate that the varying vec3 vParams is subjected to some interpolation when it goes from the vertex-shader to the fragment-shader stage, thus leading to the fragment-shader seeing e.g. 3.999999 instead of exactly 4.0.

like image 51
ThorngardSO Avatar answered Dec 02 '22 12:12

ThorngardSO