去除 GLSL 着色器产生的波纹图案

Posted

技术标签:

【中文标题】去除 GLSL 着色器产生的波纹图案【英文标题】:Removing moire patterns produced by GLSL shaders 【发布时间】:2016-04-04 01:59:46 【问题描述】:

我已经设置了这个最小的测试用例,您可以轻松地看到通过使用自定义片段着色器 (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>

更新:我尝试实现super-sampling,不确定我是否正确实现了它,但它似乎没有太大帮助。

【问题讨论】:

@WacławJasper 使用 uv 坐标只是获得这种效果的一种简单方法。接缝本身不会造成问题,否则您只会看到接缝本身的问题。 【参考方案1】:

不幸的是,这里的云纹图案是高对比度线条接近Nyquist Frequency 的结果。换句话说,没有好的方法可以让 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>

【讨论】:

感谢您的回答!我唯一可以要求的可能是有关此类事物的各种技术的更多信息或文档?他们基本上都只是模糊了高频的东西吗? 据我所知,这是唯一的解决方案:基于像素的显示器根本无法显示高于像素密度的频率。在上面的代码中,thicknessblendregion 是在屏幕空间像素中指定的,这要归功于fwidth,这意味着线条开始重叠,因为它们之间的间距小于约 3 个像素。小于这个值,线条只有 1 个像素,间隙只有 1 个像素,当线条不粘在像素网格上时,事情会变得一团糟。 顺便说一句,这段代码所基于的逻辑在 Cozzi 和 Ring 所著的《3D Engine Design for Virtual Globes》一书中有更全面的描述,代码清单 4.13。

以上是关于去除 GLSL 着色器产生的波纹图案的主要内容,如果未能解决你的问题,请参考以下文章

第二篇: WebGL 着色器和GLSL

GLSL:使用片段着色器进行对象翻译

Python之OpenGL笔记(5):OpenGL着色器语言(GLSL)应用画三角形

GLSL/C++:自定义着色器的默认行为

有啥方法可以调试 GLSL 着色器?

OpenGL + SDL2 +计算着色器=黑屏