Множественное текстурирование

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

При построении трехмерных сцен мы вряд ли будем ограничиваться одним объектом и одной текстурой. Возникает вопрос, как совместить в проекте использование сразу нескольких текстур для разных объектов? Один из способов состоит в последовательной отрисовке объектов и использовании текстур.

Расширим пример из предыдущих параграфов так, чтобы он использовал несколько объектов и текстур: два объекта, точнее визуально три - две кирпичных колонны и каменная стена над ними. Веб-страничка полностью будет выглядеть так:

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

<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;
attribute vec2 aVertexTextureCoords;
varying vec2 vTextureCoords;
uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;
  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
	vTextureCoords = aVertexTextureCoords;
	
  }
</script>

<script type="text/javascript">
var gl;
var shaderProgram;
var wallVertexBuffer;
var wallIndexBuffer;
var roofVertexBuffer;
var roofIndexBuffer;
var wallTextureCoordsBuffer;
var roofTextureCoordsBuffer;

var wallTexture; // переменная для хранения текстуры кирпичной стены
var roofTexture; // переменная для хранения текстуры каменной крыши
var angle = 2.0; //угол вращения в радианах
var zTranslation = -2.0; // смещение по оси Z

var mvMatrix = mat4.create(); 
var pMatrix = mat4.create();

// установка шейдеров
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);
	
	shaderProgram.vertexTextureAttribute = gl.getAttribLocation(shaderProgram, "aVertexTextureCoords");
	gl.enableVertexAttribArray(shaderProgram.vertexTextureAttribute);
	
	shaderProgram.MVMatrix = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    shaderProgram.ProjMatrix = gl.getUniformLocation(shaderProgram, "uPMatrix");
}
function setMatrixUniforms(){
	gl.uniformMatrix4fv(shaderProgram.ProjMatrix,false, pMatrix);
	gl.uniformMatrix4fv(shaderProgram.MVMatrix, false, mvMatrix);  
} 
// Функция создания шейдера
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 initWallBuffers() {

	var	vertices =[
				// первая колонна
				// лицевая часть
				-0.8, -0.5, 0.5,
				-0.8, 0.5, 0.5,
				 -0.4, 0.5, 0.5,
				 -0.4, -0.5, 0.5,
				// задняя часть	
				-0.8, -0.5, 0.0,
				-0.8, 0.5, 0.0,
				 -0.4, 0.5, 0.0,
				 -0.4, -0.5, 0.0,
				 
				 // левая боковая часть
				 -0.8, -0.5, 0.5,
				-0.8, 0.5, 0.5,
				-0.8, 0.5, 0.0,
				-0.8, -0.5, 0.0,
				
				// правая боковая часть
				-0.4, -0.5, 0.5,
				-0.4, 0.5, 0.5,
				-0.4, 0.5, 0.0,
				-0.4, -0.5, 0.0,
				
				// вторая колонна
				// лицевая часть
				0.8, -0.5, 0.5,
				0.8, 0.5, 0.5,
				 0.4, 0.5, 0.5,
				 0.4, -0.5, 0.5,
				// задняя часть	
				0.8, -0.5, 0.0,
				0.8, 0.5, 0.0,
				0.4, 0.5, 0.0,
				0.4, -0.5, 0.0,
				 
				 // левая боковая часть
				0.8, -0.5, 0.5,
				0.8, 0.5, 0.5,
				0.8, 0.5, 0.0,
				0.8, -0.5, 0.0,
				
				// правая боковая часть
				0.4, -0.5, 0.5,
				0.4, 0.5, 0.5,
				0.4, 0.5, 0.0,
				0.4, -0.5, 0.0
				 ];
				 
    var indices = [ // лицевая часть
				0, 1, 2, 
				2, 3, 0,
				// задняя часть
				4, 5, 6,
				6, 7, 4,
				//левая боковая часть
				8, 9, 10, 
				10, 11, 8,
				// правая боковая часть
				12, 13, 14, 
				14, 15, 12,
				// вторая колонна
				// лицевая часть
				16, 17, 18, 
				18, 19, 16,
				// задняя часть
				20, 21, 22,
				22, 23, 20,
				//левая боковая часть
				24, 25, 26, 
				26, 27, 24,
				// правая боковая часть
				28, 29, 30, 
				30, 31, 28
			];

	wallVertexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, wallVertexBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
	wallVertexBuffer.itemSize = 3;

	wallIndexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, wallIndexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
	wallIndexBuffer.numberOfItems = indices.length;	
  
  // Координаты текстуры
  var textureCoords = [];
	for (var i=0; i<8; i++) {
		textureCoords.push(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0);
	}
	// Создание буфера координат текстуры
	wallTextureCoordsBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, wallTextureCoordsBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
	wallTextureCoordsBuffer.itemSize=2;
}

function initRoofBuffers() {

	var	vertices =[
				// лицевая часть
				-1.0, 0.5, 0.5,
				-1.0, 0.8, 0.5,
				 1.0, 0.8, 0.5,
				 1.0, 0.5, 0.5,
				// задняя часть
				-1.0, 0.5, 0.0,
				-1.0, 0.8, 0.0,
				 1.0, 0.8, 0.0,
				 1.0, 0.5, 0.0,
				 // левая боковая часть
				-1.0, 0.5, 0.5,
				-1.0, 0.8, 0.5,
				-1.0, 0.8, 0.0,
				-1.0, 0.5, 0.0,
				// правая боковая часть
				1.0, 0.5, 0.5,
				1.0, 0.8, 0.5,
				1.0, 0.8, 0.0,
				1.0, 0.5, 0.0,
				// низ
				-1.0, 0.5, 0.5,
				-1.0, 0.5, 0.0,
				1.0, 0.5, 0.0,
				1.0, 0.5, 0.5,
				// верх
				-1.0, 0.8, 0.5,
				-1.0, 0.8, 0.0,
				1.0, 0.8, 0.0,
				1.0, 0.8, 0.5,
		];
				 
    var indices = [
				0, 1, 2, 
				2, 3, 0,
				// задняя часть
				4, 5, 6,
				6, 7, 4,
				//левая боковая часть
				8, 9, 10, 
				10, 11, 8,
				// правая боковая часть
				12, 13, 14, 
				14, 15, 12,
				// низ
				16, 17, 18, 
				18, 19, 16,
				// верх
				20, 21, 22, 
				22, 23, 20
			];

  roofVertexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, roofVertexBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  roofVertexBuffer.itemSize = 3;

	roofIndexBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, roofIndexBuffer);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
	roofIndexBuffer.numberOfItems = indices.length;	
  
  // Координаты текстуры
  var textureCoords = [];
	for (var i=0; i<6; i++) {
		textureCoords.push(0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0);
	}
	// Создание буфера координат текстуры
	roofTextureCoordsBuffer = gl.createBuffer();
	gl.bindBuffer(gl.ARRAY_BUFFER, roofTextureCoordsBuffer);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
	roofTextureCoordsBuffer.itemSize=2;
}
 
function wallDraw() {    

	gl.bindBuffer(gl.ARRAY_BUFFER, wallVertexBuffer);
	gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 
                         wallVertexBuffer.itemSize, gl.FLOAT, false, 0, 0);
	

	gl.bindBuffer(gl.ARRAY_BUFFER, wallTextureCoordsBuffer);
	gl.vertexAttribPointer(shaderProgram.vertexTextureAttribute,
                         wallTextureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
	gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, wallTexture);
	gl.enable(gl.DEPTH_TEST);
	gl.drawElements(gl.TRIANGLES, wallIndexBuffer.numberOfItems, gl.UNSIGNED_SHORT,0);
}
function roofDraw() {    

	gl.bindBuffer(gl.ARRAY_BUFFER, roofVertexBuffer);
	gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 
                         roofVertexBuffer.itemSize, gl.FLOAT, false, 0, 0);

	gl.bindBuffer(gl.ARRAY_BUFFER, roofTextureCoordsBuffer);
	gl.vertexAttribPointer(shaderProgram.vertexTextureAttribute,
                         roofTextureCoordsBuffer.itemSize, gl.FLOAT, false, 0, 0);
	
	gl.activeTexture(gl.TEXTURE0);
	
	gl.bindTexture(gl.TEXTURE_2D, roofTexture);
	gl.enable(gl.DEPTH_TEST);
	gl.drawElements(gl.TRIANGLES, roofIndexBuffer.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);
	mat4.perspective(pMatrix, 1.04, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0);
	mat4.identity(mvMatrix);
	mat4.translate(mvMatrix,mvMatrix,[0, 0, zTranslation]);
	mat4.rotate(mvMatrix,mvMatrix, angle, [0, 1, 0]);	
}
 function setupTextures() {
	wallTexture = gl.createTexture();
	setTexture("brickwall.png", wallTexture);
	
	roofTexture = gl.createTexture();
	setTexture("stone.jpg", roofTexture);
	
 }
function setTexture(url, texture){

	gl.bindTexture(gl.TEXTURE_2D, texture);
	var image = new Image();
	image.onload = function() {
	
		handleTextureLoaded(image, texture);	
  }
  
   image.src = url;

	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.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	gl.bindTexture(gl.TEXTURE_2D, null);
}
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){
		document.addEventListener('keydown', handleKeyDown, false);
		gl.viewportWidth = canvas.width;
		gl.viewportHeight = canvas.height;

		initShaders();	
		initRoofBuffers();
		initWallBuffers();
		setupTextures();
		(function animloop(){
			
			setupWebGL();
			setMatrixUniforms();
			wallDraw();
			roofDraw();
			requestAnimFrame(animloop, canvas);
		})();
	}
}
function handleKeyDown(e){
	switch(e.keyCode)
	{
		case 39:  
			angle+=0.1;
			break;
		case 37: 
			angle-=0.1;
			break;
		case 40: 
			zTranslation+=0.1;
			break;
		case 38: 
			zTranslation-=0.1;
			break;
	}
}
window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     ||
         function(callback, element) {
           return window.setTimeout(callback, 1000/60);
         };

})();
</script>
</body>
</html>

Визуально это будет выглядеть так:

Здесь я не использовал ничего нового, просто определил для двух объектов свой набор буферов вершин, индексов и координат текстуры. Переменная wallTexture будет хранить в себе текстуру кирпичной стены, а переменная roofTexture - текстуру каменной стены.

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

Также нам надо два раза инициализировать текстуры и загрузить в них изображения. Это делается в функции setupTextures():

function setupTextures() {
	wallTexture = gl.createTexture();
	setTexture("brickwall.png", wallTexture);
	
	roofTexture = gl.createTexture();
	setTexture("stone.jpg", roofTexture);	
 }

После срабатывания этой функции мы сможем использовать текстуры. Также для самой отрисовки каждого объекта создаем две функции: wallDraw() (отрисовка кирпичной стены) и roofDraw() (отрисовка каменной крыши).

А в главной функции последовательно вызываем настройку буферов объектов и их отрисовку:

//..........................
		initShaders();	
		initRoofBuffers();
		initWallBuffers();
		setupTextures();
		(function animloop(){
			
			setupWebGL();
			setMatrixUniforms();
			wallDraw();
			roofDraw();
			requestAnimFrame(animloop, canvas);
		})();
//......................................
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850