Как использовать много текстур для вычислений в WebGL - PullRequest
0 голосов
/ 15 марта 2019

Просто сосредоточив внимание на униформе / атрибутах / вариациях для одной пары вершинных / фрагментных шейдеров, мне интересно, как вы могли бы смоделировать следующую систему с использованием текстур .Фокусировка на 2D.

  • position: Текущий объект position .
  • translation: Предложенные объекты следующая позиция на основе некоторых вычислений ЦПвпереди.
  • скорость: скорость объекта.
  • вращение: следующее вращение объекта.
  • силы (например, гравитация или столкновение): суммарные силы объекта, действующие на него вв каждом направлении.
  • температура: температура объекта.
  • масса / плотность: масса / плотность объекта.
  • кривизна: перемещение по заранее определенной кривой (например, замедление).

Сначала я хотел сделать это:

attribute vec3 a_position;
attribute vec3 a_translation;
attribute vec3 a_velocity;
attribute vec3 a_rotation;
attribute vec3 a_force;
attribute vec3 a_temperature;
attribute vec3 a_material; // mass and density
attribute vec4 a_color;
attribute vec4 a_curvature;

Но это может столкнуться с проблемой слишком много атрибутов .

Тактогда я вспоминаю про с использованием текстур для этого.Не вдаваясь в подробности, мне просто интересно, как вы могли бы структурировать униформу / атрибуты / вариации для достижения этой цели.

attribute vec2 a_position_uv;
attribute vec2 a_translation_uv;
attribute vec2 a_velocity_uv;
attribute vec2 a_rotation_uv;
attribute vec2 a_force_uv;
attribute vec2 a_temperature_uv;
attribute vec2 a_material_uv;
attribute vec2 a_color_uv;
attribute vec2 a_curvature_uv;

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

Но я не уверен, как определить текстуры для обоих шейдеров.Интересно, если это так:

uniform sampler2D u_position_texture;
uniform sampler2D u_translation_texture;
uniform sampler2D u_velocity_texture;
uniform sampler2D u_rotation_texture;
uniform sampler2D u_force_texture;
uniform sampler2D u_temperature_texture;
uniform sampler2D u_material_texture;
uniform sampler2D u_color_texture;
uniform sampler2D u_curvature_texture;

Затем в main в вершинном шейдере мы можем использовать текстуры для вычисления позиции.

void main() {
  vec4 position = texture2D(u_position_texture, a_position_uv);
  vec4 translation = texture2D(u_translation_texture, a_translation_uv);
  // ...
  gl_Position = position * ...
}

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

attribute vec2 a_position_uv;
attribute vec2 a_translation_uv;
attribute vec2 a_velocity_uv;
attribute vec2 a_rotation_uv;
attribute vec2 a_force_uv;
attribute vec2 a_temperature_uv;
attribute vec2 a_material_uv;
attribute vec2 a_color_uv;
attribute vec2 a_curvature_uv;

uniform sampler2D u_position_texture;
uniform sampler2D u_translation_texture;
uniform sampler2D u_velocity_texture;
uniform sampler2D u_rotation_texture;
uniform sampler2D u_force_texture;
uniform sampler2D u_temperature_texture;
uniform sampler2D u_material_texture;
uniform sampler2D u_color_texture;
uniform sampler2D u_curvature_texture;

void main() {
  vec4 position = texture2D(u_position_texture, a_position_uv);
  vec4 translation = texture2D(u_translation_texture, a_translation_uv);
  // ...
  gl_Position = position * ...
}

И последний фрагментный шейдер может выглядеть следующим образом:

uniform sampler2D u_position_texture;
uniform sampler2D u_translation_texture;
uniform sampler2D u_velocity_texture;
uniform sampler2D u_rotation_texture;
uniform sampler2D u_force_texture;
uniform sampler2D u_temperature_texture;
uniform sampler2D u_material_texture;
uniform sampler2D u_color_texture;
uniform sampler2D u_curvature_texture;

varying vec2 v_foo
varying vec2 v_bar

void main() {
  // ...
  gl_Color = position * ... * v_foo * v_bar
}

Ответы [ 2 ]

2 голосов
/ 15 марта 2019

Вопрос, который вы связали, касается не слишком большого количества атрибутов , а слишком большого числа вариантов , 99,9% реализаций WebGL, поддерживающих до 16 атрибутов , что не тольконаравне с максимальным количеством текстурных блоков, поддерживаемых на большинстве платформ, но должно быть в порядке, если вам не нужно передавать все эти данные из вершины в фрагментный шейдер.Если вы не делаете больше больших дозировок, вы можете просто использовать униформу для начала.Тем не менее, если вы по какой-либо причине решите использовать текстуры, вы, вероятно, будете использовать только одну UV-координату и выровнять все свои текстуры данных, в противном случае вы бы просто удвоили свои требования к пропускной способности без всякой причины.

При этом ваш набор данных может быть немного сжат.Вы можете хранить position и rotation в качестве кватерниона (в 2D вы даже можете просто использовать vec3 с x,y,α), velocity и torque (чего нет в вашем исходном наборе данных) действительно просто дельтатекущей позиции и следующей, так что вам нужно сохранить только один из этих наборов (либо скорость / крутящий момент, либо следующая позиция / вращение), force кажется неактуальным, так как вы примените их к ЦП, mass иtemperature являются скалярными значениями, поэтому они полностью вписываются в один vec2 вместе с другим джазом.Но чем больше я пытаюсь разобраться в этом, тем более незрелым кажется это, вы не можете в действительности выполнять симуляцию на GPU, хотя половина ваших атрибутов - это атрибуты симуляции, которые не требуются для рендеринга, и кажется, что вы преждевременно оптимизируетечто-то, что даже близко не существует, так что совет: просто создайте его и посмотрите.

1 голос
/ 15 марта 2019

Ответ ЖЖ, возможно, является правильным, но если вы хотите хранить данные в текстурах, все, что вам нужно, это индекс для каждой вершины

attribute float index;

Затем вы вычисляете UV-координаты из этого

uniform vec2 textureSize;  // size of texture

float numVec4sPerElement = 8.;
float elementsPerRow = floor(textureSize.x / numVec4sPerElement);
float tx = mod(index, elementsPerRow) * numVec4sPerElement;
float ty = floor(index / elementsPerRow);
vec2 baseTexel = vec2(tx, ty) + 0.5;

Теперь вы можете вытащить данные.(примечание: предполагается, что это текстура с плавающей точкой)

vec4 position    = texture2D(dataTexture, baseTexel / textureSize);
vec4 translation = texture2D(dataTexture, (baseTexel + vec2(1,0)) / textureSize);
vec4 velocity    = texture2D(dataTexture, (baseTexel + vec2(2,0)) / textureSize);
vec4 rotation    = texture2D(dataTexture, (baseTexel + vec2(3,0)) / textureSize);
vec4 forces      = texture2D(dataTexture, (baseTexel + vec2(4,0)) / textureSize);

и т. д. *

Конечно, вы можете чередовать данные больше.Например, позиция выше - это vec4, может быть position.w - гравитация, translation.w - масса и т. Д. *

Затем вы помещаете данные в текстуру

position0, translation0, velocity0, rotation0, forces0, .... 
position1, translation1, velocity1, rotation1, forces1, .... 
position2, translation2, velocity2, rotation2, forces2, .... 
position2, translation3, velocity3, rotation3, forces3, .... 

const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.querySelector('canvas').getContext('webgl');
const ext = gl.getExtension('OES_texture_float');
if (!ext) {
  alert('need OES_texture_float');
}


