WebGL позволяет показывать в браузере потрясающую 3D-графику в реальном времени. Однако многие не знают, что WebGL – это 2D API, а не 3D API. Что это значит?
WebGL заботится только о двух вещах. Двухмерные координаты Clipspace и цвета. Ваша задача как программиста, использующего WebGL, – предоставить WebGL эти две вещи. Для этого вы предоставляете два шейдера. Вершинный шейдер, который предоставляет координаты пространства отсечения, и фрагментный шейдер, который предоставляет цвет. Координаты пространства отсечения всегда находятся в диапазоне от -1 до +1, независимо от размера холста. Вот простой пример WebGL, который показывает WebGL в его простейшей форме.
// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");
// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);
Вот два шейдера.
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0,1,0,1); // green
}
</script>
Опять же, координаты пространства отсечения всегда находятся в диапазоне от -1 до +1 независимо от размера холста. В приведенном выше примере мы просто передаем данные о позиции. Поскольку данные о положении уже находятся в пространстве отсечения, делать ничего не нужно. Если вы хотите получить 3D, вам нужно предоставить шейдеры, которые преобразуют 3D в 2D, потому что WebGL – это 2D API! Для 2D-графики, вероятно, удобнее работать с пикселями, а не с пространством отсечения. Давайте изменим шейдер, чтобы мы могли задавать прямоугольники в пикселях, а шейдер сам преобразовывал их в пространство отсечения. Вот новый вершинный шейдер.
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace, 0, 1);
}
</script>
Теперь мы можем преобразовать наши данные из clipspace в пиксели
// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
10, 20,
80, 20,
10, 30,
10, 30,
80, 20,
80, 30]), gl.STATIC_DRAW);
Вы можете заметить, что прямоугольник находится в нижней части этой области. В WebGL левый нижний угол имеет координаты 0,0. Чтобы получить более традиционный верхний левый угол, используемый для API 2D-графики, мы просто переворачиваем координату Y.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
Давайте превратим код, определяющий прямоугольник, в функцию, чтобы мы могли вызывать ее для прямоугольников разных размеров. Заодно мы сделаем цвет настраиваемым. Сначала мы заставим фрагментный шейдер принимать входные данные цвета.
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
А вот новый код, который рисует 50 прямоугольников в случайных местах и случайных цветов.
...
var colorLocation = gl.getUniformLocation(program, "u_color");
...
// Create a buffer
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// draw 50 random rectangles in random colors
for (var ii = 0; ii < 50; ++ii) {
// Setup a random rectangle
setRectangle(
gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
// Set a random color.
gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// Fills the buffer with the values that define a rectangle.
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
Надеюсь, вы убедились, что WebGL – это довольно простой API. Хотя 3D-графика может быть сложнее, эта сложность добавляется вами, программистом, в виде более сложных шейдеров. Сам WebGL API является двухмерным и довольно простым.
Что означают type="x-shader/x-vertex" и type="x-shader/x-fragment"?
Теги <script> по умолчанию содержат код JavaScript. Вы можете не указывать тип или указать type="javascript" или type="text/javascript", и браузер будет интерпретировать содержимое как JavaScript. Если вы укажете что-то другое, браузер проигнорирует содержимое тега script.
Мы можем использовать эту функцию для хранения шейдеров в тегах script. Более того, мы можем придумать свой собственный тип и в нашем JavaScript-коде искать его, чтобы решить, компилировать ли шейдер как вершинный или фрагментный.
В этом случае функция createShaderFromScriptElement ищет скрипт с указанным id, а затем проверяет type, чтобы определить, какой тип шейдера нужно создать.
Обработка изображений WebGL
Обработка изображений в WebGL не представляет сложности. Насколько легко? Подробнее об этом рассказывается ниже.
Чтобы рисовать изображения в WebGL, нужно использовать текстуры. Подобно тому, как WebGL ожидает координаты пространства отсечения при рендеринге вместо пикселей, WebGL ожидает координаты текстуры при чтении текстуры. Координаты текстуры варьируются от 0,0 до 1,0 независимо от ее размеров. Поскольку мы рисуем только один прямоугольник (точнее, два треугольника), нам нужно указать WebGL, какому месту в текстуре соответствует каждая точка в прямоугольнике. Мы передадим эту информацию из вершинного шейдера во фрагментный шейдер, используя специальный тип переменной, называемый "varying". Она называется varying, потому что она варьируется. WebGL будет интерполировать значения, которые мы предоставляем в вершинном шейдере, поскольку он рисует каждый пиксель, используя фрагментный шейдер. Используя вершинный шейдер из конца предыдущего раздела, нам нужно добавить атрибут для передачи координат текстуры, а затем передать их фрагментному шейдеру.
attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;
void main() {
...
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points
v_texCoord = a_texCoord;
}
Затем мы предоставляем фрагментный шейдер для поиска цветов из текстуры.
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// Look up a color from the texture.
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
Наконец, нам нужно загрузить изображение, создать текстуру и скопировать изображение в текстуру. Поскольку мы работаем в браузере, изображения загружаются асинхронно, поэтому нам нужно немного изменить код, чтобы дождаться загрузки текстуры. Когда оно загрузится, мы его нарисуем.
function main() {
var image = new Image();
image.src = "http://someimage/on/our/server"; // MUST BE SAME DOMAIN!!!
image.onload = function() {
render(image);
}
}
function render(image) {
...
// all the code we had before.
...
// look up where the texture coordinates need to go.
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
...
}
Не слишком интересно, поэтому давайте поработаем с этим изображением. Как насчет того, чтобы просто поменять местами красный и синий?
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
Что если мы хотим обработать изображение, учитывая другие пиксели? Поскольку WebGL ссылается на текстуры в координатах текстуры, которые идут от 0,0 до 1,0, мы можем рассчитать, на сколько нужно переместиться на 1 пиксель, используя простую математику onePixel = 1.0 / textureSize.
Вот фрагментный шейдер, который усредняет левый и правый пиксели каждого пикселя в текстуре.
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
// compute 1 pixel in texture coordinates.
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
// average the left, middle, and right pixels.
gl_FragColor = (
texture2D(u_image, v_texCoord) +
texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>
Затем нам нужно передать размер текстуры из JavaScript.
...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...
Теперь, когда мы знаем, как ссылаться на другие пиксели, давайте используем ядро свертки, чтобы выполнить ряд распространенных операций обработки изображений. В этом случае мы будем использовать ядро 3x3. Ядро свертки – это матрица 3 x 3, в которой каждый элемент представляет собой коэффициент, на который нужно умножить значение каждого из восьми пикселей, окружающих обрабатываемый пиксель. Затем мы делим результат на вес ядра (сумму всех значений в ядре) или на 1,0 (в зависимости от того, что больше). Вот хорошая статья на эту тему. А здесь вы найдете пример кода на C++. В нашем случае мы будем использовать шейдеры. Вот новый фрагментный шейдер.
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec4 colorSum =
texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ;
float kernelWeight =
u_kernel[0] +
u_kernel[1] +
u_kernel[2] +
u_kernel[3] +
u_kernel[4] +
u_kernel[5] +
u_kernel[6] +
u_kernel[7] +
u_kernel[8] ;
if (kernelWeight <= 0.0) {
kernelWeight = 1.0;
}
// Divide the sum by the weight but just use rgb
// we'll set alpha to 1.0
gl_FragColor = vec4((colorSum / kernelWeight).rgb, 1.0);
}
</script>
В JavaScript нам нужно предоставить ядро свертки.
...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
...
var edgeDetectKernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
...
Надеюсь, это убедило вас, что обработка изображений в WebGL довольно проста. Далее я расскажу, как применить к изображению несколько эффектов.
Что означают префиксы a, u и v_ перед переменными в GLSL?
Это просто соглашение о наименовании. a_ для атрибутов, которые являются данными, предоставляемыми буферами. u_ для униформ, которые являются входными данными для шейдеров, v_ для переменных, которые передаются из вершинного шейдера во фрагментный шейдер и интерполируются (или изменяются) между вершинами для каждого нарисованного пикселя.
Применение нескольких эффектов
Следующий очевидный вопрос по обработке изображений: как применить несколько эффектов?
Вы можете попробовать генерировать шейдеры на лету. Предоставьте пользователю интерфейс, в котором он сможет выбрать нужные эффекты, а затем сгенерируйте шейдер, который будет их все применять. Это не всегда возможно, но такой метод часто используется для создания эффектов в графике реального времени. Более гибкий способ – использовать ещё две текстуры и по очереди выполнять рендеринг для каждой из них, каждый раз применяя следующий эффект.
Original Image -> [Blur] -> Texture 1
Texture 1 -> [Sharpen] -> Texture 2
Texture 2 -> [Edge Detect] -> Texture 1
Texture 1 -> [Blur] -> Texture 2
Texture 2 -> [Normal] -> Canvas
Для этого нам нужно создать буферы кадров. В WebGL и OpenGL буфер кадра на самом деле является плохим именем. Буфер кадра WebGL/OpenGL – это просто набор состояний, а не буфер в прямом смысле слова. Но, прикрепив текстуру к буферу кадра, мы можем выполнить рендеринг в эту текстуру. Сначала преобразуем старый код создания текстуры в функцию.
function createAndSetupTexture(gl) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set up texture so we can render any size image and so we are
// working with pixels.
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.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
return texture;
}
// Create a texture and put the image in it.
var originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
Теперь используем эту функцию, чтобы создать ещё две текстуры и прикрепить их к двум буферам кадра.
// create 2 textures and attach them to framebuffers.
var textures = [];
var framebuffers = [];
for (var ii = 0; ii < 2; ++ii) {
var texture = createAndSetupTexture(gl);
textures.push(texture);
// make the texture the same size as the image
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer
var fbo = gl.createFramebuffer();
framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it.
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}
Теперь давайте создадим набор ядер, а затем список ядер, которые нужно применить.
// Define several convolution kernels
var kernels = {
normal: [
0, 0, 0,
0, 1, 0,
0, 0, 0
],
gaussianBlur: [
0.045, 0.122, 0.045,
0.122, 0.332, 0.122,
0.045, 0.122, 0.045
],
unsharpen: [
-1, -1, -1,
-1, 9, -1,
-1, -1, -1
],
emboss: [
-2, -1, 0,
-1, 1, 1,
0, 1, 2
]
};
// List of effects to apply.
var effectsToApply = [
"gaussianBlur",
"emboss",
"gaussianBlur",
"unsharpen"
];
И, наконец, давайте применим каждый из них, поочередно отрисовывая текстуры.
// start with the original image
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);
// don't y flip images while drawing to the textures
gl.uniform1f(flipYLocation, 1);
// loop through each effect we want to apply.
for (var ii = 0; ii < effectsToApply.length; ++ii) {
// Setup to draw into one of the framebuffers.
setFramebuffer(framebuffers[ii % 2], image.width, image.height);
drawWithKernel(effectsToApply[ii]);
// for the next draw, use the texture we just rendered to.
gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
}
// finally draw the result to the canvas.
gl.uniform1f(flipYLocation, -1); // need to y flip for canvas
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");
function setFramebuffer(fbo, width, height) {
// make this the framebuffer we are rendering to.
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Tell the shader the resolution of the framebuffer.
gl.uniform2f(resolutionLocation, width, height);
// Tell webgl the viewport setting needed for framebuffer.
gl.viewport(0, 0, width, height);
}
function drawWithKernel(name) {
// set the kernel
gl.uniform1fv(kernelLocation, kernels[name]);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
Мне нужно кое-что проверить.
Вызов gl.bindFramebuffer с null сообщает WebGL, что вы хотите выполнить рендеринг на холсте, а не в одном из буферов кадров.
WebGL должен преобразовать координаты из пространства отсечения обратно в пиксели. Это делается на основе настроек gl.viewport. По умолчанию для gl.viewport задается размер холста, который используется при инициализации WebGL. Поскольку буферы кадров, в которые мы выполняем рендеринг, имеют другой размер, чем холст, нам нужно соответствующим образом настроить область просмотра.
Наконец, в примерах основ WebGL мы перевернули координату Y при рендеринге, потому что WebGL отображает холст с 0,0 в левом нижнем углу вместо более традиционного для 2D верхнего левого угла. При рендеринге в буфер кадра это не требуется. Поскольку буфер кадров никогда не отображается, не имеет значения, какая часть является верхней, а какая – нижней. Важно лишь, чтобы пиксель 0,0 в буфере кадров соответствовал 0,0 в наших расчетах. Чтобы решить эту проблему, я добавил в шейдер ещё один вход, позволяющий задать, нужно ли переворачивать изображение.
<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...
void main() {
...
gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
...
}
</script>
Затем мы можем установить его, когда мы рендерим с помощью
...
var flipYLocation = gl.getUniformLocation(program, "u_flipY");
...
// don't flip
gl.uniform1f(flipYLocation, 1);
...
// flip
gl.uniform1f(flipYLocation, -1);
В этом примере мы использовали одну программу GLSL, которая может создавать несколько эффектов. Если вы хотите выполнить полную обработку изображений, вам, вероятно, понадобится много программ GLSL. Программа для настройки оттенка, насыщенности и яркости. Другой – для яркости и контрастности. Одна для инвертирования, другая для настройки уровней и т. д. Вам нужно будет изменить код, чтобы переключать программы GLSL и обновлять параметры для конкретной программы. Я думал написать этот пример, но лучше оставить это упражнение читателю, потому что несколько программ GLSL, каждая со своими собственными потребностями в параметрах, вероятно, означают серьезный рефакторинг, чтобы все это не превратилось в большой беспорядок. Надеюсь, что этот и предыдущие примеры сделали WebGL немного более доступным, и надеюсь, что начало с 2D поможет сделать WebGL немного проще для понимания. Если у меня будет время, я постараюсь написать ещё несколько статей о том, как создавать 3D-объекты, а также подробнее рассказать о том, как работает WebGL.
WebGL и альфа-канал
Я заметил, что у некоторых разработчиков OpenGL возникают проблемы с тем, как WebGL обрабатывает альфа-канал в заднем буфере (т. е. на холсте), поэтому я подумал, что было бы неплохо рассмотреть некоторые различия между WebGL и OpenGL, связанные с альфа-каналом.
Основное различие между OpenGL и WebGL заключается в том, что OpenGL выполняет рендеринг в задний буфер, который не компонуется ни с чем (или фактически ни с чем) диспетчером окон ОС, поэтому значение альфа-канала не имеет значения.
WebGL компонуется браузером с веб-страницей, и по умолчанию используется предварительно умноженный альфа-канал, как и в тегах .png <img> с прозрачностью и тегах 2d canvas.
В WebGL есть несколько способов сделать это более похожим на OpenGL.
1. Укажите, что WebGL должен использовать композитинг с непредумноженным альфа-каналом.
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
Значение по умолчанию – true. Конечно, результат все равно будет накладываться на страницу с любым цветом фона, который окажется под холстом (цвет фона холста, цвет фона контейнера холста, цвет фона страницы, элементы за холстом, если у холста z-index > 0, и т. д.), то есть цвет, который CSS определяет для этой области веб-страницы. Чтобы проверить, есть ли у вас проблемы с альфа-каналом, установите для фона макета яркий цвет, например красный. Вы сразу увидите, что происходит.
<canvas style="background: red;"></canvas>
Вы также можете выбрать черный цвет, чтобы скрыть все проблемы с альфа-каналом.
2. Укажите WebGL, что вам не нужен альфа-канал в заднем буфере
gl = canvas.getContext("experimental-webgl", {alpha: false});
Это позволит ему работать больше как OpenGL, поскольку задний буфер будет иметь только RGB. Это, вероятно, лучший вариант, потому что хороший браузер может увидеть, что у вас нет альфа-канала, и оптимизировать способ компоновки WebGL. Конечно, это также означает, что в заднем буфере не будет альфа-канала, поэтому, если вы используете альфа-канал в заднем буфере для каких-то целей, это может вам не подойти. Немногие приложения используют альфа-канал в заднем буфере. Думаю, это должно было быть по умолчанию.
3. Очистка альфа-канала в конце рендеринга
..
renderScene();
..
// Set the backbuffer's alpha to 1.0
gl.clearColor(1, 1, 1, 1);
gl.colorMask(false, false, false, true);
gl.clear(gl.COLOR_BUFFER_BIT);
Очистка обычно выполняется очень быстро, поскольку в большинстве аппаратных средств для этого предусмотрен специальный случай. Я делал это в большинстве своих демо. Если бы я был умным, я бы перешел на метод №2 выше. Может, я сделаю это сразу после публикации. It seems like most WebGL libraries should default to this method. Те немногие разработчики, которые используют альфа-канал для композитинга, могут запросить его. Остальные получат наилучшую производительность и минимум сюрпризов.
4. Очистите альфа-канал один раз и больше не выполняйте рендеринг в него.
// At init time. Clear the back buffer.
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Turn off rendering to alpha
gl.colorMask(true, true, true, false);
Конечно, если вы выполняете рендеринг в собственные буферы кадров, вам может потребоваться снова включить рендеринг в альфа-канал, а затем снова отключить его при переключении на рендеринг на холсте.
5. Работа с изображениями
Кроме того, если вы загружаете файлы PNG с альфа-каналом в текстуры, по умолчанию альфа-канал предварительно умножается, что обычно НЕ делается в большинстве игр. Если вы хотите предотвратить такое поведение, вам нужно сообщить об этом WebGL с помощью
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
6. Использование уравнения смешивания, которое работает с предварительно умноженным альфа-каналом
Почти все приложения OpenGL, которые я писал или над которыми работал, используют
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
Это работает для текстур без предварительно умноженного альфа-канала. Если вы действительно хотите работать с текстурами с предварительно умноженной альфой, то, вероятно, захотите
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
Это все известные мне способы. Если вы знаете другие, поделитесь ими в комментариях.