Как визуализировать отдельные пиксели для одного слоя 3DTexture в кадровом буфере? - PullRequest
0 голосов
/ 24 июня 2019

У меня есть 3D-текстура 4x4x4, которую я инициализирую и показываю правильно, чтобы закрасить сетку вершин 4x4x4 (см. Прилагаемую красную сетку с одним белым пикселем - 0,0,0).

enter image description here

Однако, когда я рендерил 4 слоя в буфере кадров (все четыре за один раз, используя gl.COLOR_ATTACHMENT0 -> gl.COLOR_ATTACHMENT3, только четыре из шестнадцати пикселей на слое успешно визуализируются моим фрагментным шейдером (для поворота) зеленый).

enter image description here

Когда я делаю только один слой с gl.COLOR_ATTACHMENT0, те же 4 пикселя отображаются правильно измененными для 1 слоя, а остальные 3 слоя остаются с исходным цветом без изменений. Когда я изменяю gl.viewport (0, 0, size, size) (size = 4 в этом примере) на что-то другое, например весь экран, или размером, отличным от 4, тогда пишутся разные пиксели, но никогда не больше 4 Моя цель - точно указать все 16 пикселей каждого слоя. Сейчас я использую цвета, как учебный опыт, но текстура действительно для информации о положении и скорости для каждой вершины для физического моделирования. Я предполагаю (ошибочное предположение?) С 64 точками / вершинами, что я запускаю вершинный шейдер и фрагментный шейдер по 64 раза каждый, окрашивая один пиксель каждый вызов.

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

//Set x,y position coordinates to be used to extract data from one plane of our data cube
//remember, z we handle as a 1 layer of our cube which is composed of a stack of x-y planes. 
const oneLayerVertices = new Float32Array(size * size * 2);
count = 0;  
for (var j = 0; j < (size); j++) {
    for (var i = 0; i < (size); i++) {
        oneLayerVertices[count] = i;
        count++;

        oneLayerVertices[count] = j;
        count++;

        //oneLayerVertices[count] = 0;
        //count++;

        //oneLayerVertices[count] = 0;
        //count++;

    }
}

const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
   position: {
      numComponents: 2,
      data: oneLayerVertices,
   },
});

А затем я использую bufferInfo следующим образом:

gl.useProgram(computeProgramInfo.program);
   twgl.setBuffersAndAttributes(gl, computeProgramInfo, bufferInfo);

   gl.viewport(0, 0, size, size); //remember size = 4

   outFramebuffers.forEach((fb, ndx) => {
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
      gl.drawBuffers([
         gl.COLOR_ATTACHMENT0,
         gl.COLOR_ATTACHMENT1,
         gl.COLOR_ATTACHMENT2,
         gl.COLOR_ATTACHMENT3
      ]);

      const baseLayerTexCoord = (ndx * numLayersPerFramebuffer);
      console.log("My baseLayerTexCoord is "+baseLayerTexCoord);
      twgl.setUniforms(computeProgramInfo, {
         baseLayerTexCoord,
         u_kernel: [
             0, 0, 0,
             0, 0, 0,
             0, 0, 0,

             0, 0, 1,
             0, 0, 0,
             0, 0, 0,

             0, 0, 0,
             0, 0, 0,
             0, 0, 0,
         ],
         u_position: inPos,      
         u_velocity: inVel,      
         loopCounter: loopCounter,   

         numLayersPerFramebuffer: numLayersPerFramebuffer
      });
      gl.drawArrays(gl.POINTS, 0, (16));
   });

VERTEX SHADER: calc_vertex:

const compute_vs = `#version 300 es
  precision highp float;
  in vec4 position;
  void main() {
    gl_Position = position;
  }
`;

ФРАГМЕНТНАЯ ШЕЙДЕР: calc_fragment:

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

out vec4 ourOutput[4];

