Строим 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 таблица’.
Или использовать простую программку здесь
С включение паузы всё просто – вставляем перед увеличением угла поворота условие:
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]); } }
И не забываем найти униформы типа 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);
Теперь попробуем поменять текстуры местами – разместить черепицу поверх надписи с данными о прозрачности из надписи:
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. …
Когда шейдеры загрузятся, программа продолжит работу.
Ура, шаг-солянка из 3 разных частей пройден! Теперь, ко всему прочему, наша программа интерактивная (с управлением пользователем), фигура сразу с несколькими текстурами, шейдеры загружаются из отдельных файлов. Можно переходить к изучению источников света в следующем шаге.