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