void main() {
   ourOutput[0] = vec4(0,1,0,1);
   ourOutput[1] = vec4(0,1,0,1);
   ourOutput[2] = vec4(0,1,0,1);
   ourOutput[3] = vec4(0,1,0,1);
}
`;

1 Ответ

0 голосов
/ 25 июня 2019

Я не уверен, что вы пытаетесь сделать и что, по вашему мнению, будут делать позиции.

У вас есть 2 варианта для симуляции GPU в WebGL2

  1. используйте обратную связь преобразования.

    В этом случае вы передаете атрибуты и генерируете данные в буферах. Фактически у вас есть атрибуты и атрибуты out, и обычно вы запускаете только вершинный шейдер. Иными словами, ваши вариации, выходные данные вашего вершинного шейдера, записываются в буфер. Таким образом, у вас есть как минимум 2 набора буферов, currentState и nextState, и ваш вершинный шейдер считывает атрибуты из currentState и записывает их в nextState

    Существует пример записи в буферы с помощью обратной связи преобразования здесь , хотя в этом примере используется только обратная связь преобразования в начале для заполнения буферов один раз.

  2. использовать текстуры, прикрепленные к кадровым буферам

    в этом случае, аналогично, у вас есть 2 текстуры, currentState и nextState. Вы устанавливаете nextState в качестве цели рендеринга и читаете из currentState для генерации следующего состояния.

    Сложность в том, что вы можете визуализировать текстуры, только выводя примитивы в вершинный шейдер. Если currentState и nextState являются 2D-текстурами, это тривиально. Просто выведите квадратик от -1,0 до +1,0 из вершинного шейдера, и все пиксели в nextState будут визуализированы.

    Если вы используете 3D-текстуру, то то же самое, за исключением того, что вы можете визуализировать только до 4 слоев за раз (ну, gl.getParameter(gl.MAX_DRAW_BUFFERS)). так что вам нужно сделать что-то вроде

    for(let layer = 0; layer < numLayers; layer += 4) {
       // setup framebuffer to use these 4 layers
       gl.drawXXX(...) // draw to 4 layers)
    }
    

    или лучше

    // at init time
    const fbs = [];
    for(let layer = 0; layer < numLayers; layer += 4) {
       fbs.push(createFramebufferForThese4Layers(layer);
    }
    
    // at draw time
    fbs.forEach((fb, ndx) => {;
       gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
       gl.drawXXX(...) // draw to 4 layers)
    });
    

    Я предполагаю, что несколько вызовов отрисовки медленнее, чем один вызов отрисовки, поэтому другое решение состоит в том, чтобы вместо этого рассматривать 2D-текстуру как 3D-массив и вычислять координаты текстуры соответствующим образом.

Я не знаю, что лучше. Если вы моделируете частицы, а им нужно только посмотреть на их собственный currentState, преобразовать обратную связь проще. Если необходимо, чтобы каждая частица могла видеть состояние других частиц, другими словами, вам нужен произвольный доступ ко всем данным, тогда единственный вариант - сохранить данные в текстурах.

Что касается позиций, я не понимаю ваш код. Позиции определяют примитивы, POINTS, LINES или TRIANGLES, поэтому как передача целочисленных значений X, Y в наш вершинный шейдер помогает вам определить POINTS, LINES или TRIANGLES?

Похоже, вы пытаетесь использовать POINTS, и в этом случае вам нужно установить gl_PointSize для размера точки, которую вы хотите нарисовать (1.0), и вам нужно преобразовать эти позиции в пространство клипа

gl_Position = vec4((position.xy + 0.5) / resolution, 0, 1);

где resolution - размер текстуры.

Но делать это будет медленно. Намного лучше просто нарисовать полноразмерный (от -1 до +1) квадрат клипа. Для каждого пикселя в месте назначения будет вызван фрагментный шейдер. gl_FragCoord.xy будет местоположением центра пикселя, визуализируемого в данный момент, поэтому первый пиксель в нижнем левом углу gl_FragCoord.xy будет (0,5, 0,5). Пиксель справа от этого будет (1,5, 0,5). Пиксель справа от этого будет (2.5, 0.5). Вы можете использовать это значение, чтобы рассчитать, как получить доступ к currentState. Предполагая, что отображение 1x1 проще всего будет:

int n = numberOfLayerThatsAttachedToCOLOR_ATTACHMENT0;
vec4 currentStateValueForLayerN = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 0), 0);
vec4 currentStateValueForLayerNPlus1 = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 1), 0);
vec4 currentStateValueForLayerNPlus2 = texelFetch(
    currentStateTexture, ivec3(gl_FragCoord.xy, n + 2), 0);
...

vec4 nextStateForLayerN = computeNextStateFromCurrentState(currentStateValueForLayerN);
vec4 nextStateForLayerNPlus1 = computeNextStateFromCurrentState(currentStateValueForLayerNPlus1);
vec4 nextStateForLayerNPlus2 = computeNextStateFromCurrentState(currentStateValueForLayerNPlus2);
...

outColor[0] = nextStateForLayerN;
outColor[1] = nextStateForLayerNPlus1;
outColor[2] = nextStateForLayerNPlus1;
...

Я не знаю, нужно ли вам это, но просто для проверки вот простой пример, который отображает разные цвета для каждого пикселя текстуры 4x4x4, а затем отображает их.

const pointVS = `
#version 300 es

