Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Removing moire patterns produced by GLSL shaders

I have setup this minimal test case, which you can easily see the moire patterns produced by undersampling the oscilating red colour using a custom fragment shader (jsfiddle).

What is the general technique for removing such patterns using GLSL? I assume it involves the derivatives extension, but I've never quite understood how to implement it. I basically have to do anti-aliasing, I think?

var canvas = document.getElementById('canvas');
var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
var camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientWidth, 1, 1000);

var geometry = new THREE.SphereGeometry(50, 50, 50);
var material = new THREE.ShaderMaterial({
  vertexShader: document.getElementById('vertex-shader').textContent,
  fragmentShader: document.getElementById('fragment-shader').textContent
});
var sphere = new THREE.Mesh(geometry, material);

scene.add(sphere);

camera.position.z = 100;

var period = 30;
var clock = new THREE.Clock();
render();

function render() {
  requestAnimationFrame(render);
  
  if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    camera.aspect = canvas.clientWidth /  canvas.clientHeight;
    camera.updateProjectionMatrix();
  }
  
  sphere.rotation.y -= clock.getDelta() * 2 * Math.PI / period;
  renderer.render(scene, camera);
}
html, body, #canvas {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<canvas id="canvas"></canvas>
<script id="vertex-shader" type="x-shader/x-vertex">
  varying vec2 vUv;

  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
  #define M_TAU 6.2831853071795864769252867665590

  varying vec2 vUv;

  void main() {
    float w = sin(500.0 * M_TAU * vUv.x) / 2.0 + 0.5;
    vec3 color = vec3(w, 0.0, 0.0);
    gl_FragColor = vec4(color, 1.0);
  }
</script>

Update: I've tried to implement super-sampling, not sure if I have implemented it correctly but it doesn't seem to help too much.

like image 897
Brendan Annable Avatar asked Dec 30 '15 05:12

Brendan Annable


1 Answers

Unfortunately, the moire pattern here is a result of the high-contrast lines approaching the Nyquist Frequency. In other words, there's no good way to have a 1- or 2-pixel-wide high-contrast line smoothly shift to the next pixel over, without either introducing such artifacts, or blurring the lines to be indistinguishable.

You mentioned the derivatives extension, and indeed that extension can be used to figure out how quickly your UVs are changing in screen space, and thus, figure out how much blurring is needed to sort of sweep this problem under the rug. In the modified version of your own example below, I attempt to use fwidth to turn the sphere red where the noise gets bad. Try playing with some of the floats that are defined to constants here, see what you can find.

var canvas = document.getElementById('canvas');
var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
var camera = new THREE.PerspectiveCamera(75, canvas.clientWidth / canvas.clientWidth, 1, 1000);

var geometry = new THREE.SphereGeometry(50, 50, 50);
var material = new THREE.ShaderMaterial({
  vertexShader: document.getElementById('vertex-shader').textContent,
  fragmentShader: document.getElementById('fragment-shader').textContent
});
var sphere = new THREE.Mesh(geometry, material);

scene.add(sphere);

camera.position.z = 100;

var period = 30;
var clock = new THREE.Clock();
render();

function render() {
  requestAnimationFrame(render);
  
  if (canvas.width !== canvas.clientWidth || canvas.height !== canvas.clientHeight) {
    renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
    camera.aspect = canvas.clientWidth /  canvas.clientHeight;
    camera.updateProjectionMatrix();
  }
  
  sphere.rotation.y -= clock.getDelta() * 2 * Math.PI / period;
  renderer.render(scene, camera);
}
html, body, #canvas {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<canvas id="canvas"></canvas>
<script id="vertex-shader" type="x-shader/x-vertex">
  varying vec2 vUv;

  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
</script>
<script id="fragment-shader" type="x-shader/x-fragment">
  #extension GL_OES_standard_derivatives : enable

  #define M_TAU 6.2831853071795864769252867665590

  varying vec2 vUv;

  void main() {
    float linecount = 200.0;
    float thickness = 0.0;
    float blendregion = 2.8;
    
    // Loosely based on https://github.com/AnalyticalGraphicsInc/cesium/blob/1.16/Source/Shaders/Materials/GridMaterial.glsl#L17-L34
    float scaledWidth = fract(linecount * vUv.s);
    scaledWidth = abs(scaledWidth - floor(scaledWidth + 0.5));
    vec2 dF = fwidth(vUv) * linecount;
    float value = 1.0 - smoothstep(dF.s * thickness, dF.s * (thickness + blendregion), scaledWidth);
    gl_FragColor = vec4(value, 0.0, 0.0, 1.0);
  }
</script>
like image 112
emackey Avatar answered Sep 21 '22 20:09

emackey