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)