背面剔除的法线变换

Normals transformation for Back-Face culling

从这里的这节课开始:WebGL 3D Perspective我正在尝试实现背面剔除没有魔法。

我正在动态计算对象 space 中的面部法线。之后,我在 m[2][3] 内设置 fudgeFactor 以获得透视图 除以 Z.

为了检查剪切矩阵是否有效,我直接使用从 WebGL 顶点着色器投影的顶点位置扩展了我的代码片段,并添加了 “投影位置” 标志。

现在,我正在尝试使用变换法线内的 z 分量来隐藏 normal.z <0 的面。这种技术在使用正交投影时效果很好——或者当 fudgeFactor 为 0 时。为什么这对透视投影也不起作用,当我自己设置 fudgeFactor 时?

为了看看发生了什么,我写了下面的最小示例来可视化法向量(感谢:Geeks3D)并为其着色。 绿色正常:面部可见,红色正常:面部被剔除。

我遵循 gman 的提示并实现了基本的多边形光栅化(在 WebGL 顶点着色器之外)作为概念证明。尝试拖动 fudgeFactor slider 并检查是否正常工作。

'use strict';

function main() {
  function setupUI() {
    webglLessonsUI.setupUI(document.getElementById('ui'), settings, [
      { type: 'checkbox', key: 'projectedP', name: 'projected position', change: draw },
      { type: 'option', key: 'visible', options: settings.visibleOptions, name: 'visibility buffer', change: draw },
      { type: 'slider', key: 'fudgeFactor', change: draw, max: 2, step: 0.001, precision: 3 },
      { type: 'slider', key: 'tX', change: draw, min: -0.5 * gl.canvas.width, max: 0.5 * gl.canvas.width },
      { type: 'slider', key: 'tY', change: draw, min: -0.5 * gl.canvas.height, max: 0.5 * gl.canvas.height },
      { type: 'slider', key: 'tZ', change: draw, min: -gl.canvas.height, max: gl.canvas.height },
      { type: 'slider', key: 'rX', change: draw, max: 360 },
      { type: 'slider', key: 'rY', change: draw, max: 360 },
      { type: 'slider', key: 'rZ', change: draw, max: 360 },
      { type: 'slider', key: 'scale', change: draw, min: -5, max: 5, step: 0.01, precision: 2 },
    ]);
  }

  function draw() {
    // assignZToWMatrix
    perspMatrix[11] = settings.fudgeFactor;
    var w = gl.canvas.clientWidth, h = gl.canvas.clientHeight, d = 400;
    var projMatrix = m4.multiply(perspMatrix, m4.projector(w, h, d));
    var worldMatrix = m4.translation(settings.tX, settings.tY, settings.tZ);
    m4.xRotate(worldMatrix, (settings.rX * Math.PI) / 180, worldMatrix);
    m4.yRotate(worldMatrix, (settings.rY * Math.PI) / 180, worldMatrix);
    m4.zRotate(worldMatrix, (settings.rZ * Math.PI) / 180, worldMatrix);
    m4.scale(worldMatrix, settings.scale, settings.scale, settings.scale, worldMatrix);
    var matrix = m4.multiply(projMatrix, worldMatrix);
    
    // set the visibility flag using polygon area
    transformVertices(matrix);
    if(settings.visible == 1) {
      // try to set the visibility flag using an early check
      transformNormals(matrix);
    }
    
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    gl.useProgram(solidInfo.program);
    gl.bindBuffer(gl.ARRAY_BUFFER, solidBufferInfo.attribs.a_projectedP.buffer);
    gl.bufferData(gl.ARRAY_BUFFER, projectedVertices, gl.STATIC_DRAW);
    webglUtils.setBuffersAndAttributes(gl, solidInfo, solidBufferInfo);
    webglUtils.setUniforms(solidInfo, { u_matrix: matrix,
                                        u_projectedP: [settings.projectedP],
                                        u_fudgeFactor: settings.fudgeFactor });

    var count = 32 * 3; // <= geometries x points-each
    webglUtils.drawBufferInfo(gl, solidBufferInfo, gl.TRIANGLES);

    gl.useProgram(normalInfo.program);
    gl.bindBuffer(gl.ARRAY_BUFFER, normalBufferInfo.attribs.a_visible.buffer);
    gl.bufferData(gl.ARRAY_BUFFER, visibility, gl.STATIC_DRAW);
    webglUtils.setBuffersAndAttributes(gl, normalInfo, normalBufferInfo);
    webglUtils.setUniforms(normalInfo, { u_matrix: matrix, 
                                         u_visible: [settings.visible] });
    webglUtils.drawBufferInfo(gl, normalBufferInfo, gl.LINES);
  }

  function calcFaceNormals(size) {
    var v = vertices, l = v.length;
    var nl = normalLines, n = normals;
    var i = 0, j = 0, k = 0;

    while (i < l) {
      var x0 = v[i++]; var y0 = v[i++]; var z0 = v[i++];
      var x1 = v[i++]; var y1 = v[i++]; var z1 = v[i++];
      var x2 = v[i++]; var y2 = v[i++]; var z2 = v[i++];

      var dx1 = x1 - x0; var dy1 = y1 - y0; var dz1 = z1 - z0;
      var dx2 = x2 - x0; var dy2 = y2 - y0; var dz2 = z2 - z0;

      var nx = dy1*dz2-dz1*dy2; var ny = dz1*dx2-dx1*dz2; var nz = dx1*dy2-dy1*dx2;

      // Normalize
      var len = Math.sqrt(nx * nx + ny * ny + nz * nz);
      nx /= len; ny /= len; nz /= len;

      // Center of the geometry
      var cx = (x0+x1+x2)/3; var cy = (y0+y1+y2)/3; var cz = (z0+z1+z2)/3;

      // Vector start point
      nl[j++] = cx; nl[j++] = cy; nl[j++] = cz;

      // Normal vector
      n[k++] = nx; n[k++] = ny; n[k++] = nz;

      // Assign a drawing length (scale) & end-point (translate)
      nl[j++] = nx*size+cx; nl[j++] = ny*size+cy; nl[j++] = nz*size+cz;
      
      // doublette needed by WebGL for single vertex processing
      n[k++] = nx; n[k++] = ny; n[k++] = nz;
    }
  }

  function transformVertices(m) {
    // same as m4.transformPoint
    function transformVertex(m,src,dst) {
      var x = src[0], y = src[1], z = src[2];
      var d = x * m[3] + y * m[7] + z * m[11] + m[15];
      dst[0] = (x * m[0] + y * m[4] + z * m[8]  + m[12]) / d;
      dst[1] = (x * m[1] + y * m[5] + z * m[9]  + m[13]) / d;
      dst[2] = (x * m[2] + y * m[6] + z * m[10] + m[14]) / d;
      return dst;
    }  

    // here we already have fudgeFactor inside m[11]
    var mv = vertices, l = mv.length, pv = projectedVertices;
    var mp0 = [0,0,0], mp1 = [0,0,0], mp2 = [0,0,0];
    var pp0 = [0,0,0], pp1 = [0,0,0], pp2 = [0,0,0];
    
    // "visible" flag inside the vertex shader visibility buffer
    var vb = visibility, j = 0;

    for(var i=0; i<l; i+=9) {
      mp0[0] = mv[i    ]; mp0[1] = mv[i + 1]; mp0[2] = mv[i + 2];
      mp1[0] = mv[i + 3]; mp1[1] = mv[i + 4]; mp1[2] = mv[i + 5];
      mp2[0] = mv[i + 6]; mp2[1] = mv[i + 7]; mp2[2] = mv[i + 8];
      // Project vertex coords
      pp0 = transformVertex(m,mp0,pp0);
      pp1 = transformVertex(m,mp1,pp1);
      pp2 = transformVertex(m,mp2,pp2);
      // Assign 
      pv[i    ] = pp0[0]; pv[i + 1] = pp0[1]; pv[i + 2] = pp0[2];
      pv[i + 3] = pp1[0]; pv[i + 4] = pp1[1]; pv[i + 5] = pp1[2]; 
      pv[i + 6] = pp2[0]; pv[i + 7] = pp2[1]; pv[i + 8] = pp2[2];

     // we need just only the sign, no need to divde as in 3.5.1
     var da = (pp0[0]*pp1[1] - pp1[0]*pp0[1]) + 
              (pp1[0]*pp2[1] - pp2[0]*pp1[1]) + 
              (pp2[0]*pp0[1] - pp0[0]*pp2[1]);
        
     // swap the sign because we inverted the Y-axis inside the projector matrix
     vb[j++] = -da;
     // doublette needed by WebGL for single vertex processing
     vb[j++] = -da;
    }
  }

  function transformNormals(m) {
    var n = normals, l = n.length;
    var x, y, z, w, tx, ty, tz, tw;
    var vb = visibility, j = 0; // Visibility buffer
    
    var t = transformedNormals, k = 0; // debug
    for(var i=0; i<l; i+=6) {
      // normal buffer doublettes are needed for WebGL single
      //  vertex processing, here we take just only one entry
      x = n[i]; y = n[i + 1]; z = n[i + 2]; w = 1;

      // fudgeFactor is inside m[11] - any way to shear normal?
      tx = x * m[0] + y * m[4] + z * m[8]  + w * m[12];
      ty = x * m[1] + y * m[5] + z * m[9]  + w * m[13];
      tz = x * m[2] + y * m[6] + z * m[10] + w * m[14];
      tw = x * m[3] + y * m[7] + z * m[11] + w * m[15]; 
      
      var idx =~ ~i/6; // triangle index
      // debug: index 21 & 22 are the top side of the middle rung
      // debug: keep the transformed vector to check what's happen
      t[k++] = tx; t[k++] = ty; t[k++] = tz; t[k++] = tw;

      // set the visibility flag
      vb[j++] = tz;
      // doublette for WebGL single vertex processing
      vb[j++] = tz;
    }
  }
  
  var canvas = document.querySelector('#canvas'),
    gl = canvas.getContext('webgl');
  var perspMatrix = m4.identity(), drawSize = 10;

  var settings = {
    visibleOptions:  [ '', 'T. NORMAL', 'POLY AREA' ],
    visible: 1,
    projectedP: true,
    fudgeFactor: 1.5,
    tX: -54, tY: -67, tZ: 0, 
    rX: 12, rY: 33, rZ: 8,
    scale: 1
  };

  var solidArrays = {
    position: { numComponents: 3, data: vertices },
    projectedP: { numComponents: 3, data: projectedVertices },
    color: { numComponents: 3, data: vertexColors }
  };

  var solidInfo = webglUtils.createProgramInfo(gl, ['vs-solid', 'fs-solid']);
  var solidBufferInfo = webglUtils.createBufferInfoFromArrays(gl, solidArrays);

  var normalArrays = {
    position: { numComponents: 3, data: normalLines },
    visible: { numComponents: 1, data: visibility },
    normal: { numComponents: 3, data: normals }
  };

  calcFaceNormals(drawSize);

  var normalInfo = webglUtils.createProgramInfo(gl, ['vs-normal', 'fs-normal']);
  var normalBufferInfo = webglUtils.createBufferInfoFromArrays(gl, normalArrays);

  draw();

  setupUI();
}

m4.projector = function (width, height, depth) {
  // Note: set 0,0 at canvas center
  return [
    2 / width, 0, 0, 0,
    0, -2 / height, 0, 0,
    0, 0, 2 / depth, 0,
    0, 0, 0, 1,
  ];
};

var vertices = new Float32Array([
  // left column front
  0,0,0,0,150,0,30,0,0,
  0,150,0,30,150,0,30,0,0,
  // top rung front
  30,0,0,30,30,0,100,0,0,
  30,30,0,100,30,0,100,0,0,
  // middle rung front
  30,60,0,30,90,0,67,60,0,
  30,90,0,67,90,0,67,60,0,
  // left column back
  0,0,30,30,0,30,0,150,30,
  0,150,30,30,0,30,30,150,30,
  // top rung back
  30,0,30,100,0,30,30,30,30,
  30,30,30,100,0,30,100,30,30,
  // middle rung back
  30,60,30,67,60,30,30,90,30,
  30,90,30,67,60,30,67,90,30,
  // top
  0,0,0,100,0,0,100,0,30,
  0,0,0,100,0,30,0,0,30,
  // top rung right
  100,0,0,100,30,0,100,30,30,
  100,0,0,100,30,30,100,0,30,
  // under top rung
  30,30,0,30,30,30,100,30,30,
  30,30,0,100,30,30,100,30,0,
  // between top rung and middle
  30,30,0,30,60,30,30,30,30,
  30,30,0,30,60,0,30,60,30,
  // top of middle rung
  30,60,0,67,60,30,30,60,30,
  30,60,0,67,60,0,67,60,30,
  // right of middle rung
  67,60,0,67,90,30,67,60,30,
  67,60,0,67,90,0,67,90,30,
  // bottom of middle rung.
  30,90,0,30,90,30,67,90,30,
  30,90,0,67,90,30,67,90,0,
  // right of bottom
  30,90,0,30,150,30,30,90,30,
  30,90,0,30,150,0,30,150,30,
  // bottom
  0,150,0,0,150,30,30,150,30,
  0,150,0,30,150,30,30,150,0,
  // left side
  0,0,0,0,0,30,0,150,30,
  0,0,0,0,150,30,0,150,0
]);

