Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

Текстурные юниты в WebGL2

Эта статья поможет вам представить, как устроены текстурные юниты в WebGL. Есть похожая статья про атрибуты.

В качестве подготовки рекомендуется прочитать Как работает WebGL, WebGL: шейдеры и GLSL, а также WebGL: текстуры.

Текстурные юниты

В WebGL есть текстуры. Текстуры — это двумерные массивы данных, которые можно передавать в шейдер. В шейдере объявляется uniform sampler примерно так:

uniform sampler2D someTexture;

Но как шейдер узнаёт, какую текстуру использовать для someTexture?

Здесь и появляются текстурные юниты. Текстурные юниты — это глобальный массив ссылок на текстуры. Можно представить, что если бы WebGL был написан на JavaScript, глобальное состояние выглядело бы так:

const gl = {
  activeTextureUnit: 0,
  textureUnits: [
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
    { TEXTURE_2D: null, TEXTURE_CUBE_MAP: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, },
  ];
}

Как видно, textureUnits — это массив. Вы присваиваете текстуру одной из точек привязки (bind points) в этом массиве текстурных юнитов. Например, назначим ourTexture в текстурный юнит 5:

// при инициализации
const ourTexture = gl.createTexture();
// здесь код инициализации текстуры

...

// при рендере
const indexOfTextureUnit = 5;
gl.activeTexture(gl.TEXTURE0 + indexOfTextureUnit);
gl.bindTexture(gl.TEXTURE_2D, ourTexture);

Затем вы сообщаете шейдеру, к какому юниту привязана текстура, вызвав:

gl.uniform1i(someTextureUniformLocation, indexOfTextureUnit);

Если бы функции activeTexture и bindTexture WebGL были реализованы на JavaScript, они выглядели бы примерно так:

// ПСЕВДОКОД!!!
gl.activeTexture = function(unit) {
  gl.activeTextureUnit = unit - gl.TEXTURE0;  // переводим в индекс с нуля
};

gl.bindTexture = function(target, texture) {
  const textureUnit = gl.textureUnits[gl.activeTextureUnit];
  textureUnit[target] = texture;
};

Можно представить, как работают и другие функции для текстур. Все они принимают target, например gl.texImage2D(target, ...) или gl.texParameteri(target). Их реализация могла бы быть такой:

// ПСЕВДОКОД!!!
gl.texImage2D = function(target, level, internalFormat, width, height, border, format, type, data) {
  const textureUnit = gl.textureUnits[gl.activeTextureUnit];
  const texture = textureUnit[target];
  texture.mips[level] = convertDataToInternalFormat(internalFormat, width, height, format, type, data);
}

gl.texParameteri = function(target, pname, value) {
  const textureUnit = gl.textureUnits[gl.activeTextureUnit];
  const texture = textureUnit[target];
  texture[pname] = value; 
}

Из приведённого выше псевдокода видно, что gl.activeTexture устанавливает внутреннюю глобальную переменную WebGL — индекс массива текстурных юнитов. После этого все остальные функции для текстур используют target (первый аргумент), который указывает на bind point текущего текстурного юнита.

Максимальное количество текстурных юнитов

WebGL требует, чтобы реализация поддерживала минимум 32 текстурных юнита. Узнать, сколько поддерживается, можно так:

const maxTextureUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);

Обратите внимание, что для вершинных и фрагментных шейдеров могут быть разные лимиты на количество юнитов. Узнать их можно так:

const maxVertexShaderTextureUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
const maxFragmentShaderTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);

Каждый из них должен поддерживать минимум 16 текстурных юнитов.

Допустим:

maxTextureUnits = 32
maxVertexShaderTextureUnits = 16
maxFragmentShaderTextureUnits = 32

Это значит, что если вы используете, например, 2 текстурных юнита в вершинном шейдере, то для фрагментного останется только 30, так как общий максимум — 32.

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus