Spec-Zone.ru › Web APIs

Использование текстур в WebGL

  • Предыдущая
  • Следующая

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

Загрузка текстур

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

Примечание: Важно отметить, что загрузка текстур подчиняется правилам кросс-доменных запросов; то есть вы можете загружать текстуры только с сайтов, для которых ваш контент имеет одобрение CORS. Подробности см. в разделе Кросс-доменные текстуры ниже.

Примечание: Добавьте эти две функции в ваш скрипт "webgl-demo.js":

//
// Initialize a texture and load an image.
// When the image finished loading copy it into the texture.
//
function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Because images have to be downloaded over the internet
  // they might take a moment until they are ready.
  // Until then put a single pixel in the texture so we can
  // use it immediately. When the image has finished downloading
  // we'll update the texture with the contents of the image.
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );

  const image = new Image();
  image.onload = () => {
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      level,
      internalFormat,
      srcFormat,
      srcType,
      image,
    );

    // WebGL1 has different requirements for power of 2 images
    // vs. non power of 2 images so check if the image is a
    // power of 2 in both dimensions.
    if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
      // Yes, it's a power of 2. Generate mips.
      gl.generateMipmap(gl.TEXTURE_2D);
    } else {
      // No, it's not a power of 2. Turn off mips and set
      // wrapping to clamp to edge
      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);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    }
  };
  image.src = url;

  return texture;
}

function isPowerOf2(value) {
  return (value & (value - 1)) === 0;
}

Процедура loadTexture() начинается с создания объекта WebGL-текстуры texture с помощью функции WebGL createTexture(). Затем она загружает один синий пиксель, используя texImage2D(). Это делает текстуру немедленно пригодной для использования в качестве однородного синего цвета, хотя может потребоваться некоторое время для загрузки нашего изображения.

Для загрузки текстуры из файла изображения она затем создает объект Image и назначает src URL-адрес изображения, которое мы хотим использовать в качестве текстуры. Функция, назначенная image.onload, будет вызвана после завершения загрузки изображения. В этот момент мы снова вызываем texImage2D(), на этот раз используя изображение как источник текстуры. После этого мы настраиваем фильтрацию и обёртку для текстуры в зависимости от того, является ли загруженное изображение степенью двойки по обеим измерениям или нет.

WebGL1 может использовать текстуры, не являющиеся степенью двойки, только с фильтрацией, установленной на NEAREST или LINEAR, и не может сгенерировать mipmap для них. Их режим обёртки также должен быть установлен на CLAMP_TO_EDGE. С другой стороны, если текстура является степенью двойки по обеим измерениям, WebGL может использовать фильтрацию более высокого качества, использовать mipmap и установить режим обёртки на REPEAT или MIRRORED_REPEAT.

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

Mipmapping и повторение UV можно отключить с помощью texParameteri(). Это позволит использовать текстуры, не являющиеся степенью двойки (NPOT), за счёт отказа от mipmapping, обёртки UV, чередования UV и контроля над тем, как устройство будет обрабатывать вашу текстуру.

// gl.NEAREST is also allowed, instead of gl.LINEAR, as neither mipmap.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// Prevents s-coordinate wrapping (repeating).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// Prevents t-coordinate wrapping (repeating).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

Опять же, при этих параметрах совместимые устройства WebGL автоматически примут любой размер для этой текстуры (до их максимальных размеров). Без выполнения вышеуказанной настройки WebGL требует, чтобы все образцы текстур NPOT возвращали прозрачный черный цвет: rgb(0 0 0 / 0%).

Для загрузки изображения добавьте вызов нашей функции loadTexture() внутри функции main(). Это можно добавить после вызова initBuffers(gl).

Но также обратите внимание: браузеры копируют пиксели из загруженного изображения сверху вниз — слева направо; но WebGL хочет пиксели снизу вверх — начиная слева.

(Для получения более подробной информации см. Почему моя WebGL-текстура перевернута?.)

Таким образом, чтобы предотвратить неправильное отображение загруженной текстуры изображения, нам также нужно вызвать pixelStorei() с параметром gl.UNPACK_FLIP_Y_WEBGL установленным в значение true — для изменения порядка пикселей на нижний-верхний, который ожидает WebGL.

Примечание: Добавьте следующий код в вашу функцию main(), сразу после вызова initBuffers():

// Load texture
const texture = loadTexture(gl, "cubetexture.png");
// Flip image pixels into the bottom-to-top order that WebGL expects.
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

Примечание: Наконец, загрузите файл cubetexture.png в ту же локальную директорию, что и ваши JavaScript-файлы.

Отображение текстуры на гранях

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

Примечание: Добавьте эту функцию в свой модуль "init-buffer.js":

function initTextureBuffer(gl) {
  const textureCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);

  const textureCoordinates = [
    // Front
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Back
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Top
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Bottom
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Right
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Left
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  ];

  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(textureCoordinates),
    gl.STATIC_DRAW,
  );

  return textureCoordBuffer;
}

Во-первых, этот код создает буфер WebGL, в который мы будем сохранять координаты текстуры для каждой грани, а затем связывает этот буфер как массив, в который будем записывать.

Массив textureCoordinates определяет координаты текстуры, соответствующие каждой вершине каждой грани. Обратите внимание, что координаты текстуры находятся в диапазоне от 0,0 до 1,0; размеры текстур нормализованы к диапазону от 0,0 до 1,0 независимо от их фактического размера в целях текстурирования.

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

Затем мы возвращаем новый буфер.

Далее, нам нужно обновить initBuffers() для создания и возврата буфера координат текстуры вместо буфера цвета.

Примечание: В функции initBuffers() вашего модуля "init-buffers.js" замените вызов initColorBuffer() следующей строкой:

const textureCoordBuffer = initTextureBuffer(gl);

Примечание: В функции initBuffers() вашего модуля "init-buffers.js" замените оператор return следующим:

return {
  position: positionBuffer,
  textureCoord: textureCoordBuffer,
  indices: indexBuffer,
};

Обновление шейдеров

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

Вершинный шейдер

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

Примечание: Обновите объявление vsSource в вашей функции main() следующим образом:

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec2 aTextureCoord;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying highp vec2 vTextureCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vTextureCoord = aTextureCoord;
    }
  `;

Ключевое изменение заключается в том, что вместо извлечения данных о цвете вершины мы извлекаем координаты текстуры и передаем их фрагментным шейдеру; это указывает на местоположение в текстуре, соответствующее вершине.

Фрагментный шейдер

Аналогично, нужно обновить фрагментный шейдер.

Примечание: Обновите объявление fsSource в вашей функции main() следующим образом:

const fsSource = `
    varying highp vec2 vTextureCoord;

    uniform sampler2D uSampler;

    void main(void) {
      gl_FragColor = texture2D(uSampler, vTextureCoord);
    }
  `;

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

Локации атрибутов и униформ

Поскольку мы изменили атрибут и добавили униформу, нам нужно найти их местоположения.

Примечание: Обновите объявление programInfo в вашей функции main() следующим образом:

const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
    textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
    uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
  },
};

Отрисовка текстурированного куба

Изменения в функции drawScene() простые.

Примечание: В функции drawScene() вашего модуля "draw-scene.js" добавьте следующую функцию:

// tell webgl how to pull out the texture coordinates from buffer
function setTextureAttribute(gl, buffers, programInfo) {
  const num = 2; // every coordinate composed of 2 values
  const type = gl.FLOAT; // the data in the buffer is 32-bit float
  const normalize = false; // don't normalize
  const stride = 0; // how many bytes to get from one set to the next
  const offset = 0; // how many bytes inside the buffer to start from
  gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
  gl.vertexAttribPointer(
    programInfo.attribLocations.textureCoord,
    num,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}

Примечание: В функции drawScene() вашего модуля "draw-scene.js" замените вызов setColorAttribute() следующей строкой:

setTextureAttribute(gl, buffers, programInfo);

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

Примечание: В вашей функции drawScene(), сразу после двух вызовов gl.uniformMatrix4fv(), добавьте следующий код:

// Tell WebGL we want to affect texture unit 0
gl.activeTexture(gl.TEXTURE0);

// Bind the texture to texture unit 0
gl.bindTexture(gl.TEXTURE_2D, texture);

// Tell the shader we bound the texture to texture unit 0
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

WebGL предоставляет как минимум 8 единиц текстур; первой из них является gl.TEXTURE0. Мы говорим WebGL, что хотим повлиять на единицу 0. Затем мы вызываем bindTexture(), которая связывает текстуру с точкой привязки TEXTURE_2D единицы текстуры 0. Затем мы говорим шейдеру, что для uSampler используется единица текстуры 0.

Наконец, добавьте texture в качестве параметра к функции drawScene() как в её определении, так и в месте вызова.

Примечание: Обновите объявление вашей функции drawScene() для добавления нового параметра:

function drawScene(gl, programInfo, buffers, texture, cubeRotation) {

Примечание: Обновите место в вашей функции main() где вы вызываете drawScene():

drawScene(gl, programInfo, buffers, texture, cubeRotation);

В этот момент вращающийся куб должен быть готов.

Просмотреть весь код | Открыть эту демонстрацию в новой вкладке

Кросс-доменные текстуры

Загрузка текстур WebGL зависит от междоменных контролей доступа. Для загрузки текстуры из другого домена необходимо получить одобрение CORS. Подробную информацию о CORS см. в документации по контролю доступа HTTP.

Поскольку WebGL теперь требует загрузки текстур из защищённых контекстов, вы не можете использовать текстуры, загруженные из file:/// URL в WebGL. Это означает, что для тестирования и развертывания вашего кода потребуется защищённый веб-сервер. Для локального тестирования ознакомьтесь с нашим руководством как настроить локальный сервер тестирования?

См. эту статью на hacks.mozilla.org для объяснения, как использовать одобренные CORS изображения в качестве текстур WebGL.

Загрязнённые (только для записи) 2D-холсты не могут использоваться в качестве текстур WebGL. 2D <canvas> становится загрязнённым, например, когда на него рисуется изображение из другого домена.

  • Предыдущая
  • Следующая

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Tutorial/Using_textures_in_WebGL

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API