Как использовать текстуры данных в WebGL - PullRequest
0 голосов
/ 10 марта 2020

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

Цель - передать информацию об источниках света (местоположение и цвет), которые будут учтены в цвете фрагмента. В идеале эта информация является динамической c как по значению, так и по длине.

Воспроизведение

Я создал упрощенную версию проблемы в этой скрипке: WebGL - Тестирование текстуры данных

Вот часть кода.

В одноразовой установке мы создаем текстуру, заполняем ее данными и применяем то, что кажется наиболее надежные настройки для этой текстуры (без мипса, без проблем с упаковкой байтов [?])

  // lookup uniforms
  var textureLocation = gl.getUniformLocation(program, "u_texture");

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // fill texture with 1x3 pixels
  const level = 0;
  const internalFormat = gl.RGBA; // I've also tried to work with gl.LUMINANCE
  //   but it feels harder to debug
  const width = 1;
  const height = 3;
  const border = 0;
  const type = gl.UNSIGNED_BYTE;
  const data = new Uint8Array([
    // R,   G,   B, A (unused)    // : 'texel' index (?)
    64, 0, 0, 0, // : 0
    0, 128, 0, 0, // : 1
    0, 0, 255, 0, // : 2
  ]);
  const alignment = 1; // should be uneccessary for this texture, but 
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment); //   I don't think this is hurting
  gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
    internalFormat, type, data);

  // set the filtering so we don't need mips and it's not filtered
  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);

В последовательности draw (которая может случиться только один раз, но, возможно, может повториться), мы усиливаем что программа должна использовать нашу текстуру

    // Tell the shader to use texture unit 0 for u_texture
    gl.activeTexture(gl.TEXTURE0);                  // added this and following line to be extra sure which texture is being used...
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.uniform1i(textureLocation, 0);

Наконец, в фрагментном шейдере мы пытаемся просто надежно использовать один «тексель» в качестве средства передачи информации. Я не могу понять, как получить значения, которые я надежно сохранил в текстуре.

precision mediump float;

// The texture.
uniform sampler2D u_texture;

void main() {

    vec4 sample_00 = texture2D(u_texture, vec2(0, 0)); 
    // This sample generally appears to be correct. 
    // Changing the provided data for the B channel of texel
    //   index 0 seems to add blue as expected

    vec4 sample_01 = texture2D(u_texture, vec2(0, 1));
    vec4 sample_02 = texture2D(u_texture, vec2(0, 2));
    // These samples are how I expected this to work since 
    //   the width of the texture is set to 1
    // For some reason 01 and 02 both show the same color

    vec4 sample_10 = texture2D(u_texture, vec2(1, 0));
    vec4 sample_20 = texture2D(u_texture, vec2(2, 0));
    // These samples are just there for testing - I don't think
    //   that they should work

    // choose which sample to display
    vec4 sample = sample_00;
    gl_FragColor = vec4(sample.x, sample.y, sample.z, 1);
}

Вопрос (ы)

Является ли использование текстуры лучшим способом сделать это? Я также слышал о способности передавать массивы векторов, но текстуры кажутся более распространенными.

Как вы должны создать текстуру? (особенно когда я указываю 'width' и 'height', я должен ссылаться на итоговые размеры texel или количество элементов gl.UNSIGNED_BYTE, которые я буду использовать для создания текстуры ?? документация texImage2D )

Как вы должны индексировать текстуру в фрагментном шейдере, если не используете «различные» типы? (т.е. я просто хочу значение одного или нескольких конкретных текселей - без интерполяции [не имеет ничего общего с вершинами])

Другие ресурсы

Я прочитал столько, сколько смог об этом пока. Вот неполный список:

Редактировать Вот еще один ресурс, который я только что нашел: Затруднения с доступом к массиву в WebGL и парой обходных путей . Вселяет надежду.

это действительно беспокоит меня.

заранее спасибо!

Ответы [ 2 ]

2 голосов
/ 10 марта 2020

Использовать однородные массивы

Связанная статья несколько устарела. WebGL поддерживает унифицированные массивы через uniform[1234][if]v, который имеет хорошую поддержку браузера

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

