Строим 3D проекты – Шаг 5
С нашими знаниями уже можно попробовать нарисовать объемную фигуру, сделать ее вращение, добавить тест глубины для прорисовки только видимой части.
Нарисуем примерно такую фигуру:
Это элементарный прямой купол (призма, лежащая на одной из боковых граней).
Мы разбили фигуру на 18 треугольников (на рисунке показаны не все). Могли разбить на 8 треугольников, но при 18 точках мы можем настроить точнее цвет или форму всей фигуры.
Пусть точка 0 будет с координатами (0.0, 0.0, 0.0). Тогда остальные точки относительно точки 0 будут с координатами:
Т. 1 (1.0, 0.0, 0.0),
Т. 2 (2.0, 0.0, 0.0),
Т. 3 (0.5, 1.0, 0.0),
Т. 4 (1.5, 1.0, 0.0),
Т. 5 (1.0, 2.0, 0.0),
Т. 6 (0.0, 0.0, -2.0),
Т. 7 (0.1, 0.0, -2.0),
Т. 8 (2.0, 0.0, -2.0),
…
Используем буфер индексов, в котором будем манипулировать номерами вершин, а не их координатами. Тогда при использовании, например, точки Т.3, нам достаточно указать ее порядковый номер “3”, а не писать каждый раз (0.5, 1.0, 0.0).
Вносим изменения в программу.
var indexBuffer05pr = null; var angle05pr = 0;
initShaders05pr(); //[1] setupBuffers05pr(); //[2] getMatrixUniforms05pr(); (function animLoop05pr() { setupWebGL05pr(); //[3] //setupDynamicBuffers05pr(); setMatrixUniforms05pr(); drawScene05pr(); //[4] requestAnimationFrame( animLoop05pr, canvas05pr); })();
//Colors. var triangleVerticeColors = [ //Prism colors ------------ -------------. //front face 1.0, 0.0, 0.0, //R 1.0, 0.8, 0.0, //Orange 1.0, 1.0, 0.0, //Yellow 0.0, 1.0, 0.0, //G 0.0, 0.8, 1.0, //light-B 0.0, 0.0, 1.0, //B //rear face 1.0, 1.0, 1.0, //White 0.0, 0.0, 0.0, //Black 0.6, 0.6, 0.6, //Dark gray 0.0, 0.0, 0.0, //Black 0.2, 0.2, 0.2, //Light gray 0.0, 0.0, 0.0, //Black ]; trianglesColorBuffer05pr = gl05pr.createBuffer(); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesColorBuffer05pr ); gl05pr.bufferData( gl05pr.ARRAY_BUFFER, new Float32Array( triangleVerticeColors ), gl05pr.STATIC_DRAW ); //Vertices. var triangleVertices = [ //Prism's points coodinates ------------- ----------------. //front face 0.0, 0.0, 0.0, //0th vertice 1.0, 0.0, 0.0, //1st vertice 2.0, 0.0, 0.0, //2nd vertice 0.5, 1.0, 0.0, //3rd vertice 1.5, 1.0, 0.0, //4th vertice 1.0, 2.0, 0.0, //5th vertice //rear face 0.0, 0.0, -2.0, //6th vertice 1.0, 0.0, -2.0, //7th vertice 2.0, 0.0, -2.0, //8th vertice 0.5, 1.0, -2.0, //9th vertice 1.5, 1.0, -2.0, //10th vertice 1.0, 2.0, -2.0, //11th vertice ]; trianglesVerticeBuffer05pr = gl05pr.createBuffer(); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesVerticeBuffer05pr ); gl05pr.bufferData( gl05pr.ARRAY_BUFFER, new Float32Array( triangleVertices ), gl05pr.STATIC_DRAW ); //Setup index buffer. 18 triangles. var VertexIndices = [ //front face 0,1,3, 1,3,4, 1,2,4, 3,4,5, //rear face 6,7,9, 7,9,10, 7,8,10, 9,10,11, //left side 0,3,6, 3,6,9, 3,5,9, 5,9,11, //right side 2,4,8, 4,8,10, 4,5,10, 5,10,11, //bottom faces 0,6,8, 8,2,0, ]; indexBuffer05pr = gl05pr.createBuffer(); gl05pr.bindBuffer( gl05pr.ELEMENT_ARRAY_BUFFER, indexBuffer05pr ); gl05pr.bufferData( gl05pr.ELEMENT_ARRAY_BUFFER, new Uint16Array( VertexIndices ), gl05pr.STATIC_DRAW ); //Will be used in drawScene05pr() function later. indexBuffer05pr.num_pts = VertexIndices.length;
Все буферы задаются один раз, поэтому режим STATIC_DRAW.
Буфер индексов имеет тип ELEMENT_ARRAY_BUFFER.
В свойство
мы записали размер массива индексов, который используем позже.
function setupWebGL05pr() { //The default value to set the color buffer. gl05pr.clearColor(0, 0.5, 0.5, 1); //Clear color and depth buffers: gl05pr.clear( gl05pr.COLOR_BUFFER_BIT | gl05pr.DEPTH_BUFFER_BIT ); //Hide the faces that should be hidden from view: gl05pr.enable( gl05pr.DEPTH_TEST ); gl05pr.viewport(0, 0, canvas05pr.width, canvas05pr.height); mat4.identity(mvMatrix05pr); //Initial scene rotation. mat4.translate(mvMatrix05pr, [0.0, 0.0, -7.0]); mat4.rotate(mvMatrix05pr, 0.50, [1.0, 0.0, 0.0]); mat4.rotate(mvMatrix05pr, -0.50, [0.0, 1.0, 0.0]); //Plus scene rotation this frame. mat4.rotate(mvMatrix05pr, angle05pr, [0.0, 1.0, 0.0]); angle05pr += 0.01; mat4.perspective(45, canvas05pr.width / canvas05pr.height, 0.1, 100.0, pMatrix05pr); }
Также, здесь уже задействованы функции теста глубины, чтобы отображались только видимые поверхности :
gl05pr.clear( gl05pr.DEPTH_BUFFER_BIT); gl05pr.enable( gl05pr.DEPTH_TEST );
//[4] function drawScene05pr() { //Move data into shader. vertexPositionAttribute05pr = gl05pr.getAttribLocation( glProgram05pr, "aVertexPosition05pr" ); gl05pr.enableVertexAttribArray( vertexPositionAttribute05pr ); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesVerticeBuffer05pr ); gl05pr.vertexAttribPointer( vertexPositionAttribute05pr, 3, gl05pr.FLOAT, false, 0, 0); vertexColorAttribute05pr = gl05pr.getAttribLocation( glProgram05pr, "aVertexColor05pr" ); gl05pr.enableVertexAttribArray( vertexColorAttribute05pr ); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesColorBuffer05pr ); gl05pr.vertexAttribPointer( vertexColorAttribute05pr, 3, gl05pr.FLOAT, false, 0, 0); //Make draw. //TYPE OF PRIMITIVE; FIRST POINT; NUM POINTS AFTER FIRST. //gl05pr.drawArrays( gl05pr.TRIANGLES, 0, 12); gl05pr.bindBuffer( gl05pr.ELEMENT_ARRAY_BUFFER, indexBuffer05pr ); //drawElements (mode, count, type, offset) - Called on a VBO of indices (of type ELEMENT_ARRAY_BUFFER ). gl05pr.drawElements( gl05pr.TRIANGLES, indexBuffer05pr.num_pts, gl05pr.UNSIGNED_SHORT, 0); }
При отключении теста глубины бывает сложно понять, что происходит:
В общем-то, только последняя строка функции drawScene05pr() рисует фигуру по буферу индексов. Для повышения производительности, можно вынести процедуру занесения данных в шейдеры в отдельную функцию и вызвать ее один раз до основного цикла, сразу после setupBuffers05pr (). Тогда коды функций будут такими:
function MoveStaticDataInShader05pr() { //Move data into shader. vertexPositionAttribute05pr = gl05pr.getAttribLocation( glProgram05pr, "aVertexPosition05pr" ); gl05pr.enableVertexAttribArray( vertexPositionAttribute05pr ); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesVerticeBuffer05pr ); gl05pr.vertexAttribPointer( vertexPositionAttribute05pr, 3, gl05pr.FLOAT, false, 0, 0); vertexColorAttribute05pr = gl05pr.getAttribLocation( glProgram05pr, "aVertexColor05pr" ); gl05pr.enableVertexAttribArray( vertexColorAttribute05pr ); gl05pr.bindBuffer( gl05pr.ARRAY_BUFFER, trianglesColorBuffer05pr ); gl05pr.vertexAttribPointer( vertexColorAttribute05pr, 3, gl05pr.FLOAT, false, 0, 0); gl05pr.bindBuffer( gl05pr.ELEMENT_ARRAY_BUFFER, indexBuffer05pr ); } //[4] function drawScene05pr() { //drawElements (mode, count, type, offset) - Called on a VBO of indices (of type ELEMENT_ARRAY_BUFFER ). gl05pr.drawElements( gl05pr.TRIANGLES, indexBuffer05pr.num_pts, gl05pr.UNSIGNED_SHORT, 0); }
Итак, к концу шага 05 мы создали программу, похожую на показанную на шаге 01. Остались только штрихи – разместить фигуру в центре координат и сместить точки Т.1 и Т.7, и вращать не только относительно оси Y. Развлекаемся как захотим, в общем.
Постойте, мы ведь реально создали с нуля подвижный объект в 3D мире! Это прорыв наших прошлых знаний и возможностей!
Что дальше? Вскоре мы научимся накладывать текстуры и управлять освещением. А ещё, немного позже – управлять тенями, улучшать производительность, интерактивно управлять объектами.
Удобно что есть оси координат и поэтому видно где находится фигура. Только в приведенной программе нет рисования осей, а у меня не получается ( Жду следующий шаг!