const vs = `
attribute float index;

uniform vec2 textureSize;
uniform sampler2D dataTexture;

uniform mat4 modelView;
uniform mat4 projection;

varying vec3 v_normal;
varying vec4 v_color;

void main() {
  float numVec4sPerElement = 3.;  // position, normal, color
  float elementsPerRow = floor(textureSize.x / numVec4sPerElement);
  float tx = mod(index, elementsPerRow) * numVec4sPerElement;
  float ty = floor(index / elementsPerRow);
  vec2 baseTexel = vec2(tx, ty) + 0.5;

  // Now you can pull out the data.

  vec3 position = texture2D(dataTexture, baseTexel / textureSize).xyz;
  vec3 normal   = texture2D(dataTexture, (baseTexel + vec2(1,0)) / textureSize).xyz;
  vec4 color    = texture2D(dataTexture, (baseTexel + vec2(2,0)) / textureSize);

  gl_Position = projection * modelView * vec4(position, 1);

  v_color = color;
  v_normal = normal;
}
`;

const fs = `
precision highp float;

varying vec3 v_normal;
varying vec4 v_color;

uniform vec3 lightDirection;

void main() {
  float light = dot(lightDirection, normalize(v_normal)) * .5 + .5;
  gl_FragColor = vec4(v_color.rgb * light, v_color.a);
}
`;

// compile shader, link, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);

// make some vertex data
const radius = 1;
const thickness = .3;
const radialSubdivisions = 20;
const bodySubdivisions = 12;
const verts = twgl.primitives.createTorusVertices(
    radius, thickness, radialSubdivisions, bodySubdivisions);
/*
  verts is now an object like this
  
  {
    position: float32ArrayOfPositions,
    normal: float32ArrayOfNormals,
    indices: uint16ArrayOfIndices,
  }
*/

// covert the vertex data to a texture
const numElements = verts.position.length / 3;
const vec4sPerElement = 3;  // position, normal, color
const maxTextureWidth = 2048;  // you could query this
const elementsPerRow = maxTextureWidth / vec4sPerElement | 0;
const textureWidth = elementsPerRow * vec4sPerElement;
const textureHeight = (numElements + elementsPerRow - 1) /
                      elementsPerRow | 0;

const data = new Float32Array(textureWidth * textureHeight * 4);
for (let i = 0; i < numElements; ++i) {
  const dstOffset = i * vec4sPerElement * 4;
  const posOffset = i * 3;
  const nrmOffset = i * 3;
  data[dstOffset + 0] = verts.position[posOffset + 0];
  data[dstOffset + 1] = verts.position[posOffset + 1];
  data[dstOffset + 2] = verts.position[posOffset + 2];
  
  data[dstOffset + 4] = verts.normal[nrmOffset + 0];
  data[dstOffset + 5] = verts.normal[nrmOffset + 1];
  data[dstOffset + 6] = verts.normal[nrmOffset + 2];  
  
  // color, just make it up
  data[dstOffset +  8] = 1;
  data[dstOffset +  9] = (i / numElements * 2) % 1;
  data[dstOffset + 10] = (i / numElements * 4) % 1;
  data[dstOffset + 11] = 1;
}

// use indices as `index`
const arrays = {
  index: { numComponents: 1, data: new Float32Array(verts.indices), },
};

// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.FLOAT, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

function render(time) {
  time *= 0.001;  // seconds
  
  twgl.resizeCanvasToDisplaySize(gl.canvas);
  
  gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.CULL_FACE);

  const fov = Math.PI * 0.25;
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const near = 0.1;
  const far = 20;
  const projection = m4.perspective(fov, aspect, near, far);
  
  const eye = [0, 0, 3];
  const target = [0, 0, 0];
  const up = [0, 1, 0];
  const camera = m4.lookAt(eye, target, up);
  const view = m4.inverse(camera);

  // set the matrix for each model in the texture data
  const modelView = m4.rotateY(view, time);
  m4.rotateX(modelView, time * .2, modelView);
  
  gl.useProgram(programInfo.program);
  
  // calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
  
  // calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
  twgl.setUniforms(programInfo, {
    lightDirection: v3.normalize([1, 2, 3]),
    textureSize: [textureWidth, textureHeight],
    projection: projection,
    modelView: modelView,
  });  
  
  // calls gl.drawArrays or gl.drawElements
  twgl.drawBufferInfo(gl, bufferInfo);

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

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

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

https://stackoverflow.com/a/54720138/128511

...