Как визуализировать отдельные пиксели для одного слоя 3D-текстуры во фреймбуфере?

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

введите описание изображения здесь

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

введите описание изображения здесь

Когда я делаю только один слой с 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));
   });

ВЕРТЕКС ШЕЙДЕР: 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);
}
`;

person billvh    schedule 24.06.2019    source источник


Ответы (1)


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

У вас есть 2 варианта моделирования графического процессора в WebGL2

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

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

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

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

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

    трудность в том, что вы можете визуализировать текстуры только путем вывода примитивов в вершинном шейдере. Если currentState и nextState - это двухмерные текстуры, это тривиально. Просто выведите квадрат от -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-массив и соответствующим образом вычислять координаты текстуры.

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

Что касается позиций, я не понимаю ваш код. Позиции определяют примитивы: 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>

person gman    schedule 25.06.2019
comment
Он рисует шейдер полного пространства клипа (rtVS / rtFS), поэтому да, все пиксели в цели рендеринга будут отображаться независимо от размера, и да, ваши значения для gl_FragCoord.xy будут for(y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { gl_FragCoord.xy = vec2(x + 0.5, y + 0.5); runFragmentShader(); } } - person gman; 26.06.2019
comment
Я могу использовать gl_FragCoord плюс мой слой, чтобы теперь устанавливать значения в любой из 64 точек. Я вижу цвета, которые я установил на экране. Однако, когда я запрашиваю с помощью texelFetch vec4 mypixel = texelFetch (currentStateTexture, ivec3 (gl_FragCoord.xy, 1), 0), а затем пытаюсь сравнить возвращаемое значение if (mypixel.r == 0.0) ... Я никогда не получить совпадение. Не могли бы вы подсказать, что я делаю не так? Я устанавливаю цвета с помощью ParticlePosition [layer] = vec4 (0.0, 1.0, 0.0, 1.0); а затем ourOutput [0] = particlePosition [0]. Все цвета и альфа, которые я устанавливаю, равны 0,0 или 1,0. - person billvh; 26.06.2019
comment
Задайте еще вопрос, где вы можете разместить свой новый код? - person gman; 26.06.2019