В своей книге К. Мацуда размещает исходный код шейдеров непосредственно в исходном коде js-файлов. Оформленный таким образом код шейдеров неудобно читать. В данной заметке рассматривается так же два дополнительных способа хранения исходного кода шейдеров и обращения к ним из кода JavaScript.
Вариант 1 (худший): размещение кода шейдеров непосредственно в коде JavaScript.
Итак, автор книги в своих начальных примерах определяет код шейдеров прямо в коде JavaScript следующим образом:
// Vertex shader programvar VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
Такой способ успешно работает во всех современных популярных браузерах. Я проверял в IE 11, Firefox 35.0.1, Google Chrome 40.0.2214 и Opera 27.0. В этом случае код шейдера неудобно читать, а в случае необходимости его правки - придётся повторно делать это во всех js-файлах, где данный код фигурирует.
Вариант 2 (не самый лучший): размещение кода шейдеров в элементе script файла html.
Как вариант, можно было бы разместить код шейдеров в качестве контента элемента script в составе исходного HTML файла:
<script type="glsl" id="VSHADER_SOURCE">
attribute vec4 a_position;
float size = 10.0;
void main(){
gl_Position = a_position;
gl_PointSize = size;
}
</script>
<script type="glsl" id="FSHADER_SOURCE">
void main(){
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
</script>
Не лучший вариант, но всё же это, на мой взгляд, более читабельно и удобно для редактирования, чем исходный вариант с конкатенацией строк. В данном случае код стал более "читабельным", но проблема многократной правки (см. вариант 1) остаётся в силе. Затем в коде JavaScript этот контент получают так:
var vsh_source = document.getElementById('VSHADER_SOURCE').textContent;
var fsh_source = document.getElementById('FSHADER_SOURCE').textContent;
UPD
Обратите внимание, что в коде используется textContent вместо innerText. Если использовать innerText, то код будет корректно работать только в браузерах Google Chrome [40.0.2214] и Opera [27.0], но не будет работать в IE [11] и Firefox [35.0.1]. В квадратных скобках указаны версии, для которых я проверял работу кода.
Вариант 3 (лучший): размещение кода шейдеров во внешних файлах (каждый шейдер в отдельном файле).
В идеале, код шейдеров должен храниться во внешних файлах. Такой код будет удобно читать и править. Кроме того, однократно внесённые изменения подхватятся всеми js-скриптами, использующими код отредактированного шейдера.
По умолчанию, объект XMLHttpRequest запрещает загрузку шейдеров из внешних файлов, когда адрес текущей html-страницы, указанной в адресной строке браузера, начинается c "file:///". Однако он не запрещает делать это, если адрес начинается с "http://localhost:[Номер порта]/".
Т.о. для полноценной разработки и тестирования WebGL приложений, нужно создать локальный web-сервер и запустить его. Существует множество способов сделать это. Я решил воспользоваться функционалом, присутствующим в Python. Скачать и установить последнюю на сегодняшний день версию (Python 3.4.2) можно с официальной страницы Python. При установке я включил опцию добавления в переменную PATH пути расположения python.exe.
После установки python локальный web-сервер запускается следующим образом:
Если в текущем каталоге (в нашем случае - в "D:\WebGL\my_sandbox\app_03") находится файл index.html (или index.htm), то браузер автоматически отобразит его содержимое. В противном же случае он в виде списка ссылок покажет перечень файлов и подкаталогов данного каталога. Для открытия в браузере нужного html файла, обозначенного в данном списке, нужно кликнуть по его имени мышкой.
Как вариант (не лучший), можно строку кода
request.open('GET', fileName, true);
заменить на
request.open('GET', fileName, false);
В этом случае запрос, отправляемый объектом XMLHttpRequest будет выполняться синхронно. Но из-за этого браузер может "подвисать" на время получения ответа.
Либо другой, более предпочтительный вариант: чтобы исправить ситуацию, нужно из кода функции main() убрать последнюю строку кода
Вариант 1 (худший): размещение кода шейдеров непосредственно в коде JavaScript.
Итак, автор книги в своих начальных примерах определяет код шейдеров прямо в коде JavaScript следующим образом:
// Vertex shader programvar VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'void main() {\n' +
' gl_Position = a_Position;\n' +
' gl_PointSize = 10.0;\n' +
'}\n';
Такой способ успешно работает во всех современных популярных браузерах. Я проверял в IE 11, Firefox 35.0.1, Google Chrome 40.0.2214 и Opera 27.0. В этом случае код шейдера неудобно читать, а в случае необходимости его правки - придётся повторно делать это во всех js-файлах, где данный код фигурирует.
Вариант 2 (не самый лучший): размещение кода шейдеров в элементе script файла html.
Как вариант, можно было бы разместить код шейдеров в качестве контента элемента script в составе исходного HTML файла:
<script type="glsl" id="VSHADER_SOURCE">
attribute vec4 a_position;
float size = 10.0;
void main(){
gl_Position = a_position;
gl_PointSize = size;
}
</script>
<script type="glsl" id="FSHADER_SOURCE">
void main(){
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
}
</script>
Не лучший вариант, но всё же это, на мой взгляд, более читабельно и удобно для редактирования, чем исходный вариант с конкатенацией строк. В данном случае код стал более "читабельным", но проблема многократной правки (см. вариант 1) остаётся в силе. Затем в коде JavaScript этот контент получают так:
var vsh_source = document.getElementById('VSHADER_SOURCE').textContent;
var fsh_source = document.getElementById('FSHADER_SOURCE').textContent;
UPD
Обратите внимание, что в коде используется textContent вместо innerText. Если использовать innerText, то код будет корректно работать только в браузерах Google Chrome [40.0.2214] и Opera [27.0], но не будет работать в IE [11] и Firefox [35.0.1]. В квадратных скобках указаны версии, для которых я проверял работу кода.
Вариант 3 (лучший): размещение кода шейдеров во внешних файлах (каждый шейдер в отдельном файле).
В идеале, код шейдеров должен храниться во внешних файлах. Такой код будет удобно читать и править. Кроме того, однократно внесённые изменения подхватятся всеми js-скриптами, использующими код отредактированного шейдера.
По умолчанию, объект XMLHttpRequest запрещает загрузку шейдеров из внешних файлов, когда адрес текущей html-страницы, указанной в адресной строке браузера, начинается c "file:///". Однако он не запрещает делать это, если адрес начинается с "http://localhost:[Номер порта]/".
Т.о. для полноценной разработки и тестирования WebGL приложений, нужно создать локальный web-сервер и запустить его. Существует множество способов сделать это. Я решил воспользоваться функционалом, присутствующим в Python. Скачать и установить последнюю на сегодняшний день версию (Python 3.4.2) можно с официальной страницы Python. При установке я включил опцию добавления в переменную PATH пути расположения python.exe.
После установки python локальный web-сервер запускается следующим образом:
- Устанавливаем текущим каталог, в котором находятся интересующие нас html странички с WebGL приложениями:
cd /D "D:\WebGL\my_sandbox\app_03" - Запускаем локальный web-сервер. Например, будем слушать порт 8001:
python -m http.server 8001 - В адресной строке браузера указываем следующий адрес:
http://localhost:8001/
Если в текущем каталоге (в нашем случае - в "D:\WebGL\my_sandbox\app_03") находится файл index.html (или index.htm), то браузер автоматически отобразит его содержимое. В противном же случае он в виде списка ссылок покажет перечень файлов и подкаталогов данного каталога. Для открытия в браузере нужного html файла, обозначенного в данном списке, нужно кликнуть по его имени мышкой.
Внимание!
Важно помнить, что код JavaScript выполняется в браузере, в то время как код шейдеров обрабатывается и выполняется в системе WebGL. Необходимо, чтобы их работа происходила синхронно. Поясню на примере.
Пример некорректной загрузки и инициализации шейдеров
Предположим, что для загрузки шейдеров мы написали такие функции:
// Чтение шейдера из файла
function readShaderFile(gl, fileName, shader) {
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if (request.readyState === 4 && request.status !== 404) {
onReadShader(gl, request.responseText, shader);
}
}
request.open('GET', fileName, true); // Создаём запрос получения файла
request.send(); // Отправляем запрос
}
// Шейдер загружен из файла
function onReadShader(gl, fileString, shader) {
if (shader == 'v') { // Вершинный шейдер
VSHADER_SOURCE = fileString;
} else
if (shader == 'f') { // Фрагментный шейдер
FSHADER_SOURCE = fileString;
}
}
Затем эти функции мы используем в нашем коде:
// Исходный код вершинного шейдера
var VSHADER_SOURCE = null;
// Исходный код фрагментного шейдера
var FSHADER_SOURCE = null;
// Метод, который будет выполнен при срабатывании события onload элемента body в
// документе HTML5.
function main(){
// Получаем холст
if(!(canvas = document.getElementById('webgl'))){
console.log('Элемент с идентификатором \'webgl\' не найден.');
return;
}
// Получаем контекст WebGL
if (!(gl = getWebGLContext(canvas))){
console.log('Не удалось получить контекст \'WebGL\'.');
return;
}
// Получаем исходный код шейдеров:
readShaderFile(gl, './shaders/point.vert', 'v');
readShaderFile(gl, './shaders/point.frag', 'f');
start(gl);
}
function start(gl) {
// Выполняем инициализацию шейдеров, а так же объекта программы (gl.program)
if(!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)){
console.log('Не удалось инициализировать шейдеры.');
return;
}
if(!gl.program){
console.log('Не инициализирован объект \'gl.program\'.');
return;
}
if((a_Position = gl.getAttribLocation(gl.program, 'a_Position')) < 0){
console.log('Не удалось получить ссылку на \'a_Position\'.');
return;
}
// Координаты точки, которую будем рисовать.
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
// Настраиваем очистку буферов
gl.clearColor(0.0, 0.0, 0.0, 1.0); // буфер цвета (формат RGBA)
gl.clearDepth(1.0); // буфер глубины
gl.clearStencil(0); // буфер трафарета
// Выполняем очистку буферов согласно обозначенным выше настройкам
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
// Рисуем точку
gl.drawArrays(gl, 0, 1);
}
Однако, открыв с помощью локального web-сервера нашу html страничку в браузере мы ничего не увидим. Затем, открыв инструменты разработчика (клавиша F12 в IE или Ctrl + Shift + I в др. браузерах) мы увидим в консоли сообщение:
Failed to compile shader: ERROR: 0:1: 'null' : syntax error cuon-utils.js:88
Failed to compile shader: ERROR: 0:1: 'null' : syntax error cuon-utils.js:88
Failed to create program cuon-utils.js:12
Не удалось инициализировать шейдеры. index.js:36
Это происходит потому, что в приведённом выше коде вызов метода request.open(...) объекта XMLHttpRequest выполняется асинхронно. Исходный код шейдеров ещё не успел обработаться в WebGL, а код JavaScript уже запустил на исполнение код функции start(gl). Поскольку к этому времени инциализация переменной FSHADER_SOURCE ещё не успела произойти, то мы и получаем ошибку инициализации шейдеров.
Исправляем ситуацию
Как вариант (не лучший), можно строку кода
request.open('GET', fileName, true);
заменить на
request.open('GET', fileName, false);
В этом случае запрос, отправляемый объектом XMLHttpRequest будет выполняться синхронно. Но из-за этого браузер может "подвисать" на время получения ответа.
Либо другой, более предпочтительный вариант: чтобы исправить ситуацию, нужно из кода функции main() убрать последнюю строку кода
start(gl);
а в конец функции onReadShader добавить вызов start(gl) после выполнения проверки инициализации переменных, содержащих исходный код шейдеров:
// Когда оба шейдера доступны, запускаем функцию start().
if (VSHADER_SOURCE && FSHADER_SOURCE) start(gl);
Теперь браузер корректно отобразит страничку нашего WebGL-приложения.
Добавил UPD.
ОтветитьУдалитьДобавил информацию о размещении шейдеров во внешних файлах.
ОтветитьУдалить