var vertexColors = new Uint8Array([
  // left column front
  100,35,60,100,35,60,100,35,60,
  200,70,120,200,70,120,200,70,120,
  // top rung front
  100,35,60,100,35,60,100,35,60,
  200,70,120,200,70,120,200,70,120,
  // middle rung front
  100,35,60,100,35,60,100,35,60,
  200,70,120,200,70,120,200,70,120,
  // left column back
  40,35,100,40,35,100,40,35,100,
  80,70,200,80,70,200,80,70,200,
  // top rung back
  40,35,100,40,35,100,40,35,100,
  80,70,200,80,70,200,80,70,200,
  // middle rung back
  40,35,100,40,35,100,40,35,100,
  80,70,200,80,70,200,80,70,200,
  // top
  35,100,105,35,100,105,35,100,105,
  70,200,210,70,200,210,70,200,210,
  // top rung right
  100,100,35,100,100,35,100,100,35,
  200,200,70,200,200,70,200,200,70,
  // under top rung
  105,50,35,105,50,35,105,50,35,
  210,100,70,210,100,70,210,100,70,
  // between top rung and middle
  105,80,35,105,80,35,105,80,35,
  210,160,70,210,160,70,210,160,70,
  // top of middle rung
  35,90,105,35,90,105,35,90,105,
  70,180,210,70,180,210,70,180,210,
  // right of middle rung
  50,35,105,50,35,105,50,35,105,
  100,70,210,100,70,210,100,70,210,
  // bottom of middle rung.
  38,105,50,38,105,50,38,105,50,
  76,210,100,76,210,100,76,210,100,
  // right of bottom
  70,105,40,70,105,40,70,105,40,
  140,210,80,140,210,80,140,210,80,
  // bottom
  45,65,55,45,65,55,45,65,55,
  90,130,110,90,130,110,90,130,110,
  // left side
  80,80,110,80,80,110,80,80,110,
  160,160,220,160,160,220,160,160,220
]);

// projection buffer: same as vertices
var projectedVertices = new Float32Array(vertices.length);
// normal buffer: 16 faces 2 triangles each, total 32 triangles => 128 elements + 128 doublettes => 64 components x 4 floats
var normalLines = new Float32Array((2 * vertices.length) / 3);
var normals = new Float32Array(2 * vertices.length / 3);
// visible buffer: 16 faces 2 triangles each, total 32 triangles => 64 components x 1 float
var visibility = new Float32Array(2 * vertices.length / 9);
// debug: normal transformation buffer has 4 components, to check also how the w is transformed
// 16 faces 2 triangles each, total 32 normals x 4 floats
var transformedNormals = new Float32Array(vertices.length / 9 * 4);

document.addEventListener('DOMContentLoaded', function (event) {
  setTimeout(main,100)
});
<!DOCTYPE html>
<html>
  <head>
    <link href="https://webglfundamentals.org/webgl/resources/webgl-tutorials.css" type="text/css" rel="stylesheet"/>
    <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
    <script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
    <script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
  </head>
  <body style="background-color: #fff;">
    <canvas id="canvas"></canvas>
    <div id="uiContainer">
      <div id="ui"></div>
    </div>

    <script id="vs-solid" type="x-shader/x-vertex">
      attribute vec3 a_position;
      attribute vec3 a_projectedP;
      attribute vec3 a_color;
      uniform bool u_projectedP;
      uniform mat4 u_matrix;
      uniform float u_fudgeFactor;
      varying vec4 v_color;
      void main() {
        vec4 pos;
        float zToDivideBy;
        if(u_projectedP) {
          pos = vec4(a_projectedP, 1.0);
          zToDivideBy = 1.0;
        } else {
          pos = u_matrix * vec4(a_position, 1.0);
          zToDivideBy = 1.0 + pos.z * u_fudgeFactor;
        }
        gl_Position = vec4(pos.xyz, zToDivideBy);
        v_color = vec4(a_color, 1.0);
      }
    </script>

    <script id="fs-solid" type="x-shader/x-fragment">
      precision mediump float;
      varying vec4 v_color;
      void main() {
         gl_FragColor = v_color;
      }
    </script>

    <script id="vs-normal" type="x-shader/x-vertex">
      attribute vec3 a_position;
      attribute vec3 a_normal;
      attribute float a_visible;
      uniform int u_visible;
      uniform mat4 u_matrix;
      varying vec4 v_color;
      void main() {
        // The x,y,z,w value we assign to gl_Position in our
        // vertex shader will be divided by w automatically.
        gl_Position = u_matrix * vec4(a_position, 1.0);
        float eps = -0.000001;
        bool vis;
        vec4 nrm = u_matrix * vec4(a_normal, 0.0);
        if(u_visible == 0) {
          // this is obviously wrong for fudgeFactor > 0 
          vis = nrm.z < eps;
        } else {
          // option 1: transformed normal.z
          // option 2: sign of polygon area
          vis = a_visible < eps;
        }
        // visible => green, hidden => red 
        v_color = vis ? vec4(0., 255., 0., 1.) : vec4(255., 0., 0., 1.);
      }
    </script>

    <script id="fs-normal" type="x-shader/x-fragment">
      precision mediump float;
      varying vec4 v_color;
      void main() {
         gl_FragColor = v_color;
      }
    </script>
  </body>