Пример ниже, использующий webGL (НЕ webGL2, который является лучшим вариантом), имеет два однородных массива

// define number of lights
#define LIGHTS 5
uniform vec3 lights[LIGHTS];
uniform vec3 lightCols[LIGHTS];

Они устанавливаются через gl.uniform3fv. Обратите внимание, что v обозначает вектор (C языковой массив)

Это избавит вас от необходимости отправлять избыточные данные (альфа-байт) и работать с точностью с плавающей запятой, а не с байтами текстуры (или с помощью расширения). Вы можете иметь текстуры с плавающей точкой)

Также вы можете отправлять только изменяемые элементы массива, а не все данные каждый раз.

const LIGHTS = 15; // Number of lights. MUST BE!!!! INT >= 1
const EXPOSURE = 1; // Scaler on final pixel colour
const shaders = {
    get locations() { return ["A_vertex","U_lights","U_lightCols"] },  // Prefix A_ or U_ used by JS name is after _
    get vertex() { return `
attribute vec2 vertex;
varying vec2 map;
void main() {
	map = vertex * 0.5 + 0.5; // map unit 0-1 over quad
	gl_Position = vec4(vertex, 0, 1);
}`;
    },
    get fragment() { return `
precision mediump float;
#define LIGHTS ${LIGHTS}
#define EXPOSURE ${EXPOSURE.toFixed(1)}
uniform vec3 lights[LIGHTS];
uniform vec3 lightCols[LIGHTS];
varying vec2 map;
void main() {
    vec3 light = vec3(0);
    for (int i = 0; i < LIGHTS; i++) {
        float level = pow(1.0-length(lights[i] - vec3(map, 0)), 4.0);
        light += lightCols[i] * level ;
    }
    gl_FragColor = vec4(light * EXPOSURE, 1.0);
}`;
    }
};
const gl = canvas.getContext("webgl", {alpha: false, depth: false, premultpliedAlpha: false, preserveDrawingBufer: true});
var shader, frame = 0; 
function resize() { 
    if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        shader.gl.viewport(0, 0, canvas.width,canvas.height);
    }
}
requestAnimationFrame(update);
function update(timer){ // Main update loop
    frame += 1;
    if (shader === undefined) {
        shader = createRender(gl);
    }
    resize();
    gl.useProgram(shader.program);
    orbit(shader, frame);
    shader.gl.drawArrays(gl.TRIANGLES, 0, 6);
    requestAnimationFrame(update);
}


function orbit(shader, frame) {
    var i = LIGHTS;
    frame /= 100;
    const l = shader.lightsBuf, lp = shader.lights, c = shader.lightColsBuf, cp = shader.lightCols;
    while (i--) {
        const idx = i * 3, r = 0.1 + i / LIGHTS;
        l[idx    ] = lp[idx    ] + Math.cos(frame) * r * Math.sin(frame/2);
        l[idx + 1] = lp[idx + 1] + Math.sin(frame) * r * Math.sin(frame/2);
        l[idx + 2] = lp[idx + 2] + Math.cos(frame/2) * r;
        c[idx    ] = cp[idx    ] + (Math.sin(frame/3) * 0.5) * Math.cos(frame);
        c[idx + 1] = cp[idx + 1] + (Math.cos(frame/3)* 0.5) * Math.cos(frame);
        c[idx + 2] = cp[idx + 2] + Math.sin(frame) * 0.5;
        frame *= 1.2;
    }
    shader.gl.uniform3fv(shader.locations.lights, l, 0, LIGHTS * 3);
    shader.gl.uniform3fv(shader.locations.lightCols, c, 0, LIGHTS * 3);
}
    


function createShader(gl, vertSrc, fragSrc, locDesc) {
    var locations = {};
    const program = gl.createProgram();
    const vShader = gl.createShader(gl.VERTEX_SHADER);
    const fShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(vShader, vertSrc);
    gl.shaderSource(fShader, fragSrc);
    gl.compileShader(vShader);
    if (!gl.getShaderParameter(vShader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(vShader)) } 
    gl.compileShader(fShader);
    if (!gl.getShaderParameter(fShader, gl.COMPILE_STATUS)) { throw new Error(gl.getShaderInfoLog(fShader)) } 
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error(gl.getProgramInfoLog(program)) } 
    gl.useProgram(program);
    for(const desc of locDesc) {
        const [type, name] = desc.split("_");
        locations[name] = gl[`get${type==="A" ? "Attrib" : "Uniform"}Location`](program, name);
    }
    return {program, locations, gl};
}

function createRender(gl) {
    const shader = createShader(gl, shaders.vertex, shaders.fragment, shaders.locations);
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, -1,1, 1,-1, 1,1]), gl.STATIC_DRAW);
    gl.enableVertexAttribArray(shader.locations.vertex);
    gl.vertexAttribPointer(shader.locations.vertex, 2, gl.FLOAT, false, 0, 0);
    shader.lightsBuf = new Float32Array(LIGHTS * 3);
    shader.lightColsBuf = new Float32Array(LIGHTS * 3);
    const lights = shader.lights = new Float32Array(LIGHTS * 3);
    const cols = shader.lightCols = new Float32Array(LIGHTS * 3);
    var i = LIGHTS * 3;
    while (i--) { (cols[i] = Math.random(),lights[i] = Math.random())}
    return shader;
}
body {
  padding: 0px;
}

canvas {
  position: absolute;
  top: 0px;
  left: 0px;
}
<canvas id="canvas"></canvas>
1 голос
/ 10 марта 2020

Для адресации отдельных пикселей в текстуре в WebGL1 используется эта формула

vec2 pixelCoord = vec2(x, y);
vec2 textureDimension = vec2(textureWidth, textureHeight)
vec2 texcoord = (pixelCoord + 0.5) / textureDimensions;
vec4 pixelValue = texture2D(someSamplerUniform, texcoord);

Поскольку координаты текстуры по краям. Если у вас есть текстура 2x1

1.0+-------+-------+
   |       |       |
   |   A   |   B   |
   |       |       |
0.0+-------+-------+
  0.0     0.5     1.0

Координата текстуры в центре пикселя A = 0,25, 0,5. Координата текстуры в центре пикселя B равна 0,75, 0,5

Если вы не следуете форумам выше и используете только pixelCoord / textureDimensions, то указание между пикселями и математические ошибки приведут к получению одного пикселя. или другой.

Конечно, если вы используете текстуры для данных, вы также, вероятно, захотите установить gl.NEAREST фильтрацию.

В WebGL2 вы можете просто использовать texelFetch

ivec2 pixelCoord = ivec2(x, y);
int mipLevel = 0;
vec4 pixelValue = texelFetch(someSamplerUniform, texcoord, mipLevel);

Рабочий пример использования текстур для данных: здесь

Является ли использование текстуры лучшим способом сделать это? Я также слышал о способности передавать массивы векторов, но текстуры кажутся более распространенными.

Для чего? Непонятно, что ты пытаешься сделать. У каждого пикселя будет свой источник света?

Как вы должны создать текстуру? (особенно когда я указываю 'width' и 'height', я должен ссылаться на итоговые размеры texel или количество элементов gl.UNSIGNED_BYTE, которые я буду использовать для создания текстуры ?? документация texImage2D)

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

vec4 datum1 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY0);
vec4 datum2 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY1);
vec4 datum3 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY2);
vec4 datum4 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY3);

, где indexTexCoordX и rowTexCoordY0-3 вычисляются из форума выше. rowTexCoordY0-3 могут быть даже константами.

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

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

Как вы должны индексировать текстуру в фрагментном шейдере, если не используете «различные» типы ? (т.е. я просто хочу значение одного или нескольких конкретных текселей - без интерполяции [не имеет ничего общего с вершинами])

Единственные изменяемые входные данные для фрагментного шейдера - это вариации, gl_FragCoord (пиксель координат записывается в) и gl_PointCoord, доступно только при рисовании POINTS. Таким образом, вы должны использовать одно из этих значений, в противном случае все остальные значения являются постоянными для всех пикселей.

...