背面剔除的法线变换 [英] Normals transformation for Back-Face culling

查看:25
本文介绍了背面剔除的法线变换的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从本课开始:

其中 xiw 和 yiw是第 i 个顶点的 x 和 y 窗口坐标n 顶点多边形(出于此目的,顶点从零开始编号)计算),而 i⊕1 是 (i+1) mod n.这个值的符号解释由

控制

void FrontFace( enum dir );

将dir设置为CCW(对应于窗口坐标中投影多边形的逆时针方向)表示在使用前应反转a的符号.设置dir为CW(对应顺时针方向)使用 a 的符号如上计算.正面确定需要一位状态,初始设置为CCW.

如果由等式 3.4 计算的面积的符号(包括可能的反转最后一次调用 FrontFace) 所指示的此符号为正,则多边形为正面;否则,它是背面.该判定结合使用使用 CullFace 启用位和模式值来决定特定的多边形被光栅化.CullFace 模式是通过调用设置的

void CullFace( enum mode );

mode 是一个符号常量:FRONTBACKFRONT_AND_BACK 之一.剔除使用符号常量 CULL_FACE 通过 EnableDisable 启用或禁用.如果任一剔除被禁用或 CullFace 模式为 BACK,则正面多边形被光栅化,而背面多边形仅在任一剔除时被光栅化被禁用或 CullFace 模式为 FRONT.CullFace 的初始设置模式是 BACK.最初,剔除被禁用.

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

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

'use strict';函数主(){常量设置 = {启用:假,正面:0,剔除面:0,软糖因子:1.42,tX: -34,tY:-47,tZ: 20,RX: 43,y: 33,rZ: 8,规模:1.77};const frontFaceOptions = [ "CCW", "CW" ];const cullFaceOptions = [ "BACK", "FRONT", "FRONT_AND_BACK" ];功能设置UI(){webglLessonsUI.setupUI(document.querySelector('#ui'), 设置, [{ type: "checkbox", key: "enabled", name: "culling enabled", },{ type: "option", key: "frontFace", options: frontFaceOptions, },{ type: "option", key: "cullFace", options: cullFaceOptions, },]);}函数绘制(时间){settings.rY = 时间 * 0.01;//分配ZToW矩阵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 矩阵 = 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);如果(设置.启用){gl.启用(gl.CULL_FACE);} 别的 {gl.disable(gl.CULL_FACE);}gl.cullFace(gl[cullFaceOptions[settings.cullFace]]);gl.frontFace(gl[frontFaceOptions[settings.frontFace]]);gl.启用(gl.DEPTH_TEST);gl.useProgram(solidInfo.program);webglUtils.setBuffersAndAttributes(gl,solidInfo,solidBufferInfo);webglUtils.setUniforms(solidInfo, {u_matrix:矩阵});无功计数 = 32 * 3;//<= 几何 x 点-每个webglUtils.drawBufferInfo(gl,solidBufferInfo,gl.TRIANGLES);请求动画帧(绘制);}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];varsolidArrays = {位置: {numComponents: 3,数据:顶点},颜色: {numComponents: 3,数据:顶点颜色},};var solidInfo = webglUtils.createProgramInfo(gl, ['vs-solid', 'fs-solid']);var solidBufferInfo = webglUtils.createBufferInfoFromArrays(gl,solidArrays);设置界面();请求动画帧(绘制);}m4.projector = 功能(宽度,高度,深度){//注意:在画布中心设置 0,0返回 [2/宽度, 0, 0, 0,0, -2/高度, 0, 0,0, 0, 2/深度, 0,0, 0, 0, 1,];};var vertices = new Float32Array([//左列前面0, 0, 0, 0, 150, 0, 30, 0, 0,0, 150, 0, 30, 150, 0, 30, 0, 0,//顶部梯级前面30, 0, 0, 30, 30, 0, 100, 0, 0,30, 30, 0, 100, 30, 0, 100, 0, 0,//中间横档前面30, 60, 0, 30, 90, 0, 67, 60, 0,30, 90, 0, 67, 90, 0, 67, 60, 0,//左列返回0, 0, 30, 30, 0, 30, 0, 150, 30,0, 150, 30, 30, 0, 30, 30, 150, 30,//返回顶部30, 0, 30, 100, 0, 30, 30, 30, 30,30, 30, 30, 100, 0, 30, 100, 30, 30,//中间向后30, 60, 30, 67, 60, 30, 30, 90, 30,30, 90, 30, 67, 60, 30, 67, 90, 30,//顶部0, 0, 0, 100, 0, 0, 100, 0, 30,0, 0, 0, 100, 0, 30, 0, 0, 30,//右上角100, 0, 0, 100, 30, 0, 100, 30, 30,100, 0, 0, 100, 30, 30, 100, 0, 30,//在顶部梯级下30, 30, 0, 30, 30, 30, 100, 30, 30,30, 30, 0, 100, 30, 30, 100, 30, 0,//在顶部梯级和中间梯级之间30, 30, 0, 30, 60, 30, 30, 30, 30,30, 30, 0, 30, 60, 0, 30, 60, 30,//中间梯级的顶部30, 60, 0, 67, 60, 30, 30, 60, 30,30, 60, 0, 67, 60, 0, 67, 60, 30,//中间横档的右边67, 60, 0, 67, 90, 30, 67, 60, 30,67, 60, 0, 67, 90, 0, 67, 90, 30,//中间横档的底部.30, 90, 0, 30, 90, 30, 67, 90, 30,30, 90, 0, 67, 90, 30, 67, 90, 0,//右下角30, 90, 0, 30, 150, 30, 30, 90, 30,30, 90, 0, 30, 150, 0, 30, 150, 30,//底部0, 150, 0, 0, 150, 30, 30, 150, 30,0, 150, 0, 30, 150, 30, 30, 150, 0,//左边0, 0, 0, 0, 0, 30, 0, 150, 30,0, 0, 0, 0, 150, 30, 0, 150, 0]);//沿法线方向展开面for (让 i = 0; i 

body {边距:0;}帆布 {边界:无;宽度:100vw;高度:100vh;显示:块;}

<link href="https://webglfundamentals.org/webgl/resources/webgl-tutorials.css" type="text/css" rel="样式表"/><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>

<script id="vs-solid" type="x-shader/x-vertex">属性 vec4 a_position;属性 vec3 a_color;统一 mat4 u_matrix;不同的 vec4 v_color;无效主(){gl_Position = u_matrix * a_position;v_color = vec4(a_color, 1.0);}<script id="fs-solid" type="x-shader/x-fragment">精密中等浮点数;不同的 vec4 v_color;无效主(){gl_FragColor = v_color;}</script>

在您链接到 Commodore 64 上的软件渲染器的评论中.在 WebGL 中模拟软件渲染器有点困难,因为重点是替换该渲染器.该代码使用三角形处理,但在 WebGL 中我们不使用三角形处理,我们使用顶点处理,而 WebGL 本身处理三角形.

无论如何,至于为什么您的代码不起作用,代码正在检查法线是否面向 Z 平面(z <0),而不是相机.要检查法线是否面向相机,您需要将变换后的法线与从相机到空间中该点的方向进行比较.本文surfaceToView 矢量.

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

尽管该技术不适用于 3D F.它只适用于凸面形状,如立方体、球体、金字塔.F 不是凸的,所以仍然会绘制内线.

在 WebGL 中去除隐藏线的最简单方法是绘制对象两次.一次使用三角形(填充深度缓冲区),然后再次使用线条.使用线条绘制时,您可以将深度测试从 LESS 切换到 LEQUAL

Starting from this lesson here: WebGL 3D Perspective I am triyng to implement Back-Face culling no magic.

I am computing on the fly the face normals in object space. After that, I am setting the fudgeFactor inside m[2][3] to get the perspective divide by Z.

To check if the shear matrix works, I expanded my snippet using directly the vertex positions projected out of the WebGL vertex shader, and added the "projected position" flag.

Now, I am triyng to use the z-component inside the transformed normals to hide the faces with normal.z <0. This technique works well using orthogonal projection - or when fudgeFactor is 0. Why this doesn't work also for perspective projection, when I set the fudgeFactor by my self?

To see what's happen, I wrote the minimal example below to visualize the normal vectors (thanks to: Geeks3D) and colored it. Green normal: face is visible, red normal: face is culled.

I followed the hints of gman and implemented the basic polygon rasterization (out of the WebGL vertex shader) as a proof of concept . Try to drag the fudgeFactor slider and check that works correctly.

'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>

I would expect my normals being sheared (rotated) by my transformation matrix, but they don't.

Please note:

  • I am aware that I can compute again the perspective-corrected normals using the screen-space vertex coords (see my transformVertices function), so I am not asking for that .
  • Moreover: I am aware that normals are usually used for shading computation, for my question this doesn't matter.

I am triyng to understand how to transform normals to implement Back-Face culling under the hood, using a matrix multiplication when I set divide by Z by my self.


EDIT - here are some references:

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)

解决方案

I'm sorry if this is not an answer to your question but "back face culling" has nothing to do with normals and you don't generally do it manually. You let WebGL do it for you

From 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.

Notice normals are never mentioned, nor is z mentioned. It happens entirely in 2D. Further, though it's not impossible, it would be difficult to back face cull using face normals (the normals shown in your example code) as each vertex is processed independently. Vertex shaders don't process triangles, they process individual vertices. So, how would they compute a normal for the triangle in order to cull it? I suppose you could pass in a face normal with each vertex or pass in all 3 vertices every time by duplicating them in a separate buffers but with their order rotated per triangle.

The easiest way to see back face culling at work is probably to draw something but separate the faces so you can see the back faces

'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>

In the comments you linked to software renderer on the Commodore 64. It's kind of hard to emulate a software renderer in WebGL because it's whole point is to replace that renderer. That code is doing things with triangles but in WebGL we don't do things with triangles, we do things with vertices and WebGL itself handles the triangles.

In any case, as for why your code is not working, the code is checking if the normal is facing the Z plane (z < 0), not the camera. To check if the normal is facing the camera you need to compare the transformed normal to the direction from the camera to that point in space. This article calls that the surfaceToView vector.

So, if you wanted to draw a wireframe box and do fake hidden line removal like the example you'd have to pass in a face normal for each vertex representing what direction that vertex points. You'd then need a modelView matrix (everything except the projection matrix). Compute the view position of the point and use that to compute an eye to point vector, you also orient the normal through that same matrix and compute the angle between them with the dot product. < 0 vs > 0 will tell you if that normal is facing away or toward. example

That technique will not work for the 3D F though. It will only work for convex shapes like a cube, sphere, pyramid. The F is not convex so inner lines will still be drawn.

The easiest way to do hidden line removal in WebGL is to draw the object twice. Once with triangles (to fill in the depth buffer) and then again with lines. You might switch the depth test from LESS to LEQUAL when drawing with lines

这篇关于背面剔除的法线变换的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