Строим 3D проекты – Шаг 7
Переходим от 2D к 3D фигурам в деле наложения текстур. В программе 5-го шага, мы нарисовали простейшую фигуру 3D. Мы закрашивали ее разными цветами и вращали вокруг нее камеру:
Теперь разберемся, как закрасить эту фигуру текстурой из графического файла.
Пример на основе простейшей 3D фигуры
1. Выбираем файл с изображением, которое мы наложим на наш объект-треугольник в качестве текстуры. Например, как и в предыдущем шаге, используем файл размером 128×128 пикселей:
Изображение в этом файле не похоже на типичную текстуру, но по нему хорошо будет видна структура текселей на объекте.
2. Заводим глобальные переменные для хранения изображения и текстуры, а также для буфера координат текстуры и для связи этого буфера с шейдером:
var textureImage07pr = null; var texture07pr = null; var trianglesTexCoordBuffer07pr = null; var vertexTexCoordAttribute07pr = null;
В отличие от предыдущего шага, мы создадим для координат текстуры свой буфер.
3. Дополним вершинный и пиксельный шейдеры для работы с текстурой. Все, что косалось работы с цветом, пока удалим. В вершинный шейдер передаем координаты текстур:
<script id="shader-vs-07pr" type="x-shader/x-vertex"> attribute vec3 aVertexPosition07pr; attribute vec2 aVertexTexCoord07pr; uniform mat4 uMVMatrix07pr; uniform mat4 uPMatrix07pr; varying highp vec2 vTextureCoord07pr; void main(void) { gl_Position = uPMatrix07pr * uMVMatrix07pr * vec4( aVertexPosition07pr, 1.0); vTextureCoord07pr = aVertexTexCoord07pr; } </script>
В пиксельном шейдере принимаем эти координаты текстур и отправляем в функцию texture2D:
<script id="shader-fs-07pr" type= "x-shader/x-fragment"> varying highp vec2 vTextureCoord07pr; uniform sampler2D uSampler07pr; void main(void) { gl_FragColor = texture2D( uSampler07pr, vec2( vTextureCoord07pr.s, vTextureCoord07pr.t) ); } </script>
4. В главный алгоритм программы добавляем функцию loadTexture () для загрузки текстуры из файла:
if(gl07pr) { initShaders07pr(); //[1] setupBuffers07pr(); //[2] MoveStaticDataInShader07pr(); getMatrixUniforms07pr(); loadTexture07pr(); (function animLoop07pr() { setupWebGL07pr(); //[3] setMatrixUniforms07pr(); drawScene07pr(); //[4] requestAnimationFrame( animLoop07pr, canvas07pr); })(); }
5. Добавляем описанную выше функцию loadTexture07pr(). В ней добавляем обработчик события textureImage07.onload, который в случае успешной загрузки изображения запускает функцию настройки текстуры:
function loadTexture07pr() { textureImage07pr = new Image(); textureImage07pr.onload = function() { setupTexture07pr(); } textureImage07pr.src = "textures/face-128x128.png"; }
Функция setupTexture07pr () точно такая же, как для 2D на предыдущем шаге. По сути, мы будем накладывать текстуру как и раньше на плоскости, просто часть из плоскостей будет направлена под углом к плоскости XY.
function setupTexture07pr() { texture07pr = gl07pr.createTexture(); gl07pr.bindTexture( gl07pr.TEXTURE_2D, texture07pr); if( !gl07pr.isTexture( texture07pr) ) console.error("Error: Texture is invalid"); else { gl07pr.pixelStorei( gl07pr.UNPACK_FLIP_Y_WEBGL, true); gl07pr.texParameteri( gl07pr.TEXTURE_2D, gl07pr.TEXTURE_MAG_FILTER, gl07pr.NEAREST); gl07pr.texParameteri( gl07pr.TEXTURE_2D, gl07pr.TEXTURE_MIN_FILTER, gl07pr.NEAREST); gl07pr.texImage2D( gl07pr.TEXTURE_2D, 0, gl07pr.RGBA, gl07pr.RGBA, gl07pr.UNSIGNED_BYTE, textureImage07pr); glProgram07pr.samplerUniform = gl07pr.getUniformLocation( glProgram07pr, "uSampler07pr"); gl07pr.uniform1i (glProgram07pr.samplerUniform, 0); } }
6. Создадим в функции setupBuffers07pr () буфер для координат текстуры по аналогии с другими буферами. Координаты текстуры зададим согласно координатам XY вершин:
var triangleTexCoords = []; //Texture coordinates. //12 vertices (s,t) = 24 coords. triangleTexCoords = [ //front face 0.0, 0.0, //0th vertice 1.0, 0.0, //1st vertice 2.0, 0.0, //2nd vertice 0.5, 1.0, //3rd vertice 1.5, 1.0, //4th vertice 1.0, 2.0, //5th vertice //rear face 0.0, 0.0, //6th vertice 1.0, 0.0, //7th vertice 2.0, 0.0, //8th vertice 0.5, 1.0, //9th vertice 1.5, 1.0, //10th vertice 1.0, 2.0, //11th vertice ]; trianglesTexCoordBuffer07pr = gl07pr.createBuffer(); gl07pr.bindBuffer( gl07pr.ARRAY_BUFFER, trianglesTexCoordBuffer07pr); gl07pr.bufferData( gl07pr.ARRAY_BUFFER, new Float32Array( triangleTexCoords), gl07pr.STATIC_DRAW);
7. И по аналогии с другими буферами, передадим буфер координат текстуры в шейдер:
function MoveStaticDataInShader07pr() { … //Texture vertexTexCoordAttribute07pr = gl07pr.getAttribLocation( glProgram07pr, "aVertexTexCoord07pr"); gl07pr.enableVertexAttribArray( vertexTexCoordAttribute07pr); gl07pr.bindBuffer( gl07pr.ARRAY_BUFFER, trianglesTexCoordBuffer07pr); gl07pr.vertexAttribPointer( vertexTexCoordAttribute07pr, 2, gl07pr.FLOAT, false, 0, 0); …
Результат программы – наложение текстуры на объект:
На рисунке показано, что помимо результата программы (зеленый треугольник), я попробовал сместить все координаты на 0,5 (синий треугольник), чтобы проверить, что все адекватно работает:
var size_texture_array = triangleTexCoords.length; for(var i=0; i < size_texture_array; i++) triangleTexCoords[i] += 0.5;
В обоих случаях, текстура нормально отобразилась только на передней и задней гранях. Так произошло, потому что боковые и нижняя грани лежат в плоскостях ZY и ZX. А координаты тектуры, созданные по плоскости XY, на них получаются прямыми линиями.
Например, левая сторона в буфере индексов состоит из 4 треугольников:
//left side 0,3,6, //tr9 3,6,9, //tr10 3,5,9, //tr11 5,9,11, //tr12
Текстурные координаты для первого треугольника:
//front face
0.0, 0.0, //0th vertice
1.0, 0.0, //1st vertice
2.0, 0.0, //2nd vertice
0.5, 1.0, //3rd vertice
1.5, 1.0, //4th vertice
1.0, 2.0, //5th vertice
//rear face
0.0, 0.0, //6th vertice
1.0, 0.0, //7th vertice
2.0, 0.0, //8th vertice
0.5, 1.0, //9th vertice
1.5, 1.0, //10th vertice
1.0, 2.0, //11th vertice
То есть, получаем прямую линию, а не треугольник, с координатами точек:
0.0, 0.0,
0.5, 1.0,
0.0, 0.0
Получается, что текстурные координаты для плоскостей ZY и ZX должны быть другими.
Нарисуем схему задания буфера индексов по развертке призмы. Нижнюю грань разобьем на 4 треугольника, также как боковые грани:
Напомню, мы задаем буфер вершин, а затем создаем буфер индексов, чтобы легче обращаться с вершинами (вместо трех координат, нам достаточно номера вершины). Порядок формирования фигуры после этого такой:
Необходимо поменять порядок формирования фигуры на такой:
Программа будет следующей:
function setupBuffers07pr() { var triangleVerticesOriginal = []; var VertexIndices = []; var triangleVertices = []; var triangleTexCoords = []; //Vertices. triangleVerticesOriginal = [ //Prism's points coodinates-----------------------------. //12 vertices (x,y,z) = 3*12 coords. //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 ]; //Index buffer. //20 triangles = 3*20 vertices (x,y,z) = 3*60 coords. VertexIndices = [ //front face 0,1,3, //tr1 1,3,4, //tr2 1,2,4, //tr3 3,4,5, //tr4 //rear face 6,7,9, //tr5 7,9,10, //tr6 7,8,10, //tr7 9,10,11, //tr8 //left side 0,3,6, //tr9 3,6,9, //tr10 3,5,9, //tr11 5,9,11, //tr12 //right side 2,4,8, //tr13 4,8,10, //tr14 4,5,10, //tr15 5,10,11, //tr16 //bottom side 0,1,6, //tr17 1,6,7, //tr18 1,2,7, //tr19 2,7,8, //tr20 ]; //60 vertices = 3*60 vertice coords, 2*60 texture coords for(var i=0; i<VertexIndices.length; ++i) //0 to 60 { var a = VertexIndices[i]; //Vertices. triangleVertices.push( triangleVerticesOriginal[a*3]); //X triangleVertices.push( triangleVerticesOriginal[a*3 + 1]); //Y triangleVertices.push( triangleVerticesOriginal[a*3 + 2]); //Z //Textures. if(i <= 24) { //Front and rear faces. Take X, Y. triangleTexCoords.push( triangleVerticesOriginal[a*3]); //X triangleTexCoords.push( triangleVerticesOriginal[a*3 + 1]); //Y } else if (24 < i && i <= 48) { //Left, right sides. Take Z, Y. //For vertical orientation: triangleTexCoords.push( triangleVerticesOriginal[a*3 + 2]); //Z triangleTexCoords.push( triangleVerticesOriginal[a*3 + 1]); //Y //For horizontal orientation: //triangleTexCoords.push( triangleVerticesOriginal[a*3 + 1]); //Y //triangleTexCoords.push( triangleVerticesOriginal[a*3 + 2]); //Z } else { //Bottom side. Take X, Z. //For vertical orientation: triangleTexCoords.push( triangleVerticesOriginal[a*3]); //X triangleTexCoords.push( triangleVerticesOriginal[a*3 + 2]); //Z //For horizontal orientation: //triangleTexCoords.push( triangleVerticesOriginal[a*3 + 2]); //Z //triangleTexCoords.push( triangleVerticesOriginal[a*3]); //X } } //Vertice trianglesVerticeBuffer07pr = gl07pr.createBuffer(); gl07pr.bindBuffer( gl07pr.ARRAY_BUFFER, trianglesVerticeBuffer07pr); gl07pr.bufferData( gl07pr.ARRAY_BUFFER, new Float32Array( triangleVertices), gl07pr.STATIC_DRAW); //Texture trianglesTexCoordBuffer07pr = gl07pr.createBuffer(); gl07pr.bindBuffer( gl07pr.ARRAY_BUFFER, trianglesTexCoordBuffer07pr); gl07pr.bufferData( gl07pr.ARRAY_BUFFER, new Float32Array( triangleTexCoords), gl07pr.STATIC_DRAW); //Will be used in drawScene07pr(). trianglesVerticeBuffer07pr.num_pts = triangleVertices.length; } function drawScene07pr() { gl07pr.drawArrays( gl07pr.TRIANGLES, 0, trianglesVerticeBuffer07pr.num_pts /3.0); }
Запускаем программу:
Чтобы рассмотреть нижнюю грань, можно повернуть вращение по оси Y:
mat4.rotate( mvMatrix07pr, angle07pr, [1.0, 0.0, 0.0]);
Текстура «вырезалась» по координатам текстуры следующим образом:
Ну и наконец, когда нам уже не надо изучать структуру текселей, применим реальную текстуру:
Шикарно, теперь мы можем создавать более реалистичные объекты! И для продвижения по пути увеличения реализма мы вскоре изучим освещение объектов. А когда-нибудь изучим и тени. Ну а пока, на следующем шаге, еще немного поиграем с текстурами, а также введем немного интерактивности в программу, изучим загрузку шейдеров из отдельных файлов.
да, карты текселей очень помогли в понимании структуры. но чтобы нарисовать модель как на первом рисунке придется попотеть!