着色器测试网格 - 线条呈锯齿状且断裂

Shader testgrid - lines jagged and breaking

我需要为测试网格地面编写一个简单的着色器。我想基本上在着色器代码中绘制平行线。

问题:随着线条与相机的距离越来越远,它们开始分裂并且出现缝隙。我明白为什么我的代码会发生这种情况 - 因为 OpenGL 将片段的位置近似为离我计算的点太远,所以它将它标记为不属于一条线。

我正在将平面矢量的实际世界位置传递到我的着色器 - 这就是我计算它的方式。

我已经玩了一个小时的算法,但似乎无法得到好的结果。

我尝试过的最好的想法是包含一个小系数,该系数随着线离相机的距离越来越远而增加 - 但结果并不令人满意。我线性地计算了系数,但我想我需要一些更聪明的公式来走这条路,因为屏幕上的线条变细的速度不是线性的。到目前为止我还想不通。目前它要么使近线太粗,这是不可取的,要么对于远线仍然有同样的问题。

为简化起见,我目前只绘制 X 轴线

我附上了一段着色器代码和问题的屏幕截图。

#version 300 es

precision highp float;
precision highp int;

in highp vec3 vertexPosition;

out mediump vec4 fragColor;

void main()
{
    highp float lineWidth = 0.2;
    highp float squareSize = 5.0f;

    highp int roundX = int(vertexPosition.x / squareSize);
    highp int roundY = int(vertexPosition.z / squareSize);
    highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
    highp float remainderY = vertexPosition.x - float(roundY)*squareSize;

    // this is the small coefficient I was trying to add to linewidth
    highp float test = abs(0.08 * float(roundX));

    if (abs(remainderX) <= (lineWidth))
    {
        fragColor = vec4(1,0,0, 1);
    }
    else
    {
        fragColor = vec4(0.8,0.8,0.8, 1);
    }
}

第一个答案修复了换行的主要问题,但引入了视觉错误。会去尝试找出原因。不管怎样,这已经是个好主意了!但是正如您所看到的那样,线在最后变得更宽。

编辑:找到了。在执行 dFdy 之前,刚刚从 vertexPosition 中删除了 Z 坐标。现在我只需要一种方法来使线条更平滑而不是像楼梯一样。

p.s。不要看代码的优化程度 - 我目前只是在寻找正确的想法 p.p.s。如果有人能告诉我如何为这个例子做简单的抗锯齿 - 这也是最受欢迎的。

重要的是 roundX 四舍五入 (round) 到最接近的整数,而不是截断:

highp int roundX = int(round(vertexPosition.x / squareSize));

highp int roundX = int(vertexPosition.x / squareSize + 0.5 * sign(vertexPosition.x));

一个可能的解决方案是通过 dFdy.
沿视口的 y 轴获得 vertexPosition.xy 的偏导数 vertexPosition.xy 的偏导数的长度给出模型中 2 个片段之间的距离 space。因此可以定义一条线的最小粗细:

vec2 dy = dFdy(vertexPosition.xy);
float minWidth = length(dy);

float w = step(max(lineWidth, minWidth), abs(remainderX));
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);

要使线条更平滑,您必须对线条颜色和底色进行插值。如果 abs(remainderX)min(lineWidth, minWidth)max(lineWidth, minWidth) 之间,则进行插值。使用 smoothstep 进行插值。例如:

highp int roundX = int(round(vertexPosition.x / squareSize));
highp float remainderX = vertexPosition.x - float(roundX)*squareSize;

vec2 dy = dFdy(vertexPosition.xy);
float minWidth = length(dy);

float w = smoothstep(min(lineWidth, minWidth), max(lineWidth, minWidth), abs(remainderX));
fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);

请参阅 Three.js 示例,它使用着色器:

(function onLoad() {
  var camera, scene, renderer, orbitControls;
  
  init();
  animate();

  function init() {
    
    renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true
    });

    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.shadowMap.enabled = true;
    document.body.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 300);
    camera.position.set(10, 15, -60);

    loader = new THREE.TextureLoader();
    loader.setCrossOrigin("");

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
    scene.add(camera);
    window.onresize = resize;
    
    orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
    
    var helper = new THREE.GridHelper(400, 10);
    helper.material.opacity = 0.25;
    helper.material.transparent = true;
    scene.add(helper);

    var axis = new THREE.AxesHelper(1000);
    scene.add(axis);
    
    var material = new THREE.ShaderMaterial({  
          vertexShader: document.getElementById('vertex-shader').textContent,
          fragmentShader: document.getElementById('fragment-shader').textContent,
    });
    material.extensions = {
      derivatives: true
    }

    var geometry = new THREE.BoxGeometry( 100, 0.1, 100 );
    var mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
  }

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

  function animate() {
    requestAnimationFrame(animate);
    orbitControls.update();
    render();
  }

  function render() {
    renderer.render(scene, camera);
  }
})();
<script type='x-shader/x-vertex' id='vertex-shader'>
varying vec3 vertexPosition;
void main() {
    vertexPosition = position.zyx;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
</script>

<script type='x-shader/x-fragment' id='fragment-shader'>
precision highp float;

varying vec3 vertexPosition;

int round(float x)
{
  return int(x + 0.5 * sign(x));
}

void main(){
    vec4 fragColor;

    highp float lineWidth = 0.2;
    highp float squareSize = 5.0;

    highp int roundX = round(vertexPosition.x / squareSize);
    highp float remainderX = vertexPosition.x - float(roundX)*squareSize;
    
    vec2 dy = dFdy(vertexPosition.xy);
    float minWidth = length(dy);

    float w = smoothstep(min(lineWidth, minWidth), max(lineWidth, minWidth), abs(remainderX));
    //float w = step(max(lineWidth, minWidth), abs(remainderX));
    fragColor = mix(vec4(1.0, 0.0, 0.0, 1.0), vec4(0.8, 0.8, 0.8, 1.0), w);

    gl_FragColor = fragColor;
}
</script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>