</html>

我希望我的法线被我的变换矩阵剪切(旋转),但它们没有。

请注意:

我正在尝试了解如何转换法线以在后台实现背面剔除,当我自己设置 除以 Z 时使用矩阵乘法。


编辑 - 这里有一些参考:

Backface Culling in Object Space

A Compact Method for Backface Culling

Back Face Culling - Dot/Cross Products

Another look at 3D graphics: fast hidden faces removal (back-face culling)

如果这不是您问题的答案,我很抱歉,但“背面剔除”与法线无关,您通常不会手动执行此操作。你让 WebGL 为你做这件事

来自 the spec

3.5.1 Basic Polygon Rasterization

The first step of polygon rasterization is to determine if the polygon is back facing or front facing. This determination is made based on the sign of the (clipped or unclipped) polygon’s area computed in window coordinates. One way to compute this area is

where xiw and yiw are the x and y window coordinates of the ith vertex of the n-vertex polygon (vertices are numbered starting at zero for purposes of this computation) and i⊕1 is (i+1) mod n. The interpretation of the sign of this value is controlled with

void FrontFace( enum dir );

Setting dir to CCW (corresponding to counter-clockwise orientation of the projected polygon in window coordinates) indicates that the sign of a should be reversed prior to use. Setting dir to CW (corresponding to clockwise orientation) uses the sign of a is as computed above. Front face determination requires one bit of state, and is initially set to CCW.

If the sign of the area computed by equation 3.4 (including the possible reversal of this sign as indicated by the last call to FrontFace) is positive, the polygon is front facing; otherwise, it is back facing. This determination is used in conjunction with the CullFace enable bit and mode value to decide whether or not a particular polygon is rasterized. The CullFace mode is set by calling

void CullFace( enum mode );

mode is a symbolic constant: one of FRONT, BACK or FRONT_AND_BACK. Culling is enabled or disabled with Enable or Disable using the symbolic constant CULL_FACE. Front facing polygons are rasterized if either culling is disabled or the CullFace mode is BACK while back facing polygons are rasterized only if either culling is disabled or the CullFace mode is FRONT. The initial setting of the CullFace mode is BACK. Initially, culling is disabled.

