|
| 1 | +Title: WebGL2 Использование 2 или более текстур |
| 2 | +Description: Как использовать 2 или более текстур в WebGL |
| 3 | +TOC: Использование 2 или более текстур |
| 4 | + |
| 5 | + |
| 6 | +Эта статья является продолжением [Обработки изображений в WebGL](webgl-image-processing.html). |
| 7 | +Если вы не читали её, я рекомендую [начать оттуда](webgl-image-processing.html). |
| 8 | + |
| 9 | +Теперь самое время ответить на вопрос: "Как использовать 2 или более текстур?" |
| 10 | + |
| 11 | +Это довольно просто. Давайте [вернемся на несколько уроков назад к нашему |
| 12 | +первому шейдеру, который рисует одно изображение](webgl-image-processing.html) и обновим его для 2 изображений. |
| 13 | + |
| 14 | +Первое, что нам нужно сделать - это изменить наш код, чтобы мы могли загрузить 2 изображения. Это не |
| 15 | +действительно WebGL вещь, это HTML5 JavaScript вещь, но мы можем с этим справиться. |
| 16 | +Изображения загружаются асинхронно, что может потребовать некоторого привыкания, если вы не |
| 17 | +начинали с веб-программирования. |
| 18 | + |
| 19 | +Есть в основном 2 способа, которыми мы могли бы это обработать. Мы могли бы попытаться структурировать наш код |
| 20 | +так, чтобы он работал без текстур, и по мере загрузки текстур программа обновлялась. |
| 21 | +Мы сохраним этот метод для более поздней статьи. |
| 22 | + |
| 23 | +В данном случае мы будем ждать загрузки всех изображений перед тем, как что-либо рисовать. |
| 24 | + |
| 25 | +Сначала давайте изменим код, который загружает изображение, в функцию. Это довольно просто. |
| 26 | +Он создает новый объект `Image`, устанавливает URL для загрузки и устанавливает обратный вызов, |
| 27 | +который будет вызван, когда изображение закончит загружаться. |
| 28 | + |
| 29 | +```js |
| 30 | +function loadImage (url, callback) { |
| 31 | + var image = new Image(); |
| 32 | + image.src = url; |
| 33 | + image.onload = callback; |
| 34 | + return image; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +Теперь давайте создадим функцию, которая загружает массив URL и генерирует массив изображений. |
| 39 | +Сначала мы устанавливаем `imagesToLoad` равным количеству изображений, которые мы собираемся загрузить. Затем мы делаем |
| 40 | +обратный вызов, который мы передаем в `loadImage`, уменьшаем `imagesToLoad`. Когда `imagesToLoad` становится |
| 41 | +равным 0, все изображения загружены, и мы передаем массив изображений в обратный вызов. |
| 42 | + |
| 43 | +```js |
| 44 | +function loadImages(urls, callback) { |
| 45 | + var images = []; |
| 46 | + var imagesToLoad = urls.length; |
| 47 | + |
| 48 | + // Вызывается каждый раз, когда изображение заканчивает загружаться. |
| 49 | + var onImageLoad = function() { |
| 50 | + --imagesToLoad; |
| 51 | + // Если все изображения загружены, вызываем обратный вызов. |
| 52 | + if (imagesToLoad === 0) { |
| 53 | + callback(images); |
| 54 | + } |
| 55 | + }; |
| 56 | + |
| 57 | + for (var ii = 0; ii < imagesToLoad; ++ii) { |
| 58 | + var image = loadImage(urls[ii], onImageLoad); |
| 59 | + images.push(image); |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +Теперь мы вызываем loadImages так: |
| 65 | + |
| 66 | +```js |
| 67 | +function main() { |
| 68 | + loadImages([ |
| 69 | + "resources/leaves.jpg", |
| 70 | + "resources/star.jpg", |
| 71 | + ], render); |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +Далее мы изменяем шейдер для использования 2 текстур. В данном случае мы будем умножать одну текстуру на другую. |
| 76 | + |
| 77 | +``` |
| 78 | +#version 300 es |
| 79 | +precision highp float; |
| 80 | +
|
| 81 | +// наши текстуры |
| 82 | +*uniform sampler2D u_image0; |
| 83 | +*uniform sampler2D u_image1; |
| 84 | +
|
| 85 | +// координаты текстуры, переданные из вершинного шейдера. |
| 86 | +in vec2 v_texCoord; |
| 87 | +
|
| 88 | +// нам нужно объявить выход для фрагментного шейдера |
| 89 | +out vec2 outColor; |
| 90 | +
|
| 91 | +void main() { |
| 92 | +* vec4 color0 = texture2D(u_image0, v_texCoord); |
| 93 | +* vec4 color1 = texture2D(u_image1, v_texCoord); |
| 94 | +* outColor = color0 * color1; |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +Нам нужно создать 2 WebGL объекта текстур. |
| 99 | + |
| 100 | +```js |
| 101 | + // создаем 2 текстуры |
| 102 | + var textures = []; |
| 103 | + for (var ii = 0; ii < 2; ++ii) { |
| 104 | + var texture = gl.createTexture(); |
| 105 | + gl.bindTexture(gl.TEXTURE_2D, texture); |
| 106 | + |
| 107 | + // Устанавливаем параметры, чтобы нам не нужны были мипы |
| 108 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
| 109 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
| 110 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); |
| 111 | + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); |
| 112 | + |
| 113 | + // Загружаем изображение в текстуру. |
| 114 | + var mipLevel = 0; // самый большой мип |
| 115 | + var internalFormat = gl.RGBA; // формат, который мы хотим в текстуре |
| 116 | + var srcFormat = gl.RGBA; // формат данных, которые мы поставляем |
| 117 | + var srcType = gl.UNSIGNED_BYTE; // тип данных, которые мы поставляем |
| 118 | + gl.texImage2D(gl.TEXTURE_2D, |
| 119 | + mipLevel, |
| 120 | + internalFormat, |
| 121 | + srcFormat, |
| 122 | + srcType, |
| 123 | + images[ii]); |
| 124 | + |
| 125 | + // добавляем текстуру в массив текстур. |
| 126 | + textures.push(texture); |
| 127 | + } |
| 128 | +``` |
| 129 | + |
| 130 | +WebGL имеет что-то, называемое "блоками текстур". Вы можете думать об этом как о массиве ссылок |
| 131 | +на текстуры. Вы говорите шейдеру, какой блок текстуры использовать для каждого сэмплера. |
| 132 | + |
| 133 | +```js |
| 134 | + // ищем местоположения сэмплеров. |
| 135 | + var u_image0Location = gl.getUniformLocation(program, "u_image0"); |
| 136 | + var u_image1Location = gl.getUniformLocation(program, "u_image1"); |
| 137 | + |
| 138 | + ... |
| 139 | + |
| 140 | + // устанавливаем, какие блоки текстур использовать для рендеринга. |
| 141 | + gl.uniform1i(u_image0Location, 0); // блок текстуры 0 |
| 142 | + gl.uniform1i(u_image1Location, 1); // блок текстуры 1 |
| 143 | +``` |
| 144 | + |
| 145 | +Затем мы должны привязать текстуру к каждому из этих блоков текстур. |
| 146 | + |
| 147 | +```js |
| 148 | + // Устанавливаем каждый блок текстуры для использования определенной текстуры. |
| 149 | + gl.activeTexture(gl.TEXTURE0); |
| 150 | + gl.bindTexture(gl.TEXTURE_2D, textures[0]); |
| 151 | + gl.activeTexture(gl.TEXTURE1); |
| 152 | + gl.bindTexture(gl.TEXTURE_2D, textures[1]); |
| 153 | +``` |
| 154 | + |
| 155 | +2 изображения, которые мы загружаем, выглядят так: |
| 156 | + |
| 157 | +<style>.glocal-center { text-align: center; } .glocal-center-content { margin-left: auto; margin-right: auto; }</style> |
| 158 | +<div class="glocal-center"><table class="glocal-center-content"><tr><td><img src="../resources/leaves.jpg" /> <img src="../resources/star.jpg" /></td></tr></table></div> |
| 159 | + |
| 160 | +И вот результат, если мы умножим их вместе, используя WebGL. |
| 161 | + |
| 162 | +{{{example url="../webgl-2-textures.html" }}} |
| 163 | + |
| 164 | +Некоторые вещи, которые я должен разобрать. |
| 165 | + |
| 166 | +Простой способ думать о блоках текстур - это что-то вроде этого: Все функции текстур |
| 167 | +работают с "активным блоком текстуры". "Активный блок текстуры" - это просто глобальная переменная, |
| 168 | +которая является индексом блока текстуры, с которым вы хотите работать. Каждый блок текстуры в WebGL2 имеет 4 цели. |
| 169 | +Цель TEXTURE_2D, цель TEXTURE_3D, цель TEXTURE_2D_ARRAY и цель TEXTURE_CUBE_MAP. |
| 170 | +Каждая функция текстуры работает с указанной целью на текущем активном блоке текстуры. |
| 171 | +Если бы вы реализовали WebGL в JavaScript, это выглядело бы примерно так: |
| 172 | + |
| 173 | +```js |
| 174 | +var getContext = function() { |
| 175 | + var textureUnits = [ |
| 176 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 177 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 178 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 179 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 180 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 181 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 182 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 183 | + { TEXTURE_2D: null, TEXTURE_3D: null, TEXTURE_2D_ARRAY: null, TEXTURE_CUBE_MAP: null, }, |
| 184 | + ]; |
| 185 | + var activeTextureUnit = 0; |
| 186 | + |
| 187 | + var activeTexture = function(unit) { |
| 188 | + // конвертируем enum блока в индекс. |
| 189 | + var index = unit - gl.TEXTURE0; |
| 190 | + // Устанавливаем активный блок текстуры |
| 191 | + activeTextureUnit = index; |
| 192 | + }; |
| 193 | + |
| 194 | + var bindTexture = function(target, texture) { |
| 195 | + // Устанавливаем текстуру для цели активного блока текстуры. |
| 196 | + textureUnits[activeTextureUnit][target] = texture; |
| 197 | + }; |
| 198 | + |
| 199 | + var texImage2D = function(target, ...args) { |
| 200 | + // Вызываем texImage2D на текущей текстуре активного блока текстуры |
| 201 | + var texture = textureUnits[activeTextureUnit][target]; |
| 202 | + texture.image2D(...args); |
| 203 | + }; |
| 204 | + |
| 205 | + var texImage3D = function(target, ...args) { |
| 206 | + // Вызываем texImage3D на текущей текстуре активного блока текстуры |
| 207 | + var texture = textureUnits[activeTextureUnit][target]; |
| 208 | + texture.image3D(...args); |
| 209 | + }; |
| 210 | + |
| 211 | + // возвращаем WebGL API |
| 212 | + return { |
| 213 | + activeTexture: activeTexture, |
| 214 | + bindTexture: bindTexture, |
| 215 | + texImage2D: texImage2D, |
| 216 | + texImage3D: texImage3D, |
| 217 | + }; |
| 218 | +}; |
| 219 | +``` |
| 220 | + |
| 221 | +Шейдеры принимают индексы в блоки текстур. Надеюсь, это делает эти 2 строки более ясными. |
| 222 | + |
| 223 | +```js |
| 224 | + gl.uniform1i(u_image0Location, 0); // блок текстуры 0 |
| 225 | + gl.uniform1i(u_image1Location, 1); // блок текстуры 1 |
| 226 | +``` |
| 227 | + |
| 228 | +Одна вещь, о которой нужно знать: при установке uniform'ов вы используете индексы для блоков текстур, |
| 229 | +но при вызове gl.activeTexture вы должны передать специальные константы gl.TEXTURE0, gl.TEXTURE1 и т.д. |
| 230 | +К счастью, константы последовательные, поэтому вместо этого: |
| 231 | + |
| 232 | +```js |
| 233 | + gl.activeTexture(gl.TEXTURE0); |
| 234 | + gl.bindTexture(gl.TEXTURE_2D, textures[0]); |
| 235 | + gl.activeTexture(gl.TEXTURE1); |
| 236 | + gl.bindTexture(gl.TEXTURE_2D, textures[1]); |
| 237 | +``` |
| 238 | + |
| 239 | +Мы могли бы сделать это: |
| 240 | + |
| 241 | +```js |
| 242 | + gl.activeTexture(gl.TEXTURE0 + 0); |
| 243 | + gl.bindTexture(gl.TEXTURE_2D, textures[0]); |
| 244 | + gl.activeTexture(gl.TEXTURE0 + 1); |
| 245 | + gl.bindTexture(gl.TEXTURE_2D, textures[1]); |
| 246 | +``` |
| 247 | + |
| 248 | +или это: |
| 249 | + |
| 250 | +```js |
| 251 | + for (var ii = 0; ii < 2; ++ii) { |
| 252 | + gl.activeTexture(gl.TEXTURE0 + ii); |
| 253 | + gl.bindTexture(gl.TEXTURE_2D, textures[ii]); |
| 254 | + } |
| 255 | +``` |
| 256 | + |
| 257 | +Надеюсь, этот небольшой шаг помогает объяснить, как использовать несколько текстур в одном вызове отрисовки в WebGL. |
0 commit comments