Реализация глубинного тестирования для полупрозрачных объектов - PullRequest
0 голосов
/ 06 июня 2018

Последние два дня я тщательно троллил в интернете, чтобы разобраться в глубинном тестировании полупрозрачных объектов.Я прочитал несколько статей / учебных пособий по этому вопросу, и в теории, я думаю, я понимаю, как это работает.Однако ни один из них не дает мне пример кода.

У меня есть три требования для моего глубинного тестирования полупрозрачных объектов:

  1. Это должно быть независимо от порядка.

  2. Должно работать, если два квада одного и того же объекта пересекаются друг с другом.Оба полупрозрачные.Представьте себе объект травы, который выглядит как X, если смотреть сверху:

enter image description here

Он должен правильно отображать полупрозрачный игрок rgba(0, 1, 0, 0.5), за окном здания rgba(0, 0, 1, 0.5), но перед фоновым объектом rgba(1, 0, 0, 1):

enter image description here

Крайняя левая линия - это то, как я представляю, как свет / цвет меняется, когда он проходит через полупрозрачные объекты к камере

Заключительные мысли

Я подозреваю, что лучший подход - это сделать пилинг по глубине, но мне все еще не хватает реализации / примера.Я склоняюсь к этому подходу, потому что игра 2.5D и поскольку она может стать опасной для производительности (много слоев для очистки), не нужно будет более двух полупрозрачных объектов для «очистки».

Я уже знаком с кадровыми буферами и с тем, как их кодировать (выполняя некоторые эффекты постобработки с ними).Я буду использовать их, верно?

Большая часть знаний об opengl взята из этого урока , но он охватывает раздельное тестирование глубины и полупрозрачность.Он также, к сожалению, вообще не покрывает прозрачность, независимую от порядка (см. Нижнюю часть страницы смешивания).

Наконец, пожалуйста, не отвечайте только теоретически.например,

Рисование непрозрачного, рисование прозрачного, повторное рисование непрозрачного и т. д.

Мой идеальный ответ будет содержать код конфигурации буферов, шейдеров и снимки экранакаждый проход с объяснением того, что он делает.

Используемый язык программирования также не слишком важен, если он использует OpenGL 4 или новее.Код non-opengl может быть псевдо (мне все равно, как вы сортируете массив или создаете окно GLFW).

РЕДАКТИРОВАТЬ:

Я обновляю свой вопрос, чтобы просто иметь такпример текущего состояния моего кода.В этом примере сначала рисуется полупрозрачный проигрыватель (зеленый), затем непрозрачный фон (красный), а затем полупрозрачное окно (синий).Однако глубина должна быть рассчитана по положению Z квадрата, а не по порядку, в котором он нарисован.

(function() {
   // your page initialization code here
   // the DOM will be available here
  var script = document.createElement('script');
  script.onload = function () {
    main();
  };
  script.src = 'https://mdn.github.io/webgl-examples/tutorial/gl-matrix.js';
  document.head.appendChild(script); //or something of the likes
})();