uniform int size;
uniform highp sampler3D tex;
out vec4 v_color;

void main() {
  int x = gl_VertexID % size;
  int y = (gl_VertexID / size) % size;
  int z = gl_VertexID / (size * size);
  
  v_color = texelFetch(tex, ivec3(x, y, z), 0);
  
  gl_PointSize = 8.0;
  
  vec3 normPos = vec3(x, y, z) / float(size); 
  gl_Position = vec4(
     mix(-0.9, 0.6, normPos.x) + mix(0.0,  0.3, normPos.y),
     mix(-0.6, 0.9, normPos.z) + mix(0.0, -0.3, normPos.y),
     0,
     1);
}
`;

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

in vec4 v_color;
out vec4 outColor;

void main() {
  outColor = v_color;
}
`;

const rtVS = `
#version 300 es
in vec4 position;
void main() {
  gl_Position = position;
}
`;

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

uniform vec2 resolution;
out vec4 outColor[4];

void main() {
  vec2 xy = gl_FragCoord.xy / resolution;
  outColor[0] = vec4(1, 0, xy.x, 1);
  outColor[1] = vec4(0.5, xy.yx, 1);
  outColor[2] = vec4(xy, 0, 1);
  outColor[3] = vec4(1, vec2(1) - xy, 1);
}
`;

function main() {
  const gl = document.querySelector('canvas').getContext('webgl2');
  if (!gl) {
    return alert('need webgl2');
  }
  
  const pointProgramInfo = twgl.createProgramInfo(gl, [pointVS, pointFS]);
  const rtProgramInfo = twgl.createProgramInfo(gl, [rtVS, rtFS]);
  
  const size = 4;
  const numPoints = size * size * size;
  const tex = twgl.createTexture(gl, {
    target: gl.TEXTURE_3D,
    width: size,
    height: size,
    depth: size,
  });
  
  const clipspaceFullSizeQuadBufferInfo = twgl.createBufferInfoFromArrays(gl, {
    position: {
      data: [
        -1, -1,
         1, -1,
        -1,  1,
        
        -1,  1,
         1, -1,
         1,  1,
      ],
      numComponents: 2,
    },
  });
  
  const fb = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
  for (let i = 0; i < 4; ++i) {
    gl.framebufferTextureLayer(
        gl.FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0 + i,
        tex,
        0, // mip level
        i, // layer
    );
  }
  
  gl.drawBuffers([
     gl.COLOR_ATTACHMENT0,
     gl.COLOR_ATTACHMENT1,
     gl.COLOR_ATTACHMENT2,
     gl.COLOR_ATTACHMENT3,
  ]);

  gl.viewport(0, 0, size, size);
  gl.useProgram(rtProgramInfo.program);
  twgl.setBuffersAndAttributes(
      gl,
      rtProgramInfo,
      clipspaceFullSizeQuadBufferInfo);
  twgl.setUniforms(rtProgramInfo, {
    resolution: [size, size],
  });
  twgl.drawBufferInfo(gl, clipspaceFullSizeQuadBufferInfo);
  
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.drawBuffers([
     gl.BACK,
  ]);
  
  gl.useProgram(pointProgramInfo.program);
  twgl.setUniforms(pointProgramInfo, {
    tex,
    size,
  });
  gl.drawArrays(gl.POINTS, 0, numPoints);
}
main();
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
...