Understanding GLSL Shaders for Web Graphics
Overview
GLSL (OpenGL Shading Language) is a C-like, strongly-typed language executed directly by the GPU’s graphics pipeline. It enables advanced visual effects and computations in web-based 3D graphics, complementing JavaScript rendering APIs like WebGL.
Key Insights
- GPU Offloading: Shaders run on the GPU, optimizing computationally intensive graphics operations and freeing the CPU.
- Core Shader Types: Vertex Shaders and Fragment (Pixel) Shaders are primary for web graphics.
- Vertex Shader Function: Transforms 3D vertex positions, defining object geometry and scene placement.
- Fragment Shader Function: Computes pixel colors and attributes, determining an object’s visual appearance.
- Complexity: GLSL involves significant vector and matrix mathematics, making it more complex than JavaScript.
- Three.js Integration: Libraries like Three.js abstract WebGL complexity, simplifying shader implementation and scene management.
Technical Details
Shaders are fundamental GPU-processed functions for rendering graphics. They operate in parallel, simultaneously processing numerous vertices or pixels.
Core Shader Types
Vertex Shaders
A vertex shader executes once per vertex, manipulating 3D vertex coordinates and projecting them onto a 2D screen.
- Purpose: Sets the built-in
gl_Positionvariable, storing the final projected vertex position. - Execution: The
void main()function defines the vertex’s transformation logic.
Fragment Shaders
A fragment shader executes once per pixel (or fragment), determining its final color.
- Purpose: Sets the built-in
gl_FragColorvariable, specifying the fragment’s RGBA (red, green, blue, alpha) color. - Color Representation: RGBA values are floating-point numbers from
0.0(minimum intensity/transparency) to1.0(maximum intensity/opacity).
Implementing Shaders with Three.js
Integrate custom GLSL shaders into a Three.js scene to render a simple cube. Three.js simplifies the underlying WebGL setup.
Environment Setup
A basic Three.js project requires the Three.js library and a <canvas> element.
HTML Structure
Embed GLSL shader code and main JavaScript logic within script tags in the HTML:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>MDN Games: Shaders demo</title>
<style>
html,
body,
canvas {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
font-size: 0;
}
</style>
<script src="three.min.js"></script>
</head>
<body>
<script id="vertexShader" type="x-shader/x-vertex">
// Vertex shader code here
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
// Fragment shader code here
</script>
<script>
// Three.js scene setup and application logic here
</script>
</body>
</html>
Cube Integration
Begin with a standard Three.js cube setup. Modify its material to utilize custom shaders, reusing existing renderer, camera, and lights.
Vertex Shader Implementation
This shader translates the cube’s position:
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x+10.0, position.y, position.z+5.0, 1.0);
}
gl_Position: The output variable for the vertex’s final position.projectionMatrix,modelViewMatrix: Three.js-provided matrices for camera projection and model-view transformations.vec4(position.x+10.0, position.y, position.z+5.0, 1.0): Defines the new vertex position, shifting the cube 10 units along the X-axis and 5 units along the Z-axis. The fourth component (1.0) is for homogeneous coordinates.
Fragment Shader Implementation
This shader assigns a static blue color to the cube:
void main() {
gl_FragColor = vec4(0.0, 0.58, 0.86, 1.0);
}
gl_FragColor: The output variable for the pixel’s final RGBA color.vec4(0.0, 0.58, 0.86, 1.0): Sets the color to light blue (0% red, 58% green, 86% blue, 100% opaque).
Applying the Shaders
Integrate GLSL code into the Three.js scene by creating a ShaderMaterial:
// Comment out or remove the basic material
// const basicMaterial = new THREE.MeshBasicMaterial({color: 0x0095DD});
// Create ShaderMaterial
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent,
});
// Assign the shader material to the cube
// const cube = new THREE.Mesh(boxGeometry, basicMaterial);
const cube = new THREE.Mesh(boxGeometry, shaderMaterial);
This configuration instructs Three.js to compile and execute the provided GLSL shaders for the cube object, overriding default material properties.
Complete Code Example
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>MDN Games: Shaders demo</title>
<style>
body {
margin: 0;
padding: 0;
font-size: 0;
}
canvas {
width: 100%;
height: 100%;
}
</style>
<script src="https://end3r.github.io/MDN-Games-3D/Shaders/js/three.min.js"></script>
</head>
<body>
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x+10.0, position.y, position.z+5.0, 1.0);
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(0.0, 0.58, 0.86, 1.0);
}
</script>
<script>
const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(WIDTH, HEIGHT);
renderer.setClearColor(0xdddddd, 1);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(70, WIDTH / HEIGHT);
camera.position.z = 50;
scene.add(camera);
const boxGeometry = new THREE.BoxGeometry(10, 10, 10);
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: document.getElementById("vertexShader").textContent,
fragmentShader: document.getElementById("fragmentShader").textContent,
});
const cube = new THREE.Mesh(boxGeometry, shaderMaterial);
scene.add(cube);
cube.rotation.set(0.4, 0.2, 0);
function render() {
requestAnimationFrame(render);
renderer.render(scene, camera);
}
render();
</script>
</body>
</html>