Шейдеры

Шейдер — это программа, выполняемая на графическом процессоре в процессе обработки сцены для определения окончательных параметров объекта или изображения. Ничего не понятно? Это неудивительно, поскольку само определение шейдера в последнее время несколько размылось. В данном случае я бы предопределил их как хелперы, помощники, берущие на себя работу по прорисовке изображения. Впрочем, все будет ясно в процессе создания сцены. А пока создадим эти шейдеры:

<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition;

Uniform mat4 uMVMatrix: uniform mat4 uPMatrix;

Void main(void) {

Gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); }

</script>

<script id="shader-fs" type="x-shader/x-fragment"> void main(void) {

Gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);

}

</script>

Это не Javascript, а специальный шейдерный язык (GLSL — The OpenGL Shading Language), основанный на ANSI C.

В OpenGL ES 2.0 существуют два типа шейдеров — вершинные и пиксельные (vertex & fragment соответственно). Важно, что в первую очередь выполняется вершинный шейдер. Он оперирует всеми вершинами фигуры.

Пиксельный шейдер выполняется почти перед самым выводом кадра на экран для каждого пикселя, используя данные, переданные вершинным шейдером.

Комбинация вершинного и пиксельного шейдеров называется шейдерной программой (вообще, существует еще один вид шейдеров — геометрические шейдеры (Geometry Shader), но о них пока речь не идет).

Рассмотрим код подробней.

В реализации вершинного шейдера мы видим две переменные типа uniform — uMVMatrix и uPMatrix. Их важное свойство заключается в том, что они могут быть доступны вне кода шейдера, чем мы непременно воспользуемся в дальнейшем. Нетрудно понять, что первая из них содержит матрицу model-view, а вторая — матрицу проекции нашей фигуры. Функция Main перемножает описанную вершину с двумя этими матрицами и возвращает результат как конечную позицию вершины.

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

Теперь нам нужен код, инициализирующий шейдеры. Он несложен. Сначала напишем небольшую функцию getShader:

Function getShader(gl, id) { var shader:

Var shaderScript = document. getElementByld(id); var str = ””;

Var k = shaderScript. firstChild; while (k) {

If (k. nodeType == 3) { str += k. textContent;

}

K = k. nextSibling;

}

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

}

Gl. shaderSource(shader, str): gl. compileShader(shader): return shader:

}

Тут все, правда, очень просто — мы получаем код шейдера с HTML-страницы по соответствующему ID, создаем шейдер и передаем его объекту WebGL.

Теперь мы можем инициализировать наши шейдеры:

Var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs");

ShaderProgram = gl. createProgram(); gl. attachShader(shaderProgram, vertexShader); gl. attachShader(shaderProgram, fragmentShader); gl. linkProgram(shaderProgram); gl. useProgram(shaderProgram);

ShaderProgram. vertexPositionAttribute = gl. getAttribLocation(shaderProgram, "aVertexPosition");

Gl. enableVertexAttribArray(shaderProgram. vertexPositionAttribute);

ShaderProgram. pMatrixUniform = gl. getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram. mvMatrixUniform = gl. getUniformLocation(shaderProgram, "uMVMatrix");

Что здесь происходит? Сначала мы получаем оба шейдера, затем создаем… ну да, программу. В данном случае исполняемую программу, причем исполняемую на стороне WebGL, то есть взаимодействующую непосредственно с видеокартой. С ней мы связываем полученные шейдеры.

Далее мы создаем у этой программы новое свойство — vertex-PositionAttribute и передаем WebGL представление значения атрибута с помощью массива.

В завершение shaderProgram получает ссылки на две uniform-переменные.

Если вы тоже ничего не поняли — обобщаю: пиксельные и вершинные шейдеры загружаются из тестового непредставления на вебстранице, компилируются в исполняемую программу и передаются объекту WebGL для использования в отрисовке нашей 3D-сцены.

Теперь совершенно понятно, что делает функция setMatrixUni-form, — используя ссылки на uniform-переменные, которые представляют нашу матрицу (проекции и model-view), мы отправляем их в WebGL. То есть вот так из JavaScript в WebGL.

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

Первое, что мы сейчас сделаем, — добавим изображению цвета. Сложность тут в том, что на самом деле мы получили не монолитный восьмиугольник, а сочетание треугольников. Следовательно, градиентная заливка разными цветами будет выглядеть довольно психоделично. Поэтому слабонервных просьба ограничиться одним цветом, а мы продолжим.

Прежде всего в описании нашей фигуры зададим цвета:

Var myColorBuffer = gl. createBuffer(); gl. bindBuffer(gl. ARRAY_BUFFER, myColorBuffer); var colors = [

1

0,

0.0,

0.0,

1.0,

1

0,

1.0,

0.0,

1.0,

0

0,

1.0,

1.0,

1.0,

1

0,

0.0,

1.0,

1.0,

1

0,

1.0,

0.0,

1.0,

0

0,

1.0,

1.0,

1.0,

1

0,

1.0,

0.0,

1.0,

0

0,

1.0,

1.0,

1.0

];

Gl. bufferData(gl. ARRAY_BUFFER, new Float32Array(colors), gl. STATIC_DRAW); tmyColorBuffer. itemSize = 4; myColorBuffer. numItems = 8;

Тут мы устанавливаем цвета для каждой из вершин нашей фигуры. Цвет задается четырьмя параметрами, отвечающими, соответственно, за интенсивность красного, зеленого, синего и альфа-канала (это, разумеется, не единственная схема, но самая простая и близкая любому веб-разработчику). Соответственно, параметр — itemSize будет иметь другое значение.

Далее мы модернизируем шейдеры. Вершинный шейдер теперь будет оперировать дополнительными переменными:

<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; attribute vec4 aVertexColor;

Uniform mat4 uMVMatrix; uniform mat4 uPMatrix: varying vec4 vColor;

Void main(void) {

Gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor;

}

</script>

Пиксельный станет значительно «умнее»:

<script id=”shader-fs” type=”x-shader/x-fragment”> precision mediump float:

#ifdef GL_ES precision highp float:

#endif

Varying vec4 vColor: void main(void) {

Gl_FragColor = vColor;

}

</script>

Тут мы устанавливаем точность для операций с плавающей точкой, принимаем переменную vColor, содержащую «сглаженный» (полученный в результате линейной интерполяции) цвет и устанавливаем значение этого цвета для пикселя.

Осталось немного — в код инициализации шейдеров добавим две строчки:

ShaderProgram. vertexColorAttribute = gl. getAttribLocation(shaderProgram, ”aVertexColor”);

Gl. enableVertexAttribArray(shaderProgram. vertexColorAttribute): shaderProgram. pMatrixUniform = gl. getUniformLocation(shaderProgram, ”uPMatrix”); shaderProgram. mvMatrixUniform = gl. getUniformLocation(shaderProgram, ”uMVMatr

Тут мы просто получаем ссылки на атрибуты цвета для каждой вершины для передачи в вершинный шейдер. Соответственно, теперь мы передаем два параметра:

Gl. enable(gl. DEPTH_TEST); draw(myShapeBuffer, myColorBuffer):

В функции draw() изменится тоже немного:

Function draw(varBuffer, colorBufer) {

BindBuffer(gl. ARRAY_BUFFER, varBuffer);

VertexAttribPointer(shaderProgram. vertexPositionAttribute, varBuffer. itemSize, FLOAT, false, 0, 0); bindBuffer(gl. ARRAY_BUFFER, colorBufer);

VertexAttribPointer(shaderProgram. vertexColorAttribute, colorBufer. itemSize, FLOAT, false, 0, 0);

Результат — на рис. 64. Не впечатляет? Ну, я старался… Если серьезно, предлагаю читателю на досуге самостоятельно поупражняться с матрицей цветов для получения более вменяемого результата.

Рис. 64. Мостим многоугольник — немного психоделии

А пока займемся пространственным расположением нашей фигуры.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *