WebGL - есть ли альтернатива встраиванию шейдеров в HTML?
Популярным способом использования шейдеров GLSL в WebGL, по-видимому, является их внедрение в основной файл html.
Шейдеры вершин и фрагментов встроены в теги, например:
<script id="shader-fs" type="x-shader/x-fragment">
Это то же самое соглашение, которое я вижу в примерах WebGL на странице Mozilla Developer Network.
Это отлично подходит для простых приложений, но когда у вас сложное приложение с несколькими шейдерами, html файл становится загроможденным. (Я продолжаю редактировать неправильный шейдер!) Также, если вы хотите повторно использовать ваши шейдеры, эта схема неудобна.
Итак, я думал о том, чтобы поместить эти шейдеры в отдельные файлы XML и загрузить их с помощью XMLHttpRequest(). Затем я увидел, что у кого-то была такая же идея:
http://webreflection.blogspot.com/2010/09/fragment-and-vertex-shaders-my-way-to.html
Мне нравится предложение использовать файлы .c, поскольку это дает вам подсветку синтаксиса и другие удобства редактора для GLSL.
Но проблема с вышеприведенным подходом заключается в том, что (насколько я понимаю) XMLHttpRequest() не может загружать локальный файл .c, то есть на стороне клиента, при разработке и тестировании приложения WebGL. Но в этот процесс сложно загружать его на сервер.
Итак, если я хочу сохранить шейдеры из html файла, это единственный вариант для их вставки в виде строк в коде? Но это затруднит запись, а также отладка...
Я был бы признателен за любые предложения по управлению несколькими шейдерами GLSL в приложениях WebGL.
Привет
Изменить (май 05 2011)
Поскольку я использую Mac для разработки, я решил включить сервер Apache и поместил свой код webgl под http://localhost/~username/. Это оборачивает проблему файла: протокол отключается во время разработки. Теперь код загрузки файла javascript работает локально, поскольку используется http:, а не файл:. Просто подумал, что я поставил бы это здесь, если кто-нибудь найдет это полезным.
Ответы
Ответ 1
Да, локальный сервер действительно единственный путь, если вы хотите использовать XHR. Я написал несколько уроков по WebGL и часто думал о том, чтобы отойти от встраивания шейдеров в HTML, но меня напугало количество объяснений о безопасности в сети, которые мне нужно написать...
К счастью, это очень легко запустить сервер. Просто откройте оболочку, а затем
cd path-to-files
python -m SimpleHTTPServer
Затем укажите в браузере
http://localhost:8000
Это работает для простых случаев, таких как текстуры и GLSL. Для просмотра видео и аудио смотрите
Что является более быстрой альтернативой http.server (или SimpleHTTPServer) в Python?
С другой стороны, каждый браузер, который поддерживает WebGL, поддерживает многострочные литералы шаблона ES6, поэтому, если вам не нужны старые браузеры, вы можете просто поместить свои шейдеры в JavaScript с помощью обратных ссылок, подобных этой
var vertexShaderSource = '
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
';
Ответ 2
Я использовал require.js текстовый плагин.
Вот фрагмент:
define(
/* Dependencies (I also loaded the gl-matrix library) */
["glmatrix", "text!shaders/fragment.shader", "text!shaders/vertex.shader"],
/* Callback when all has been loaded */
function(glmatrix, fragmentShaderCode, vertexShaderCode) {
....
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderCode);
gl.compileShader(vertexShader);
....
}
);
Структура каталогов выглядит следующим образом:
~require-gl-shaders/
|~js/
| |+lib/
| |~shaders/
| | |-fragment.shader
| | `-vertex.shader
| |-glmatrix.js - gl-matrix library
| |-shader.js
| |-text.js - require.js text plugin
|-index.html
|-main.js
`-require.js - the require.js library
Лично у меня была небольшая кривая обучения с требованием, но это действительно помогло мне сохранить более чистый код.
Ответ 3
Мой приятель создал приятный объект utils с некоторыми удобными функциями для этого типа сценария. Вы сохранили бы свои шейдеры в текстовых файлах в папке с названием "шейдеры":
имя_файла: vertex.shader
attribute vec3 blah;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
uniform mat3 uNMatrix;
void main(void) {
magic goes here
}
filename: fragment.shader
#ifdef GL_ES
precision highp float;
#endif
varying vec4 vYadaYada;
uniform sampler2D uSampler;
void main(void) {
fragic magic goes here
}
И вы просто вызываете это, чтобы создать новую программу с этими шейдерными файлами:
var shaderProgram = utils.addShaderProg(gl, 'vertex.shader', 'fragment.shader');
И вот объект сладкого использования для обработки бизнеса:
utils = {};
utils.allShaders = {};
utils.SHADER_TYPE_FRAGMENT = "x-shader/x-fragment";
utils.SHADER_TYPE_VERTEX = "x-shader/x-vertex";
utils.addShaderProg = function (gl, vertex, fragment) {
utils.loadShader(vertex, utils.SHADER_TYPE_VERTEX);
utils.loadShader(fragment, utils.SHADER_TYPE_FRAGMENT);
var vertexShader = utils.getShader(gl, vertex);
var fragmentShader = utils.getShader(gl, fragment);
var prog = gl.createProgram();
gl.attachShader(prog, vertexShader);
gl.attachShader(prog, fragmentShader);
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {alert("Could not initialise main shaders");}
return prog;
};
utils.loadShader = function(file, type) {
var cache, shader;
$.ajax({
async: false, // need to wait... todo: deferred?
url: "shaders/" + file, //todo: use global config for shaders folder?
success: function(result) {
cache = {script: result, type: type};
}
});
// store in global cache
uilts.allShaders[file] = cache;
};
utils.getShader = function (gl, id) {
//get the shader object from our main.shaders repository
var shaderObj = utils.allShaders[id];
var shaderScript = shaderObj.script;
var shaderType = shaderObj.type;
//create the right shader
var shader;
if (shaderType == "x-shader/x-fragment") {
shader = gl.createShader(gl.FRAGMENT_SHADER);
} else if (shaderType == "x-shader/x-vertex") {
shader = gl.createShader(gl.VERTEX_SHADER);
} else {
return null;
}
//wire up the shader and compile
gl.shaderSource(shader, shaderScript);
gl.compileShader(shader);
//if things didn't go so well alert
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert(gl.getShaderInfoLog(shader));
return null;
}
//return the shader reference
return shader;
};//end:getShader
Благодарим приятеля за сладкое кодовое... наслаждайтесь его вкладом в сообщество webgl.. упрощает упрощение управления программами/шейдерами.
Ответ 4
Следуя подсказке @droidballoon, я закончил использование stack.gl, который "является открытой экосистемой программного обеспечения для WebGL, построенной поверх браузера и npm".
Его glslify предоставляет преобразование браузера, которое может использоваться совместно с gl-shader в для загрузки шейдеров. Javascript будет выглядеть примерно так:
var glslify = require('glslify');
var loadShader = require('gl-shader');
var createContext = require('gl-context');
var canvas = document.createElement('canvas');
var gl = createContext(canvas);
var shader = loadShader(
gl,
glslify('./shader.vert'),
glslify('./shader.frag')
);
Ответ 5
Я использую это:
https://www.npmjs.com/package/webpack-glsl-loader
Это соответствует приоритету, чтобы подсветка синтаксиса не имела надлежащих файлов glsl вместо текстовых фрагментов.
Я сообщу позже, как это работает.
[edit 17 августа 2015 г.] Этот подход работает для меня нормально. Предполагается, что веб-пакет находится в процессе сборки, но это не так уж и плохо.
[править 11 июня 2016 года] https://github.com/kulicuu/Spacewar_WebGL_React содержит рабочий пример импорта файлов glsl через сборку Webpack. Сама игра должна быть разработана в течение ближайшей недели.
Ответ 6
Хороший способ сделать это через расширение browserify-shader для браузера.
Ответ 7
Если вы можете использовать скрипты на стороне сервера, вы можете написать небольшой script, который читает в файлах шейдера и возвращает файл JavaScript со сценариями в глобальном объекте. Таким образом, вы можете включить его с помощью простого старого < script src= "shader? Prefix = foo" > и отредактировать скрипты как .c файлы.
Что-то вроде этого Ruby CGI script
require 'cgi'
require 'json'
cgi = CGI.new
prefix = File.expand_path(cgi["prefix"])
cwd = Dir.getwd + "/"
exit!(1) unless prefix.start_with?(cwd)
shader = prefix + ".c"
source = File.read(shader)
cgi.out("text/javascript") {
<<-EOF
if (typeof Shaders == 'undefined') Shaders = {};
Shaders[#{cgi["prefix"]}] = #{source.to_json};
EOF
}
Ответ 8
Вы можете поместить свои шейдеры в разные файлы так же, как вы помещаете свой javascript-код в разные файлы. Эта библиотека https://github.com/codecruzer/webgl-shader-loader-js выполняет это со знакомым синтаксисом:
Пример использования (взято стенографически со страницы выше):
[index.html]:
<script data-src="shaders/particles/vertex.js" data-name="particles"
type="x-shader/x-vertex"></script>
<script data-src="shaders/particles/fragment.js" data-name="particles"
type="x-shader/x-fragment"></script>
[example.js]:
SHADER_LOADER.load (
function (data)
{
var particlesVertexShader = data.particles.vertex;
var particlesFragmentShader = data.particles.fragment;
}
);
Ответ 9
Я также использовал Require.js для организации своих файлов, но вместо использования текстового плагина, например, @Vlr, у меня есть script, который берет шейдеры и преобразует его в модуль Require.js, который Затем я могу использовать его в другом месте. Итак, файл шейдера, simple.frag
, как это:
uniform vec3 uColor;
void main() {
gl_FragColor = vec4(uColor, 1.0);
}
Будет преобразован в файл shader.js
:
define( [], function() {
return {
fragment: {
simple: [
"uniform vec3 uColor;",
"void main() {",
" gl_FragColor = vec4(uColor, 1.0);",
"}",
].join("\n"),
},
}
} );
Какой выглядит грязно, но идея заключается не в том, что он читается человеком. Затем, если я хочу использовать этот шейдер где-нибудь, я просто втягиваю модуль shader
и получаю доступ к нему с помощью shader.fragment.simple
, например:
var simple = new THREE.ShaderMaterial( {
vertexShader: shader.vertex.simple,
fragmentShader: shader.fragment.simple
} );
Я написал сообщение в блоге с более подробной информацией и ссылками на демо-код здесь: http://www.pheelicks.com/2013/12/webgl-working-with-glsl-source-files/
Ответ 10
Возможно, не лучший способ, но я использую php. Я помещаю шейдеры в отдельный файл, а затем вы просто используете:
<?php include('shaders.html'); ?>
отлично работает для меня.
Ответ 11
Не точное решение, но для меня это хорошо.
Я использую Pug (старый Jade) для компиляции HTML, и я использую встроенные шейдеры script теги
script#vertexShader(type="x-shader/x-vertex")
include shader.vert
script#fragmentShader(type="x-shader/x-fragment")
include shader.frag
Результат тот же, HTML с встроенным кодом, но вы можете работать с шейдером отдельно.
Ответ 12
Используйте макросы C #include
и gcc -E
(клавиша -E запускает препроцессор без компилятора)
Добавьте это в свой файл js:
const shader = '
#include "shader.fg"
'
и использовать оболочку после:
mov main.js main.c
gcc -E --no-warnings main.c | sed '/^#.*/d' > main.js
sed
здесь просто удаляет дополнительные комментарии, сгенерированные препроцессором
Оно работает! ;)