//
// Start here
//
function main() {
  const canvas = document.querySelector('#glcanvas');
  const gl = canvas.getContext('webgl', {alpha:false});

  // If we don't have a GL context, give up now

  if (!gl) {
    alert('Unable to initialize WebGL. Your browser or machine may not support it.');
    return;
  }

  // Vertex shader program

  const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying lowp vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vColor = aVertexColor;
    }
  `;

  // Fragment shader program

  const fsSource = `
    varying lowp vec4 vColor;

    void main(void) {
      gl_FragColor = vColor;
    }
  `;

  // Initialize a shader program; this is where all the lighting
  // for the vertices and so forth is established.
  const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

  // Collect all the info needed to use the shader program.
  // Look up which attributes our shader program is using
  // for aVertexPosition, aVevrtexColor and also
  // look up uniform locations.
  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
      vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
    },
  };

  // Here's where we call the routine that builds all the
  // objects we'll be drawing.
  const buffers = initBuffers(gl);

  // Draw the scene
  drawScene(gl, programInfo, buffers);
}

//
// initBuffers
//
// Initialize the buffers we'll need. For this demo, we just
// have one object -- a simple two-dimensional square.
//
function initBuffers(gl) {
  // Create a buffer for the square's positions.

  const positionBuffer0 = gl.createBuffer();

  // Select the positionBuffer as the one to apply buffer
  // operations to from here out.

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer0);

  // Now create an array of positions for the square.

  var positions = [
     0.5,  0.5,
    -0.5,  0.5,
     0.5, -0.5,
    -0.5, -0.5,
  ];

  // Now pass the list of positions into WebGL to build the
  // shape. We do this by creating a Float32Array from the
  // JavaScript array, then use it to fill the current buffer.

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  // Now set up the colors for the vertices

  var colors = [
    0.0,  1.0,  0.0,  0.5,    // white
    0.0,  1.0,  0.0,  0.5,    // red
    0.0,  1.0,  0.0,  0.5,    // green
    0.0,  1.0,  0.0,  0.5,    // blue
  ];

  const colorBuffer0 = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer0);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);



  // Create a buffer for the square's positions.

  const positionBuffer1 = gl.createBuffer();

  // Select the positionBuffer as the one to apply buffer
  // operations to from here out.

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer1);

  // Now create an array of positions for the square.

  positions = [
     2.0,  0.4,
    -2.0,  0.4,
     2.0, -2.0,
    -2.0, -2.0,
  ];

  // Now pass the list of positions into WebGL to build the
  // shape. We do this by creating a Float32Array from the
  // JavaScript array, then use it to fill the current buffer.

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  // Now set up the colors for the vertices

  colors = [
    1.0,  0.0,  0.0,  1.0,    // white
    1.0,  0.0,  0.0,  1.0,    // red
    1.0,  0.0,  0.0,  1.0,    // green
    1.0,  0.0,  0.0,  1.0,    // blue
  ];

  const colorBuffer1 = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer1);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
  
  // Create a buffer for the square's positions.

  const positionBuffer2 = gl.createBuffer();

  // Select the positionBuffer as the one to apply buffer
  // operations to from here out.

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer2);

  // Now create an array of positions for the square.

  positions = [
     1.0,  1.0,
    -0.0,  1.0,
     1.0, -1.0,
    -0.0, -1.0,
  ];

  // Now pass the list of positions into WebGL to build the
  // shape. We do this by creating a Float32Array from the
  // JavaScript array, then use it to fill the current buffer.

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

  // Now set up the colors for the vertices

  colors = [
    0.0,  0.0,  1.0,  0.5,    // white
    0.0,  0.0,  1.0,  0.5,    // red
    0.0,  0.0,  1.0,  0.5,    // green
    0.0,  0.0,  1.0,  0.5,    // blue
  ];

  const colorBuffer2 = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer2);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);



  return {
    position0: positionBuffer0,
    color0: colorBuffer0,
    position1: positionBuffer1,
    color1: colorBuffer1,
    position2: positionBuffer2,
    color2: colorBuffer2,
  };
}

//
// Draw the scene.
//
function drawScene(gl, programInfo, buffers) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  //gl.clearDepth(1.0);                 // Clear everything
  gl.disable(gl.DEPTH_TEST)
  gl.enable(gl.BLEND)
  gl.blendEquation(gl.FUNC_ADD)
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

  // Clear the canvas before we start drawing on it.


  // Create a perspective matrix, a special matrix that is
  // used to simulate the distortion of perspective in a camera.
  // Our field of view is 45 degrees, with a width/height
  // ratio that matches the display size of the canvas
  // and we only want to see objects between 0.1 units
  // and 100 units away from the camera.

  const fieldOfView = 45 * Math.PI / 180;   // in radians
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.1;
  const zFar = 100.0;
  const projectionMatrix = mat4.create();

  // note: glmatrix.js always has the first argument
  // as the destination to receive the result.
  mat4.perspective(projectionMatrix,
                   fieldOfView,
                   aspect,
                   zNear,
                   zFar);

  // Set the drawing position to the "identity" point, which is
  // the center of the scene.
  const modelViewMatrix = mat4.create();

  // Now move the drawing position a bit to where we want to
  // start drawing the square.

  mat4.translate(modelViewMatrix,     // destination matrix
                 modelViewMatrix,     // matrix to translate
                 [-0.0, 0.0, -6.0]);  // amount to translate

  function drawSquare(positionbuffer, colorbuffer) {
  // Tell WebGL how to pull out the positions from the position
  // buffer into the vertexPosition attribute
  {
    const numComponents = 2;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY_BUFFER, positionbuffer);
    gl.vertexAttribPointer(
        programInfo.attribLocations.vertexPosition,
        numComponents,
        type,
        normalize,
        stride,
        offset);
    gl.enableVertexAttribArray(
        programInfo.attribLocations.vertexPosition);
  }

  // Tell WebGL how to pull out the colors from the color buffer
  // into the vertexColor attribute.
  {
    const numComponents = 4;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY_BUFFER, colorbuffer);
    gl.vertexAttribPointer(
        programInfo.attribLocations.vertexColor,
        numComponents,
        type,
        normalize,
        stride,
        offset);
    gl.enableVertexAttribArray(
        programInfo.attribLocations.vertexColor);
  }

  // Tell WebGL to use our program when drawing

  gl.useProgram(programInfo.program);

  // Set the shader uniforms

  gl.uniformMatrix4fv(
      programInfo.uniformLocations.projectionMatrix,
      false,
      projectionMatrix);
  gl.uniformMatrix4fv(
      programInfo.uniformLocations.modelViewMatrix,
      false,
      modelViewMatrix);

  {
    const offset = 0;
    const vertexCount = 4;
    gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
  }
  }
  drawSquare(buffers.position0, buffers.color0); // Player
  drawSquare(buffers.position1, buffers.color1); // Background
  drawSquare(buffers.position2, buffers.color2); // Window
}

//
// Initialize a shader program, so WebGL knows how to draw our data
//
function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

  // Create the shader program

  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  // If creating the shader program failed, alert

  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
    return null;
  }

  return shaderProgram;
}

//
// creates a shader of the given type, uploads the source and
// compiles it.
//
function loadShader(gl, type, source) {
  const shader = gl.createShader(type);

  // Send the source to the shader object

  gl.shaderSource(shader, source);

  // Compile the shader program

  gl.compileShader(shader);

  // See if it compiled successfully

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title></title>
</head>
<body>
  <canvas id="glcanvas" width="640" height="480"></canvas>

</body>
</html>

Ответы [ 2 ]

0 голосов
/ 09 июня 2018

Кажется, это то, что делает бумага, связанная ripi2

function main() {
  const m4 = twgl.m4;
  const gl = document.querySelector('canvas').getContext('webgl2', {alpha: false});
  if (!gl) {
    alert('need WebGL2');
    return;
  }
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    alert('EXT_color_buffer_float');
    return;
  }

  const vs = `
  #version 300 es
  layout(location=0) in vec4 position;
  uniform mat4 u_matrix;
  void main() {
    gl_Position = u_matrix * position;
  }
  `;

  const checkerFS = `
  #version 300 es
  precision highp float;

  uniform vec4 color1;
  uniform vec4 color2;

  out vec4 fragColor;

  void main() {
    ivec2 grid = ivec2(gl_FragCoord.xy) / 32;
    fragColor = mix(color1, color2, float((grid.x + grid.y) % 2));
  }
  `;

  const transparentFS = `
  #version 300 es
  precision highp float;
  uniform vec4 Ci;

  out vec4 fragData[2];

  float w(float z, float a) {
    return a * max(pow(10.0,-2.0),3.0*pow(10.0,3.0)*pow((1.0 - z), 3.));
  }

  void main() {
    float ai = Ci.a;
    float zi = gl_FragCoord.z;

    float wresult = w(zi, ai);
    fragData[0] = vec4(Ci.rgb * wresult, ai);
    fragData[1].r = ai * wresult;
  }
  `;

  const compositeFS = `
  #version 300 es
  precision highp float;
  uniform sampler2D ATexture;
  uniform sampler2D BTexture;

  out vec4 fragColor;

  void main() {
    vec4 accum = texelFetch(ATexture, ivec2(gl_FragCoord.xy), 0);
    float r = accum.a;
    accum.a = texelFetch(BTexture, ivec2(gl_FragCoord.xy), 0).r;
    fragColor = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);
  }
  `;

  const checkerProgramInfo = twgl.createProgramInfo(gl, [vs, checkerFS]);
  const transparentProgramInfo = twgl.createProgramInfo(gl, [vs, transparentFS]);
  const compositeProgramInfo = twgl.createProgramInfo(gl, [vs, compositeFS]);

  const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);

  const fbi = twgl.createFramebufferInfo(
    gl,
    [
      { internalFormat: gl.RGBA32F, minMag: gl.NEAREST },
      { internalFormat: gl.R32F, minMag: gl.NEAREST },
    ]);

  function render(time) {
    time *= 0.001;

    twgl.setBuffersAndAttributes(gl, transparentProgramInfo, bufferInfo);

    // drawOpaqueSurfaces();
    gl.useProgram(checkerProgramInfo.program);
    gl.disable(gl.BLEND);
    twgl.setUniforms(checkerProgramInfo, {
      color1: [.5, .5, .5, 1],
      color2: [.7, .7, .7, 1],
      u_matrix: m4.identity(),
    });
    twgl.drawBufferInfo(gl, bufferInfo);

    twgl.bindFramebufferInfo(gl, fbi);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
    gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 1]));
    gl.clearBufferfv(gl.COLOR, 1, new Float32Array([1, 1, 1, 1]));

    gl.depthMask(false);
    gl.enable(gl.BLEND);
    gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);

    gl.useProgram(transparentProgramInfo.program);

    // drawTransparentSurfaces();
    const quads = [
       [ .4,  0,  0, .4],
       [ .4, .4,  0, .4],
       [  0, .4,  0, .4],
       [  0, .4, .4, .4],
       [  0, .0, .4, .4],
       [ .4, .0, .4, .4],
    ];
    quads.forEach((color, ndx) => {
      const u = ndx / (quads.length - 1);
      // change the order every second
      const v = ((ndx + time | 0) % quads.length) / (quads.length - 1);
      const xy = (u * 2 - 1) * .25;
      const z = (v * 2 - 1) * .25;
      let mat = m4.identity();
      mat = m4.translate(mat, [xy, xy, z]);
      mat = m4.scale(mat, [.3, .3, 1]);
      twgl.setUniforms(transparentProgramInfo, {
        Ci: color,
        u_matrix: mat,
      });
      twgl.drawBufferInfo(gl, bufferInfo);
    });

    twgl.bindFramebufferInfo(gl, null);
    gl.drawBuffers([gl.BACK]);

    gl.blendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);

    gl.useProgram(compositeProgramInfo.program);

    twgl.setUniforms(compositeProgramInfo, {
      ATexture: fbi.attachments[0],
      BTexture: fbi.attachments[1],
      u_matrix: m4.identity(),
    });

    twgl.drawBufferInfo(gl, bufferInfo);

    /* only needed if {alpha: false} not passed into getContext
    gl.colorMask(false, false, false, true);
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.colorMask(true, true, true, true);
    */

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

Несколько замечаний:

  • Используется WebGL2, но это должно быть возможно в WebGL1, вам придется изменитьшейдеры для использования GLSL ES 1.0.
  • Используются текстуры с плавающей точкой.В статье упоминается, что вы также можете использовать текстуры наполовину.Обратите внимание, что рендеринг как текстур пополам, так и текстур с плавающей точкой является дополнительной функцией даже в WebGL2.Я считаю, что большинство мобильных устройств могут рендериться вдвое, но не плавать.
  • Используется весовое уравнение 10 из статьи.В статье 4 весовых уравнения.7, 8, 9 и 10. Чтобы выполнить 7, 8 или 9, вам нужно будет передать пространство вида z из вершинного шейдера в фрагментный шейдер
  • Это переключает порядок рисования каждыйsecond

Код довольно прост.

Создает 3 шейдера.Один, чтобы нарисовать шахматную доску, чтобы у нас было что-то непрозрачное, чтобы видеть прозрачные вещи, нарисованные выше.Одним из них является прозрачный объект шейдера.Последний - шейдер, который создает прозрачные элементы в сцене.

Затем он создает 2 текстуры, текстуру RGBA32F с плавающей точкой и текстуру R32F с плавающей точкой (только для красного канала).Он прикрепляет их к кадровому буферу.(все это делается в функции 1, twgl.createFramebufferInfo. Эта функция по умолчанию делает текстуры того же размера, что и холст.

Мы делаем один квад, который идет от -1 до + 1

Мы используем этот квад для рисования шахматной доски на холсте

Затем мы включаем смешивание, настраиваем уравнения смешивания, как сказано в документе, переключаемся на рендеринг в наш кадровый буфер, очищаем этот кадровый буфер.до 0,0,0,1 и 1. Это версия, в которой у нас нет отдельных функций смешивания для буфера отрисовки. Если вы переключаетесь на версию, которая может использовать отдельные функции смешивания для буфера отрисовки, вам необходимо очиститьразные значения и использовать другой шейдер (см. статью)

Используя наш прозрачный шейдер, мы в одном и том же квадрате нарисовали 6 прямоугольников одного сплошного цвета. Я просто использовал сплошной цвет, чтобы упростить его.разные Z и Z меняются каждую секунду, чтобы увидеть, как меняются результаты Z.

В шейдере Ci - это цвет ввода.Это будет предварительно умноженный альфа-цвет согласно бумаге.Функция fragData [0] is the "accumulate" texture and fragData [1] is the "revealage" texture and is only one channel, red. The w` представляет уравнение 10 из статьи.

