Как сделать дозирование без UBO? - PullRequest
0 голосов
/ 15 февраля 2019

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

Моя проблема в том, чтопредельные размеры для униформ, отличных от UBO, крайне низки, как минимум в 256 поплавках.Если мой материал использует, скажем, 8 поплавков, и если вы учитываете матрицу модели, у меня едва хватает формы, чтобы нарисовать 10 моделей в одной партии, что на самом деле не достаточно.

есть ли надежда сделать это без UBO?Текстуры - это вариант?Как люди делают пакетирование без UBO WebGL2?

Подробнее: у меня нет скинов или сложных анимаций, у меня просто есть несколько шейдеров (diffuse, cook-torrance и т. Д.), И каждая модель имеет разные настройки материала для каждого шейдеранапример, цвет, шероховатость, показатель преломления, который может динамически изменяться пользователем (поэтому невозможно выпекать их в массив вершин, потому что у нас есть некоторые высокополигональные данные, также пользователи могут переключать шейдеры, и не все шейдеры имеют одинаковое числопараметров), а также карты материалов, очевидно.Сама геометрия статична и просто имеет линейное преобразование для каждой модели.По большей части все сетки разные, поэтому создание геометрии не очень поможет, но я могу взглянуть на это позже.

Спасибо

1 Ответ

0 голосов
/ 16 февраля 2019

Я не знаю, что это на самом деле быстрее, чем множество вызовов отрисовки, но здесь мы рисуем 4 модели с помощью одного вызова отрисовки

Это работает путем добавления идентификатора для каждой модели.Таким образом, для каждой вершины в модели # 0 ставится 0, для каждой вершины в модели # 1 ставится 1 и т. Д.

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

Для WebGL1

attribute float modelId;

...

#define TEXTURE_WIDTH ??
#define COLOR_OFFSET    ((0.0 + 0.5) / TEXTURE_WIDTH)
#define MATERIAL_OFFSET ((1.0 + 0.5) / TEXTURE_WIDTH)

float modelOffset = (modelId + .5) / textureHeight;
vec4 color = texture2D(perModelData, vec2(COLOR_OFFSET, modelOffset));
vec4 roughnessIndexOfRefaction = texture2D(perModelData, 
                                           vec2(MATERIAL_OFFSET, modelOffset));

и т. Д.

Пока вы рисуете не более gl.getParameter(gl.MAX_TEXTURE_SIZE) моделей, это будет работать.Если у вас их больше, либо используйте больше вызовов отрисовки, либо измените вычисления координат текстуры, чтобы в строке было более одной модели

В WebGL2 вы бы изменили код на использование texelFetch и целых чисел без знака

in uint modelId;

...

#define COLOR_OFFSET    0
#define MATERIAL_OFFSET 1

vec4 color = texelFetch(perModelData, uvec2(COLOR_OFFSET, modelId));
vec4 roughnessIndexOfRefaction = texelFetch(perModelData, 
                                            uvec2(MATERIAL_OFFSET, modelId));

пример 4 моделей, нарисованных с помощью одного колл-колла.Для каждой модели матрица и цвет модели сохраняются в текстуре.

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 COMMON_STUFF = `
#define TEXTURE_WIDTH 5.0
#define MATRIX_ROW_0_OFFSET ((0. + 0.5) / TEXTURE_WIDTH)
#define MATRIX_ROW_1_OFFSET ((1. + 0.5) / TEXTURE_WIDTH)
#define MATRIX_ROW_2_OFFSET ((2. + 0.5) / TEXTURE_WIDTH)
#define MATRIX_ROW_3_OFFSET ((3. + 0.5) / TEXTURE_WIDTH)
#define COLOR_OFFSET        ((4. + 0.5) / TEXTURE_WIDTH)
`;

