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

logoas CogITas

Сомневаться - Трудиться - Любить

× 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 проекты – Шаг 8

Эволюция клавиатуры

Эволюция клавиатуры

 

   К настоящему моменту мы уже написали программу, в которой вращается текстурированная 3D фигура. Программа выполняется на браузере клиента, но загружается с сервера. Прежде чем идти дальше, внесем некоторые улучшения: управление с клавиатуры, смешение текcтур, подключение шейдеров из файлов по технологии AJAX.

 

Управление с помощью клавиатуры

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

   Для начала заведем переменные, хранящие состояние управляемых свойств:

var paused08pr = false;
var useTexture08pr = true;

   Переменная paused08pr выставлена в false, то есть, вращение изначально не на паузе.

   Переменная useTexture08pr выставлена в true, то есть, текстура изначально отображается.

   Мы планируем включать/отключать текстуру по нажатию клавиши ‘T’, а ставить на паузу вращение по клавише ‘Пробел’.   Чтобы отследить нажатие клавиш на клавиатуре, мы добавим обработчик событий. Запишем его для удобства в отдельный файл:

<script src="js/keyboard08pr.js"></script>

   Обработчик события addEventListener при нажатии клавиши вызовет нашу функцию myKeyupFunction08pr, в которой мы определим, какая клавиша нажата, и что делать:

document.addEventListener("keyup", myKeyupFunction08pr);

function myKeyupFunction08pr(evt)
{
    switch(evt.keyCode)
	{
		case 32: //'space'
				paused08pr = !paused08pr;
				break;		

		case 84: //'t'
				useTexture08pr =!useTexture08pr;
				if(useTexture08pr)
				{
					gl08pr.uniform1i (glProgram08pr.uDoTexturing08pr, 1);
				}
				else
				{
					gl08pr.uniform1i (glProgram08pr.uDoTexturing08pr, 0);
				}
				break;				

		default:
				break;
	}
}

   ASCII коды, например, 84 для клавиши T, можно узнать в интернете по запросу ‘ASCII таблица’.

ASCII коды и клавиши

ASCII коды и клавиши

 

   Или использовать простую программку здесь

   Можно было также сделать обработчик с помощью библиотеки jQuery, как показано ниже, но я не вижу преимуществ такого кода:
<script src=".../.../js_std/jquery.min.js"></script>
$(document).keyup (
                        function (evt)
                        {
                            switch (evt.keyCode)
                                               {
                                                                       case 32: //'space'
                                                                                              paused08pr = !paused08pr;                                                                                        
                                                                                              break;                                  
                                                                       case 84: //'t'                                                                                              
                                                                                              useTexture08pr =!useTexture08pr;   
                                                                                              if (useTexture08pr)
                                                                                              {
                                                                                                                      gl08pr.uniform1i (glProgram08pr.uDoTexturing08pr, 1);
                                                                                              }
                                                                                              else
                                                                                              {
                                                                                                                      gl08pr.uniform1i (glProgram08pr.uDoTexturing08pr, 0);
                                                                                              }                                                                                           
                                                                                              break;                                                                                 
                                                                       default:
                                                                                              break;   
                                               }                                                                                                                   
                        }
);

   С включение паузы всё просто – вставляем перед увеличением угла поворота условие:

if( !paused08pr )
		angle08pr += 0.005;

   С отключением текстуры сложнее. По коду видно, что для отключения мы посылаем 0 в шейдер в униформу uDoTexturing08pr. Добавим эту униформу в пиксельный шейдер:

<script id="shader-fs-08pr" type="x-shader/x-fragment">
       varying highp vec2 vTextureCoord08pr;
       uniform sampler2D uSampler08pr;

      uniform int uDoTexturing08pr;

	void main(void)
	{
		if(uDoTexturing == 1)
		{
			gl_FragColor = texture2D( uSampler08pr, vec2( vTextureCoord08pr.st ) );
		}
		else
		{
			gl_FragColor = vec4(1.0, 0.1, 0.1, 1.0);  //RED
		}
	}

</script>  

   В случае отключения текстуры, мы зальем фигуру красным цветом, но можно было бы и разными цветами, как на шаге 5.

   В конец функции loadTexture08pr добавляем поиск униформы и задаем ей начальное значение 1 (включена текстура):

//Connect to shader.------------------------
glProgram08pr.uDoTexturing08pr = gl08pr.getUniformLocation (glProgram08pr, "uDoTexturing08pr");
gl08pr.uniform1i ( glProgram08pr.uDoTexturing08pr, 1);

   Теперь становится понятно действие, которое мы задали выше для нажатия клавиши ‘T’.

   С отключенной текстурой фигура (залитая одним цветом) выглядит плоской:

Где тут что?

Где тут что?

 

Наложение нескольких текстур

   Если нам необходимо, чтобы на объекте были нанесены сразу текстуры из двух файлов, то придется загрузить текстуры в две отдельных переменные, а затем вывести их уже не только с помощью texture2D (), но и с помощью mix (). Для начала используем текстуру в виде надписи, чтобы было легко визуально отследить ее тексели и их направление:

