Introduction

Welcome back to OpenGL Fundamentals: Your First Triangle! In our previous lesson, we successfully set up vertex data and uploaded our triangle geometry to the GPU. However, as we mentioned at the end, our window remained blank despite all that careful preparation. That's because we're missing a crucial piece of the puzzle: shaders.

Today, in our third lesson, we'll write our first GPU programs that will finally bring our triangle to life on the screen. Shaders are small programs written in GLSL (OpenGL Shading Language) that run directly on the graphics card, telling it exactly how to process our vertex data and determine the final pixel colors. We'll create two essential shaders: a vertex shader to position our triangle and a fragment shader to color it. By the end of this lesson, we'll have a complete rendering pipeline that transforms our vertex data into visible pixels.

Understanding the Programmable Pipeline

Modern OpenGL operates on a programmable pipeline, which means we have direct control over how the GPU processes our geometry. This is fundamentally different from older fixed-function pipelines, where rendering behavior was predetermined. With programmable shaders, we can customize every step of the rendering process.

The two most essential stages we need to program are the vertex stage and the fragment stage. The vertex shader runs once for each vertex in our triangle, transforming 3D positions and preparing data for the next stages. The fragment shader then runs for each pixel that needs to be colored, determining the final color output. Between these stages, the GPU automatically handles tasks like connecting vertices into triangles and determining which pixels are covered.

Think of shaders as specialized functions: the vertex shader is like a position calculator that decides where each corner of our triangle appears on screen (this gets mapped onto the normalized OpenGL coordinate space!), while the fragment shader is like a paint bucket that decides what color to fill each pixel. Both work together to transform our simple array of numbers into visible geometry.

Writing Our Vertex Shader

Let's create our first shader file: the vertex shader that will process our triangle's position data. This shader needs to take our input vertices and transform them into the format OpenGL expects.

glsl
#version 450 core
layout(location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0);
}

Every GLSL shader begins with a version declaration specifying which version of the shading language we're using. Version 450 core corresponds to OpenGL 4.5 and uses the modern core profile, avoiding any deprecated features. The layout(location = 0) directive tells OpenGL this input variable corresponds to vertex attribute 0, which matches how we configured our vertex attributes in the previous lesson with glVertexAttribPointer(0, ...).

The in vec3 aPos declares an input variable that receives our 3D position data for each vertex. Inside the main() function, we set gl_Position to a 4D vector created from our 3D position plus a w component of 1.0. This fourth component is required for homogeneous coordinates, which OpenGL uses internally for transformations. For now, we're simply passing our vertex positions through unchanged.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal