Your First Shaders
Shadertoy provides iResolution, iTime, iMouse, and expects mainImage:
4.1 Shadertoy: a gradient
Shadertoy provides iResolution, iTime, iMouse, and expects mainImage:
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy; // normalized pixel coordinates (0..1)
vec3 col = 0.5 + 0.5 * cos(iTime + uv.xyx + vec3(0, 2, 4)); // animated colors
fragColor = vec4(col, 1.0);
}4.2 A minimal OpenGL vertex + fragment shader pair
#version 330 core
layout (location = 0) in vec3 aPos; // vertex attribute 0: position
layout (location = 1) in vec3 aColor; // vertex attribute 1: color
out vec3 vColor; // passed to the fragment shader (interpolated)
void main() {
vColor = aColor;
gl_Position = vec4(aPos, 1.0); // already in clip space for this example
}#version 330 core
in vec3 vColor; // interpolated from the vertex shader
out vec4 FragColor; // color written to the framebuffer
void main() {
FragColor = vec4(vColor, 1.0);
}4.3 Host code (C++ with GLFW + GLAD) — compile, link, draw
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <cstdio>
const char* vsSrc = R"(#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos, 1.0); })";
const char* fsSrc = R"(#version 330 core
in vec3 vColor;
out vec4 FragColor;
void main() { FragColor = vec4(vColor, 1.0); })";
GLuint compile(GLenum type, const char* src) {
GLuint s = glCreateShader(type);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
GLint ok; glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) { char log[1024]; glGetShaderInfoLog(s, 1024, nullptr, log); printf("Shader error:\n%s\n", log); }
return s;
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* win = glfwCreateWindow(800, 600, "GLSL Triangle", nullptr, nullptr);
glfwMakeContextCurrent(win);
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
GLuint prog = glCreateProgram();
GLuint vs = compile(GL_VERTEX_SHADER, vsSrc), fs = compile
…4.4 WebGL 2 (JavaScript) — the same triangle
<canvas id="c" width="640" height="480"></canvas>
<script>
const gl = document.getElementById("c").getContext("webgl2");
const vs = `#version 300 es
in vec2 aPos;
in vec3 aColor;
out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos, 0.0, 1.0); }`;
const fs = `#version 300 es
precision highp float;
in vec3 vColor;
out vec4 outColor;
void main() { outColor = vec4(vColor, 1.0); }`;
function compile(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) console.error(gl.getShaderInfoLog(s));
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vs));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fs));
gl.linkProgram(prog);
const data = new Float32Array([-0.5,-0.5, 1,0,0, 0.5,-0.5, 0,1,0, 0,0.5, 0,0,1]);
const vao = gl.createVertexArray(); gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const posLoc = gl.getAttribLocation(prog, "aPos"), colLoc = gl.getAttribLocation(prog, "aColor");
gl.enableVertexAttribArray(posLoc); gl.vertexAttribPointer(posLoc,
…4.5 A fullscreen shader without vertex buffers
A common trick: generate a fullscreen triangle from gl_VertexID:
#version 330 core
out vec2 vUV;
void main() {
vec2 pos = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); // (0,0), (2,0), (0,2)
vUV = pos;
gl_Position = vec4(pos * 2.0 - 1.0, 0.0, 1.0); // covers the whole screen
}
// Host: bind an empty VAO and call glDrawArrays(GL_TRIANGLES, 0, 3)