Тестовая текстура с прозрачным фоном

Тестовая текстура с прозрачным фоном

 

   Данная техтура записана в файл формата png, который позволяет не хранить фон. За счет этого, при наложении на нижнюю текстуру, мы можем выставить полную прозрачность фона, и увидеть нижнюю текстуру.

   Готовим пиксельный шейдер:

<script id="shader-fs-08pr" type="x-shader/x-fragment">
   varying highp vec2 vTextureCoord08pr;
   uniform sampler2D uSampler08pr;
   uniform sampler2D uSampler208pr;

   uniform int uDoTexturing08pr;

   void main(void)
   {
	if(uDoTexturing08pr == 1)
	{
		//gl_FragColor = texture2D(uSampler08pr, vec2( vTextureCoord08pr.st ) );

		highp vec4 tileColor = texture2D( uSampler08pr, vec2( vTextureCoord08pr.st ));
		highp vec4 logoColor = texture2D( uSampler208pr, vec2( vTextureCoord08pr.st ));

		gl_FragColor = mix(tileColor, logoColor, logoColor.a);
	}
	else
	{
		gl_FragColor = vec4(1.0, 0.1, 0.1, 1.0);  //RED
	}
   }
</script>

   Мы завели вторую униформу типа sampler2D для второй текстуры.  В tileColor мы берем цвет пикселя по первой текстуре, а в logoColor – соответственно по второй текстуре. В mix () мы накладываем на пиксель первой текстуры, пиксель второй текстуры. Прозрачность второй текстуры выставляем logoColor.a, которая равна нулю в местах фона, как описано выше.

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

var textureImage08pr = [];
var texture08pr = [];
var TILE_TEXTURE08pr = 0;
var LOGO_TEXTURE08pr = 1;

Загружаем несколько текстур:

function loadTexture08pr()
{
	//0th texture----------------------------
	textureImage08pr [TILE_TEXTURE08pr] = new Image();
	textureImage08pr[ TILE_TEXTURE08pr ].onload = function()
	{
		setupTexture08pr( TILE_TEXTURE08pr );
		gl08pr.uniform1i( glProgram08pr.samplerUniform, TILE_TEXTURE08pr );
	}
	textureImage08pr[ TILE_TEXTURE08pr ].src = "textures/tiles-128x128.jpg";

	//1st texture-----------------------------
	textureImage08pr[ LOGO_TEXTURE08pr ] = new Image();
	textureImage08pr[ LOGO_TEXTURE08pr ].onload = function()
	{
		setupTexture08pr( LOGO_TEXTURE08pr );
		gl08pr.uniform1i( glProgram08pr.samplerUniform2, LOGO_TEXTURE08pr);
	}
	textureImage08pr[ LOGO_TEXTURE08pr].src = "textures/logo-512px.png";

	//Connect to shader.------------------------
	glProgram08pr.uDoTexturing08pr = gl08pr.getUniformLocation (glProgram08pr, "uDoTexturing08pr");
	gl08pr.uniform1i ( glProgram08pr.uDoTexturing08pr, 1);
}

function setupTexture08pr(i)
{
	gl08pr.activeTexture (gl08pr.TEXTURE0 + i);

	texture08pr[i] = gl08pr.createTexture();
	gl08pr.bindTexture (gl08pr.TEXTURE_2D, texture08pr[i]);

	if( !gl08pr.isTexture (texture08pr[i]) )
		console.error("Error: Texture is invalid");
	else
	{
		gl08pr.pixelStorei( gl08pr.UNPACK_FLIP_Y_WEBGL, true);
		gl08pr.texParameteri( gl08pr.TEXTURE_2D, gl08pr.TEXTURE_MAG_FILTER, gl08pr.NEAREST);
		gl08pr.texParameteri( gl08pr.TEXTURE_2D, gl08pr.TEXTURE_MIN_FILTER, gl08pr.NEAREST);

		gl08pr.texImage2D( gl08pr.TEXTURE_2D, 0, gl08pr.RGBA, gl08pr.RGBA, gl08pr.UNSIGNED_BYTE, textureImage08pr[i]);
	}
}
Строку с установкой активной текстуры:

 

                gl08pr.activeTexture (gl08pr.TEXTURE0 + i);

 

можно было записать и в более понятном виде:

 

                if (i == 0) gl08pr.activeTexture ( gl08pr.TEXTURE0 );
                else          gl08pr.activeTexture ( gl08pr.TEXTURE1 );

 

но так менее удобно, если текстур будет много.

 

   И не забываем найти униформы типа sampler2D:

