T.M. SoftStudio

feci quod potui, faciant meliora potentes

Введение в WebGL


Технология WebGL позволяет создавать 3D-графику на Web-странице, опираясь на HTML5 и используя JavaScript.


WebGL представляет собой спецификацию, которая реализована Web-браузерами Firefox/4.0, Safari 5.x, Chrome, Opera 12.


JavaScript-код, использующий программный интерфейс API, который определен WebGL-спецификацией, выполняется с помощью HTML5 элемента Canvas.


WebGL-спецификация основывается на спецификации OpenGL ES 2.0 — усеченной версии спецификации OpenGL 2.0, предназначенной для реализации во встроенных и мобильных устройствах.


Модель программирования чистого WebGL

Для создания WebGL-приложения на базе WebGL API необходимо:

  1. Создать HTML5 элемент canvas.

  2. Получить контекст элемента canvas.

  3. Инициализировать рабочую область отображения графики.

  4. Создать буфер данных вершин модели.

  5. Создать матрицу преобразования вершин модели на экран.

  6. Создать шейдер, определяющий алгоритм рисования графики.

  7. Инициализировать шейдер с параметрами.

  8. Нарисовать графику.

HTML5 элемент canvas и его контекст

Включаем элемент canvas в HTML-страницу:


<body onload="onLoad();">


<canvas id="webgl" style="border: none;" width="500" height="500"></canvas>


</body>


При загрузке HTML-страницы вызываем функцию onLoad():


function onLoad() {

var canvas = document.getElementById("webgl");

var gl = initWebGL(canvas);

initViewport(gl, canvas);

initMatrices();

var model = createModel(gl);

initShader(gl);

draw(gl, model);

}


В функции onLoad() получаем JavaScript-объект canvas, представляющий HTML-элемент canvas и вызываем функцию initWebGL(canvas), возвращающую контекст элемента canvas:


function initWebGL(canvas) {


var gl;

try

{

gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

}

catch (e)

{

var msg = "Error creating WebGL Context!: " + e.toString();

alert(msg);

throw Error(msg);

}


return gl;

}


Рабочая область отображения графики

В функции onLoad() вызываем функцию initViewport(gl, canvas), инициализирующую рабочую область отображения графики:


function initViewport(gl, canvas)

{

gl.viewport(0, 0, canvas.width, canvas.height);

}


Буфер данных вершин модели

В функции onLoad() вызываем функцию createModel(gl), создающую буфер данных вершин модели и возвращающую объект model, содержащий вершинный буфер, количество вершин, размер вектора вершины и тип примитивов, из которых составлена модель. Например, для квадрата это будет выглядеть так:


function createSquare(gl) {

var vertexBuffer;

vertexBuffer = gl.createBuffer();

gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);

var verts = [

.5, .5, 0.0,

-.5, .5, 0.0,

.5, -.5, 0.0,

-.5, -.5, 0.0

];

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);

var square = {buffer:vertexBuffer, vertSize:3, nVerts:4, primtype:gl.TRIANGLE_STRIP};

return square;

}


Матрица трансформации модели в пространство камеры и матрица проекции модели на рабочую область рисования

Камера по умолчанию всегда расположена в начале системы координат. Разместим модель по оси z перед камерой, определив матрицу трансформаций модели. Также определим матрицу проекции модели на рабочую область рисования, установив размер поля зрения камеры в 45 градусов. В функции onLoad() вызываем функцию initMatrices(), создающую матрицы:



function initMatrices()

{

modelViewMatrix = new Float32Array(

[1, 0, 0, 0,

0, 1, 0, 0,

0, 0, 1, 0,

0, 0, -5, 1]);

projectionMatrix = new Float32Array(

[2.41421, 0, 0, 0,

0, 2.41421, 0, 0,

0, 0, -1.002002, -1,

0, 0, -0.2002002, 0]);

}


Шейдер

В функции onLoad() вызываем функцию createShader(), создающую вершинный и фрагментный шейдеры:



