Строим 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]);
Текстура «вырезалась» по координатам текстуры следующим образом:

Ну и наконец, когда нам уже не надо изучать структуру текселей, применим реальную текстуру:

Шикарно, теперь мы можем создавать более реалистичные объекты! И для продвижения по пути увеличения реализма мы вскоре изучим освещение объектов. А когда-нибудь изучим и тени. Ну а пока, на следующем шаге, еще немного поиграем с текстурами, а также введем немного интерактивности в программу, изучим загрузку шейдеров из отдельных файлов.
да, карты текселей очень помогли в понимании структуры. но чтобы нарисовать модель как на первом рисунке придется попотеть!