WebGL зависит от пункта назначения.Это означает, что он выполняет 1 операцию для каждого результата, который он хочет записать в место назначения.Единственными видами назначений, которые вы можете установить, являются точки (квадраты пикселей), линии и треугольники в 2D-плоскости.Это означает, что для записи в 3D-текстуру потребуется обрабатывать каждую плоскость отдельно.В лучшем случае вы можете выполнить N плоскостей отдельно, где N равно 4–8, настроив несколько вложений в буфер кадров до максимально допустимых вложений
Так что я предполагаю, что вы понимаете, как выполнить рендеринг до 100слои 1 за один раз.Во время инициализации либо создайте 100 кадровых буферов и прикрепите разные слои к каждому.ИЛИ, во время рендеринга обновите один кадровый буфер с другим вложением.Зная, сколько проверки происходит, я бы выбрал создание 100 кадровых буферов
Так что
const framebuffers = [];
for (let layer = 0; layer < numLayers; ++layer) {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture,
0, layer);
framebuffers.push(fb);
}
теперь во время рендеринга для каждого слоя
framebuffers.forEach((fb, layer) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// pass in the layer number to the shader it can use for calculations
gl.uniform1f(layerLocation, layer);
....
gl.drawXXX(...);
});
WebGL1 не поддерживает 3Dтекстур, поэтому мы знаем, что вы используете WebGL2, так как вы упомянули об использовании sampler3D
.
В WebGL2 вы обычно используете #version 300 es
в верхней части ваших шейдеров, чтобы показать, что вы хотите использовать более современный GLSL ES 3.00.
Для рисования на нескольких слоях необходимо сначала выяснить, сколько слоев вы хотите визуализировать.WebGL2 поддерживает минимум 4 одновременно, поэтому мы можем просто предположить 4 слоя.Для этого вы должны прикрепить 4 слоя к каждому фреймбуферу
const layersPerFramebuffer = 4;
const framebuffers = [];
for (let baseLayer = 0; baseLayer < numLayers; baseLayer += layersPerFramebuffer) {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let layer = 0; layer < layersPerFramebuffer; ++layer) {
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + layer, texture, 0, baseLayer + layer);
}
framebuffers.push(fb);
}
В шейдерах GLSL ES 3.0 не используется gl_FragCoord
, они используют определяемый пользователем вывод, поэтому мы объявляем вывод массива
out vec4 ourOutput[4];
, а затем используйте это так же, как вы ранее использовали gl_FragColor
, за исключением добавления индекса.Ниже мы обрабатываем 4 слоя.Мы только передаем vec2 для v_texCoord
и вычисляем третью координату на основе baseLayerTexCoord
, что мы передаем при каждом вызове отрисовки.
varying vec2 v_texCoord;
uniform float baseLayerTexCoord;
vec4 results[4];
vec3 onePixel = vec3(1.0, 1.0, 1.0)/u_textureSize;
const int numLayers = 4;
for (int layer = 0; layer < numLayers; ++layer) {
vec3 baseTexCoord = vec3(v_texCoord, baseLayerTexCoord + onePixel * float(layer));
vec4 currentState = texture(u_image, baseTexCoord);
float fTotal = 0.0;
for (int i=-1; i<=1; i++){
for (int j=-1; j<=1; j++){
for (int k=-1; k<=1; k++){
if (i == 0 && j == 0 && k == 0) continue;
vec3 neighborCoord = baseTexCoord + vec3(onePixel.x*float(i), onePixel.y*float(j), onePixel.z*float(k));
vec4 neighborState;
if (neighborCoord.x < 0.0 || neighborCoord.y < 0.0 || neighborCoord.z < 0.0 || neighborCoord.x >= 1.0 || neighborCoord.y >= 1.0 || neighborCoord.z >= 1.0){
neighborState = vec4(0.0,0.0,0.0,1.0);
} else {
neighborState = texture(u_image, neighborCoord);
}
float deltaP = neighborState.r - currentState.r; //Distance from neighbor
float springDeltaLength = (deltaP - u_springOrigLength[counter]);
//Add the force on our point of interest from the current neighbor point. We'll be adding up to 26 of these together.
fTotal += u_kSpring[counter]*springDeltaLength;
}
}
}
float acceleration = fTotal/u_mass;
float velocity = acceleration*u_dt + currentState.g;
float position = velocity*u_dt + currentState.r;
results[layer] = vec4(position,velocity,acceleration,1);
}
ourOutput[0] = results[0];
ourOutput[1] = results[1];
ourOutput[2] = results[2];
ourOutput[3] = results[3];
Последнее, что нам нужно сделать, - это вызватьgl.drawBuffers
, чтобы сообщить WebGL2, где хранить результаты.Поскольку мы делаем 4 слоя за раз, мы будем использовать
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
]);
framebuffers.forEach((fb, ndx) => {
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.uniform1f(baseLayerTexCoordLocation, (ndx * layersPerFramebuffer + 0.5) / numLayers);
....
gl.drawXXX(...);
});
Пример:
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert('need webgl2');
}
const ext = gl.getExtension('EXT_color_buffer_float');
if (!ext) {
return alert('need EXT_color_buffer_float');
}
const vs = `#version 300 es
in vec4 position;
out vec2 v_texCoord;
void main() {
gl_Position = position;
// position will be a quad -1 to +1 so we
// can use that for our texcoords
v_texCoord = position.xy * 0.5 + 0.5;
}
`;
const fs = `#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform float baseLayerTexCoord;
uniform highp sampler3D u_image;
uniform mat3 u_kernel[3];
out vec4 ourOutput[4];
void main() {
vec3 textureSize = vec3(textureSize(u_image, 0));
vec3 onePixel = vec3(1.0, 1.0, 1.0)/textureSize;
const int numLayers = 4;
vec4 results[4];
for (int layer = 0; layer < numLayers; ++layer) {
vec3 baseTexCoord = vec3(v_texCoord, baseLayerTexCoord + onePixel * float(layer));
float fTotal = 0.0;
vec4 color;
for (int i=-1; i<=1; i++){
for (int j=-1; j<=1; j++){
for (int k=-1; k<=1; k++){
vec3 neighborCoord = baseTexCoord + vec3(onePixel.x*float(i), onePixel.y*float(j), onePixel.z*float(k));
color += u_kernel[k + 1][j + 1][i + 1] * texture(u_image, neighborCoord);
}
}
}
results[layer] = color;
}
ourOutput[0] = results[0];
ourOutput[1] = results[1];
ourOutput[2] = results[2];
ourOutput[3] = results[3];
}
`;
const vs2 = `#version 300 es
uniform vec4 position;
uniform float size;
void main() {
gl_Position = position;
gl_PointSize = size;
}
`;
const fs2 = `#version 300 es
precision highp float;
uniform highp sampler3D u_image;
uniform float slice;
out vec4 outColor;
void main() {
outColor = texture(u_image, vec3(gl_PointCoord.xy, slice));
}
`;
const computeProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
const drawProgramInfo = twgl.createProgramInfo(gl, [vs2, fs2]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: {
numComponents: 2,
data: [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
},
});
function create3DTexture(gl, size) {
const tex = gl.createTexture();
const data = new Float32Array(size * size * size * 4);
for (let i = 0; i < data.length; i += 4) {
data[i + 0] = i % 100 / 100;
data[i + 1] = i % 10000 / 10000;
data[i + 2] = i % 100000 / 100000;
data[i + 3] = 1;
}
gl.bindTexture(gl.TEXTURE_3D, tex);
gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA32F, size, size, size, 0, gl.RGBA, gl.FLOAT, data);
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return tex;
}
const size = 100;
let inTex = create3DTexture(gl, size);
let outTex = create3DTexture(gl, size);
const numLayers = size;
const layersPerFramebuffer = 4;
function makeFramebufferSet(gl, tex) {
const framebuffers = [];
for (let baseLayer = 0; baseLayer < numLayers; baseLayer += layersPerFramebuffer) {
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let layer = 0; layer < layersPerFramebuffer; ++layer) {
gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + layer, tex, 0, baseLayer + layer);
}
framebuffers.push(fb);
}
return framebuffers;
};
let inFramebuffers = makeFramebufferSet(gl, inTex);
let outFramebuffers = makeFramebufferSet(gl, outTex);
function render() {
gl.viewport(0, 0, size, size);
gl.useProgram(computeProgramInfo.program);
twgl.setBuffersAndAttributes(gl, computeProgramInfo, bufferInfo);
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 * layersPerFramebuffer + 0.5) / numLayers;
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_image: inTex,
});
gl.drawArrays(gl.TRIANGLES, 0, 6);
});
{
const t = inFramebuffers;
inFramebuffers = outFramebuffers;
outFramebuffers = t;
}
{
const t = inTex;
inTex = outTex;
outTex = t;
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.drawBuffers([gl.BACK]);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.useProgram(drawProgramInfo.program);
const slices = 10.0;
const sliceSize = 25.0
for (let slice = 0; slice < slices; ++slice) {
const sliceZTexCoord = (slice / slices * size + 0.5) / size;
twgl.setUniforms(drawProgramInfo, {
position: [
((slice * (sliceSize + 1) + sliceSize * .5) / gl.canvas.width * 2) - 1,
0,
0,
1,
],
slice: sliceZTexCoord,
size: sliceSize,
});
gl.drawArrays(gl.POINTS, 0, 1);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
function glEnumToString(gl, v) {
const hits = [];
for (const key in gl) {
if (gl[key] === v) {
hits.push(key);
}
}
return hits.length ? hits.join(' | ') : `0x${v.toString(16)}`;
}
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
Некоторые другие вещи, на которые следует обратить внимание: В GLSL ES 3.00 вам не нужно передавать размер текстуры, так как вы можете запросить размер текстуры с помощью функции textureSize
.Возвращает ivec2
или ivec3
в зависимости от типа текстуры.
Вы также можете использовать texelFetch
вместо texture
.texelFetch
принимает целочисленную координату текселя и уровень mip, например, vec4 color = texelFetch(some3DTexture, ivec3(12, 23, 45), 0);
получает тексель при x = 12, y = 23, z = 45 от уровня mip 0. Это означает, что вам не нужно делать математику о'onePixel` у вас есть в коде, если вам легче работать с пикселями вместо нормализованных координат текстуры.