Sending JavaScript variables to fragment shader
The short answer is you have basically 2 options
Pass values from JavaScript to GLSL by uniform.
For example if you want to pass a float create a float uniform
uniform float foo;
In JavaScript compile and link that shader, then lookup the location of the uniform
var locationOfFoo = gl.getUniformLocation(someProgram, "foo");
You can now pass a value to GLSL with
gl.useProgram(someProgram) gl.uniform1f(locationOfFoo, valueToPass);
Manipulate strings before compiling the shader
#define MAX_INTERATIONS %maxIterations% #define XMIN %xMin%
...
var maxIterations = 123; var xMin = 4.5; shaderSource = shaderSource.replace(/%maxIterations%/g, maxIterations); shaderSource = shaderSource.replace(/%xMin%/g, xMin);
(1) above is for passing stuff that changes often. #2 is for changing a shader before it's compiled. #1 is a technique used in pretty much 100% of WebGL programs. #2 is used often when generating shaders on the fly which many game engines do.
It took me about 45 minutes to implement gman's answer because I kept making stupid little mistakes. So here is a full working code sample that creates an adjustable tile-map.
Tested In: Chrome, Internet Explorer, and Edge:
<!DOCTYPE HTML >
<html lang="en">
<head>
<meta charset="UTF-8">
<title> GL_TILE_TESTBED </title>
<!-- AUTHOR: John Mark Isaac Madison -->
<!-- EMAIL : [email protected] -->
<!-- SSSSSSSSS SHADER_SECTION START SSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<style>
p{ font-size:12pt;}
h3,p,input,button,br{
padding:0px;
margin:0px;
font-family:"Andale Mono";
}
button,input{
padding:10px;
}
</style>
<script id="VERT_SHADER" type="NOT_JAVASCRIPT">
precision highp float;
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0, 1);
}
</script>
<script id="FRAG_SHADER" type="NOT_JAVASCRIPT">
//Must declare precision before declaring
//any uniforms:
////////////////////////////////////////////////
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
precision mediump int;
////////////////////////////////////////////////
#define CANVAS_WID 640.0
#define CANVAS_HIG 480.0
#define TIL_WID 64.0
#define TIL_HIG 64.0
//Uniforms exposed to HTML/JAVASCRIPT:
uniform float TIL_WID_EDIT;
uniform float TIL_HIG_EDIT;
float til_wid;
float til_hig;
void main() {
//If uniforms have not set by user,
//use the default values set by the #define(s)
//==========================================//
if(TIL_WID_EDIT > 0.0){
til_wid = TIL_WID_EDIT;
}else{
til_wid = TIL_WID;
}
if(TIL_HIG_EDIT > 0.0){
til_hig = TIL_HIG_EDIT;
}else{
til_hig = TIL_HIG;
}
//==========================================//
//NOTE: on "gl_FragCoord" range:
//******************************************//
//web-gl: In terms of pixel/canvas coords.
//OpenGL: In terms of 0 to 1.
//******************************************//
//:Calculate number of tiles shown on screen:
//:This may be fractional:
float NUM_TIL_X = CANVAS_WID / til_wid;
float NUM_TIL_Y = CANVAS_HIG / til_hig;
vec2 FC_MOD;
FC_MOD.x = gl_FragCoord.x;
FC_MOD.y = gl_FragCoord.y;
//You want all tiles to have the full range
//of colors, so you always modulate by
//CANVAS_WID and CANVAS_HIG, You scale by the
//# of tiles on each axis which means the
//gradient becomes steeper as the # of tiles
//increases.
FC_MOD.x = mod( gl_FragCoord.x*NUM_TIL_X, CANVAS_WID );
FC_MOD.y = mod( gl_FragCoord.y*NUM_TIL_Y, CANVAS_HIG );
//[N]ormalize values into range 0 to 1:
//NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
float norm_X = (FC_MOD.x) / CANVAS_WID;
float norm_Y = (FC_MOD.y) / CANVAS_HIG;
//NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN//
//Use [B]lue channel because why not?
//BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
float GRAD_X = gl_FragCoord.x / CANVAS_WID;
//BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB//
//Set the final [F]ragment colors:
//FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF//
gl_FragColor = vec4(norm_X, norm_Y, GRAD_X, 1.0);
//FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF//
}
</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- SSSSSSSSSS SHADER_SECTION END SSSSSSSSSS -->
</head>
<!-- HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -->
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
<body onload="ON_LOADED_FUNCTION()" >
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
<h3> Open GL Tile TestBed <h3>
<p> Author: John Mark Isaac Madison <p>
<p> Email : [email protected] <p>
<canvas id="glCanvas"></canvas>
</br>
<button onClick="PUT_WID();">TILE_WIDTH__IN_PIXELS</button>
<input type="text" id="INPUT_WID" value="45">
</br>
</br>
<button onClick="PUT_HIG();">TILE_HEIGHT_IN_PIXELS</button>
<input type="text" id="INPUT_HIG" value="45">
</br>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<script id="BOILER_PLATE_CODE">
function ON_LOADED_FUNCTION(){
console.log("[ON_LOADED_FUNCTION]");
main();
}
//:Takes the gl context object, if the input
//:is null, we likely failed to get the
//:context.
function HAS_OPEN_GL_CHECK(gl){
// Only continue if WebGL is
// available and working
if (!gl) {
var msg = "";
msg += "[Unable to initialize WebGL.]";
msg += "[your browser or machine may]";
msg += "[not support it.]"
alert( msg );
return;
}
}
function GET_INPUT_BOX_VALUE( elem_id ){
var box; //DOM input box
var val; //Value in input box.
box = document.getElementById( elem_id );
val = box.value;
return (0 + val); //cast to number.
}
function PUT_WID(){
assert_program_and_gl_exist();
var val = GET_INPUT_BOX_VALUE("INPUT_WID");
SET_ATTR("TIL_WID_EDIT", val);
}
function PUT_HIG(){
assert_program_and_gl_exist();
var val = GET_INPUT_BOX_VALUE("INPUT_HIG");
SET_ATTR("TIL_HIG_EDIT", val);
}
function SET_ATTR(gl_var_name, val){
if(val < 0 || val > 256 ){
alert("choose value between 0 to 256");
return;
}
var loc; //<--location of variable.
loc = gl.getUniformLocation(
program ,
gl_var_name
);
gl.useProgram(program);
gl.uniform1f(loc, val);
}
function assert_program_and_gl_exist(){
if(!program){慌("[NO_PROGRAM_EXISTS]");}
if(!gl ){慌("[NO_GL_EXISTS]");}
}
//慌: "disconcerted, be confused, lose one's head"
//慌: In Code: ~Panic~
function 慌( panic_message ){
console.log( panic_message );
alert ( panic_message );
throw ( panic_message );
}
function makeOpenGlContextUsingCanvas(c){
//:Try what works in chrome and all the
//:respectable browsers first:
gl = c.getContext("webgl");
if(!gl){
console.log("[Probably_In_IE]");
gl = c.getContext("experimental-webgl");
}else{
console.log("[Probably_NOT_IE]");
}
HAS_OPEN_GL_CHECK( gl );
return gl;
}
//: No "var" prefix, making them global:
function initGlobals(){
canvas = document.querySelector("#glCanvas");
if(!canvas){
alert("FAILED_TO_GET_CANVAS");
}else{
console.log("[GOT_CANVAS]");
}
gl = makeOpenGlContextUsingCanvas(canvas);
//These dimensions are hard-coded into
//fragment shader code, so be careful
//about changing them:
canvas.width = 640;
canvas.height= 480;
buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
1.0, 1.0]),
gl.STATIC_DRAW
);
//G == Global Container.
//To fix problems with rendering in I.E.
//(Internet Explorer)
//GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
var G = {};
G.canvas = canvas;
G.gl = gl;
G.buffer = buffer;
if( ! G.canvas ||
! G.gl ||
! G.buffer ){
慌("[Global_Container_Broken]");
}
return G;
//GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG//
}
function main(){
G = initGlobals();
HAS_OPEN_GL_CHECK( G );
gl.viewport(0,0,gl.drawingBufferWidth, gl.drawingBufferHeight);
setup();
render();
}
function setup(){
var frag_dom = document.getElementById("FRAG_SHADER");
var frag_src = frag_dom.text;
console.log( frag_src );
F = createShader(
gl,gl.FRAGMENT_SHADER, frag_src
);
var vert_dom = document.getElementById("VERT_SHADER");
var vert_src = vert_dom.text;
console.log( vert_src );
V = createShader(
gl, gl.VERTEX_SHADER, vert_src
);
//**** MAKE "program" a GLOBAL VAR ****//
program = createProgram(gl,V,F);
gl.useProgram( program );
if(!program){
慌("PROGRAM_IS_NULL");
}
}
function render(){
window.requestAnimationFrame(render,canvas);
// Set clear color to black, fully opaque
gl.clearColor(0.0, 0.0, 0.5, 1.0);
// Clear the color buffer with specified clear color
gl.clear(gl.COLOR_BUFFER_BIT);
//Directly before call to gl.drawArrays:
positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray( positionLocation );
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function createShader(gl,type,source){
//:Error Check For Bad Inputs:
if(!gl ){慌("[NULL_GL]");}
if(!type ){慌("[NULL_TY]");}
if(!source){慌("[NULL_SR]");}
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var res = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if( res ){
console.log("[SHADER_COMPILED!]");
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
慌("[FAILED_TO_COMPILE_SHADER]");
}
//:gl : openGL context :
//:vert: vertex shader :
//:frag: fragment shader:
function createProgram(gl,vert, frag){
var program = gl.createProgram();
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram (program);
var res = gl.getProgramParameter(program, gl.LINK_STATUS);
if( res ){
console.log("[PROGRAM_CREATED!]");
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
</script>
<!-- SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS -->
<!-- BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB -->
</body>
</html>