const vs = `
attribute vec4 position;
attribute vec3 normal;
attribute float modelId;

uniform float textureHeight;
uniform sampler2D perModelDataTexture;
uniform mat4 projection;
uniform mat4 view;

varying vec3 v_normal;
varying float v_modelId;

${COMMON_STUFF}

void main() {
  v_modelId = modelId;  // pass to fragment shader

  float modelOffset = (modelId + 0.5) / textureHeight;

  // note: in WebGL2 better to use texelFetch
  mat4 model = mat4(
    texture2D(perModelDataTexture, vec2(MATRIX_ROW_0_OFFSET, modelOffset)),
    texture2D(perModelDataTexture, vec2(MATRIX_ROW_1_OFFSET, modelOffset)),
    texture2D(perModelDataTexture, vec2(MATRIX_ROW_2_OFFSET, modelOffset)),
    texture2D(perModelDataTexture, vec2(MATRIX_ROW_3_OFFSET, modelOffset)));
  
  gl_Position = projection * view * model * position;
  v_normal = mat3(view) * mat3(model) * normal;
}
`;

const fs = `
precision highp float;

varying vec3 v_normal;
varying float v_modelId;

uniform float textureHeight;
uniform sampler2D perModelDataTexture;
uniform vec3 lightDirection;

${COMMON_STUFF}

void main() {
  float modelOffset = (v_modelId + 0.5) / textureHeight;

  vec4 color = texture2D(perModelDataTexture, vec2(COLOR_OFFSET, modelOffset));
  
  float l = dot(lightDirection, normalize(v_normal)) * .5 + .5;
  
  gl_FragColor = vec4(color.rgb * l, color.a);
}
`;

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

// make some vertex data
const modelVerts = [
  twgl.primitives.createSphereVertices(1, 6, 4),
  twgl.primitives.createCubeVertices(1, 1, 1),
  twgl.primitives.createCylinderVertices(1, 1, 10, 1),
  twgl.primitives.createTorusVertices(1, .2, 16, 8),
];
// merge all the vertices into one
const arrays = twgl.primitives.concatVertices(modelVerts);
// fill an array so each vertex of each model has a modelId
const modelIds = new Uint16Array(arrays.position.length / 3);
let offset = 0;
modelVerts.forEach((verts, modelId) => {
  const end = offset + verts.position.length / 3;
  while(offset < end) {
    modelIds[offset++] = modelId;
  }
});
arrays.modelId = { numComponents: 1, data: modelIds };
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);

const numModels = modelVerts.length;
const tex = gl.createTexture();
const textureWidth = 5; // 4x4 matrix, 4x1 color
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, numModels, 0, gl.RGBA, gl.FLOAT, null);
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);

// this data is for the texture, one row per model
// first 4 pixels are the model matrix, 5 pixel is the color
const perModelData = new Float32Array(textureWidth * numModels * 4);
const stride = textureWidth * 4;
const modelOffset = 0;
const colorOffset = 16;

// set the colors at init time
for (let modelId = 0; modelId < numModels; ++modelId) {
  perModelData.set([r(), r(), r(), 1], modelId * stride + colorOffset);
}

function r() {
  return Math.random();
}

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, 10];
  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 mat = m4.identity();
  for (let modelId = 0; modelId < numModels; ++modelId) {
    const t = time * (modelId + 1) * 0.3;
    m4.identity(mat);
    m4.rotateX(mat, t, mat);
    m4.rotateY(mat, t, mat);
    m4.translate(mat, [0, 0, Math.sin(t * 1.1) * 4], mat);
    m4.rotateZ(mat, t, mat);
    
    perModelData.set(mat, modelId * stride + modelOffset);
  }
  
  // upload the texture data
  gl.bindTexture(gl.TEXTURE_2D, tex);
  gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, textureWidth, numModels, 
                   gl.RGBA, gl.FLOAT, perModelData);
  
  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]),
    perModelDataTexture: tex,
    textureHeight: numModels,
    projection,
    view,
  });  
  
  // 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>

Здесь 2000 моделей за один розыгрыш

https://jsfiddle.net/greggman/g2tcadho/

...