function getMatrixUniforms08pr()
{
	glProgram08pr.mvMatrixUniform = gl08pr.getUniformLocation (glProgram08pr, "uMVMatrix08pr");
	glProgram08pr.pMatrixUniform  = gl08pr.getUniformLocation (glProgram08pr, "uPMatrix08pr");

	glProgram08pr.samplerUniform = gl08pr.getUniformLocation (glProgram08pr, "uSampler08pr");
	glProgram08pr.samplerUniform2 = gl08pr.getUniformLocation (glProgram08pr, "uSampler208pr");
}

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

Смешение двух текстур

Смешение двух текстур на разных гранях

 

   Не на всех гранях надписи нужного направления. Для исправления, нужно было бы брать вторую текстуру по отдельным координатам текстуры.

   Поэкспериментируем с параметрами смешения в mix (). Напомню, начальные параметры:

gl_FragColor = mix(tileColor, logoColor, logoColor.a);

Попробуем смешение выставить 50%:

gl_FragColor = mix(tileColor, logoColor, 0.5);

Смешение текстур 50 на 50

Смешение текстур 50 на 50

 

   Теперь попробуем поменять текстуры местами – разместить черепицу поверх надписи с данными о прозрачности из надписи:

gl_FragColor = mix(logoColor, tileColor, logoColor.a);

Смешение в другом порядке

Смешение в другом порядке

 

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

Карбофосный дождь

Карбофосный дождь

 

Загрузка шейдеров из отдельных файлов по AJAX

   Раз уж мы все равно загружаем программу с сервера, то нам разрешено загружать оттуда и файлы с шейдерами. Хранить шейдеры в отдельных файлах удобно – их легче сравнивать друг с другом, можно использовать один шейдер для нескольких программ. Запишем шейдеры в файлы shaders/01shader08pr.vs и shaders/02shader08pr.fs. Без шейдеров, код html становиться совсем небольшим:

<!doctype html>
<html>
<head>
   <title>WebGL 3D prism - texture</title>
   <script src="../../js_std/gl-matrix-min.js"></script>
    <script src="js/vars08pr.js"></script>
    <script src="js/code08pr.js"></script>
   <script src="js/keyboard08pr.js"></script>
</head>

<body >
   <label id= "errorMessage76canvas08pr" style="color: red;"> </label>
   <canvas id= "my-canvas08pr" class= "canvas-adaptive" width="600" height="600">
      Error from Html: Your browser does not support the HTML5 canvas element.
   </canvas>
</body>
</html>

   Технология AJAX (asynchronous JavaScript and XML) позволяет подгрузить в нашу программу часть данных с сервера. То есть, чтобы получить какие-либо данные когда-либо, нам не нужно перезагружать страницу в браузере. Для загрузки шейдеров используем следующий шаблон:

function initShaders08pr()
{
	//Get shader source.
	var fs_source = null;
	var vs_source = null;

//----Begin Ajax---------------------------------------------------
	var xhr = new XMLHttpRequest();

//Overriding the Mime type is required.
	xhr.overrideMimeType('text/xml');

	//For synchronous request - false third parameter.

//VS
	xhr.open('GET', 'shaders/01shader08pr.vs', false);
	xhr.send(null);
	if (xhr.readyState == xhr.DONE)
	{
		if(xhr.status === 200)
			vs_source = xhr.responseXML.documentElement.firstChild.data;
		else
			console.error("Error: " + xhr.statusText);
	}

	//FS
	xhr.open('GET', 'shaders/02shader08pr.fs', false);
	xhr.send(null);
	if (xhr.readyState == xhr.DONE)
	{
		if(xhr.status === 200)
			fs_source = xhr.responseXML.documentElement.firstChild.data;
		else
			console.error("Error: " + xhr.statusText);
	}
//----End Ajax---------------------------------------------------

	//1.Create and compile shaders.

…

   Когда шейдеры загрузятся, программа продолжит работу.

   Для загрузки по AJAX с помощью библиотеки jQuery можно было использовать код ниже. Но опять же, не вижу преимуществ у такого кода.
                        <script src=".../js/jquery.min.js"></script>
//----Begin Ajax---------------------------------------------------
                                                                                              //get shader sources with jQuery Ajax
                                                                                              $.ajax
                                                                                              ({
                                                                                                  async: false,
                                                                                                  url: 'shaders/01shader08pr.vs',
                                                                                                  success: function (data) {vs_source = data.firstChild.textContent;},
                                                                                                  dataType: 'xml'
                                                                                              });
                                                                                              $.ajax
                                                                                              ({
                                                                                                  async: false,
                                                                                                  url: 'shaders/02shader08pr.fs',
                                                                                                  success: function (data) {fs_source = data.firstChild.textContent;},
                                                                                                  dataType: 'xml'
                                                                                              });
//----End Ajax---------------------------------------------------

  

 
Ура, шаг-солянка из 3 разных частей пройден! Теперь, ко всему прочему, наша программа интерактивная (с управлением пользователем), фигура сразу с несколькими текстурами, шейдеры загружаются из отдельных файлов. Можно переходить к изучению источников света в следующем шаге.

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

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

Все статьи


Оставить комментарий

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

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