There are many situations in which it‘s useful for WebGL applications to transform shaders in various ways. ANGLE’s shader translator can be used for this purpose: compiling it with Emscripten allows it to be invoked from a web page. This wiki page provides some preliminary details about how to do this.
Pull top of tree ANGLE.
Install the Emscripten toolchain per the instructions.
Symlink (preferred) or copy the ANGLE directory into ...emsdk/emscripten/master.
Put a shader to compile into a file (named with .vert or .frag suffix) in the same directory. For example, put the following shader from the [WebGL Aquarium] (http://webglsamples.org/aquarium/aquarium.html) into aq-fish-nm.frag
:
precision mediump float; uniform vec4 lightColor; varying vec4 v_position; varying vec2 v_texCoord; varying vec3 v_tangent; // #normalMap varying vec3 v_binormal; // #normalMap varying vec3 v_normal; varying vec3 v_surfaceToLight; varying vec3 v_surfaceToView; uniform vec4 ambient; uniform sampler2D diffuse; uniform vec4 specular; uniform sampler2D normalMap; // #normalMap uniform float shininess; uniform float specularFactor; // #fogUniforms vec4 lit(float l ,float h, float m) { return vec4(1.0, max(l, 0.0), (l > 0.0) ? pow(max(0.0, h), m) : 0.0, 1.0); } void main() { vec4 diffuseColor = texture2D(diffuse, v_texCoord); mat3 tangentToWorld = mat3(v_tangent, // #normalMap v_binormal, // #normalMap v_normal); // #normalMap vec4 normalSpec = texture2D(normalMap, v_texCoord.xy); // #normalMap vec3 tangentNormal = normalSpec.xyz - vec3(0.5, 0.5, 0.5); // #normalMap tangentNormal = normalize(tangentNormal + vec3(0, 0, 2)); // #normalMap vec3 normal = (tangentToWorld * tangentNormal); // #normalMap normal = normalize(normal); // #normalMap vec3 surfaceToLight = normalize(v_surfaceToLight); vec3 surfaceToView = normalize(v_surfaceToView); vec3 halfVector = normalize(surfaceToLight + surfaceToView); vec4 litR = lit(dot(normal, surfaceToLight), dot(normal, halfVector), shininess); vec4 outColor = vec4( (lightColor * (diffuseColor * litR.y + diffuseColor * ambient + specular * litR.z * specularFactor * normalSpec.a)).rgb, diffuseColor.a); // #fogCode gl_FragColor = outColor; }
Compile the shader translator, the translator sample, and the shader all together:
./emcc -Iangle/include -Iangle/src angle/samples/translator/translator.cpp angle/src/compiler/preprocessor/*.cpp angle/src/compiler/translator/*.cpp angle/src/compiler/translator/timing/*.cpp angle/src/compiler/translator/depgraph/*.cpp angle/src/third_party/compiler/*.cpp angle/src/common/*.cpp -o translator.html --preload-file aq-fish-nm.frag -s NO_EXIT_RUNTIME=1
Serve up the resulting translator.html via python -m SimpleHTTPServer
. Navigate the browser to localhost:8000.
The translator sample will run, displaying its output into the text area on the page. Since it isn't receiving any input, it simply outputs a help message and exits.
To invoke the translator again, processing the shader we included along with the source code, open the JavaScript console and type:
Module['callMain'](['-s=w', '-u', 'aq-fish-nm.frag'])
The active uniforms and their types will be printed to the text area after the translator sample processes the shader.
It's clearly not useful to have to compile the shader in to the Emscripten-translated executable. It would be helpful to define a simple wrapper function which can easily be called from JavaScript and which defines enough parameters to pass in a shader as a string, transform it somehow or compile it to another language target, and return the compiled result (or other information). A simple JavaScript library that wraps all of the interactions with the Emscripten binary would be useful.
It‘s not feasible to interact with the translator’s data structures, nor traverse the AST from JavaScript. The code that operates upon the shader must be written in C++ and compiled in to the shader translator.
emcc should be integrated better with ANGLE's build system.