GLSL着色器产生的moire图案去除

9

我已经建立了一个最简单的测试案例,你可以通过使用自定义的片段着色器 (jsfiddle),轻松地看到由于欠采样振荡的红色颜色而产生的莫尔纹图案。

GLSL中消除这种图案的一般技术是什么?我想这需要使用导数扩展,但我从来没有完全理解过如何实现它。我基本上要做抗锯齿处理,对吧?

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>

更新:我尝试实现了超采样,但不确定是否实现正确,似乎并没有太大帮助。


@WacławJasper 使用uv坐标只是获得这种效果的简单方法。接缝本身并没有造成问题,否则您只会在接缝周围看到问题。 - Brendan Annable
1个回答

9

很遗憾,这里的moire图案是高对比度线条接近Nyquist频率的结果。换句话说,没有好的方法让1或2像素宽的高对比度线条平滑地移动到下一个像素,而不是引入这样的伪影,或者使线条模糊不可分辨。

您提到了导数扩展,确实可以使用该扩展来确定UV在屏幕空间中变化的速度,从而确定需要多少模糊来解决这个问题。在下面修改后的示例中,我尝试使用fwidth将球体变为红色,以显示噪声变坏的位置。尝试调整一些定义为常量的浮点数,看看你能发现什么。

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>


谢谢您的回答!我能否再请问一下有关此类技术的进一步信息或文档呢?它们是否都只是模糊高频内容? - Brendan Annable
1
据我所知,这是唯一的解决方案:基于像素的显示器无法显示高于像素密度的频率。在上面的代码中,thicknessblendregion由于使用了fwidth而以屏幕空间像素为单位进行指定,这意味着当它们之间的距离小于约3个像素时,线条开始重叠。小于此值时,您只有1个像素用于线条和1个像素用于间隙,当线条不粘附到像素网格时,情况会变得混乱。 - emackey
1
顺便说一下,该代码所基于的逻辑在Cozzi和Ring的书《虚拟地球3D引擎设计》中更详细地描述,第4.13节。 - emackey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接