注意从未提及法线,也未提及 z。它完全发生在 2D 中。此外,虽然这并非不可能,但由于每个顶点都是独立处理的,因此很难使用面法线(示例代码中显示的法线)进行背面剔除。顶点着色器不处理三角形,它们处理单个顶点。那么,他们将如何计算三角形的法线以将其剔除?我想你可以传递一个与每个顶点法线的面,或者每次传递所有 3 个顶点,方法是在单独的缓冲区中复制它们,但它们的顺序按三角形旋转。

在工作中看到背面剔除的最简单方法可能是绘制一些东西但将面分开以便您可以看到背面

'use strict';

function main() {
  const settings = {
    enabled: false,
    frontFace: 0,
    cullFace: 0,
    fudgeFactor: 1.42,
    tX: -34,
    tY: -47,
    tZ: 20,
    rX: 43,
    rY: 33,
    rZ: 8,
    scale: 1.77
  };
  const frontFaceOptions = [ "CCW", "CW" ];
  const cullFaceOptions = [ "BACK", "FRONT", "FRONT_AND_BACK" ];

  function setupUI() {
   
    webglLessonsUI.setupUI(document.querySelector('#ui'), settings, [
      { type: "checkbox", key: "enabled", name: "culling enabled", },
      { type: "option",   key: "frontFace", options: frontFaceOptions, },
      { type: "option",   key: "cullFace", options: cullFaceOptions, },
    ]);
  }

  function draw(time) {
    settings.rY = time * 0.01;
    // assignZToWMatrix
    perspMatrix[11] = settings.fudgeFactor;
    var projMatrix = m4.multiply(perspMatrix, m4.projector(gl.canvas.clientWidth, gl.canvas.clientHeight, 400));
    var worldMatrix = m4.translation(settings.tX, settings.tY, settings.tZ);
    m4.translate(worldMatrix, settings.tX, settings.tY, settings.tZ, worldMatrix);
    m4.xRotate(worldMatrix, (settings.rX * Math.PI) / 180, worldMatrix);
    m4.yRotate(worldMatrix, (settings.rY * Math.PI) / 180, worldMatrix);
    m4.zRotate(worldMatrix, (settings.rZ * Math.PI) / 180, worldMatrix);
    m4.scale(worldMatrix, settings.scale, settings.scale, settings.scale, worldMatrix);
    var matrix = m4.multiply(projMatrix, worldMatrix);

    webglUtils.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    if (settings.enabled) {
      gl.enable(gl.CULL_FACE);
    } else {
      gl.disable(gl.CULL_FACE);
    }
    gl.cullFace(gl[cullFaceOptions[settings.cullFace]]);
    gl.frontFace(gl[frontFaceOptions[settings.frontFace]]);
    
    gl.enable(gl.DEPTH_TEST);

    gl.useProgram(solidInfo.program);
    webglUtils.setBuffersAndAttributes(gl, solidInfo, solidBufferInfo);
    webglUtils.setUniforms(solidInfo, {
      u_matrix: matrix
    });

    var count = 32 * 3; // <= geometries x points-each
    webglUtils.drawBufferInfo(gl, solidBufferInfo, gl.TRIANGLES);
    
    requestAnimationFrame(draw);
  }

  var canvas = document.querySelector('#canvas'),
    gl = canvas.getContext('webgl');
  var perspMatrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];

  var solidArrays = {
    position: {
      numComponents: 3,
      data: vertices
    },
    color: {
      numComponents: 3,
      data: vertexColors
    },
  };

  var solidInfo = webglUtils.createProgramInfo(gl, ['vs-solid', 'fs-solid']);
  var solidBufferInfo = webglUtils.createBufferInfoFromArrays(gl, solidArrays);

  setupUI();
  requestAnimationFrame(draw);
}

m4.projector = function(width, height, depth) {
  // Note: set 0,0 at canvas center
  return [
    2 / width, 0, 0, 0,
    0, -2 / height, 0, 0,
    0, 0, 2 / depth, 0,
    0, 0, 0, 1,
  ];
};

var vertices = new Float32Array([
  // left column front
  0, 0, 0, 0, 150, 0, 30, 0, 0,
  0, 150, 0, 30, 150, 0, 30, 0, 0,
  // top rung front
  30, 0, 0, 30, 30, 0, 100, 0, 0,
  30, 30, 0, 100, 30, 0, 100, 0, 0,
  // middle rung front
  30, 60, 0, 30, 90, 0, 67, 60, 0,
  30, 90, 0, 67, 90, 0, 67, 60, 0,
  // left column back
  0, 0, 30, 30, 0, 30, 0, 150, 30,
  0, 150, 30, 30, 0, 30, 30, 150, 30,
  // top rung back
  30, 0, 30, 100, 0, 30, 30, 30, 30,
  30, 30, 30, 100, 0, 30, 100, 30, 30,
  // middle rung back
  30, 60, 30, 67, 60, 30, 30, 90, 30,
  30, 90, 30, 67, 60, 30, 67, 90, 30,
  // top
  0, 0, 0, 100, 0, 0, 100, 0, 30,
  0, 0, 0, 100, 0, 30, 0, 0, 30,
  // top rung right
  100, 0, 0, 100, 30, 0, 100, 30, 30,
  100, 0, 0, 100, 30, 30, 100, 0, 30,
  // under top rung
  30, 30, 0, 30, 30, 30, 100, 30, 30,
  30, 30, 0, 100, 30, 30, 100, 30, 0,
  // between top rung and middle
  30, 30, 0, 30, 60, 30, 30, 30, 30,
  30, 30, 0, 30, 60, 0, 30, 60, 30,
  // top of middle rung
  30, 60, 0, 67, 60, 30, 30, 60, 30,
  30, 60, 0, 67, 60, 0, 67, 60, 30,
  // right of middle rung
  67, 60, 0, 67, 90, 30, 67, 60, 30,
  67, 60, 0, 67, 90, 0, 67, 90, 30,
  // bottom of middle rung.
  30, 90, 0, 30, 90, 30, 67, 90, 30,
  30, 90, 0, 67, 90, 30, 67, 90, 0,
  // right of bottom
  30, 90, 0, 30, 150, 30, 30, 90, 30,
  30, 90, 0, 30, 150, 0, 30, 150, 30,
  // bottom
  0, 150, 0, 0, 150, 30, 30, 150, 30,
  0, 150, 0, 30, 150, 30, 30, 150, 0,
  // left side
  0, 0, 0, 0, 0, 30, 0, 150, 30,
  0, 0, 0, 0, 150, 30, 0, 150, 0
]);

// spread the faces in the direction of their normals
for (let i = 0; i < vertices.length; i += 9) {
  // get a view of each position in place (works because it's a typedarray)
  const p0 = vertices.subarray(i + 0, i + 3);
  const p1 = vertices.subarray(i + 3, i + 6);
  const p2 = vertices.subarray(i + 6, i + 9);
  
  // compute the normal
  const n = m4.normalize(m4.cross(
     m4.subtractVectors(p1, p0),
     m4.subtractVectors(p2, p1)));
     
  // scale by 10
  m4.scaleVector(n, 10, n);
  
  // add the normal to each vertex of the triangle
  // this will move it in the direction of the normal
  m4.addVectors(p0, n, p0);
  m4.addVectors(p1, n, p1);
  m4.addVectors(p2, n, p2);
}

