WebGL - is there an alternative to embedding shaders in HTML?

ぃ、小莉子 提交于 2019-11-27 18:09:42
Giles Thomas

Yup, a local server's really the only way to go if you want to use XHR. I've written a bunch of WebGL lessons, and have often considered moving away from embedding the shaders in the HTML, but have been scared off by amount of explanation about web security I'd need to write...

Fortunately it's super easy to run a server. Just open a shell then

cd path-to-files
python -m SimpleHTTPServer

Then point your browser to

http://localhost:8000

That works for simple cases like textures and GLSL. For video and audio streaming see

What is a faster alternative to Python's http.server (or SimpleHTTPServer)?

On the other hand every browser that supports WebGL supports ES6 mutli-line template literals so if you don't care about old browsers you can just put you shaders in JavaScript using backticks like this

var vertexShaderSource = `
  attribute vec4 position;
  uniform mat4 u_matrix;

  void main() {
    gl_Position = u_matrix * position;
  }
`;

I've been using require.js's text plugin.

Here's a snippet:

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);
        ....
    }
);

The directory structure is as follows:

~require-gl-shaders/
 |~js/
 | |+lib/
 | |~shaders/
 | | |-fragment.shader
 | | `-vertex.shader
 | |-glmatrix.js - gl-matrix library
 | |-shader.js
 | |-text.js     - require.js's text plugin
 |-index.html
 |-main.js
 `-require.js    - the require.js library

Personally, I had a little bit of learning curve with require, but it really helped me keep cleaner code.

My buddy created a nice utils object with some handy functions for this type of scenario. You would store your shaders in plain text files in a folder called "shaders":

filename : 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      
}

And you simply call this to create a new program with these shader files:

var shaderProgram = utils.addShaderProg(gl, 'vertex.shader', 'fragment.shader');    

And here is the sweet util object to handle biz:

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

Thanks buddy for the sweet codeezy.. enjoy his contribution to the webgl community.. makes it way easier to simplify program / shader management.

Following @droidballoon's hint I ended up using stack.gl which "is an open software ecosystem for WebGL, built on top of browserify and npm".

Its glslify provides a browserify transform which can be used in conjunction with gl-shader in order to load shaders. The Javascript would look something like this:

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')
);

I am using this: https://www.npmjs.com/package/webpack-glsl-loader It fits priority to keep syntax highlighting from having proper glsl files instead of text fragments. I'll report later how it works.

[edit Aug-17, 2015] This approach is working fine for me. It assumes webpack is in your build flow, but that's not such a bad thing.

[edit 11-June-2016] https://github.com/kulicuu/Spacewar_WebGL_React has a working example for importing glsl files through a Webpack build. The game itself should be developed over the coming week.

A nice way of doing it is through the browserify-shader extension to Browserify.

If you can use server-side scripting, you could write a small script that reads in the shader files and returns a JavaScript file with the scripts in a global object. That way you can include it using plain-old <script src="shader?prefix=foo"> and edit the scripts as .c files.

Something like this 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
}

I've also been using Require.js to organize my files, but rather than use the text plugin, like @Vlr suggests, I have a script which takes the shaders and converts it into a Require.js module which I can then use elsewhere. So a shader file, simple.frag like this:

uniform vec3 uColor;

void main() {
  gl_FragColor = vec4(uColor, 1.0);
}

Will be converted into a file shader.js:

define( [], function() {
  return {
    fragment: {
      simple: [
        "uniform vec3 uColor;",

        "void main() {",
        "  gl_FragColor = vec4(uColor, 1.0);",
        "}",
      ].join("\n"),
    },
  }
} );

Which looks messy, but the idea isn't that it is human readable. Then if I want to use this shader someplace, I just pull in the shader module and access it using shader.fragment.simple, like so:

var simple = new THREE.ShaderMaterial( {
  vertexShader: shader.vertex.simple,
  fragmentShader: shader.fragment.simple
} );

I've written up a blog post with more details and links to demo code here: http://www.pheelicks.com/2013/12/webgl-working-with-glsl-source-files/

You can place your shaders in different files just like you put your javascript code in different files. This library https://github.com/codecruzer/webgl-shader-loader-js accomplishes that with a familiar syntax:

Example Usage (taken verbatim from above page):

[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;
        }
    );

Might not be the best way but I'm using php. I put the shaders in a separate file and then you just use:

<?php include('shaders.html'); ?>

works great for me.

Is not the exact solution, but is good for me. I use Pug (old Jade) for compile HTML, and I use includes inside shaders script tags

script#vertexShader(type="x-shader/x-vertex")
    include shader.vert

script#fragmentShader(type="x-shader/x-fragment")
    include shader.frag

The result is the same, a HTML with the code inline, but you can work the shader separately.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!