Подумать только

logoas CogITas

Dubito - Ergo - Sum

× Error from canvas.getContext(): Maybe your browser or hardware (GPU) does not appear to support WebGL. The Canvas for WebGL below will be empty.
Mouse и Num Lock

Кроем крышу черепицей. Недорого. :)

Строим 3D проекты – Шаг 7

Детализация текстур
Детализация текстур

 

   Переходим от 2D к 3D фигурам в деле наложения текстур. В программе 5-го шага, мы нарисовали простейшую фигуру 3D. Мы закрашивали ее разными цветами и вращали вокруг нее камеру:

3D объект с наложением цвета
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);

   …

   Результат программы – наложение текстуры на объект:

Частично текстурированная  3D фигура
Частично текстурированная 3D фигура

 

   На рисунке показано, что помимо результата программы (зеленый треугольник), я попробовал сместить все координаты на 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 треугольника, также как боковые грани:

Структура разбиения фигуры на треугольники
Структура детализации фигуры треугольниками

 

Развертка фигуры и порядок индексов вершин в треугольниках

Развертка фигуры и порядок индексов вершин в треугольниках

 

   Напомню, мы задаем буфер вершин, а затем создаем буфер индексов, чтобы легче обращаться с вершинами (вместо трех координат, нам достаточно номера вершины). Порядок формирования фигуры после этого такой:

  • задаем буфер координат текстуры
  • рисуем фигуру по буферу индексов (с помощью drawElements).

Необходимо поменять порядок формирования фигуры на такой:

  • генерируем с помощью буферов вершин и индексов новый буфер вершин (в котором будут последовательно записаны каждая вершина каждого из 20 треугольников), и одновременно формируем буфер координат текстуры. При этом буфер координат текстуры формируем с учетом текущей стороны призмы.
  • рисуем фигуру по новому буферу вершин (с помощью drawArrays).

   Программа будет следующей:

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);
}
   Команда вида array_name.push (varA) добавляет элемент varA в конец массива array_name.

   Запускаем программу:

Полностью текстурированная фигура
Полностью текстурированная фигура

 

   Чтобы рассмотреть нижнюю грань, можно повернуть вращение по оси Y:

mat4.rotate( mvMatrix07pr, angle07pr, [1.0, 0.0, 0.0]);

   Текстура «вырезалась» по координатам текстуры следующим образом:

Карта текселей
Карта текселей

 

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

Текстура черепицы
Текстура черепицы

 

Error from Html: Your browser does not support the HTML5 canvas element.
 

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

<< Предыдущий шаг

Следующий шаг >>

Все статьи


Комментарии к “Кроем крышу черепицей. Недорого. :)”

  • Ziga говорит:

    да, карты текселей очень помогли в понимании структуры. но чтобы нарисовать модель как на первом рисунке придется попотеть!

    Ответить
    • Андрей Свирский говорит:

      Ad primos ictus non corruit ardua quercus.

      С первого удара не падает высокий дуб.

      Ответить
  • Оставить комментарий для Андрей Свирский Отменить

    Ваш email не будет отображаться. Обязательные поля помечены *

    (Чтобы установить аватар, необходимо зарегистрировать свой e-mail на gravatar.com. Как это сделать, написано в статье http://cogitas.ru/robots-avatar-icon-wordpress.html)