Using textures in WebGL

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

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

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

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

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

Код ниже показывает как выглядит загрузка текстуры.

function initTextures() {
  cubeTexture = gl.createTexture();
  cubeImage = new Image();
  cubeImage.onload = function() { handleTextureLoaded(cubeImage, cubeTexture); }
  cubeImage.src = "cubetexture.png";
}

function handleTextureLoaded(image, texture) {
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
  gl.generateMipmap(gl.TEXTURE_2D);
  gl.bindTexture(gl.TEXTURE_2D, null);
}

Функция initTextures() начинается с создания объекта текстуры cubeTexture вызовом функции createTexture(). Затем создаем объект Image и загружаем в него избражение для нашей текстуры. Коллбек handleTextureLoaded() будет вызван, как только изображение будет полностью загружено.

Для создания текстуры, мы указываем, что новая текстура - это текущая рабочая текстура связывая ее с gl.TEXTURE_2D. После этого, загруженное изображение посылается в texImage2D(), чтобы записать графическую информацию в текстуру.

Важно: ширина и высота текстуры в подавляющем большинстве случаем должна быть кратна целочисленной положительной степени числа 2 (т.е. 1, 2, 4, 8, 16, и так далее). Для исключительных ситуаций смотретие "Non power-of-two textures" ниже.

Следующие две строки устанавливают фильтрацию для текстуры. Это управляет тем, как изображение будет отображено при вытягивании или сжатии. В данном случае используем линейную для вытягивания и mipmap для сжатия. После того как мы получили mipmsp вызвав generateMipMap(), завершаем сообщая WebGL, что мы закончили связыванием null с gl.TEXTURE_2D.

Текстуры с неправильными размерами сторон

Вообще говоря, использование текстур со сторонами кратными степеням двойки - это идеальное решение. Они эффективно хранятся в видео памяти и не имеют ограничений на то, как они могут использоваться. Художник должен масштабировать избражение так чтобы его стороны были равны 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, или 2048 пикселей. Многие, но не все устройства могут поддерживать сторооны равные 4096 пикселям, а некоторые даже 8192 и выше.

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

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

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

Мипмаппинг и UV повторение могут быть оключены с использованием  texParameteri() при создании вашей текстуры используя bindTexture().

// 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);

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

Текстура загружена и готова к использованию. Но перед использованием мы должны создать соответствие VU координат с положением а гранях. Это заменяет весь предыдущий код для задания цветов граням в функции initBuffers().

cubeVerticesTextureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVerticesTextureCoordBuffer);
  
var 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);

Сначала в коде создается GL буфер в котором мы будем хранть UV координаты текстуры для каждой грани. затем мы свяжем этот буфер с массив из которого будем рисовать. Массив textureCoordinates определяет координаты текстуры соотвествующей каждой вершине каждой грани. Отметьте, что координаты текстуры лежат в промежутке от 0.0 до 1.0; размерность текстуры нормализована в пределах от 0.0 до 1.0 не взирая на ее настоящий размер. После определения массива, мы посылваем массив в буфер, теперь GL имеет данные для отрисовки.

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

Шейдерную програму так же нужно обновить.

First, let's take a look at the very simple change needed in initShaders():

textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
gl.enableVertexAttribArray(textureCoordAttribute);
gl.vertexAttribPointer(texCoordAttribute, 2, gl.FLOAT, false, 0, 0);

This replaces the code that set up the vertex color attribute with one that contains the texture coordinate for each vertex.

The vertex shader

Next, we need to replace the vertex shader so that instead of fetching color data, it instead fetches the texture coordinate data.

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;
  attribute vec2 aTextureCoord;
    
  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;
      
  varying highp vec2 vTextureCoord;
  
  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
    vTextureCoord = aTextureCoord;
  }
</script>

The key change here is that instead of fetching the vertex color, we're setting the texture coordinates; this will indicate the location within the texture corresponding to the vertex.

The fragment shader

The fragment shader likewise needs to be updated:

<script id="shader-fs" type="x-shader/x-fragment">
  varying highp vec2 vTextureCoord;
      
  uniform sampler2D uSampler;
      
  void main(void) {
    gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
  }
</script>

Instead of assigning a color value to the fragment's color, the fragment's color is computed by fetching the texel (that is, the pixel within the texture) that the sampler says best maps to the fragment's position.

Drawing the textured cube

The change to the drawScene() function is simple (except that for the purpose of clarity, I've removed the code that causes the cube to translate through space while animating; instead it just rotates).

The code to map colors to the texture is gone, replaced with this:

gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, cubeTexture);
gl.uniform1i(gl.getUniformLocation(shaderProgram, "uSampler"), 0);

GL provides 32 texture registers; the first of these is gl.TEXTURE0. We bind our previously-loaded texture to that register, then set the shader sampler uSampler (specified in the shader program) to use that texture.

At this point, the rotating cube should be good to go.

View the complete code | Open this demo on a new page

Cross-domain textures

Loading of WebGL textures is subject to cross-domain access controls. In order for your content to load a texture from another domain, CORS approval needs to be be obtained. See HTTP access control for details on CORS.

See this hacks.mozilla.org article for an explanation of how to use CORS-approved images as WebGL textures, with a self-contained example.

Note: CORS support for WebGL textures and the crossOrigin attribute for image elements is implemented in Gecko 8.0.

Tainted (write-only) 2D canvases can't be used as WebGL textures. A 2D <canvas> becomes tainted, for example, when a cross-domain image is drawn on it.

Note: CORS support for Canvas 2D drawImage is implemented in Gecko 9.0. This means that using a cross-domain image with CORS approval does no longer taint the 2D canvas, so the 2D canvas remains usable as the source of a WebGL texture.

Note: CORS support for cross-domain videos and the crossorigin attribute for <video> elements is implemented in Gecko 12.0.

Метки документа и участники

 Внесли вклад в эту страницу: KTatyana, mrBizik, Kran440
 Обновлялась последний раз: KTatyana,