Текстурирование

Введение в текстурирование

Последнее обновление: 1.11.2015

Ранее мы познакомились с окраской трехмерных предметов. Теперь мы можем сделать их красивыми, цветистыми. Однако при имитации объектов реального мира сложно будет подобрать цвета и произвести раскраску таким образом, чтобы объект выглядел так же, как и в реальности. Гораздо проще воспользоваться текстурированием - то есть нанести на поверхность объекта какие-то изображения, которые помогут имитировать реальность.

Для начала рассмотрим процесс текстурирования на примере двухмерного объекта - прямоугольника. Я сразу приведу полный код страницы:

<!DOCTYPE html>
<html>
<head>
<title>3D in WebGL!</title>
<meta charset="utf-8" />
</head>
<body>
<canvas id="canvas3D" width="400" height="300">Ваш браузер не поддерживает элемент canvas</canvas>

<script id="shader-fs" type="x-shader/x-fragment">
precision highp float;
uniform sampler2D uSampler;
varying vec2 vTextureCoords;

  void main(void) {
    gl_FragColor = texture2D(uSampler, vTextureCoords);
  }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
varying vec2 vTextureCoords;

  void main(void) {
    gl_Position = vec4(aVertexPosition, 1.0);
	vTextureCoords = vec2(aVertexPosition.x+0.5,aVertexPosition.y+0.5);
  }
</script>

<script type="text/javascript">
var gl;
var shaderProgram;
var vertexBuffer;
var indexBuffer;

var texture; // переменная для хранения текстуры

function initShaders() {
	var fragmentShader = getShader(gl.FRAGMENT_SHADER, 'shader-fs');
	var vertexShader = getShader(gl.VERTEX_SHADER, 'shader-vs');

	shaderProgram = gl.createProgram();

	gl.attachShader(shaderProgram, vertexShader);
	gl.attachShader(shaderProgram, fragmentShader);

	gl.linkProgram(shaderProgram);
	 
	if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
		alert("Не удалось установить шейдеры");
	}
	 
	gl.useProgram(shaderProgram);

	shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
	gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
}

// Функция создания шейдера
function getShader(type,id) {
	var source = document.getElementById(id).innerHTML;

	var shader = gl.createShader(type);
	
	gl.shaderSource(shader, source);

	gl.compileShader(shader);
  
	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
		alert("Ошибка компиляции шейдера: " + gl.getShaderInfoLog(shader));
		gl.deleteShader(shader);   
		return null;
	}
	return shader;  
}

function initBuffers() {

	var	vertices =[
				-0.5, -0.5, 0.5,
				-0.5, 0.5, 0.5,
				 0.5, 0.5, 0.5,
				 0.5, -0.5, 0.5
				 ];

  vertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  vertexBuffer.itemSize = 3;
 
	var indices = [0, 1, 2, 2, 3, 0];
	indexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
	indexBuffer.numberOfItems = indices.length;	
}
 
function draw() {    

	gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
	gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 
                         vertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
	
	gl.drawElements(gl.TRIANGLES, indexBuffer.numberOfItems, gl.UNSIGNED_SHORT,0);
	
}
function setupWebGL()
{
	gl.clearColor(0.0, 0.0, 0.0, 1.0); 	
	gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT); 	
				
	gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
}
 
function setTextures(){
	texture = gl.createTexture();
	gl.bindTexture(gl.TEXTURE_2D, texture);
	 var image = new Image();
 
	image.onload = function() {
	
		handleTextureLoaded(image, texture);
		
		setupWebGL();
		draw(); 
	}
	image.src = "brickwall.png";
	
	shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
	gl.uniform1i(shaderProgram.samplerUniform, 0);
}
 function handleTextureLoaded(image, texture) {

	gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
}
window.onload=function(){

	var canvas = document.getElementById("canvas3D");
	try {
		gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
	}
	catch(e) {}
  
	  if (!gl) {
		alert("Ваш браузер не поддерживает WebGL");
	  }
	if(gl){
		gl.viewportWidth = canvas.width;
		gl.viewportHeight = canvas.height;

		initShaders();
		initBuffers();
		
		setTextures();	
	}
}
</script>
</body>
</html>

В данном случае я использовал в качестве текстуры изображение кирпичной стены, поэтому я получил следующий результат:

Текстурирование в WebGL

Теперь разберем узловые моменты. Первым делом нам нужно где-то держать в памяти загруженную структуру. Для этого создается глобальная переменная var texture;.

Большая часть программы должна быть вам знакома по предыдущим примерам. Основное отличие от предыдущих примеров - функция setTextures(). Эта функция затем будет вызываться в главной функции вместо метода отрисовки draw(). Поэтому остановимся на функции setTextures().

Для начала мы создаем текстуру: texture = gl.createTexture();

Загрузка текстуры

Для установки изображения в качестве текстуры нам нужен элемент img. Изображение, установленное для данного элемента, и будет устанавливаться в качестве текстуры.

Тут есть два способа: мы можем заранее определить в структуре DOM веб-страницы элемент img и с помощью его атрибута src установить какое-либо изображение. Либо мы можем динамически в коде javascript создать данный элемент, как продемонстрировано в данном примере.

Поскольку текстура не сразу загружается, то используем обработку события onload:

image.onload = function() {
	
	handleTextureLoaded(image, texture);
		
	setupWebGL();
	draw(); 
}
image.src = "brickwall.png";

Также мы устанавливаем некоторую картинку. В моем случае это изображение 128х128 brickwall.png. Сама установка текстуры происходит в функции handleTextureLoaded(image, texture);. И далее, когда все настройки текстурирования сделаны, происходит отрисовка.

Функция handleTextureLoaded() выполняет важную роль по настройке всех параметров текстурирования.

 function handleTextureLoaded(image, texture) {

	gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
}

Прежде чем перейти к использованию текстуры, ее надо связать с объектом текстуры texture: gl.bindTexture(gl.TEXTURE_2D, texture);. Этот метод действует аналогично вызову gl.bindBuffer() при связывании буфера вершин.

Используемый далее метод gl.pixelStorei() указывает далее идущему методу gl.texImage2D(), как текстура должна позиционироваться. Так, в данном случае мы передаем в качестве параметра значение gl.UNPACK_FLIP_Y_WEBGL - этот параметр указывает методу gl.texImage2D(), что изображение надо перевернуть относительно горизонтальной оси. Для чего это надо?

Все дело в том, что стандартный объект Image, который в данном случае выбран в качестве источника для поставки текстуры, имеет другую систему координат:

Поэтому нам надо совместить две координатные системы, чтобы в будущем было проще работать с текстурой.

Затем можно уже загрузить в текстуру изображение. Это делается с помощью метода gl.texImage2D. Полностью параметры этого мы метода мы разберем далее, а пока, я думаю, понятно, что изображение из элемента image загружается в текстуру.

И метод gl.texParameteri выполняет настройку параметров текстурирования. Первый вызов этого метода устанавливает значение для параметра gl.TEXTURE_MAG_FILTER - тем самым мы определяем рендеринг текстуры, если она будет увеличена. Второй вызов метода gl.texParameteri, наоборот, определяет поведение рендеринг текстуры, если она будет уменьшена.

Использование текстуры в шейдере

Чтобы использовать текстуру в шейдере и там ее применить к объекту, нам остается в конце функции setTextures установить семплер:

shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
gl.uniform1i(shaderProgram.samplerUniform, 0);

Семплер будет использоваться для забора из текстуры текселей и совмещения их с пикселями объекта на экране. (Тексель представляет собой пиксель на текстуре.) В вершинном шейдере мы используем следующий код:

attribute vec3 aVertexPosition;
varying vec2 vTextureCoords;

  void main(void) {
    gl_Position = vec4(aVertexPosition, 1.0);
	vTextureCoords = vec2(aVertexPosition.x+0.5,aVertexPosition.y+0.5);
  }

Вектор vTextureCoords затем будет передан во фрагментный шейдер. Здесь же мы устанавливаем координаты: vTextureCoords = vec2(aVertexPosition.x+0.5,aVertexPosition.y+0.5);. Поскольку вершины реального объекта расположены в пределах от (-0.5, -0.5) до (0.5, 0.5) (так мы определили в нашем буфере вершин), то к координатам вершины, передаваемой через атрибут aVertexPosition, мы прибавляем 0.5. Таким образом. в последствии мы сможем совместить объект с текстурой, у которой все очки находятся в пределах от (0.0, 0.0) до (1.0, 1.0).

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

А во фрагментном шейдере приводится в действие семплер:

precision highp float;
uniform sampler2D uSampler;
varying vec2 vTextureCoords;

  void main(void) {
    gl_FragColor = texture2D(uSampler, vTextureCoords);
  }

С одной стороны, кажется, что все вместе, все этапы - так много всего надо делать, но на самом деле не особо то и много, и далее будет еще больше. Итак, суммируя все, можно определить следующие этапы текстурирования:

  1. Определение и создание объекта текстуры с помощью метода gl.createTexture().

  2. Создание элемента html, который будет выступать источником для текстуры, например, Image, и установка его атрибута src.

  3. Определение метода onload, который будет содержать логику, срабатывающую при загрузке изображения в элемент Image.

  4. Привязка текстуры с помощью метода gl.bindTexture().

  5. Переворачивание текстуры для совмещения ее с координатной системой объекта Image с помощью метода gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

  6. Загрузка текстуры в GPU с помощью метода gl.texImage2D().

  7. Установка параметров текстуры с помощью метода gl.texParameteri().

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850