var vertexColors = new Uint8Array([
  // left column front
  100, 35, 60, 100, 35, 60, 100, 35, 60,
  200, 70, 120, 200, 70, 120, 200, 70, 120,
  // top rung front
  100, 35, 60, 100, 35, 60, 100, 35, 60,
  200, 70, 120, 200, 70, 120, 200, 70, 120,
  // middle rung front
  100, 35, 60, 100, 35, 60, 100, 35, 60,
  200, 70, 120, 200, 70, 120, 200, 70, 120,
  // left column back
  40, 35, 100, 40, 35, 100, 40, 35, 100,
  80, 70, 200, 80, 70, 200, 80, 70, 200,
  // top rung back
  40, 35, 100, 40, 35, 100, 40, 35, 100,
  80, 70, 200, 80, 70, 200, 80, 70, 200,
  // middle rung back
  40, 35, 100, 40, 35, 100, 40, 35, 100,
  80, 70, 200, 80, 70, 200, 80, 70, 200,
  // top
  35, 100, 105, 35, 100, 105, 35, 100, 105,
  70, 200, 210, 70, 200, 210, 70, 200, 210,
  // top rung right
  100, 100, 35, 100, 100, 35, 100, 100, 35,
  200, 200, 70, 200, 200, 70, 200, 200, 70,
  // under top rung
  105, 50, 35, 105, 50, 35, 105, 50, 35,
  210, 100, 70, 210, 100, 70, 210, 100, 70,
  // between top rung and middle
  105, 80, 35, 105, 80, 35, 105, 80, 35,
  210, 160, 70, 210, 160, 70, 210, 160, 70,
  // top of middle rung
  35, 90, 105, 35, 90, 105, 35, 90, 105,
  70, 180, 210, 70, 180, 210, 70, 180, 210,
  // right of middle rung
  50, 35, 105, 50, 35, 105, 50, 35, 105,
  100, 70, 210, 100, 70, 210, 100, 70, 210,
  // bottom of middle rung.
  38, 105, 50, 38, 105, 50, 38, 105, 50,
  76, 210, 100, 76, 210, 100, 76, 210, 100,
  // right of bottom
  70, 105, 40, 70, 105, 40, 70, 105, 40,
  140, 210, 80, 140, 210, 80, 140, 210, 80,
  // bottom
  45, 65, 55, 45, 65, 55, 45, 65, 55,
  90, 130, 110, 90, 130, 110, 90, 130, 110,
  // left side
  80, 80, 110, 80, 80, 110, 80, 80, 110,
  160, 160, 220, 160, 160, 220, 160, 160, 220
]);

main();
body {
  margin: 0;
}

canvas {
  border: none;
  width: 100vw;
  height: 100vh;
  display: block;
}
<link href="https://webglfundamentals.org/webgl/resources/webgl-tutorials.css" type="text/css" rel="stylesheet"/>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/m4.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>
<canvas id="canvas"></canvas>
<div id="uiContainer" style="top: 40px;">
  <div id="ui"></div>
</div>

<script id="vs-solid" type="x-shader/x-vertex">
  attribute vec4 a_position;
  attribute vec3 a_color;
  uniform mat4 u_matrix;
  varying vec4 v_color;
  void main() {
    gl_Position = u_matrix * a_position;
    v_color = vec4(a_color, 1.0);
  }
</script>

<script id="fs-solid" type="x-shader/x-fragment">
  precision mediump float;
  varying vec4 v_color;
  void main() {
     gl_FragColor = v_color;
  }
</script>

在您链接到 Commodore 64 上的软件渲染器的评论中。在 WebGL 中模拟软件渲染器有点困难,因为它的全部意义在于替换该渲染器。该代码使用三角形来处理,但在 WebGL 中我们不使用三角形来处理,我们使用顶点来处理,而 WebGL 本身会处理三角形。

无论如何,至于为什么你的代码不起作用,代码正在检查法线是否面向 Z 平面 (z < 0),而不是相机。要检查法线是否面向相机,您需要将变换后的法线与从相机到 space 中的那个点的方向进行比较。 This article 调用 surfaceToView 向量。

因此,如果您想绘制一个线框框并像示例那样去除伪造的隐藏线,您必须为每个顶点传递一个面法线,表示该顶点指向的方向。然后你需要一个 modelView 矩阵(除了投影矩阵之外的所有东西)。计算点的视图位置并使用它来计算眼睛到点向量,您还可以通过相同的矩阵定向法线并使用点积计算它们之间的角度。 < 0 vs > 0 会告诉你法线是背向还是朝向。 example

虽然该技术不适用于 3D F。它只适用于立方体、球体、金字塔等凸形。 F 不是凸的,所以内线仍然会被绘制。

在WebGL 中去除隐藏线最简单的方法是绘制对象两次。一次使用三角形(填充深度缓冲区),然后再次使用线条。当用线

绘图时,您可以将深度测试从 LESS 切换到 LEQUAL