WebGL GLSL Shader
- Overview
- Steps to Build GLSL Shader Program
- Load Shaders from <script> Elements
- Load Shaders from Files
- Example
GLSL Shader
GLSL (OpenGL Shading Language) Shader is a user-defined custom rendering program that is directly running on the GPU. WebGL requires 2 shaders; a vertex shader is to process each vertex to transform, and a fragment shader is to process the input fragment (colour + depth) for lightning calculation and texture mapping.
- Vertex Shader Program: Transform vertex attributes (position, normal texture coords, ...) from object space to clip space with model, view and projection matrices
- Fragment Shader Program: Calculate the colour of input fragment in window space and/or modulate the texture map to the fragment, right before drawing it to the framebuffer.
GLSL is a C-like programming language, so, it must be compiled, linked and built to an executable program before using it. Please visit GLSL wiki page to learn GLSL syntax.
WebGL2 (GLSL v3) has a slight different syntax compared to WebGL v1 (GLSL v1). However, it is backward compatiable. That is you can use GLSL v1 shaders in WebGL2. To use GLSL v3 shaders explicitly in WebGL2, you must include "#version 300 es" directive at the first line of each shader code. And, attribute and varying qualifiers, and gl_FragColor built-in variable are removed from GLSL v3. You need to use in instead of attribute, and out instead of varying in GLSL v3. Plus, the layout location qualifiers are also supported in GLSL v3.
Steps to Build GLSL Shader Program
The steps to prepare a GLSL shader program are;
- Parse GLSL vertex and fragment shader source codes
- Create 2 shader objects with gl.createShader() for each vertex and fragment
- Attach the shader source codes to the shader objects with gl.shaderSource()
- Compile both shader objects with gl.compileShader()
- Create a shader program with gl.createProgram()
- Attach both shader objects to the shader program with gl.attachShader()
- Link the shader program with gl.linkProgram()
You can check the result of the shader compilation with gl.getShaderParameter(shaderId, gl.COMPILE_STATUS). If it is failed to compile, it returns false. And, gl.getProgramParameter(programId, gl.LINK_STATUS) returns false if it is failed to link the shader program.
Once a shader program is successfully built, you can feed varying and uniform inputs from your JavaScript to GPU's shader program before rendering the frame.
Load Shaders from <script> Element
You can directly define GLSL shaders in the HTML page using <script> tag.
// in HTML, define shader codes
<!-- vertex shader -->
<script id="vert-flat" type="x-shader/x-vertex">
#version 300 es
in vec3 vertexPosition;
uniform mat4 matrixModelViewProjection;
void main(void)
{
// transform vertex position to clip space
gl_Position = matrixModelViewProjection * vec4(vertexPosition, 1.0);
}
</script>
<!-- fragment shader -->
<script id="frag-flat" type="x-shader/x-frag">
#version 300 es
uniform vec4 materialDiffuse;
out vec4 fragColor;
void main(void)
{
fragColor = materialDiffuse;
}
</script>
...
Then, you can parse the shader codes from the script tags and compile, link them to build a shader program. Please see loadShaderById() in webglUtils.js.
// in JavaScript, load shader codes
let vertDom = document.getElementById("vert-flat");
let vertSource = vertDom.firstChild.textContent;
let fragDom = document.getElementById("frag-flat");
let fragSource = fragDom.firstChild.textContent;
// create shader objects
let vertShader = gl.createShader(gl.VERTEX_SHADER);
let fragShader = gl.createShader(gl.FRAGMENT_SHADER);
// attach shader codes to shader objects
gl.shaderSource(vertShader, vertSource);
gl.shaderSource(fragShader, fragSource);
// compile shader objects
gl.compileShader(vertShader);
gl.compileShader(fragShader);
// create a shader program
let program = gl.createProgram();
// attach shader objects to the program
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
// link shader program
gl.linkProgram(program);
// bind shader program
gl.useProgram(program);
// setup attributes
program.attribute = {};
program.attribute.position = gl.getAttribLocation(program, "vertexPosition");
gl.enableVertexAttribArray(program.attribute.position);
// setup uniforms
program.uniform = {};
program.uniform.matrixMVP = gl.getUniformLocation(program, "matrixModelViewProjection");
program.uniform.materialDiffuse = gl.getUniformLocation(program, "materialDiffuse");
...
// pass attribute values
gl.bindBuffer(gl.ARRAY_BUFFER, gl.vboVertex); // bind VBO
gl.vertexAttribPointer(program.attribute.position, 3, gl.FLOAT, false, 0, 0);
// pass uniform values
gl.uniformMatrix4fv(program.uniform.matrixMVP, new Float32Array(16));
gl.uniform4fv(program.uniform.materialDiffuse, new Float32Array([1,1,1,1]));
Load Shaders from Files
Building a shader program from files is same as loading from <script> tags, except loading the shader sources from files. Because it requires file IO operation, it must perform asynchronously, and the files must be located in the same origin for security.
createShaderProgram() is a utility function to load a pair of vertex and fragment shader codes and configure the locations of attributes and uniforms. Please see createShaderProgram() in webglUtils.js for detail.
// load shaders async
let program = await createShaderProgram(gl, "gles_flat.vert", "gles_flat.frag");
// bind shader program
gl.useProgram(program);
// set attributes
gl.bindBuffer(gl.ARRAY_BUFFER, gl.vboVertex); // bind VBO
gl.enableVertexAttribArray(program.attribute.position);
gl.vertexAttribPointer(program.attribute.position, 3, gl.FLOAT, false, 0, 0);
// set uniforms
gl.uniformMatrix4fv(program.uniform.matrixMVP, new Float32Array(16));
gl.uniform4fv(program.uniform.materialDiffuse, new Float32Array([1,1,1,1]));
You may use ShaderManager class to maintain multiple shader programs in one place. The core interfaces of ShaderManager are;
- load(vertFile, fragFile, name): It loads a pair of vertex and fragment shader from files, and return a Promise object, so you can execute a callback function after the shader program is loaded.
- getProgram(name): It returns the shader program by the given name. If not found, it returns null.
gl.shaderManager = new ShaderManager();
// using ShaderManager
// load() returns a Promise object
gl.shaderManager.load("gles_flat.vert", "gles_flat.frag", "flat").then(program =>
{
// set attribs and uniforms
gl.useProgram(program);
gl.enableVertexAttribArray(program.attribute.position);
gl.vertexAttribPointer(program.attribute.position, 3, gl.FLOAT, false, 0, 0);
gl.uniformMatrix4fv(program.uniform.matrixMVP, new Float32Array(16));
gl.uniform4fv(program.uniform.materialDiffuse, new Float32Array([1,1,1,1]));
});
...
// get shader program from ShaderManager
let program = gl.shaderManager.getProgram("flat");
gl.useProgram(program);
...
Example
This example is loading various GLSL shaders from either <script> elements or files, and displaying the detail information of the current shader object.
// minimal JS implementation
// See the full JS code from the download link
// global var
gl = {};
// get WebGL RC from canvas
let canvas = document.getElementById("webglView");
gl = canvas.getContext("webgl2");
// init GL
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
// load a GLSL shader from files
// createShaderProgram() returns a Promise object
let program = await createShaderProgram(gl, "gles_flat.vert", "gles_flat.frag");
// bind shader program before use it
gl.useProgram(program);
// set attribute values
gl.enableVertexAttribArray(program.attribute.vertexPosition);
gl.vertexAttribPointer(program.attribute.vertexPosition, 3, gl.FLOAT, false, 0, 0);
// set uniform values
gl.uniform4fv(program.uniform.materialDiffuse, new Float32Array([1,1,1,1]));
Fullscreen Demo: test_shader.html
GitHub Repo: test_shader.html, test_shader_min.html