После того, как все 6 квадратов нарисованы, мы переключаемся обратно на рендеринг на холст и используем шейдер композиции.чтобы объединить результат прозрачности с непрозрачным содержимым холста.

Вот пример с некоторой геометрией.Различия:

  • Используются уравнения (7) из бумаги вместо (10)
  • . Чтобы сделать правильный zbuffering, буфер глубины должен использоваться совместно при непрозрачном и прозрачном рендеринге.,Так что есть 2 кадра буфера.Один буфер имеет глубину RGBA8 +, другой - глубину RGBA32F + R32F +.Буфер глубины используется совместно.
  • Прозрачный рендеринг вычисляет простое освещение и затем использует результат как значение Ci из бумаги
  • После наложения прозрачного в непрозрачный нам все еще нужно скопироватьнепрозрачный в холст, чтобы увидеть результат

function main() {
  const m4 = twgl.m4;
  const v3 = twgl.v3;
  const gl = document.querySelector('canvas').getContext('webgl2', {alpha: false});
  if (!gl) {
    alert('need WebGL2');
    return;
  }
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    alert('EXT_color_buffer_float');
    return;
  }

  const vs = `
  #version 300 es
  layout(location=0) in vec4 position;
  layout(location=1) in vec3 normal;
  uniform mat4 u_projection;
  uniform mat4 u_modelView;
  
  out vec4 v_viewPosition;
  out vec3 v_normal;

  void main() {
    gl_Position = u_projection * u_modelView * position;
    v_viewPosition = u_modelView * position;
    v_normal = (u_modelView * vec4(normal, 0)).xyz;
  }
  `;

  const checkerFS = `
  #version 300 es
  precision highp float;

  uniform vec4 color1;
  uniform vec4 color2;

  out vec4 fragColor;

  void main() {
    ivec2 grid = ivec2(gl_FragCoord.xy) / 32;
    fragColor = mix(color1, color2, float((grid.x + grid.y) % 2));
  }
  `;
  
  const opaqueFS = `
  #version 300 es
  precision highp float;
  
  in vec4 v_viewPosition;
  in vec3 v_normal;
  
  uniform vec4 u_color;
  uniform vec3 u_lightDirection;
  
  out vec4 fragColor;
  
  void main() {
    float light = abs(dot(normalize(v_normal), u_lightDirection));
    fragColor = vec4(u_color.rgb * light, u_color.a);
  }
  `;

  const transparentFS = `
  #version 300 es
  precision highp float;
  uniform vec4 u_color;
  uniform vec3 u_lightDirection;
  
  in vec4 v_viewPosition;
  in vec3 v_normal;
  
  out vec4 fragData[2];

  // eq (7)
  float w(float z, float a) {
    return a * max(
      pow(10.0, -2.0),
      min(
        3.0 * pow(10.0, 3.0),
        10.0 /
        (pow(10.0, -5.0) + 
         pow(abs(z) / 5.0, 2.0) +
         pow(abs(z) / 200.0, 6.0)
        )
      )
    );
  }

  void main() {
    float light = abs(dot(normalize(v_normal), u_lightDirection));
    vec4 Ci = vec4(u_color.rgb * light, u_color.a);
  
    float ai = Ci.a;
    float zi = gl_FragCoord.z;

    float wresult = w(zi, ai);
    fragData[0] = vec4(Ci.rgb * wresult, ai);
    fragData[1].r = ai * wresult;
  }
  `;

  const compositeFS = `
  #version 300 es
  precision highp float;
  uniform sampler2D ATexture;
  uniform sampler2D BTexture;
  
  out vec4 fragColor;

  void main() {
    vec4 accum = texelFetch(ATexture, ivec2(gl_FragCoord.xy), 0);
    float r = accum.a;
    accum.a = texelFetch(BTexture, ivec2(gl_FragCoord.xy), 0).r;
    fragColor = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);
  }
  `;
  
  const blitFS = `
  #version 300 es
  precision highp float;
  uniform sampler2D u_texture;
  
  out vec4 fragColor;

  void main() {
    fragColor = texelFetch(u_texture, ivec2(gl_FragCoord.xy), 0);
  }
  `;

  const checkerProgramInfo = twgl.createProgramInfo(gl, [vs, checkerFS]);
  const opaqueProgramInfo = twgl.createProgramInfo(gl, [vs, opaqueFS]);
  const transparentProgramInfo = twgl.createProgramInfo(gl, [vs, transparentFS]);
  const compositeProgramInfo = twgl.createProgramInfo(gl, [vs, compositeFS]);
  const blitProgramInfo = twgl.createProgramInfo(gl, [vs, blitFS]);

  const xyQuadVertexArrayInfo = makeVAO(checkerProgramInfo, twgl.primitives.createXYQuadBufferInfo(gl));
  const sphereVertexArrayInfo = makeVAO(transparentProgramInfo, twgl.primitives.createSphereBufferInfo(gl, 1, 16, 12));
  const cubeVertexArrayInfo = makeVAO(opaqueProgramInfo, twgl.primitives.createCubeBufferInfo(gl, 1, 1));
  
  function makeVAO(programInfo, bufferInfo) {
    return twgl.createVertexArrayInfo(gl, programInfo, bufferInfo);
  }
  
  // In order to do proper zbuffering we need to share
  // the depth buffer 
  
  const opaqueAttachments = [
    { internalFormat: gl.RGBA8, minMag: gl.NEAREST },
    { format: gl.DEPTH_COMPONENT16, minMag: gl.NEAREST },
  ];
  const opaqueFBI = twgl.createFramebufferInfo(gl, opaqueAttachments);
  
  const transparentAttachments = [
    { internalFormat: gl.RGBA32F, minMag: gl.NEAREST },
    { internalFormat: gl.R32F, minMag: gl.NEAREST },
    { format: gl.DEPTH_COMPONENT16, minMag: gl.NEAREST, attachment: opaqueFBI.attachments[1] },
  ];
  const transparentFBI = twgl.createFramebufferInfo(gl, transparentAttachments);

  function render(time) {
    time *= 0.001;

    if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
      // if the canvas is resized also resize the framebuffer
      // attachments (the depth buffer will be resized twice 
      // but I'm too lazy to fix it)
      twgl.resizeFramebufferInfo(gl, opaqueFBI, opaqueAttachments);
      twgl.resizeFramebufferInfo(gl, transparentFBI, transparentAttachments);
    }
    
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const fov = 45 * Math.PI / 180;
    const zNear = 0.1;
    const zFar = 500;
    
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [0, 0, -5];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);

    const lightDirection = v3.normalize([1, 3, 5]);

    twgl.bindFramebufferInfo(gl, opaqueFBI);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0]);    
    gl.depthMask(true);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.bindVertexArray(xyQuadVertexArrayInfo.vertexArrayObject);

    // drawOpaqueSurfaces();
    // draw checkerboard
    gl.useProgram(checkerProgramInfo.program);
    gl.disable(gl.DEPTH_TEST);
    gl.disable(gl.BLEND);
    twgl.setUniforms(checkerProgramInfo, {
      color1: [.5, .5, .5, 1],
      color2: [.7, .7, .7, 1],
      u_projection: m4.identity(),
      u_modelView: m4.identity(),
    });
    twgl.drawBufferInfo(gl, xyQuadVertexArrayInfo);

    // draw a cube with depth buffer
    gl.enable(gl.DEPTH_TEST);
    
    {
      gl.useProgram(opaqueProgramInfo.program);
      gl.bindVertexArray(cubeVertexArrayInfo.vertexArrayObject);
      let mat = view;
      mat = m4.rotateX(mat, time * .1);
      mat = m4.rotateY(mat, time * .2);
      mat = m4.scale(mat, [1.5, 1.5, 1.5]);
      twgl.setUniforms(opaqueProgramInfo, {
        u_color: [1, .5, .2, 1],
        u_lightDirection: lightDirection,
        u_projection: projection,
        u_modelView: mat,
      });    
      twgl.drawBufferInfo(gl, cubeVertexArrayInfo);
    }
    
    twgl.bindFramebufferInfo(gl, transparentFBI);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
    // these values change if using separate blend functions
    // per attachment (something WebGL2 does not support)
    gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 1]));
    gl.clearBufferfv(gl.COLOR, 1, new Float32Array([1, 1, 1, 1]));

    gl.depthMask(false);  // don't write to depth buffer (but still testing)
    gl.enable(gl.BLEND);
    // this changes if using separate blend functions per attachment
    gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);

    gl.useProgram(transparentProgramInfo.program);
    gl.bindVertexArray(sphereVertexArrayInfo.vertexArrayObject);

    // drawTransparentSurfaces();
    const spheres = [
       [ .4,  0,  0, .4],
       [ .4, .4,  0, .4],
       [  0, .4,  0, .4],
       [  0, .4, .4, .4],
       [  0, .0, .4, .4],
       [ .4, .0, .4, .4],
    ];
    spheres.forEach((color, ndx) => {
      const u = ndx + 2;
      let mat = view;
      mat = m4.rotateX(mat, time * u * .1);
      mat = m4.rotateY(mat, time * u * .2);
      mat = m4.translate(mat, [0, 0, 1 + ndx * .1]);
      twgl.setUniforms(transparentProgramInfo, {
        u_color: color,
        u_lightDirection: lightDirection,
        u_projection: projection,
        u_modelView: mat,
      });
      twgl.drawBufferInfo(gl, sphereVertexArrayInfo);
    });

    // composite transparent results with opaque
    twgl.bindFramebufferInfo(gl, opaqueFBI);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0]);

    gl.disable(gl.DEPTH_TEST);
    gl.blendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);

    gl.useProgram(compositeProgramInfo.program);
    gl.bindVertexArray(xyQuadVertexArrayInfo.vertexArrayObject);

    twgl.setUniforms(compositeProgramInfo, {
      ATexture: transparentFBI.attachments[0],
      BTexture: transparentFBI.attachments[1],
      u_projection: m4.identity(),
      u_modelView: m4.identity(),
    });

    twgl.drawBufferInfo(gl, xyQuadVertexArrayInfo);

    /* only needed if {alpha: false} not passed into getContext
    gl.colorMask(false, false, false, true);
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.colorMask(true, true, true, true);
    */
    
    // draw opaque color buffer into canvas
    // could probably use gl.blitFramebuffer
    gl.disable(gl.BLEND);
    twgl.bindFramebufferInfo(gl, null);
    gl.useProgram(blitProgramInfo.program);
    gl.bindVertexArray(xyQuadVertexArrayInfo.vertexArrayObject);

    twgl.setUniforms(blitProgramInfo, {
      u_texture: opaqueFBI.attachments[0],
      u_projection: m4.identity(),
      u_modelView: m4.identity(),
    });
    twgl.drawBufferInfo(gl, xyQuadVertexArrayInfo);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>