function createShader(gl, str, type) {

var shader;

if (type == "fragment") {

shader = gl.createShader(gl.FRAGMENT_SHADER);

} else if (type == "vertex") {

shader = gl.createShader(gl.VERTEX_SHADER);

} else {

return null;

}



gl.shaderSource(shader, str);

gl.compileShader(shader);



if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {

alert(gl.getShaderInfoLog(shader));

return null;

}

return shader;

}


Функция createShader(gl, str, type) получает в качестве параметра исходный код вершинного и фрагментного шейдеров. Поэтому необходимо создать код шейдеров:


var vertexShaderSource =

" attribute vec3 vertexPos;\n" +

" uniform mat4 modelViewMatrix;\n" +

" uniform mat4 projectionMatrix;\n" +

" void main(void) {\n" +

"gl_Position = projectionMatrix * modelViewMatrix * \n" +

" vec4(vertexPos, 1.0);\n" +

" }\n";


var fragmentShaderSource =

" void main(void) {\n" +

" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n" +

"}\n";


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


Существует другой способ определения шейдеров — это создание кода шейдеров в тегах

<script> c их последующим разбором в функции:


<script id="shader-fs" type="x-shader/x-fragment">

precision mediump float;


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 vertexPos;


uniform mat4 modelViewMatrix;

uniform mat4 projectionMatrix;


void main(void) {

gl_Position = projectionMatrix * modelViewMatrix *

vec4(vertexPos, 1.0);

}

</script>


function createShader(gl, id) {

var shaderScript = document.getElementById(id);

if (!shaderScript) {

return null;

}


var str = "";

var k = shaderScript.firstChild;

while (k) {

if (k.nodeType == 3)

str += k.textContent;

k = k.nextSibling;

}


var shader;

if (shaderScript.type == "x-shader/x-fragment") {

shader = gl.createShader(gl.FRAGMENT_SHADER);

} else if (shaderScript.type == "x-shader/x-vertex") {

shader = gl.createShader(gl.VERTEX_SHADER);

} else {

return null;

}


gl.shaderSource(shader, str);

gl.compileShader(shader);


if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {

alert(gl.getShaderInfoLog(shader));

return null;

}


return shader;

}

Инициализация шейдера



В функции onLoad() вызываем функцию initShader(gl), в которой на основе исходного кода создаем объекты вершинного и фрагментного шейдеров, создаем объект программы, присоединяем шейдеры к программе, собираем программу:



var shaderProgram, shaderVertexPositionAttribute, shaderProjectionMatrixUniform, shaderModelViewMatrixUniform;



function initShader(gl) {

var fragmentShader = createShader(gl, fragmentShaderSource, "fragment");

var vertexShader = createShader(gl, vertexShaderSource, "vertex");



shaderProgram = gl.createProgram();

gl.attachShader(shaderProgram, vertexShader);

gl.attachShader(shaderProgram, fragmentShader);

gl.linkProgram(shaderProgram);



shaderVertexPositionAttribute = gl.getAttribLocation(shaderProgram, "vertexPos");

gl.enableVertexAttribArray(shaderVertexPositionAttribute);

shaderProjectionMatrixUniform = gl.getUniformLocation(shaderProgram, "projectionMatrix");

shaderModelViewMatrixUniform = gl.getUniformLocation(shaderProgram, "modelViewMatrix");



if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {

alert("Could not initialise shaders");

}

}


Рисование в рабочей области



В функции onLoad() вызываем функцию draw(gl, model), в которой устанавливается фон рабочей области рисования, вершинный буфер контекста, программа контекста, параметры шейдера и производится визуализация модели:



function draw(gl, obj) {

gl.clearColor(0.0, 0.0, 0.0, 1.0);

gl.clear(gl.COLOR_BUFFER_BIT);



gl.bindBuffer(gl.ARRAY_BUFFER, obj.buffer);

gl.useProgram(shaderProgram);



gl.vertexAttribPointer(shaderVertexPositionAttribute, obj.vertSize, gl.FLOAT, false, 0, 0);

gl.uniformMatrix4fv(shaderProjectionMatrixUniform, false, projectionMatrix);

gl.uniformMatrix4fv(shaderModelViewMatrixUniform, false, modelViewMatrix);



gl.drawArrays(obj.primtype, 0, obj.nVerts);

}