Прежде чем приступить непосредственно к рисованию и созданию объектов, посмотрим, что собой представляет конвейер WebGL. Схематично его можно представить следующим образом:
Теперь разберем поэтапно.
Вначале мы создаем набор вершин в буфере вершин (Vertex Buffer). По этим вершинам впоследствии будут составлены геометрические примитивы, а из примитивов - объекты. И проводим некоторую предобработку.
Затем содержимое буфера вершин поступает на обработку в вершинный шейдер (Vertex Shader). Шейдер производит над вершинами некоторые трансформации, например, применяет матрицы преобразования и т.д. Шейдеры пишутся самим разработчиком, поэтому программист может применить различные преобразования по своему усмотрению.
На следующем этапе (Primitive Assembly) конвейер получает результат вершинного шейдера и пытается измененные вершины сопоставить в отдельные примитивы - линии, треугольники, спрайты. Также на этом этапе определяется, входит ли примитив в видимое пространство. Если нет, то он обрезается. Оставшиеся примитивы передаются на следующий этап конвейера.
Далее на этапе растеризации (Rasterization) полученные примитивы преобразуются в фрагменты, которые можно представить как пиксели, которые затем будут отрисованы на экране
И затем в дело вступает фрагментный шейдер (Fragment shader). (В технологиях Direct3D, XNAпрямым аналогом является пиксельный шейдер). Фрагментый шейдер производит преобразования с цветовой составляющей примитивов, наполняет их цветом, точнее окрашивает пиксели, и в качестве вывода передает на следующий этап измененные фрагменты.
Следующий этап представляет собой ряд преобразований над полученными с фрагментного шейдера фрагментами. Собственно он состоит из нескольких подэтапов:
Scissor Test: на этом этапе проверяется, находится ли фрагмент в пределах отсекающего прямоугольника. Если фрагмент находится в пределах этого прямоугольника, то он передается на следующий этап. Если же нет, то он отбрасывается и больше не принимает участия в обработке.
Multisample Fragment Operations: на данном этапе у каждого фрагмента изменяются цветовые составляющие, производится сглаживание (anti-alising), чтобы объект выглядел более плавно на экране.
Stencil Test: здесь фрагмент передается в буфер трафаретов (stencil buffer). Если вкратце, то в этом буфере дополнительно отбрасываются те фрагменты, которые не должны отображаться на экране. Как правило, данный буфер используется для создания различного рода эффектов, например, эффект теней.
Depth Buffer Test - тест буфера глубины. В буфере глубины (depth buffer, а также называется, z-buffer) сравнивается z-компонента фрагмента, и если она больше значения в буфере глубины, то, следовательно, данный фрагмент расположен к смотрящему на трехмерную сцену ближе, чем предыдущий фрагмент, поэтому текущий фрагмент проходит тест. Если же z-компонента больше значения в буфере глубины, то, следовательно, данный фрагмент находится дальше, поэтому он не должен быть виден и отбрасывается.
Blending: на данном этапе происходите небольшое смешение цветов, например, для создания прозрачных объектов.
Dithering: здесь происходит смешение цветов, для создания тонов и полутонов.
Frame Buffer: и здесь наконец полученные после предобработки фрагменты превращаются в пиксели на экране.
Итак, мы рассмотрели этапы конвейера. Данные этапы рисуют нам некоторый алгоритм действий, от которого затем мы будет отталкиваться. И реальности создание программы разбивается также на некоторые этапы:
Создание и настройка шейдеров
Создание и настройка буфера вершин, которые в последствии образуют геометрическую фигуру
Отрисовка фигуры
Если мы посмотрим на простейшую программу по отрисовке треугольника из предыдущей главы, то увидим, что сначала срабатывают функция
initShaders()
, производящая инициализацию шейдеров и их настройку. Шейдеры являются обязательным звеном в конвейере WebGL, поэтому
без них нам сложно будет построить программу.
Затем в дело вступает функция initBuffers()
, устанавливающая буфер точек, по которым идет отрисовка.
И на финальном этапе происходит отрисовка в функции draw()
- при помощи шейдеров буфер вершин превращается в геометрическую фигуру.
Далее приводится полный код программы:
<!DOCTYPE html> <html> <head> <title>Привет WebGL!</title> <meta charset="utf-8"> </head> <body> <canvas id="canvas3D" width="400" height="300">Ваш браузер не поддерживает элемент canvas</canvas> <!-- фрагментный шейдер --> <script id="shader-fs" type="x-shader/x-fragment"> void main(void) { gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); } </script> <!-- вершинный шейдер --> <script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; void main(void) { gl_Position = vec4(aVertexPosition, 1.0); } </script> <script type="text/javascript"> var gl; var shaderProgram; var vertexBuffer; // установка шейдеров function initShaders() { // получаем шейдеры var fragmentShader = getShader(gl.FRAGMENT_SHADER, 'shader-fs'); var vertexShader = getShader(gl.VERTEX_SHADER, 'shader-vs'); //создаем объект программы шейдеров shaderProgram = gl.createProgram(); // прикрепляем к ней шейдеры gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); // связываем программу с контекстом webgl gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Не удалсь установить шейдеры"); } gl.useProgram(shaderProgram); // установка атрибута программы shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); // подключаем атрибут для использования gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); } // Функция создания шейдера по типу и id источника в структуре DOM function getShader(type,id) { var source = document.getElementById(id).innerHTML; // создаем шейдер по типу var shader = gl.createShader(type); // установка источника шейдера gl.shaderSource(shader, source); // компилируем шейдер gl.compileShader(shader); if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("Ошибка компиляции шейдера: " + gl.getShaderInfoLog(shader)); gl.deleteShader(shader); return null; } return shader; } // установка буфера вершин function initBuffers() { // установка буфера вершин vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); // массив координат вершин объекта var triangleVertices = [ 0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW); // указываем кол-во точек vertexBuffer.itemSize = 3; vertexBuffer.numberOfItems = 3; } // отрисовка function draw() { // установка области отрисовки gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT); // указываем, что каждая вершина имеет по три координаты (x, y, z) gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, vertexBuffer.itemSize, gl.FLOAT, false, 0, 0); // отрисовка примитивов - треугольников gl.drawArrays(gl.TRIANGLES, 0, vertexBuffer.numberOfItems); } window.onload=function(){ // получаем элемент canvas var canvas = document.getElementById("canvas3D"); try { // Сначала пытаемся получить стандартный контекст WegGK. // Если не получится, обращаемся к экспериментальному контексту gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) {} // Если контекст не удалось получить, выводим сообщение if (!gl) { alert("Ваш браузер не поддерживает WebGL"); } if(gl){ // установка размеров области рисования gl.viewportWidth = canvas.width; gl.viewportHeight = canvas.height; // установка шейдеров initShaders(); // установка буфера вершин initBuffers(); // покрасим в красный цвет фон gl.clearColor(1.0, 0.0, 0.0, 1.0); // отрисовка сцены draw(); } } </script> </body> </html>