Мне кажется, что вместо того, чтобы использовать стандартное смешивание OpenGL для последних 2 шагов (составной, затем блиц), мы могли бы изменить составной шейдер, чтобы онберет 3 текстуры (ATexutre, BTexture, opaqueTexture) и смешивается в шейдере, выводя непосредственно на холст.Это было бы быстрее.

function main() {
  const m4 = twgl.m4;
  const v3 = twgl.v3;
  const gl = document.querySelector('canvas').getContext('webgl2', {alpha: false});
  if (!gl) {
    alert('need WebGL2');
    return;
  }
  const ext = gl.getExtension('EXT_color_buffer_float');
  if (!ext) {
    alert('EXT_color_buffer_float');
    return;
  }

  const vs = `
  #version 300 es
  layout(location=0) in vec4 position;
  layout(location=1) in vec3 normal;
  uniform mat4 u_projection;
  uniform mat4 u_modelView;
  
  out vec4 v_viewPosition;
  out vec3 v_normal;

  void main() {
    gl_Position = u_projection * u_modelView * position;
    v_viewPosition = u_modelView * position;
    v_normal = (u_modelView * vec4(normal, 0)).xyz;
  }
  `;

  const checkerFS = `
  #version 300 es
  precision highp float;

  uniform vec4 color1;
  uniform vec4 color2;

  out vec4 fragColor;

  void main() {
    ivec2 grid = ivec2(gl_FragCoord.xy) / 32;
    fragColor = mix(color1, color2, float((grid.x + grid.y) % 2));
  }
  `;
  
  const opaqueFS = `
  #version 300 es
  precision highp float;
  
  in vec4 v_viewPosition;
  in vec3 v_normal;
  
  uniform vec4 u_color;
  uniform vec3 u_lightDirection;
  
  out vec4 fragColor;
  
  void main() {
    float light = abs(dot(normalize(v_normal), u_lightDirection));
    fragColor = vec4(u_color.rgb * light, u_color.a);
  }
  `;

  const transparentFS = `
  #version 300 es
  precision highp float;
  uniform vec4 u_color;
  uniform vec3 u_lightDirection;
  
  in vec4 v_viewPosition;
  in vec3 v_normal;
  
  out vec4 fragData[2];

  // eq (7)
  float w(float z, float a) {
    return a * max(
      pow(10.0, -2.0),
      min(
        3.0 * pow(10.0, 3.0),
        10.0 /
        (pow(10.0, -5.0) + 
         pow(abs(z) / 5.0, 2.0) +
         pow(abs(z) / 200.0, 6.0)
        )
      )
    );
  }

  void main() {
    float light = abs(dot(normalize(v_normal), u_lightDirection));
    vec4 Ci = vec4(u_color.rgb * light, u_color.a);
  
    float ai = Ci.a;
    float zi = gl_FragCoord.z;

    float wresult = w(zi, ai);
    fragData[0] = vec4(Ci.rgb * wresult, ai);
    fragData[1].r = ai * wresult;
  }
  `;

  const compositeFS = `
  #version 300 es
  precision highp float;
  uniform sampler2D ATexture;
  uniform sampler2D BTexture;
  uniform sampler2D opaqueTexture;
  
  out vec4 fragColor;

  void main() {
    vec4 accum = texelFetch(ATexture, ivec2(gl_FragCoord.xy), 0);
    float r = accum.a;
    accum.a = texelFetch(BTexture, ivec2(gl_FragCoord.xy), 0).r;
    vec4 transparentColor = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);
    vec4 opaqueColor = texelFetch(opaqueTexture, ivec2(gl_FragCoord.xy), 0);
    //  gl.blendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA);
    fragColor = transparentColor * (1. - r) + opaqueColor * r;
  }
  `;
  
  const checkerProgramInfo = twgl.createProgramInfo(gl, [vs, checkerFS]);
  const opaqueProgramInfo = twgl.createProgramInfo(gl, [vs, opaqueFS]);
  const transparentProgramInfo = twgl.createProgramInfo(gl, [vs, transparentFS]);
  const compositeProgramInfo = twgl.createProgramInfo(gl, [vs, compositeFS]);

  const xyQuadVertexArrayInfo = makeVAO(checkerProgramInfo, twgl.primitives.createXYQuadBufferInfo(gl));
  const sphereVertexArrayInfo = makeVAO(transparentProgramInfo, twgl.primitives.createSphereBufferInfo(gl, 1, 16, 12));
  const cubeVertexArrayInfo = makeVAO(opaqueProgramInfo, twgl.primitives.createCubeBufferInfo(gl, 1, 1));
  
  function makeVAO(programInfo, bufferInfo) {
    return twgl.createVertexArrayInfo(gl, programInfo, bufferInfo);
  }
  
  // In order to do proper zbuffering we need to share
  // the depth buffer 
  
  const opaqueAttachments = [
    { internalFormat: gl.RGBA8, minMag: gl.NEAREST },
    { format: gl.DEPTH_COMPONENT16, minMag: gl.NEAREST },
  ];
  const opaqueFBI = twgl.createFramebufferInfo(gl, opaqueAttachments);
  
  const transparentAttachments = [
    { internalFormat: gl.RGBA32F, minMag: gl.NEAREST },
    { internalFormat: gl.R32F, minMag: gl.NEAREST },
    { format: gl.DEPTH_COMPONENT16, minMag: gl.NEAREST, attachment: opaqueFBI.attachments[1] },
  ];
  const transparentFBI = twgl.createFramebufferInfo(gl, transparentAttachments);

  function render(time) {
    time *= 0.001;

    if (twgl.resizeCanvasToDisplaySize(gl.canvas)) {
      // if the canvas is resized also resize the framebuffer
      // attachments (the depth buffer will be resized twice 
      // but I'm too lazy to fix it)
      twgl.resizeFramebufferInfo(gl, opaqueFBI, opaqueAttachments);
      twgl.resizeFramebufferInfo(gl, transparentFBI, transparentAttachments);
    }
    
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const fov = 45 * Math.PI / 180;
    const zNear = 0.1;
    const zFar = 500;
    
    const projection = m4.perspective(fov, aspect, zNear, zFar);
    const eye = [0, 0, -5];
    const target = [0, 0, 0];
    const up = [0, 1, 0];
    const camera = m4.lookAt(eye, target, up);
    const view = m4.inverse(camera);

    const lightDirection = v3.normalize([1, 3, 5]);

    twgl.bindFramebufferInfo(gl, opaqueFBI);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0]);    
    gl.depthMask(true);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    gl.bindVertexArray(xyQuadVertexArrayInfo.vertexArrayObject);

    // drawOpaqueSurfaces();
    // draw checkerboard
    gl.useProgram(checkerProgramInfo.program);
    gl.disable(gl.DEPTH_TEST);
    gl.disable(gl.BLEND);
    twgl.setUniforms(checkerProgramInfo, {
      color1: [.5, .5, .5, 1],
      color2: [.7, .7, .7, 1],
      u_projection: m4.identity(),
      u_modelView: m4.identity(),
    });
    twgl.drawBufferInfo(gl, xyQuadVertexArrayInfo);

    // draw a cube with depth buffer
    gl.enable(gl.DEPTH_TEST);
    
    {
      gl.useProgram(opaqueProgramInfo.program);
      gl.bindVertexArray(cubeVertexArrayInfo.vertexArrayObject);
      let mat = view;
      mat = m4.rotateX(mat, time * .1);
      mat = m4.rotateY(mat, time * .2);
      mat = m4.scale(mat, [1.5, 1.5, 1.5]);
      twgl.setUniforms(opaqueProgramInfo, {
        u_color: [1, .5, .2, 1],
        u_lightDirection: lightDirection,
        u_projection: projection,
        u_modelView: mat,
      });    
      twgl.drawBufferInfo(gl, cubeVertexArrayInfo);
    }
    
    twgl.bindFramebufferInfo(gl, transparentFBI);
    gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
    // these values change if using separate blend functions
    // per attachment (something WebGL2 does not support)
    gl.clearBufferfv(gl.COLOR, 0, new Float32Array([0, 0, 0, 1]));
    gl.clearBufferfv(gl.COLOR, 1, new Float32Array([1, 1, 1, 1]));

    gl.depthMask(false);  // don't write to depth buffer (but still testing)
    gl.enable(gl.BLEND);
    // this changes if using separate blend functions per attachment
    gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);

    gl.useProgram(transparentProgramInfo.program);
    gl.bindVertexArray(sphereVertexArrayInfo.vertexArrayObject);

    // drawTransparentSurfaces();
    const spheres = [
       [ .4,  0,  0, .4],
       [ .4, .4,  0, .4],
       [  0, .4,  0, .4],
       [  0, .4, .4, .4],
       [  0, .0, .4, .4],
       [ .4, .0, .4, .4],
    ];
    spheres.forEach((color, ndx) => {
      const u = ndx + 2;
      let mat = view;
      mat = m4.rotateX(mat, time * u * .1);
      mat = m4.rotateY(mat, time * u * .2);
      mat = m4.translate(mat, [0, 0, 1 + ndx * .1]);
      twgl.setUniforms(transparentProgramInfo, {
        u_color: color,
        u_lightDirection: lightDirection,
        u_projection: projection,
        u_modelView: mat,
      });
      twgl.drawBufferInfo(gl, sphereVertexArrayInfo);
    });

    // composite transparent results with opaque
    twgl.bindFramebufferInfo(gl, null);

    gl.disable(gl.DEPTH_TEST);
    gl.disable(gl.BLEND);
    
    gl.useProgram(compositeProgramInfo.program);
    gl.bindVertexArray(xyQuadVertexArrayInfo.vertexArrayObject);

    twgl.setUniforms(compositeProgramInfo, {
      ATexture: transparentFBI.attachments[0],
      BTexture: transparentFBI.attachments[1],
      opaqueTexture: opaqueFBI.attachments[0],
      u_projection: m4.identity(),
      u_modelView: m4.identity(),
    });

    twgl.drawBufferInfo(gl, xyQuadVertexArrayInfo);

    /* only needed if {alpha: false} not passed into getContext
    gl.colorMask(false, false, false, true);
    gl.clearColor(1, 1, 1, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.colorMask(true, true, true, true);
    */
    
    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}
main();
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
0 голосов
/ 07 июня 2018

У меня есть три требования для моего глубинного тестирования полупрозрачных объектов

На самом деле довольно редко встречаются самопересекающиеся объекты с частично прозрачными (фактически смешанными) образцами.Общие случаи для самопересекающейся геометрии - трава и листья.Однако в этих случаях фактические участки, покрытые травой и листьями, не являются прозрачными - они непрозрачны.

Распространенным решением здесь является альфа-тестирование.Визуализируйте листья как непрозрачный (не смешанный) квад (с обычным тестом глубины и записью) и отбросьте фрагменты, которые имеют недостаточную альфа (например, потому что они находятся за пределами листа).Поскольку отдельные образцы здесь непрозрачны, то вы получаете независимость порядка бесплатно, потому что тест глубины работает так, как вы ожидаете для непрозрачного объекта.

Если вы хотите смешать края, включите альфа-покрытие и позвольтеразрешение нескольких образцов немного очистит края.

Для небольшого количества фактически прозрачного материала, который вы оставили, тогда обычно вам нужно выполнить обратную сортировку на ЦП и выполнить ее посленепрозрачный проход.

Правильное OIT возможно, но, как правило, это довольно дорогостоящий метод, поэтому я еще не видел, чтобы кто-нибудь на самом деле использовал его вне академической среды (по крайней мере, в мобильных реализациях OpenGL ES).

...