Day 3 Capstone · 5 Labs · Maritime Surveillance Scene
Maritime Surveillance Scene
Five labs that apply every Day 3 technique to a single naval surveillance scenario. Lab 1 builds a procedural sonar display from pure GLSL math. Lab 2 places 2,000 instanced contact markers. Lab 3 wraps the scene in FBO post-processing. Lab 4 adds stencil selection outline and transparent sweep blending. Lab 5 combines all four into one complete scene. Every lab is one complete main.cpp.
Procedural GLSL Instancing FBO Post-Processing Stencil Buffer Alpha Blending
📁 Project Setup — One Folder, All 5 Labs

All 5 labs share one folder: C:\Labs\SurveillanceScene\. CMakeLists.txt is written once and never changes. Each lab replaces only src\main.cpp and rebuilds.

Project Structure
C:\Labs\SurveillanceScene\
├── CMakeLists.txt     <-- write once, never change
└── src\
    └── main.cpp           <-- replace with each lab's complete file
CMakeLists.txt — All 5 Labs
CMake
cmake_minimum_required(VERSION 3.20)
project(SurveillanceScene)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenGL REQUIRED)
set(GLFW_DIR $ENV{GLFW_DIR})
set(GLEW_DIR $ENV{GLEW_DIR})
set(GLM_DIR  $ENV{GLM_DIR})
include_directories(
    ${GLFW_DIR}/include
    ${GLEW_DIR}/include
    ${GLM_DIR}
)
add_executable(SurveillanceScene src/main.cpp)
target_link_libraries(SurveillanceScene
    OpenGL::GL
    ${GLFW_DIR}/lib-vc2022/glfw3.lib
    ${GLEW_DIR}/lib/Release/x64/glew32s.lib
)
Build & Run — Developer Command Prompt
cd C:\Labs\SurveillanceScene
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
build\Release\SurveillanceScene.exe
Lab 1 of 5 ⬛ Beginner · ~30 min
Procedural Sonar Display — Zero Texture Files
Build a naval sonar display entirely inside the fragment shader. A fullscreen quad carries the visual: range rings from fract(), 8 bearing lines from mod(), a rotating sweep arc from atan() and mod(), and 4 pulsing contact blips from length(). No image files, no texture uploads, no per-vertex colour — pure math per pixel.
📋 Demo 11: Procedural Radar ⏱ ~30 min New: fract · atan · mod · smoothstep · fullscreen quad
➕ What You Build in This Lab
  • Navy-blue sonar display (circular, fullscreen) — deep sea colour palette
  • Range rings via fract(dist * uRingCount) — R key cycles count
  • 8 bearing lines every 45° via mod(angle, π/4)
  • Rotating sweep arc with fading trail: atan() + mod() + smoothstep()
  • 4 contacts: friendly (slow blue), hostile (fast red), unknown ×2 (yellow)
  • H key: toggle hard (step) vs soft (smoothstep) edges — live aliasing demo
🎯 What You Will See
Screen: Navy-green sonar display, range rings, bearing lines, rotating sweep, 4 pulsing contacts
R key: Ring count cycles 1→4→8→12 — fract() tiling density changes live
H key: Hard edges (step) vs smooth (smoothstep) — aliased jagged rings appear
B key: Toggle bearing lines — isolate the mod() angle pattern
S key: Toggle sweep arc — isolate the atan()/mod() animation
💡 Why the Fullscreen Quad Has No MVP Matrix

The quad vertices are in NDC: (-1,-1) to (1,1). They cover the entire screen with no transformation. The vertex shader passes them through unchanged. All visual computation happens in the fragment shader — the geometry is just a carrier. Same pattern used in Labs 3, 4, 5 for the FBO second pass.

Objectives — Check Off As You Complete
  • Fullscreen quad VAO+VBO: 6 vertices, 2 triangles, NDC positions + UV (0→1)
  • Fragment shader: fract() tiling range rings at uRingCount density
  • Fragment shader: mod(angle, π/4) bearing lines every 45°
  • Fragment shader: atan(uv.y, uv.x) polar angle for sweep
  • Fragment shader: mod(uTime*speed, 2π) rotating sweep with smoothstep trail
  • Fragment shader: 4 blip() calls with different positions and pulse speeds
  • H key shows visible aliasing when step() replaces smoothstep()
  • R, B, S keys toggle features independently
src/main.cpp — Lab 1 Complete File
src/main.cpp — Lab 1: Procedural Sonar
C++
// ═══════════════════════════════════════════════════════════════════════════
//  RR GRAPHICS LAB — DAY 3 · LAB 1 of 5
//  Maritime Surveillance Scene — Procedural GLSL Sonar Display
//  By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
//  PROJECT NAME: SurveillanceScene  (same folder for all 5 labs)
//  FOLDER:       C:\Labs\SurveillanceScene\
//
//  WHAT THIS LAB DOES:
//  Builds a procedural sonar/radar display using only GLSL math — no image
//  files, no texture uploads. A fullscreen quad carries the fragment shader
//  that computes the entire visual: range rings (fract), bearing lines (mod/atan),
//  rotating sonar sweep (atan+mod+smoothstep), and 4 contact blips with
//  independent pulse rates. You add one new function at a time and observe
//  exactly what each GLSL built-in contributes.
//
//  WHAT YOU WILL SEE:
//  - Navy-blue/green sonar display (circular, fullscreen)
//  - Range rings tiling outward from centre via fract()
//  - 8 bearing lines at 45° intervals via mod(angle, π/4)
//  - Rotating sonar sweep with fading trail (atan + mod + smoothstep)
//  - 4 contact blips at fixed bearings with different pulse speeds
//  - Contacts pulse: friendly (slow blue), hostile (fast red), unknown (yellow)
//
//  KEYS:
//  R        — change ring count  (1 → 4 → 8 → 12 → back to 1)
//  B        — toggle bearing lines on/off
//  S        — toggle sweep on/off
//  H        — toggle hard (step) vs soft (smoothstep) edges
//  ESC      — quit
//
//  BUILDS ON: Demo 11 (procedural radar shader)
//  YOUR JOB:  Try writing each section of the fragment shader from memory
//             before looking at the solution. The Demo 11 code is your reference.
// ═══════════════════════════════════════════════════════════════════════════

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <cmath>

// ─────────────────────────────────────────────────────────────────────────────
// VERTEX SHADER — fullscreen quad, no MVP, just pass UV through
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
    #version 330 core
    layout(location = 0) in vec2 aPos;
    layout(location = 1) in vec2 aUV;
    out vec2 vUV;
    void main() {
        gl_Position = vec4(aPos, 0.0, 1.0);
        vUV = aUV;
    }
)GLSL";

// ─────────────────────────────────────────────────────────────────────────────
// FRAGMENT SHADER — the entire sonar display computed per pixel
//
// Every function in here is a teachable unit:
//   ring()      → fract() tiling pattern
//   bearing()   → mod() repeating angle grid
//   sweep arc   → atan() polar angle + mod() wrap + smoothstep() fade
//   blip()      → length() distance + smoothstep() soft circle
//
// Uniforms:
//   uTime       → seconds since start (animation driver)
//   uRingCount  → how many range rings
//   uUseSoft    → 1=smoothstep edges, 0=step edges (aliasing demo)
//   uShowBearing→ 1=show bearing lines, 0=hide
//   uShowSweep  → 1=show sonar sweep, 0=hide
// ─────────────────────────────────────────────────────────────────────────────
const char* fragSrc = R"GLSL(
    #version 330 core
    in vec2 vUV;
    out vec4 FragColor;

    uniform float uTime;
    uniform float uRingCount;
    uniform int   uUseSoft;
    uniform int   uShowBearing;
    uniform int   uShowSweep;

    // ── EDGE FUNCTION ─────────────────────────────────────────────────────────
    // Returns 0→1 transition at threshold t, soft or hard depending on uUseSoft
    float edge(float lo, float hi, float x) {
        return (uUseSoft == 1) ? smoothstep(lo, hi, x) : step((lo+hi)*0.5, x);
    }

    // ── RANGE RING ────────────────────────────────────────────────────────────
    // fract(dist * ringCount): repeating 0→1 pattern creates concentric rings
    // Each ring = a thin bright band where fract is near 0 or 1
    float rangeRing(float dist) {
        float r = fract(dist * uRingCount);
        // Thin bright band at the "zero crossing" of the fract pattern
        float ring = (1.0 - edge(0.0, 0.04, r)) + (1.0 - edge(0.96, 1.0, r));
        return clamp(ring, 0.0, 1.0) * (1.0 - edge(0.46, 0.50, dist)); // fade at edge
    }

    // ── BEARING LINE ──────────────────────────────────────────────────────────
    // mod(angle + π, π/4): creates a repeating pattern every 45°
    // Returns 1.0 on each of 8 bearing lines, 0.0 between them
    float bearingLine(float angle, float dist) {
        float period = 3.14159 / 4.0;   // π/4 = 45 degrees in radians
        float a = mod(angle + 3.14159, period);  // 0→period repeating
        // Thin line where a is near 0 or near period (the bearing direction)
        float line = 1.0 - edge(0.04, 0.08, min(a, period - a));
        return line * (1.0 - edge(0.02, 0.05, dist));  // no line at centre
    }

    // ── CONTACT BLIP ─────────────────────────────────────────────────────────
    // Soft circle centred at pos, radius r, pulsing at rate pulseSpeed
    float blip(vec2 uv, vec2 pos, float r, float pulseSpeed) {
        float d    = length(uv - pos);
        float base = 1.0 - edge(r * 0.7, r, d);  // soft circle
        float pulse = 0.75 + 0.25 * sin(uTime * pulseSpeed);
        return base * pulse;
    }

    void main() {
        // Centre UV at (0,0): range -0.5 to +0.5
        vec2 uv   = vUV - 0.5;
        float dist  = length(uv);
        float angle = atan(uv.y, uv.x);  // polar angle: -π to +π

        // Circular display boundary — discard outside
        float inDisp = 1.0 - edge(0.46, 0.50, dist);
        if (inDisp < 0.01) {
            // Dark ring around the display
            FragColor = vec4(0.01, 0.02, 0.06, 1.0);
            return;
        }

        // ── BASE COLOUR: deep navy with slight green phosphor tint ─────────
        vec3 col = mix(vec3(0.01, 0.04, 0.10), vec3(0.02, 0.10, 0.06), inDisp);

        // ── OUTER BOUNDARY RING ────────────────────────────────────────────
        float outerRing = 1.0 - edge(0.44, 0.46, dist);
        outerRing *= edge(0.42, 0.44, dist);
        col += outerRing * vec3(0.05, 0.7, 0.3);

        // ── RANGE RINGS ────────────────────────────────────────────────────
        float rings = rangeRing(dist / 0.46);   // normalise dist to display radius
        col += rings * 0.10 * vec3(0.05, 0.8, 0.35);

        // ── BEARING LINES ──────────────────────────────────────────────────
        if (uShowBearing == 1) {
            float bearing = bearingLine(angle, dist);
            col += bearing * 0.06 * vec3(0.05, 0.6, 0.25);
        }

        // ── CENTRE DOT ─────────────────────────────────────────────────────
        col += (1.0 - edge(0.003, 0.010, dist)) * vec3(0.2, 1.0, 0.5);

        // ── SONAR SWEEP ARC ───────────────────────────────────────────────
        if (uShowSweep == 1) {
            float sweepAngle = mod(uTime * 0.7, 6.2832);  // rotate at 0.7 rad/s
            float angleDiff  = mod(sweepAngle - angle + 6.2832, 6.2832);
            float trail      = (1.0 - smoothstep(0.0, 1.1, angleDiff))
                             * (1.0 - edge(0.0, 0.46, dist));
            float head       = (1.0 - smoothstep(0.0, 0.05, angleDiff));
            col += trail * vec3(0.04, 0.55, 0.20);   // green trail
            col += head  * vec3(0.10, 1.00, 0.40);   // bright head
        }

        // ── CONTACT BLIPS ─────────────────────────────────────────────────
        // 4 contacts at different bearings and ranges
        // Friendly (blue-white, slow pulse), Hostile (red, fast pulse),
        // Unknown ×2 (yellow, medium pulse)
        float b1 = blip(uv, vec2( 0.22,  0.08), 0.018, 1.5);  // Friendly
        float b2 = blip(uv, vec2(-0.18, -0.25), 0.018, 4.0);  // Hostile — fast
        float b3 = blip(uv, vec2( 0.05,  0.30), 0.018, 2.5);  // Unknown 1
        float b4 = blip(uv, vec2(-0.28,  0.15), 0.018, 2.0);  // Unknown 2

        col += b1 * vec3(0.4, 0.7, 1.0);   // blue-white (friendly)
        col += b2 * vec3(1.0, 0.2, 0.2);   // red (hostile)
        col += b3 * vec3(1.0, 0.9, 0.1);   // yellow (unknown)
        col += b4 * vec3(1.0, 0.8, 0.1);   // yellow (unknown)

        // ── VIGNETTE: slight darkening at the display edges ────────────────
        float vig = 1.0 - smoothstep(0.30, 0.46, dist) * 0.4;
        col *= vig;

        FragColor = vec4(col, 1.0);
    }
)GLSL";

unsigned int compileShader(unsigned int type, const char* src) {
    unsigned int id = glCreateShader(type);
    glShaderSource(id, 1, &src, NULL); glCompileShader(id);
    int ok; char log[512];
    glGetShaderiv(id, GL_COMPILE_STATUS, &ok);
    if (!ok) { glGetShaderInfoLog(id, 512, NULL, log); std::cerr << "Shader error:\n" << log; }
    return id;
}
unsigned int createShaderProgram(const char* vs, const char* fs) {
    unsigned int v = compileShader(GL_VERTEX_SHADER, vs);
    unsigned int f = compileShader(GL_FRAGMENT_SHADER, fs);
    unsigned int p = glCreateProgram();
    glAttachShader(p, v); glAttachShader(p, f); glLinkProgram(p);
    glDeleteShader(v); glDeleteShader(f); return p;
}
void onResize(GLFWwindow* w, int W, int H) { glViewport(0, 0, W, H); }
void processInput(GLFWwindow* w) {
    if (glfwGetKey(w, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(w, true);
}

int main() {
    std::cout << "\n=== Day 3 Lab 1 — Procedural Sonar Display ===\n\n";
    std::cout << "R=ring count | B=bearing lines | S=sweep | H=soft/hard | ESC=quit\n\n";

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window = glfwCreateWindow(768, 768,
        "Surveillance Scene — Lab 1: Procedural Sonar", NULL, NULL);
    if (!window) { glfwTerminate(); return -1; }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, onResize);
    glewExperimental = GL_TRUE; glewInit();
    std::cout << "GPU: " << glGetString(GL_RENDERER) << "\n\n";

    unsigned int shader = createShaderProgram(vertSrc, fragSrc);
    std::cout << "[1] Sonar shader compiled. ID = " << shader << "\n";

    // Fullscreen quad: 2 triangles, NDC coords (-1→1) + UV (0→1)
    float quad[] = {
        -1.0f,-1.0f, 0.0f,0.0f,
         1.0f,-1.0f, 1.0f,0.0f,
         1.0f, 1.0f, 1.0f,1.0f,
         1.0f, 1.0f, 1.0f,1.0f,
        -1.0f, 1.0f, 0.0f,1.0f,
        -1.0f,-1.0f, 0.0f,0.0f
    };
    unsigned int VAO, VBO;
    glGenVertexArrays(1, &VAO); glBindVertexArray(VAO);
    glGenBuffers(1, &VBO); glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void*)(2*sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);
    std::cout << "[2] Fullscreen quad VAO ready\n";

    int uTimeLoc    = glGetUniformLocation(shader, "uTime");
    int uRingsLoc   = glGetUniformLocation(shader, "uRingCount");
    int uSoftLoc    = glGetUniformLocation(shader, "uUseSoft");
    int uBearLoc    = glGetUniformLocation(shader, "uShowBearing");
    int uSweepLoc   = glGetUniformLocation(shader, "uShowSweep");
    std::cout << "[3] Render loop. R=rings B=bearing S=sweep H=edges ESC=quit\n\n";

    // Ring count cycles: 1 → 4 → 8 → 12 → back to 1
    float ringCounts[] = {1.0f, 4.0f, 8.0f, 12.0f};
    int ringIdx = 1;
    int useSoft = 1, showBearing = 1, showSweep = 1;
    bool rPrev = false, bPrev = false, sPrev = false, hPrev = false;

    while (!glfwWindowShouldClose(window)) {
        processInput(window);

        bool rNow = (glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS);
        bool bNow = (glfwGetKey(window, GLFW_KEY_B) == GLFW_PRESS);
        bool sNow = (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS);
        bool hNow = (glfwGetKey(window, GLFW_KEY_H) == GLFW_PRESS);

        if (rNow && !rPrev) {
            ringIdx = (ringIdx + 1) % 4;
            std::cout << "Ring count: " << (int)ringCounts[ringIdx] << "\n";
        }
        if (bNow && !bPrev) { showBearing = 1-showBearing; std::cout << (showBearing ? "Bearing ON\n" : "Bearing OFF\n"); }
        if (sNow && !sPrev) { showSweep = 1-showSweep; std::cout << (showSweep ? "Sweep ON\n" : "Sweep OFF\n"); }
        if (hNow && !hPrev) { useSoft = 1-useSoft; std::cout << (useSoft ? "Soft edges\n" : "Hard edges (step)\n"); }
        rPrev=rNow; bPrev=bNow; sPrev=sNow; hPrev=hNow;

        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader);

        glUniform1f(uTimeLoc,  (float)glfwGetTime());
        glUniform1f(uRingsLoc, ringCounts[ringIdx]);
        glUniform1i(uSoftLoc,  useSoft);
        glUniform1i(uBearLoc,  showBearing);
        glUniform1i(uSweepLoc, showSweep);

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shader);
    glfwTerminate();
    return 0;
}
Expected Terminal Output
[1] Sonar shader compiled. ID = 1
[2] Fullscreen quad VAO ready
[3] Render loop. R=rings B=bearing S=sweep H=edges ESC=quit
Ring count: 8
Hard edges (step) <-- H key: aliasing appears on ring edges
Screen: Navy sonar display with rings, sweep, 4 colour-coded contacts
🔬 Experiments
  • Press H to compare edges: Watch ring boundaries shift from smooth anti-aliased curves to jagged aliased steps. This is the clearest demonstration of why smoothstep() is not optional in production shaders.
  • Increase sweep speed: In the fragment shader change uTime * 0.7 to uTime * 3.0. Rebuild — fast search-pattern sweep. Reduce to 0.1 for slow long-range sonar.
  • Move the hostile contact: Change blip 2 position from vec2(-0.18, -0.25) to vec2(0.30, -0.10). Rebuild — contact moves to new bearing.
✓ Lab 1 Deliverable
Complete sonar display from pure GLSL math
  • Circular navy/green display with range rings visible
  • 8 bearing lines at 45° intervals
  • Rotating sweep with green trail
  • 4 pulsing contacts (blue/red/yellow)
  • H key: aliasing clearly visible when smoothstep replaced with step
Lab 2 of 5 ⬛⬛ Intermediate · ~35 min
2,000 Instanced Buoy Markers
Switches to a 3D scene with a fly camera. 500–2,000 buoy markers are placed across the surveillance area with a single glDrawArraysInstanced() call. Per-instance data (XZ position + threat type) comes from a second VBO. gl_InstanceID drives a unique vertical bob phase per buoy — simulating ocean wave motion. Toggle instanced vs non-instanced to see the frame-time difference in the terminal.
📋 Demo 12: Instancing ⏱ ~35 min New: two per-instance attributes · gl_InstanceID bob · FPS measurement
➕ What This Lab Adds Over Lab 1
  • Fly camera: WASD + Q/E height + mouse look + scroll zoom
  • 2 per-instance attributes in one VBO: XZ position (vec2) + threat type (float)
  • stride = sizeof(InstData) = 12 bytes; attr 1 at offset 0, attr 2 at offset 8
  • gl_InstanceID drives unique bob phase — each buoy moves differently
  • I key: instanced vs loop — terminal shows FPS + draw call count each second
  • + / - keys: 100 → 2,000 instances — watch non-instanced degrade first
🎯 What You Will See
Screen: Dark sea area with 500 diamond buoys. Blue/red/yellow colour coding by threat type.
Each buoy: Bobs vertically at its own phase — sea looks alive
I key: Instanced (1 call, 60 fps at 2000) vs non-instanced (N calls, visible drop)
+ key: Step up to 2,000 — instanced holds, non-instanced struggles
Three Attributes From Two VBOs — Stride and Divisor Layout
  Geometry VBO (per-vertex, divisor=0):    Instance VBO (per-instance, divisor=1):
  vertex[0]: (0, 1, 0)  -- top point        instance[0]: x=-12.4, z=8.2, type=1.0
  vertex[1]: (0.5,0,0.5)-- right-front      instance[1]: x= 3.7,  z=-5.8,type=0.0
  vertex[2]: (0, 1, 0)                      instance[2]: x= 8.1,  z=11.3, type=2.0
  ...                                       (2000 unique positions)

  struct InstData { float x, z, type; };  // stride=12 bytes
  glVertexAttribDivisor(1, 1)  <- XZ   advances once per instance
  glVertexAttribDivisor(2, 1)  <- type advances once per instance

  In vertex shader:
  float bobY = sin(uTime * 1.2 + float(gl_InstanceID) * 0.73) * 0.04;
Objectives — Check Off As You Complete
  • Two VBOs: geoVBO (geometry, divisor=0) + instVBO (XZ+type, divisor=1)
  • instVBO stride = sizeof(InstData) = 12 bytes. Attr 1 at offset 0, attr 2 at offset 8
  • glVertexAttribDivisor(1,1) and glVertexAttribDivisor(2,1) both called
  • Vertex shader: float(gl_InstanceID)*0.73 gives unique bob phase per buoy
  • I key toggles instanced/non-instanced, terminal shows FPS every second
  • WASD + Q/E + mouse fly camera fully functional
src/main.cpp — Lab 2 Complete File
src/main.cpp — Lab 2: Instanced Buoys
C++
// ═══════════════════════════════════════════════════════════════════════════
//  RR GRAPHICS LAB — DAY 3 · LAB 2 of 5
//  Maritime Surveillance Scene — 2,000 Instanced Buoy Markers
//  By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
//  PROJECT NAME: SurveillanceScene
//  FOLDER:       C:\Labs\SurveillanceScene\
//
//  WHAT THIS LAB ADDS OVER LAB 1:
//  - Switches from fullscreen quad to a real 3D scene (fly camera from Day 2)
//  - 2,000 floating buoy markers drawn with ONE glDrawArraysInstanced call
//  - Per-instance VBO: position (XZ plane) + threat type (float for colour)
//  - gl_InstanceID used to compute a unique Y-axis bob (wave simulation)
//  - I key: toggle instanced vs non-instanced for frame time comparison
//  - + / - keys: scale instance count from 100 to 2,000
//  - Terminal: fps + draw call count printed every second
//
//  WHAT YOU WILL SEE:
//  - Dark tactical background
//  - 2,000 small diamond marker shapes spread across the sea area
//  - Blue (friendly) / Red (hostile) / Yellow (unknown) colour coding
//  - Each marker bobs slightly up and down with a unique phase offset
//  - Fly camera (WASD + mouse) lets you inspect from any angle
//
//  BUILDS ON: Demo 12 (instancing) + Day 2 Lab 2 (fly camera)
// ═══════════════════════════════════════════════════════════════════════════

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include <chrono>

// ─────────────────────────────────────────────────────────────────────────────
// INSTANCED VERTEX SHADER
//
// Three attribute sources:
//   location 0: aPos           (per-vertex, XYZ, the diamond shape)
//   location 1: aInstXZ        (per-instance, vec2, world X/Z position)
//   location 2: aInstType      (per-instance, float, 0=friendly 1=hostile 2=unknown)
//
// gl_InstanceID drives the bob phase so each buoy moves at a different rate
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
    #version 330 core
    layout(location = 0) in vec3 aPos;       // per-vertex diamond geometry
    layout(location = 1) in vec2 aInstXZ;    // per-instance world position (X, Z)
    layout(location = 2) in float aInstType; // per-instance type: 0/1/2

    out vec3 vColour;

    uniform mat4 uView;
    uniform mat4 uProjection;
    uniform float uTime;

    void main() {
        // Unique bob phase per instance using gl_InstanceID
        // Different instances start at different points in their sine wave
        float phase  = float(gl_InstanceID) * 0.73;   // arbitrary irrational offset
        float bobY   = sin(uTime * 1.2 + phase) * 0.04; // ±0.04 units vertical bob

        // Final world position: per-instance XZ + bobbing Y
        vec3 worldPos = vec3(aInstXZ.x, bobY, aInstXZ.y) + aPos * 0.08;
        //                   ↑ instance position            ↑ scaled marker geometry

        // No model matrix needed — instances are placed directly in world space
        gl_Position = uProjection * uView * vec4(worldPos, 1.0);

        // Per-instance colour from type attribute (0=friendly, 1=hostile, 2=unknown)
        int type = int(aInstType + 0.5);  // round to nearest int
        if      (type == 0) vColour = vec3(0.20, 0.55, 1.00); // blue  — friendly
        else if (type == 1) vColour = vec3(1.00, 0.20, 0.20); // red   — hostile
        else                vColour = vec3(1.00, 0.85, 0.10); // yellow — unknown
    }
)GLSL";

const char* fragSrc = R"GLSL(
    #version 330 core
    in vec3 vColour;
    out vec4 FragColor;
    void main() { FragColor = vec4(vColour, 1.0); }
)GLSL";

unsigned int compileShader(unsigned int t, const char* s) {
    unsigned int id = glCreateShader(t);
    glShaderSource(id,1,&s,NULL); glCompileShader(id);
    int ok; char log[512];
    glGetShaderiv(id,GL_COMPILE_STATUS,&ok);
    if(!ok){glGetShaderInfoLog(id,512,NULL,log);std::cerr<<log;}
    return id;
}
unsigned int mkProg(const char* vs, const char* fs) {
    unsigned int v=compileShader(GL_VERTEX_SHADER,vs), f=compileShader(GL_FRAGMENT_SHADER,fs);
    unsigned int p=glCreateProgram();
    glAttachShader(p,v); glAttachShader(p,f); glLinkProgram(p);
    glDeleteShader(v); glDeleteShader(f); return p;
}

glm::vec3 camPos(0,4.0f,18.0f), camFront(0,-0.2f,-1), camUp(0,1,0);
float yaw=-90, pitch=-10, lastX=450, lastY=300, fov=50;
bool firstMouse=true;

void mouseCallback(GLFWwindow* w, double x, double y) {
    if(firstMouse){lastX=(float)x;lastY=(float)y;firstMouse=false;}
    float dx=((float)x-lastX)*0.1f, dy=(lastY-(float)y)*0.1f;
    lastX=(float)x; lastY=(float)y; yaw+=dx; pitch+=dy;
    if(pitch>89)pitch=89; if(pitch<-89)pitch=-89;
    glm::vec3 f;
    f.x=cos(glm::radians(yaw))*cos(glm::radians(pitch));
    f.y=sin(glm::radians(pitch));
    f.z=sin(glm::radians(yaw))*cos(glm::radians(pitch));
    camFront=glm::normalize(f);
}
void scrollCallback(GLFWwindow* w, double xo, double yo) {
    fov-=(float)yo; if(fov<5)fov=5; if(fov>90)fov=90;
}
void onResize(GLFWwindow* w,int W,int H){glViewport(0,0,W,H);}

bool useInstancing=true, iPrev=false, plusPrev=false, minusPrev=false;
int instanceCount = 500;

void processInput(GLFWwindow* w, float dt) {
    if(glfwGetKey(w,GLFW_KEY_ESCAPE)==GLFW_PRESS) glfwSetWindowShouldClose(w,true);
    float s=8.0f*dt;
    if(glfwGetKey(w,GLFW_KEY_W)==GLFW_PRESS) camPos+=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_S)==GLFW_PRESS) camPos-=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_A)==GLFW_PRESS) camPos-=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_D)==GLFW_PRESS) camPos+=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_Q)==GLFW_PRESS) camPos.y+=s;
    if(glfwGetKey(w,GLFW_KEY_E)==GLFW_PRESS) camPos.y-=s;

    bool iNow=(glfwGetKey(w,GLFW_KEY_I)==GLFW_PRESS);
    bool plusNow=(glfwGetKey(w,GLFW_KEY_EQUAL)==GLFW_PRESS);
    bool minusNow=(glfwGetKey(w,GLFW_KEY_MINUS)==GLFW_PRESS);
    if(iNow&&!iPrev){useInstancing=!useInstancing;std::cout<<(useInstancing?"INSTANCED\n":"NON-INSTANCED\n");}
    if(plusNow&&!plusPrev){instanceCount=std::min(instanceCount+250,2000);std::cout<<"Count: "<<instanceCount<<"\n";}
    if(minusNow&&!minusPrev){instanceCount=std::max(instanceCount-250,100);std::cout<<"Count: "<<instanceCount<<"\n";}
    iPrev=iNow; plusPrev=plusNow; minusPrev=minusNow;
}

int main() {
    std::cout << "\n=== Day 3 Lab 2 — 2,000 Instanced Buoy Markers ===\n\n";
    std::cout << "I=toggle instanced | +/-=count | WASD+Q/E=fly | Mouse=look | ESC=quit\n\n";

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window=glfwCreateWindow(1024,680,
        "Surveillance Scene — Lab 2: Instancing",NULL,NULL);
    if(!window){glfwTerminate();return-1;}
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window,onResize);
    glfwSetCursorPosCallback(window,mouseCallback);
    glfwSetScrollCallback(window,scrollCallback);
    glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);
    glewExperimental=GL_TRUE; glewInit();
    glEnable(GL_DEPTH_TEST);
    std::cout << "GPU: " << glGetString(GL_RENDERER) << "\n\n";

    unsigned int shader = mkProg(vertSrc, fragSrc);
    std::cout << "[1] Shader compiled. ID = " << shader << "\n";

    // ── BUOY MARKER GEOMETRY (per-vertex, attribute 0) ────────────────────────
    // Small diamond shape (4-pointed star cross-section)
    // Centered at origin, rendered at 8% scale in vertex shader (aPos * 0.08)
    float markerVerts[] = {
    //   X      Y      Z
         0.0f,  1.0f,  0.0f,  // top
         0.5f,  0.0f,  0.5f,  // right-front
         0.0f,  1.0f,  0.0f,  // top (repeat for triangle fan)
         0.5f,  0.0f, -0.5f,  // right-back
         0.0f,  1.0f,  0.0f,
        -0.5f,  0.0f, -0.5f,  // left-back
         0.0f,  1.0f,  0.0f,
        -0.5f,  0.0f,  0.5f,  // left-front
         0.0f,  1.0f,  0.0f,
         0.5f,  0.0f,  0.5f,  // close loop
         // bottom half (mirror)
         0.0f, -0.6f,  0.0f,
         0.5f,  0.0f,  0.5f,
         0.0f, -0.6f,  0.0f,
         0.5f,  0.0f, -0.5f,
         0.0f, -0.6f,  0.0f,
        -0.5f,  0.0f, -0.5f,
         0.0f, -0.6f,  0.0f,
        -0.5f,  0.0f,  0.5f,
         0.0f, -0.6f,  0.0f,
         0.5f,  0.0f,  0.5f,
    };
    int markerVertCount = sizeof(markerVerts) / (3 * sizeof(float));

    unsigned int geoVBO;
    glGenBuffers(1,&geoVBO);
    glBindBuffer(GL_ARRAY_BUFFER,geoVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(markerVerts),markerVerts,GL_STATIC_DRAW);
    std::cout << "[2] Geometry VBO: " << sizeof(markerVerts) << " bytes (" << markerVertCount << " vertices)\n";

    // ── INSTANCE DATA: XZ position + type (per-instance, attributes 1 & 2) ───
    const int MAX_INST = 2000;
    std::mt19937 rng(77);
    std::uniform_real_distribution<float> posDist(-20.0f, 20.0f);
    std::uniform_int_distribution<int>    typeDist(0, 2);

    struct InstData { float x, z, type; };
    std::vector<InstData> instData(MAX_INST);
    for (auto& d : instData) {
        d.x = posDist(rng); d.z = posDist(rng);
        d.type = (float)typeDist(rng);
    }

    unsigned int instVBO;
    glGenBuffers(1,&instVBO);
    glBindBuffer(GL_ARRAY_BUFFER,instVBO);
    glBufferData(GL_ARRAY_BUFFER,MAX_INST*sizeof(InstData),instData.data(),GL_STATIC_DRAW);
    std::cout << "[3] Instance VBO: " << MAX_INST << " entries ("
              << MAX_INST*sizeof(InstData) << " bytes)\n";

    // ── VAO SETUP: two VBOs, three attributes ─────────────────────────────────
    unsigned int VAO;
    glGenVertexArrays(1,&VAO);
    glBindVertexArray(VAO);

    // Attr 0: geometry position (per-vertex, from geoVBO)
    glBindBuffer(GL_ARRAY_BUFFER,geoVBO);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);

    // Attr 1: instance XZ position (per-instance)
    // Attr 2: instance type (per-instance)
    // Both from instVBO — stride = sizeof(InstData) = 12 bytes
    glBindBuffer(GL_ARRAY_BUFFER,instVBO);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(InstData),(void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribDivisor(1,1);  // advance once per instance

    glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(InstData),(void*)(2*sizeof(float)));
    glEnableVertexAttribArray(2);
    glVertexAttribDivisor(2,1);  // advance once per instance

    glBindVertexArray(0);
    std::cout << "[4] VAO: attr0(geo,div=0) attr1(instXZ,div=1) attr2(instType,div=1)\n";
    std::cout << "[5] Render loop. I=toggle instancing | +/-=count | ESC=quit\n\n";

    int uViewLoc = glGetUniformLocation(shader,"uView");
    int uProjLoc = glGetUniformLocation(shader,"uProjection");
    int uTimeLoc = glGetUniformLocation(shader,"uTime");

    float lastFrame=0;
    auto lastReport = std::chrono::steady_clock::now();
    int framesSinceReport=0;

    // Non-instanced fallback: store positions as a uniform array
    // (simplified: just use instData directly via glVertexAttrib)

    while (!glfwWindowShouldClose(window)) {
        float now=(float)glfwGetTime();
        float dt=now-lastFrame; lastFrame=now;
        processInput(window,dt);

        glClearColor(0.02f,0.04f,0.08f,1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        glUseProgram(shader);

        glm::mat4 view = glm::lookAt(camPos,camPos+camFront,camUp);
        glm::mat4 proj = glm::perspective(glm::radians(fov),1024.0f/680.0f,0.1f,100.0f);
        glUniformMatrix4fv(uViewLoc,1,GL_FALSE,glm::value_ptr(view));
        glUniformMatrix4fv(uProjLoc,1,GL_FALSE,glm::value_ptr(proj));
        glUniform1f(uTimeLoc,now);

        glBindVertexArray(VAO);

        if (useInstancing) {
            glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, markerVertCount, instanceCount);
        } else {
            // Non-instanced: draw each marker with a manual loop
            for (int i = 0; i < instanceCount; i++) {
                glVertexAttrib2f(1, instData[i].x, instData[i].z);
                glVertexAttrib1f(2, instData[i].type);
                glDrawArrays(GL_TRIANGLE_STRIP, 0, markerVertCount);
            }
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
        framesSinceReport++;

        auto now2 = std::chrono::steady_clock::now();
        double sec = std::chrono::duration<double>(now2-lastReport).count();
        if (sec >= 1.0) {
            std::cout << "Instances: " << instanceCount
                      << " | " << (useInstancing ? "INSTANCED (1 call)" : "NON-INSTANCED (" + std::to_string(instanceCount) + " calls)")
                      << " | FPS: " << (int)(framesSinceReport/sec) << "\n";
            lastReport=now2; framesSinceReport=0;
        }
    }

    glDeleteVertexArrays(1,&VAO);
    glDeleteBuffers(1,&geoVBO);
    glDeleteBuffers(1,&instVBO);
    glDeleteProgram(shader);
    glfwTerminate();
    return 0;
}
Expected Terminal Output
[2] Geometry VBO: 240 bytes (20 vertices)
[3] Instance VBO: 2000 entries (24000 bytes)
[4] VAO: attr0(geo,div=0) attr1(instXZ,div=1) attr2(instType,div=1)
Instances: 500 | INSTANCED (1 call) | FPS: 62
Instances: 2000 | NON-INSTANCED (2000 calls) | FPS: 28
🔬 Experiments
  • Comment out glVertexAttribDivisor(1, 1): All buoys stack at position[0] — only one visible. Restore the line. Shows exactly what divisor=1 does.
  • Change bob multiplier from 0.73 to 0.0: All buoys bob in perfect synchrony. Change to 1.57 for quarter-cycle offsets. Shows how gl_InstanceID creates visual variation.
  • Press + to 2,000 then I to non-instanced: Watch FPS drop. Press I back — frame rate recovers instantly. Live proof of draw call overhead.
✓ Lab 2 Deliverable
2,000 instanced buoys with fly camera
  • 500+ colour-coded buoys visible with unique bob motion per buoy
  • I key toggles modes — FPS visible in terminal each second
  • Non-instanced mode noticeably slower at 2,000 instances
  • Full fly camera: inspect scene from above and below
Lab 3 of 5 ⬛⬛ Intermediate · ~35 min
FBO Sonar Post-Processing on the Buoy Scene
Adds the full FBO two-pass pipeline to Lab 2. Pass 1 renders all 500 instanced buoys into the FBO colour texture. Pass 2 draws a fullscreen quad applying one of five interchangeable effects: normal, sonar phosphor (with scan lines), NVG green, edge glow, and thermal false colour. The fly camera remains fully functional in all modes.
📋 Demo 13: FBO Post-Processing ⏱ ~35 min New: FBO setup · colour texture · depth RBO · second-pass quad
➕ What This Lab Adds Over Lab 2
  • FBO: glGenFramebuffers → colour texture (NULL data) → depth RBO → attach both → check completeness
  • Pass 1: buoy scene drawn into FBO (depth test active inside FBO)
  • Pass 2: fullscreen quad samples FBO texture, applies post-processing effect
  • Sonar phosphor: luminance → green + scan lines via mod(floor(vUV.y*res),2) + vignette
  • NVG: luminance remapped to green channel
  • Edge glow: Sobel-like finite difference gradient on FBO texture
  • Thermal: luminance remapped to blue→red false colour
🎯 What You Will See
F2 Sonar Phosphor: Scene shifts to green phosphor with horizontal scan lines — 1980s sonar tube look
F3 NVG Green: Night vision tint — bright buoys glow, dark areas fall to black
F4 Edge Glow: Buoy boundaries glow green on black background
F5 Thermal: False colour — cool areas deep blue, bright contacts red/orange
Camera: Fully functional in all modes — FBO re-renders each frame
💡 The Scene Shader Is Completely Unchanged

The instanced buoy shader from Lab 2 is identical here. The FBO is a transparent wrapper: the scene renders exactly as before, just into a texture. The post-processing shader then applies its effect to that texture. This separation — scene unchanged, effect in a separate pass — is how every post-processing system in real-time engines works.

Objectives — Check Off As You Complete
  • FBO created with glGenFramebuffers — colour texture attached at GL_COLOR_ATTACHMENT0
  • Depth RBO attached at GL_DEPTH_STENCIL_ATTACHMENT (depth test works inside FBO)
  • glCheckFramebufferStatus returns GL_FRAMEBUFFER_COMPLETE before render loop
  • Pass 1: glBindFramebuffer(fbo) → clear → draw 500 instanced buoys
  • Pass 2: glBindFramebuffer(0) → glDisable(depth) → bind colourTex → draw quad
  • F1–F5 switch uEffect uniform — all 5 effects visually distinct
  • Camera still works in all effect modes (scene re-renders each frame)
src/main.cpp — Lab 3 Complete File
src/main.cpp — Lab 3: FBO Post-Processing
C++
// ═══════════════════════════════════════════════════════════════════════════
//  RR GRAPHICS LAB — DAY 3 · LAB 3 of 5
//  Maritime Surveillance Scene — FBO Sonar-Sweep Post-Processing
//  By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
//  PROJECT NAME: SurveillanceScene
//  FOLDER:       C:\Labs\SurveillanceScene\
//
//  WHAT THIS LAB ADDS OVER LAB 2:
//  - Full FBO two-pass pipeline:
//      Pass 1: render 3D buoy scene into FBO colour texture
//      Pass 2: draw fullscreen quad, apply sonar-green tint + scan-line effect
//  - The post-processing effect is a "sonar return" look:
//      - Desaturate and green-tint (sonar phosphor)
//      - Horizontal scan lines via mod(gl_FragCoord.y, 2.0) < 1.0
//      - Edge glow: bright pixels stay bright, darks get darker (contrast)
//      - Vignette: darkens edges of the display
//  - F1: normal (no effect)    F2: sonar phosphor    F3: NVG green
//  - F4: edge glow             F5: thermal (false colour)
//  - 2,000 buoys still present from Lab 2, now seen through the effect
//
//  BUILDS ON: Lab 2 (instanced buoys) + Demo 13 (FBO two-pass)
// ═══════════════════════════════════════════════════════════════════════════

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <cmath>

const int WIN_W = 1024, WIN_H = 680;

// ── PASS 1: scene shader (same instanced buoy from Lab 2) ──────────────────
const char* sceneVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec2 aInstXZ;
    layout(location=2) in float aInstType;
    out vec3 vColour;
    uniform mat4 uView, uProjection;
    uniform float uTime;
    void main(){
        float phase = float(gl_InstanceID)*0.73;
        float bobY  = sin(uTime*1.2+phase)*0.04;
        vec3 wPos   = vec3(aInstXZ.x,bobY,aInstXZ.y)+aPos*0.08;
        gl_Position = uProjection*uView*vec4(wPos,1.0);
        int type=int(aInstType+0.5);
        if(type==0)      vColour=vec3(0.20,0.55,1.00);
        else if(type==1) vColour=vec3(1.00,0.20,0.20);
        else             vColour=vec3(1.00,0.85,0.10);
    }
)GLSL";
const char* sceneFrag = R"GLSL(
    #version 330 core
    in vec3 vColour;
    out vec4 FragColor;
    void main(){ FragColor=vec4(vColour,1.0); }
)GLSL";

// ── PASS 2: post-processing quad shader ────────────────────────────────────
const char* quadVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec2 aPos;
    layout(location=1) in vec2 aUV;
    out vec2 vUV;
    void main(){ gl_Position=vec4(aPos,0.0,1.0); vUV=aUV; }
)GLSL";

const char* quadFrag = R"GLSL(
    #version 330 core
    in vec2 vUV;
    out vec4 FragColor;
    uniform sampler2D uScreenTex;
    uniform int uEffect;
    uniform vec2 uRes;

    void main(){
        vec3 col = texture(uScreenTex, vUV).rgb;

        if(uEffect == 0){
            // Normal — no effect
            FragColor = vec4(col, 1.0);

        } else if(uEffect == 1){
            // Sonar phosphor: green-tint + scan lines + vignette
            float lum  = dot(col, vec3(0.299,0.587,0.114));
            // Map luminance to green-phosphor colour
            vec3 sonar = mix(vec3(0.01,0.05,0.03), vec3(0.15,1.00,0.45), lum);
            // Scan lines: every other pixel row is slightly dimmed
            float scan = 1.0 - 0.18 * mod(floor(vUV.y * uRes.y), 2.0);
            // Vignette: darken edges
            vec2 uvc = vUV - 0.5;
            float vig = 1.0 - dot(uvc,uvc)*2.2;
            FragColor = vec4(sonar * scan * max(vig,0.1), 1.0);

        } else if(uEffect == 2){
            // NVG green: simulated night-vision
            float lum = dot(col, vec3(0.299,0.587,0.114));
            FragColor = vec4(0.05, lum*1.3+0.04, 0.05, 1.0);

        } else if(uEffect == 3){
            // Edge glow: Sobel-like contrast amplifier
            vec2 px = 1.0/uRes;
            float L=dot(texture(uScreenTex,vUV-vec2(px.x,0)).rgb,vec3(1.0/3.0));
            float R=dot(texture(uScreenTex,vUV+vec2(px.x,0)).rgb,vec3(1.0/3.0));
            float U=dot(texture(uScreenTex,vUV+vec2(0,px.y)).rgb,vec3(1.0/3.0));
            float D=dot(texture(uScreenTex,vUV-vec2(0,px.y)).rgb,vec3(1.0/3.0));
            float edge=sqrt((R-L)*(R-L)+(U-D)*(U-D))*6.0;
            vec3 glow=col+vec3(0.05,0.6,0.2)*clamp(edge,0.0,1.0);
            FragColor=vec4(clamp(glow,0.0,1.0),1.0);

        } else if(uEffect == 4){
            // Thermal false colour: cool=blue, warm=red
            float lum=dot(col,vec3(0.299,0.587,0.114));
            vec3 cold=vec3(0.0,0.0,0.8);
            vec3 warm=vec3(1.0,0.1,0.0);
            FragColor=vec4(mix(cold,warm,lum),1.0);
        }
    }
)GLSL";

unsigned int compileShader(unsigned int t,const char* s){
    unsigned int id=glCreateShader(t);glShaderSource(id,1,&s,NULL);glCompileShader(id);
    int ok;char log[512];glGetShaderiv(id,GL_COMPILE_STATUS,&ok);
    if(!ok){glGetShaderInfoLog(id,512,NULL,log);std::cerr<<log;}return id;
}
unsigned int mkProg(const char* vs,const char* fs){
    unsigned int v=compileShader(GL_VERTEX_SHADER,vs),f=compileShader(GL_FRAGMENT_SHADER,fs);
    unsigned int p=glCreateProgram();glAttachShader(p,v);glAttachShader(p,f);glLinkProgram(p);
    glDeleteShader(v);glDeleteShader(f);return p;
}

glm::vec3 camPos(0,4.0f,18.0f),camFront(0,-0.2f,-1),camUp(0,1,0);
float yaw=-90,pitch=-10,lastX=512,lastY=340,fov=50;
bool firstMouse=true;
int activeEffect=0;
bool f1p=false,f2p=false,f3p=false,f4p=false,f5p=false;

void mouseCallback(GLFWwindow* w,double x,double y){
    if(firstMouse){lastX=(float)x;lastY=(float)y;firstMouse=false;}
    float dx=((float)x-lastX)*0.1f,dy=(lastY-(float)y)*0.1f;
    lastX=(float)x;lastY=(float)y;yaw+=dx;pitch+=dy;
    if(pitch>89)pitch=89;if(pitch<-89)pitch=-89;
    glm::vec3 f;
    f.x=cos(glm::radians(yaw))*cos(glm::radians(pitch));
    f.y=sin(glm::radians(pitch));
    f.z=sin(glm::radians(yaw))*cos(glm::radians(pitch));
    camFront=glm::normalize(f);
}
void scrollCallback(GLFWwindow* w,double xo,double yo){fov-=(float)yo;if(fov<5)fov=5;if(fov>90)fov=90;}
void onResize(GLFWwindow* w,int W,int H){glViewport(0,0,W,H);}

void processInput(GLFWwindow* w,float dt){
    if(glfwGetKey(w,GLFW_KEY_ESCAPE)==GLFW_PRESS)glfwSetWindowShouldClose(w,true);
    float s=8.0f*dt;
    if(glfwGetKey(w,GLFW_KEY_W)==GLFW_PRESS)camPos+=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_S)==GLFW_PRESS)camPos-=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_A)==GLFW_PRESS)camPos-=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_D)==GLFW_PRESS)camPos+=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_Q)==GLFW_PRESS)camPos.y+=s;
    if(glfwGetKey(w,GLFW_KEY_E)==GLFW_PRESS)camPos.y-=s;
    bool k1=(glfwGetKey(w,GLFW_KEY_F1)==GLFW_PRESS);
    bool k2=(glfwGetKey(w,GLFW_KEY_F2)==GLFW_PRESS);
    bool k3=(glfwGetKey(w,GLFW_KEY_F3)==GLFW_PRESS);
    bool k4=(glfwGetKey(w,GLFW_KEY_F4)==GLFW_PRESS);
    bool k5=(glfwGetKey(w,GLFW_KEY_F5)==GLFW_PRESS);
    const char* names[]={"Normal","Sonar Phosphor","NVG Green","Edge Glow","Thermal"};
    if(k1&&!f1p){activeEffect=0;std::cout<<"Effect: "<<names[0]<<"\n";}
    if(k2&&!f2p){activeEffect=1;std::cout<<"Effect: "<<names[1]<<"\n";}
    if(k3&&!f3p){activeEffect=2;std::cout<<"Effect: "<<names[2]<<"\n";}
    if(k4&&!f4p){activeEffect=3;std::cout<<"Effect: "<<names[3]<<"\n";}
    if(k5&&!f5p){activeEffect=4;std::cout<<"Effect: "<<names[4]<<"\n";}
    f1p=k1;f2p=k2;f3p=k3;f4p=k4;f5p=k5;
}

int main(){
    std::cout<<"\n=== Day 3 Lab 3 — FBO Sonar Post-Processing ===\n\n";
    std::cout<<"F1=normal | F2=sonar | F3=NVG | F4=edge | F5=thermal | WASD+Q/E=fly\n\n";

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window=glfwCreateWindow(WIN_W,WIN_H,
        "Surveillance Scene — Lab 3: FBO Post-Processing",NULL,NULL);
    if(!window){glfwTerminate();return-1;}
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window,onResize);
    glfwSetCursorPosCallback(window,mouseCallback);
    glfwSetScrollCallback(window,scrollCallback);
    glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);
    glewExperimental=GL_TRUE;glewInit();
    glEnable(GL_DEPTH_TEST);
    std::cout<<"GPU: "<<glGetString(GL_RENDERER)<<"\n\n";

    unsigned int sceneShader=mkProg(sceneVert,sceneFrag);
    unsigned int quadShader =mkProg(quadVert, quadFrag);
    std::cout<<"[1] Scene shader="<<sceneShader<<" Quad shader="<<quadShader<<"\n";

    // FBO setup
    unsigned int fbo,colourTex,depthRBO;
    glGenFramebuffers(1,&fbo);
    glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    glGenTextures(1,&colourTex);
    glBindTexture(GL_TEXTURE_2D,colourTex);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIN_W,WIN_H,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,colourTex,0);
    glGenRenderbuffers(1,&depthRBO);
    glBindRenderbuffer(GL_RENDERBUFFER,depthRBO);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,WIN_W,WIN_H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,depthRBO);
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
        std::cerr<<"ERROR: FBO incomplete\n";
    else std::cout<<"[2] FBO complete. colourTex="<<colourTex<<"\n";
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    // Instance data
    const int INST=500;
    std::mt19937 rng(77);
    std::uniform_real_distribution<float> pd(-20.0f,20.0f);
    std::uniform_int_distribution<int>    td(0,2);
    struct ID{float x,z,type;};
    std::vector<ID> inst(INST);
    for(auto& d:inst){d.x=pd(rng);d.z=pd(rng);d.type=(float)td(rng);}

    // Buoy geometry
    float mk[]={
         0.0f, 1.0f, 0.0f,  0.5f, 0.0f, 0.5f,
         0.0f, 1.0f, 0.0f,  0.5f, 0.0f,-0.5f,
         0.0f, 1.0f, 0.0f, -0.5f, 0.0f,-0.5f,
         0.0f, 1.0f, 0.0f, -0.5f, 0.0f, 0.5f,
         0.0f, 1.0f, 0.0f,  0.5f, 0.0f, 0.5f,
         0.0f,-0.6f, 0.0f,  0.5f, 0.0f, 0.5f,
         0.0f,-0.6f, 0.0f,  0.5f, 0.0f,-0.5f,
         0.0f,-0.6f, 0.0f, -0.5f, 0.0f,-0.5f,
         0.0f,-0.6f, 0.0f, -0.5f, 0.0f, 0.5f,
         0.0f,-0.6f, 0.0f,  0.5f, 0.0f, 0.5f,
    };
    int mkCnt=sizeof(mk)/(3*sizeof(float));

    unsigned int geoVBO,instVBO,sceneVAO;
    glGenBuffers(1,&geoVBO);glBindBuffer(GL_ARRAY_BUFFER,geoVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(mk),mk,GL_STATIC_DRAW);
    glGenBuffers(1,&instVBO);glBindBuffer(GL_ARRAY_BUFFER,instVBO);
    glBufferData(GL_ARRAY_BUFFER,INST*sizeof(ID),inst.data(),GL_STATIC_DRAW);
    glGenVertexArrays(1,&sceneVAO);glBindVertexArray(sceneVAO);
    glBindBuffer(GL_ARRAY_BUFFER,geoVBO);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER,instVBO);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(ID),(void*)0);
    glEnableVertexAttribArray(1);glVertexAttribDivisor(1,1);
    glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(ID),(void*)(2*sizeof(float)));
    glEnableVertexAttribArray(2);glVertexAttribDivisor(2,1);
    glBindVertexArray(0);
    std::cout<<"[3] Scene VAO ready ("<<INST<<" buoys)\n";

    // Fullscreen quad
    float quad[]={
        -1,-1,0,0,  1,-1,1,0,  1,1,1,1,
         1, 1,1,1, -1, 1,0,1, -1,-1,0,0
    };
    unsigned int quadVAO,quadVBO;
    glGenVertexArrays(1,&quadVAO);glBindVertexArray(quadVAO);
    glGenBuffers(1,&quadVBO);glBindBuffer(GL_ARRAY_BUFFER,quadVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(quad),quad,GL_STATIC_DRAW);
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)(2*sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    glUseProgram(quadShader);
    glUniform1i(glGetUniformLocation(quadShader,"uScreenTex"),0);
    glUniform2f(glGetUniformLocation(quadShader,"uRes"),(float)WIN_W,(float)WIN_H);
    std::cout<<"[4] Quad VAO ready. Render loop starting.\n\n";

    float lastFrame=0;
    while(!glfwWindowShouldClose(window)){
        float now=(float)glfwGetTime();
        float dt=now-lastFrame;lastFrame=now;
        processInput(window,dt);

        glm::mat4 view=glm::lookAt(camPos,camPos+camFront,camUp);
        glm::mat4 proj=glm::perspective(glm::radians(fov),(float)WIN_W/WIN_H,0.1f,100.0f);

        // ── PASS 1: scene → FBO ────────────────────────────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        glEnable(GL_DEPTH_TEST);
        glClearColor(0.02f,0.04f,0.08f,1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        glUseProgram(sceneShader);
        glUniformMatrix4fv(glGetUniformLocation(sceneShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(sceneShader,"uProjection"),1,GL_FALSE,glm::value_ptr(proj));
        glUniform1f(glGetUniformLocation(sceneShader,"uTime"),now);
        glBindVertexArray(sceneVAO);
        glDrawArraysInstanced(GL_TRIANGLE_STRIP,0,mkCnt,INST);

        // ── PASS 2: effect → screen ────────────────────────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        glDisable(GL_DEPTH_TEST);
        glClearColor(0,0,0,1);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(quadShader);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,colourTex);
        glUniform1i(glGetUniformLocation(quadShader,"uEffect"),activeEffect);
        glBindVertexArray(quadVAO);
        glDrawArrays(GL_TRIANGLES,0,6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1,&sceneVAO);
    glDeleteVertexArrays(1,&quadVAO);
    glDeleteBuffers(1,&geoVBO);glDeleteBuffers(1,&instVBO);glDeleteBuffers(1,&quadVBO);
    glDeleteTextures(1,&colourTex);
    glDeleteRenderbuffers(1,&depthRBO);
    glDeleteFramebuffers(1,&fbo);
    glDeleteProgram(sceneShader);glDeleteProgram(quadShader);
    glfwTerminate();
    return 0;
}
Expected Terminal Output
[1] Scene shader=1 Quad shader=2
[2] FBO complete. colourTex=1
[3] Scene VAO ready (500 buoys)
[4] Quad VAO ready. Render loop starting.
Effect: Sonar Phosphor
Effect: NVG Green
Screen: Buoy scene transformed by selected effect. Camera still works.
🔬 Experiments
  • Change scan-line frequency: In the sonar phosphor shader, change floor(vUV.y * 680.0) to 340.0 for thicker lines, or 2720.0 for very fine ones. Rebuild each time.
  • Fly camera while in F4 edge mode: Edges update every frame. The FBO captures new frames continuously — proves the architectural separation between scene and effect.
  • Comment out glDisable(GL_DEPTH_TEST) before pass 2: Screen goes black or shows artefacts. Restore and confirm it returns.
✓ Lab 3 Deliverable
Buoy scene with 5 live post-processing effects
  • FBO complete message printed before render loop
  • All 5 F-key effects visually distinct and correct
  • Sonar phosphor has visible horizontal scan lines
  • Camera works in all modes — effects follow camera movement
Lab 4 of 5 ⬛⬛⬛ Intermediate+ · ~40 min
Stencil Selection Outline + Transparent Sweep Blending
Replaces the buoy grid with the Day 2 vessel hull. Adds the two-pass stencil selection outline (F1 select, F2 deselect) and a transparent radar sweep arc composited with alpha blending. Threat status (T key) changes outline and sweep colour. Everything renders inside the FBO — sonar phosphor wraps the whole scene including outline and sweep.
📋 Demo 14: Stencil + Blending ⏱ ~40 min New: stencil write/read · GL_NOTEQUAL · glDepthMask(GL_FALSE)
➕ What This Lab Adds Over Lab 3
  • 3D Phong-lit vessel hull replaces the instanced buoy grid
  • Stencil pass 1: vessel drawn with GL_ALWAYS func → writes 1 to every vessel pixel
  • Stencil pass 2: vessel scaled 1.055x with GL_NOTEQUAL → only rim rendered
  • Outline colour: blue (friendly) or red (hostile) driven by T key
  • Sweep arc drawn last with GL_BLEND (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
  • glDepthMask(GL_FALSE) before sweep — prevents depth occlusion by transparent geometry
  • X key toggles sonar phosphor on/off
🎯 What You Will See
Screen: Lit vessel hull through sonar phosphor effect
F1 key: Blue selection outline appears around vessel perimeter
T key: Vessel darkens to red, outline turns red, sweep turns red
Sweep: Vessel hull visible THROUGH the rotating arc — blending working
X key: Toggle sonar phosphor to see outline without the effect
Draw Order Inside the FBO (Critical — Cannot Be Changed)
  Draw order inside FBO (cannot be changed):
  1. Clear colour + depth + stencil
  2. Draw vessel  (depth ON, stencil GL_ALWAYS  -> writes 1 to every vessel pixel)
  3. Draw outline (depth OFF, stencil GL_NOTEQUAL 1 -> only rim rendered)
  4. Draw sweep   (BLEND ON, glDepthMask GL_FALSE  -> transparent, drawn LAST)

  FBO Pass 2:
  5. glBindFramebuffer(0) -> draw fullscreen quad with post-processing effect
Objectives — Check Off As You Complete
  • Stencil op GL_REPLACE set before vessel draw pass
  • Stencil func GL_ALWAYS set before vessel draw (writes 1 to all vessel pixels)
  • Stencil func GL_NOTEQUAL set before outline draw (renders only the rim)
  • Depth test disabled during outline draw (outline shows over everything)
  • GL_BLEND enabled with GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA before sweep
  • glDepthMask(GL_FALSE) before sweep draw, GL_TRUE restored after
  • Sweep drawn after vessel and outline (vessel visible through sweep)
  • F1=select, F2=deselect, T=threat colour, X=sonar toggle all work
src/main.cpp — Lab 4 Complete File
src/main.cpp — Lab 4: Stencil + Blend
C++
// ═══════════════════════════════════════════════════════════════════════════
//  RR GRAPHICS LAB — DAY 3 · LAB 4 of 5
//  Maritime Surveillance Scene — Stencil Vessel Selection Outline
//  By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
//  PROJECT NAME: SurveillanceScene
//  FOLDER:       C:\Labs\SurveillanceScene\
//
//  WHAT THIS LAB ADDS OVER LAB 3:
//  - Replaces the instanced buoy grid with a 3D vessel (from Day 2)
//  - Two-pass stencil selection outline on the vessel:
//      Pass 1: draw vessel, write stencil=1 for every covered pixel
//      Pass 2: draw expanded vessel (scale 1.06×), render only where stencil≠1
//  - Blending: semi-transparent threat-level overlay quad drawn over vessel
//    (alpha=0.25 red tint if vessel is "hostile", blue if friendly)
//  - F1/F2: select/deselect vessel → outline appears/disappears
//  - T key: toggle threat status (friendly blue → hostile red)
//  - All three fragment tests active: depth + stencil + blend
//  - FBO still in use: sonar phosphor effect applied to entire scene
//
//  BUILDS ON: Lab 3 (FBO) + Demo 14 (stencil + blending)
// ═══════════════════════════════════════════════════════════════════════════

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <cmath>

const int WIN_W=1024, WIN_H=680;

// ── VESSEL SHADER (Phong + normals) ────────────────────────────────────────
const char* vesselVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec3 aNormal;
    out vec3 vFragPos, vNormal;
    uniform mat4 uModel,uView,uProjection;
    void main(){
        vFragPos=vec3(uModel*vec4(aPos,1.0));
        vNormal=mat3(transpose(inverse(uModel)))*aNormal;
        gl_Position=uProjection*uView*uModel*vec4(aPos,1.0);
    }
)GLSL";
const char* vesselFrag = R"GLSL(
    #version 330 core
    in vec3 vFragPos,vNormal;
    out vec4 FragColor;
    uniform vec3 uLightPos,uViewPos,uObjColour;
    void main(){
        vec3 n=normalize(vNormal);
        vec3 ld=normalize(uLightPos-vFragPos);
        vec3 vd=normalize(uViewPos-vFragPos);
        vec3 amb=0.18*vec3(1.0,0.95,0.85);
        float diff=max(dot(n,ld),0.0);
        vec3 diffuse=diff*vec3(1.0,0.95,0.85);
        float spec=pow(max(dot(vd,reflect(-ld,n)),0.0),32.0)*0.5;
        FragColor=vec4((amb+diffuse+spec*vec3(1.0))*uObjColour,1.0);
    }
)GLSL";

// ── OUTLINE SHADER ─────────────────────────────────────────────────────────
const char* outlineVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec3 aNormal;
    uniform mat4 uModel,uView,uProjection;
    uniform float uThick;
    void main(){
        vec3 exp=aPos+aNormal*uThick;
        gl_Position=uProjection*uView*uModel*vec4(exp,1.0);
    }
)GLSL";
const char* outlineFrag = R"GLSL(
    #version 330 core
    out vec4 FragColor;
    uniform vec3 uOutlineColour;
    void main(){FragColor=vec4(uOutlineColour,1.0);}
)GLSL";

// ── SWEEP OVERLAY SHADER ────────────────────────────────────────────────────
const char* sweepVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec2 aPos;
    layout(location=1) in vec2 aUV;
    out vec2 vUV;
    void main(){gl_Position=vec4(aPos,0.0,1.0);vUV=aUV;}
)GLSL";
const char* sweepFrag = R"GLSL(
    #version 330 core
    in vec2 vUV;
    out vec4 FragColor;
    uniform float uTime;
    uniform vec3  uThreatColour;
    void main(){
        vec2 uv=vUV-0.5;
        float d=length(uv);
        float angle=atan(uv.y,uv.x);
        if(d>0.48) discard;
        float sw=mod(uTime*0.9,6.2832);
        float diff=mod(sw-angle+6.2832,6.2832);
        float trail=(1.0-smoothstep(0.0,0.9,diff))*(1.0-smoothstep(0.0,0.46,d));
        if(trail<0.01) discard;
        FragColor=vec4(uThreatColour,trail*0.30);
    }
)GLSL";

// ── FBO QUAD SHADERS (sonar phosphor effect) ───────────────────────────────
const char* quadVert = R"GLSL(
    #version 330 core
    layout(location=0) in vec2 aPos;
    layout(location=1) in vec2 aUV;
    out vec2 vUV;
    void main(){gl_Position=vec4(aPos,0.0,1.0);vUV=aUV;}
)GLSL";
const char* quadFrag = R"GLSL(
    #version 330 core
    in vec2 vUV; out vec4 FragColor;
    uniform sampler2D uTex;
    uniform int uEffect;
    void main(){
        vec3 c=texture(uTex,vUV).rgb;
        if(uEffect==0){FragColor=vec4(c,1.0);}
        else{
            float l=dot(c,vec3(0.299,0.587,0.114));
            vec3 s=mix(vec3(0.01,0.05,0.02),vec3(0.10,1.00,0.40),l);
            float sc=1.0-0.18*mod(floor(vUV.y*680.0),2.0);
            vec2 uvc=vUV-0.5;
            float v=1.0-dot(uvc,uvc)*2.2;
            FragColor=vec4(s*sc*max(v,0.1),1.0);
        }
    }
)GLSL";

unsigned int compileShader(unsigned int t,const char* s){
    unsigned int id=glCreateShader(t);glShaderSource(id,1,&s,NULL);glCompileShader(id);
    int ok;char log[512];glGetShaderiv(id,GL_COMPILE_STATUS,&ok);
    if(!ok){glGetShaderInfoLog(id,512,NULL,log);std::cerr<<log;}return id;
}
unsigned int mkProg(const char* vs,const char* fs){
    unsigned int v=compileShader(GL_VERTEX_SHADER,vs),f=compileShader(GL_FRAGMENT_SHADER,fs);
    unsigned int p=glCreateProgram();glAttachShader(p,v);glAttachShader(p,f);glLinkProgram(p);
    glDeleteShader(v);glDeleteShader(f);return p;
}

glm::vec3 camPos(0,2.0f,6.0f),camFront(0,-0.1f,-1),camUp(0,1,0);
float yaw=-90,pitch=-5,lastX=512,lastY=340,fov=45;
bool firstMouse=true;
bool selected=false,hostile=false;
bool f1p=false,f2p=false,tPrev=false,xPrev=false;
int fboEffect=1; // start with sonar phosphor

void mouseCallback(GLFWwindow* w,double x,double y){
    if(firstMouse){lastX=(float)x;lastY=(float)y;firstMouse=false;}
    float dx=((float)x-lastX)*0.1f,dy=(lastY-(float)y)*0.1f;
    lastX=(float)x;lastY=(float)y;yaw+=dx;pitch+=dy;
    if(pitch>89)pitch=89;if(pitch<-89)pitch=-89;
    glm::vec3 f;
    f.x=cos(glm::radians(yaw))*cos(glm::radians(pitch));
    f.y=sin(glm::radians(pitch));
    f.z=sin(glm::radians(yaw))*cos(glm::radians(pitch));
    camFront=glm::normalize(f);
}
void scrollCallback(GLFWwindow* w,double xo,double yo){fov-=(float)yo;if(fov<5)fov=5;if(fov>90)fov=90;}
void onResize(GLFWwindow* w,int W,int H){glViewport(0,0,W,H);}

void processInput(GLFWwindow* w,float dt){
    if(glfwGetKey(w,GLFW_KEY_ESCAPE)==GLFW_PRESS)glfwSetWindowShouldClose(w,true);
    float s=3.0f*dt;
    if(glfwGetKey(w,GLFW_KEY_W)==GLFW_PRESS)camPos+=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_S)==GLFW_PRESS)camPos-=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_A)==GLFW_PRESS)camPos-=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_D)==GLFW_PRESS)camPos+=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_Q)==GLFW_PRESS)camPos.y+=s;
    if(glfwGetKey(w,GLFW_KEY_E)==GLFW_PRESS)camPos.y-=s;
    bool f1=(glfwGetKey(w,GLFW_KEY_F1)==GLFW_PRESS);
    bool f2=(glfwGetKey(w,GLFW_KEY_F2)==GLFW_PRESS);
    bool tNow=(glfwGetKey(w,GLFW_KEY_T)==GLFW_PRESS);
    bool xNow=(glfwGetKey(w,GLFW_KEY_X)==GLFW_PRESS);
    if(f1&&!f1p){selected=true;std::cout<<"Vessel SELECTED\n";}
    if(f2&&!f2p){selected=false;std::cout<<"Vessel deselected\n";}
    if(tNow&&!tPrev){hostile=!hostile;std::cout<<(hostile?"HOSTILE\n":"Friendly\n");}
    if(xNow&&!xPrev){fboEffect=1-fboEffect;std::cout<<(fboEffect?"Sonar ON\n":"Normal\n");}
    f1p=f1;f2p=f2;tPrev=tNow;xPrev=xNow;
}

int main(){
    std::cout<<"\n=== Day 3 Lab 4 — Stencil Outline + Blend ===\n\n";
    std::cout<<"F1=select vessel | F2=deselect | T=threat status | X=sonar fx | WASD+Q/E fly\n\n";

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window=glfwCreateWindow(WIN_W,WIN_H,
        "Surveillance Scene — Lab 4: Stencil + Blend",NULL,NULL);
    if(!window){glfwTerminate();return-1;}
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window,onResize);
    glfwSetCursorPosCallback(window,mouseCallback);
    glfwSetScrollCallback(window,scrollCallback);
    glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);
    glewExperimental=GL_TRUE;glewInit();
    std::cout<<"GPU: "<<glGetString(GL_RENDERER)<<"\n\n";

    unsigned int vesselShader=mkProg(vesselVert,vesselFrag);
    unsigned int outlineShader=mkProg(outlineVert,outlineFrag);
    unsigned int sweepShader=mkProg(sweepVert,sweepFrag);
    unsigned int quadShader=mkProg(quadVert,quadFrag);
    std::cout<<"[1] Shaders: vessel="<<vesselShader<<" outline="<<outlineShader
             <<" sweep="<<sweepShader<<" quad="<<quadShader<<"\n";

    // FBO
    unsigned int fbo,colTex,dRBO;
    glGenFramebuffers(1,&fbo);glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    glGenTextures(1,&colTex);glBindTexture(GL_TEXTURE_2D,colTex);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIN_W,WIN_H,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,colTex,0);
    glGenRenderbuffers(1,&dRBO);glBindRenderbuffer(GL_RENDERBUFFER,dRBO);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,WIN_W,WIN_H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,dRBO);
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)
        std::cerr<<"FBO error\n";
    else std::cout<<"[2] FBO complete\n";
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    // Vessel hull geometry (pos + normal)
    float hull[]={
        -1.2f,0.2f,-0.3f,0,1,0,  1.2f,0.2f,-0.3f,0,1,0,  1.2f,0.2f,0.3f,0,1,0,
         1.2f,0.2f,0.3f,0,1,0,  -1.2f,0.2f,0.3f,0,1,0,  -1.2f,0.2f,-0.3f,0,1,0,
        -1.2f,-0.2f,-0.3f,0,-1,0,  1.2f,-0.2f,-0.3f,0,-1,0,  1.2f,-0.2f,0.3f,0,-1,0,
         1.2f,-0.2f,0.3f,0,-1,0,  -1.2f,-0.2f,0.3f,0,-1,0,  -1.2f,-0.2f,-0.3f,0,-1,0,
        -1.2f,-0.2f,0.3f,0,0,1,  1.2f,-0.2f,0.3f,0,0,1,  1.2f,0.2f,0.3f,0,0,1,
         1.2f,0.2f,0.3f,0,0,1,  -1.2f,0.2f,0.3f,0,0,1,  -1.2f,-0.2f,0.3f,0,0,1,
        -1.2f,-0.2f,-0.3f,0,0,-1,  1.2f,-0.2f,-0.3f,0,0,-1,  1.2f,0.2f,-0.3f,0,0,-1,
         1.2f,0.2f,-0.3f,0,0,-1,  -1.2f,0.2f,-0.3f,0,0,-1,  -1.2f,-0.2f,-0.3f,0,0,-1,
         1.2f,-0.2f,-0.3f,1,0,0,  1.2f,-0.2f,0.3f,1,0,0,  1.2f,0.2f,0.3f,1,0,0,
         1.2f,0.2f,0.3f,1,0,0,  1.2f,0.2f,-0.3f,1,0,0,  1.2f,-0.2f,-0.3f,1,0,0,
        -1.2f,-0.2f,-0.3f,-1,0,0,  -1.2f,-0.2f,0.3f,-1,0,0,  -1.2f,0.2f,0.3f,-1,0,0,
        -1.2f,0.2f,0.3f,-1,0,0,  -1.2f,0.2f,-0.3f,-1,0,0,  -1.2f,-0.2f,-0.3f,-1,0,0,
    };
    unsigned int hVAO,hVBO;
    glGenVertexArrays(1,&hVAO);glBindVertexArray(hVAO);
    glGenBuffers(1,&hVBO);glBindBuffer(GL_ARRAY_BUFFER,hVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(hull),hull,GL_STATIC_DRAW);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    // Fullscreen quad
    float qd[]={-1,-1,0,0,  1,-1,1,0,  1,1,1,1,  1,1,1,1,  -1,1,0,1,  -1,-1,0,0};
    unsigned int qVAO,qVBO;
    glGenVertexArrays(1,&qVAO);glBindVertexArray(qVAO);
    glGenBuffers(1,&qVBO);glBindBuffer(GL_ARRAY_BUFFER,qVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(qd),qd,GL_STATIC_DRAW);
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)0);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)(2*sizeof(float)));
    glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    glUseProgram(quadShader);
    glUniform1i(glGetUniformLocation(quadShader,"uTex"),0);
    std::cout<<"[3] VAOs ready. F1=select F2=deselect T=threat X=sonar\n\n";

    float lastFrame=0;
    while(!glfwWindowShouldClose(window)){
        float now=(float)glfwGetTime();
        float dt=now-lastFrame;lastFrame=now;
        processInput(window,dt);

        glm::vec3 lp(cos(now*0.5f)*4.0f,3.0f,sin(now*0.5f)*4.0f);
        glm::mat4 view=glm::lookAt(camPos,camPos+camFront,camUp);
        glm::mat4 proj=glm::perspective(glm::radians(fov),(float)WIN_W/WIN_H,0.1f,100.0f);
        glm::mat4 model=glm::mat4(1.0f);

        // ── PASS 1: render to FBO ─────────────────────────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_STENCIL_TEST);
        glClearColor(0.02f,0.04f,0.08f,1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

        // Draw vessel — write stencil=1 if selected
        if(selected){
            glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
            glStencilFunc(GL_ALWAYS,1,0xFF);
            glStencilMask(0xFF);
        } else {
            glDisable(GL_STENCIL_TEST);
        }
        glm::vec3 objCol=hostile?glm::vec3(0.6f,0.1f,0.1f):glm::vec3(0.5f,0.54f,0.58f);
        glUseProgram(vesselShader);
        glUniformMatrix4fv(glGetUniformLocation(vesselShader,"uModel"),1,GL_FALSE,glm::value_ptr(model));
        glUniformMatrix4fv(glGetUniformLocation(vesselShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(vesselShader,"uProjection"),1,GL_FALSE,glm::value_ptr(proj));
        glUniform3fv(glGetUniformLocation(vesselShader,"uLightPos"),1,glm::value_ptr(lp));
        glUniform3fv(glGetUniformLocation(vesselShader,"uViewPos"),1,glm::value_ptr(camPos));
        glUniform3fv(glGetUniformLocation(vesselShader,"uObjColour"),1,glm::value_ptr(objCol));
        glBindVertexArray(hVAO);
        glDrawArrays(GL_TRIANGLES,0,36);

        // Draw outline (stencil pass 2)
        if(selected){
            glStencilFunc(GL_NOTEQUAL,1,0xFF);
            glStencilMask(0x00);
            glDisable(GL_DEPTH_TEST);
            glm::vec3 outCol=hostile?glm::vec3(1.0f,0.1f,0.1f):glm::vec3(0.2f,0.7f,1.0f);
            glUseProgram(outlineShader);
            glUniformMatrix4fv(glGetUniformLocation(outlineShader,"uModel"),1,GL_FALSE,glm::value_ptr(model));
            glUniformMatrix4fv(glGetUniformLocation(outlineShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
            glUniformMatrix4fv(glGetUniformLocation(outlineShader,"uProjection"),1,GL_FALSE,glm::value_ptr(proj));
            glUniform1f(glGetUniformLocation(outlineShader,"uThick"),0.055f);
            glUniform3fv(glGetUniformLocation(outlineShader,"uOutlineColour"),1,glm::value_ptr(outCol));
            glBindVertexArray(hVAO);
            glDrawArrays(GL_TRIANGLES,0,36);
            glStencilMask(0xFF);
            glDisable(GL_STENCIL_TEST);
            glEnable(GL_DEPTH_TEST);
        }

        // Draw radar sweep (blending)
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
        glDepthMask(GL_FALSE);
        glDisable(GL_STENCIL_TEST);
        glm::vec3 sweepCol=hostile?glm::vec3(1.0f,0.15f,0.15f):glm::vec3(0.1f,0.85f,0.3f);
        glUseProgram(sweepShader);
        glUniform1f(glGetUniformLocation(sweepShader,"uTime"),now);
        glUniform3fv(glGetUniformLocation(sweepShader,"uThreatColour"),1,glm::value_ptr(sweepCol));
        glBindVertexArray(qVAO);
        glDrawArrays(GL_TRIANGLES,0,6);
        glDepthMask(GL_TRUE);
        glDisable(GL_BLEND);

        // ── PASS 2: FBO → screen ──────────────────────────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        glDisable(GL_DEPTH_TEST);
        glDisable(GL_STENCIL_TEST);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(quadShader);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,colTex);
        glUniform1i(glGetUniformLocation(quadShader,"uEffect"),fboEffect);
        glBindVertexArray(qVAO);
        glDrawArrays(GL_TRIANGLES,0,6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1,&hVAO);glDeleteVertexArrays(1,&qVAO);
    glDeleteBuffers(1,&hVBO);glDeleteBuffers(1,&qVBO);
    glDeleteTextures(1,&colTex);glDeleteRenderbuffers(1,&dRBO);
    glDeleteFramebuffers(1,&fbo);
    glDeleteProgram(vesselShader);glDeleteProgram(outlineShader);
    glDeleteProgram(sweepShader);glDeleteProgram(quadShader);
    glfwTerminate();
    return 0;
}
Expected Terminal Output
[1] Shaders: vessel=1 outline=2 sweep=3 quad=4
[2] FBO complete
[3] VAOs ready. F1=select F2=deselect T=threat X=sonar
Vessel SELECTED
HOSTILE
Sonar ON
Screen: Vessel with blue outline + green sweep through sonar phosphor
🔬 Experiments
  • Draw sweep before vessel: Vessel overwrites sweep completely — transparency lost. Restore correct order. Proves transparent draws must be last.
  • Comment out glDepthMask(GL_FALSE): Sweep writes depth values. Parts of vessel disappear behind the sweep's depth. Restore and confirm vessel shows through again.
  • Change outline thickness from 0.055 to 0.15: Thick glowing halo appears. Change to 0.008 for a hairline. Shows how normal displacement controls rim width.
✓ Lab 4 Deliverable
Vessel with stencil outline + transparent sweep through sonar effect
  • Vessel hull visible through sonar phosphor
  • F1 shows selection outline rim (no fill on interior)
  • Vessel hull visible THROUGH the transparent sweep arc
  • T key changes outline and sweep colour correctly (blue/green vs red)
Lab 5 of 5 ⬛⬛⬛⬛ Challenge · ~45 min
Full Scene Capstone — All Day 3 Systems Active Simultaneously
The Day 3 capstone. All four previous labs combined into one maritime surveillance scene: 500 instanced buoy markers across the sea area, the Phong-lit vessel at the centre, stencil selection outline, transparent radar sweep, and FBO post-processing wrapping the entire result. Four F-key effects. Four interactive toggles. This is the complete display.
📋 All Day 3 Demos ⏱ ~45 min All systems: instancing + FBO + stencil + blending
➕ What This Lab Combines
  • Lab 2: 500 instanced buoys with per-instance XZ + type + unique bob phase
  • Lab 4: Phong vessel with stencil selection outline (O key) + threat status (T key)
  • Lab 4: Transparent radar sweep arc (G key) composited last via blending
  • Lab 3: FBO captures entire scene (buoys + vessel + outline + sweep) into texture
  • F1–F4 post-processing: normal / sonar phosphor / NVG / edge glow
  • Draw order: buoys → vessel → outline → sweep → FBO pass 2
🎯 What You Will See
Screen: 500 colour-coded buoys + lit vessel at centre
O key: Selection outline around vessel (blue=friendly, red=hostile)
T key: Vessel darkens red, outline and sweep turn red
G key: Transparent sweep toggles — buoys visible through it
F2: Entire scene (buoys + vessel + outline + sweep) shifts to sonar phosphor
Fly camera: WASD + Q/E — see the full field of buoys from above
💡 Why Draw Order Is the Most Critical Part of This Lab

Five draw calls happen in a specific order every frame. Change any one and the result breaks: buoys after vessel = depth artefacts. Outline before vessel = stencil mask not set. Sweep before vessel = vessel overwrites it, transparency lost. FBO pass 2 anywhere except after everything else = you post-process an incomplete frame. The draw order is a contract between all four rendering systems.

Objectives — Check Off As You Complete
  • FBO with colour texture + depth RBO, completeness checked before render loop
  • Buoy VAO: two per-instance attributes (XZ + type) with glVertexAttribDivisor(1) both
  • Vessel VAO: Phong layout (pos + normal, stride=24 bytes)
  • Correct draw order inside FBO: buoys → vessel → outline → sweep
  • GL_STENCIL_BUFFER_BIT included in glClear each frame
  • glDepthMask(GL_FALSE) before sweep, GL_TRUE restored after
  • FBO pass 2 applies uFX for post-processing effect
  • O, T, G toggles produce correct visual results
  • F1–F4 effects visibly distinct across the complete scene
  • Can explain (to instructor) why each draw must be in its position
src/main.cpp — Lab 5 Complete File (Day 3 Capstone)
src/main.cpp — Lab 5: Full Capstone
C++
// ═══════════════════════════════════════════════════════════════════════════
//  RR GRAPHICS LAB — DAY 3 · LAB 5 of 5  (Day 3 Capstone)
//  Maritime Surveillance Scene — Full Scene, All Day 3 Systems Active
//  By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
//  PROJECT NAME: SurveillanceScene
//  FOLDER:       C:\Labs\SurveillanceScene\
//
//  WHAT THIS LAB DOES:
//  Combines every Day 3 concept into one complete maritime surveillance scene:
//
//  LAYER 1 — PROCEDURAL SONAR (Lab 1):
//    A sonar display overlay rendered via fullscreen quad (separate pass)
//    Showing range rings, sweep, and contact positions matching the 3D scene
//
//  LAYER 2 — INSTANCED CONTACTS (Lab 2):
//    500 buoy markers placed across the sea area via glDrawArraysInstanced
//    Colour-coded by threat type, each bobbing at a unique phase
//
//  LAYER 3 — FBO POST-PROCESSING (Lab 3):
//    Full scene captured to FBO, sonar-phosphor effect applied
//    F-keys switch between Normal / Sonar-Phosphor / NVG / Edge
//
//  LAYER 4 — STENCIL OUTLINE + BLENDING (Lab 4):
//    Selected vessel renders with stencil outline (F1/F2)
//    Radar sweep arc composited as transparent overlay (blending)
//    Threat status changes outline and sweep colour (T key)
//
//  DRAW ORDER (critical):
//    1. FBO Pass 1: ocean plane → instanced buoys → vessel → outline → sweep
//    2. FBO Pass 2: fullscreen quad with post-processing effect (screen)
//
//  KEYS:
//    WASD + Q/E  — fly camera
//    Mouse       — look around
//    F1/F2/F3/F4 — post-processing: normal / sonar / NVG / edge
//    O           — toggle outline (select/deselect vessel)
//    T           — toggle threat status (friendly / hostile)
//    S           — toggle sweep on/off
//    ESC         — quit
// ═══════════════════════════════════════════════════════════════════════════

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
#include <vector>
#include <random>
#include <cmath>

const int WIN_W=1024, WIN_H=680;

// ── All shaders from Labs 1-4 combined ─────────────────────────────────────
const char* buoyVert=R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec2 aXZ;
    layout(location=2) in float aType;
    out vec3 vCol;
    uniform mat4 uView,uProj;
    uniform float uTime;
    void main(){
        float ph=float(gl_InstanceID)*0.73;
        float by=sin(uTime*1.2+ph)*0.04;
        vec3 wp=vec3(aXZ.x,by,aXZ.y)+aPos*0.08;
        gl_Position=uProj*uView*vec4(wp,1.0);
        int t=int(aType+0.5);
        if(t==0)vCol=vec3(0.2,0.55,1.0);
        else if(t==1)vCol=vec3(1.0,0.2,0.2);
        else vCol=vec3(1.0,0.85,0.1);
    }
)GLSL";
const char* buoyFrag=R"GLSL(
    #version 330 core
    in vec3 vCol; out vec4 FragColor;
    void main(){FragColor=vec4(vCol,1.0);}
)GLSL";

const char* vesselVert=R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec3 aN;
    out vec3 vFP,vN;
    uniform mat4 uModel,uView,uProj;
    void main(){
        vFP=vec3(uModel*vec4(aPos,1.0));
        vN=mat3(transpose(inverse(uModel)))*aN;
        gl_Position=uProj*uView*uModel*vec4(aPos,1.0);
    }
)GLSL";
const char* vesselFrag=R"GLSL(
    #version 330 core
    in vec3 vFP,vN; out vec4 FragColor;
    uniform vec3 uLP,uVP,uOC;
    void main(){
        vec3 n=normalize(vN),ld=normalize(uLP-vFP),vd=normalize(uVP-vFP);
        vec3 a=0.18*vec3(1.0,0.95,0.85);
        float d=max(dot(n,ld),0.0);
        float s=pow(max(dot(vd,reflect(-ld,n)),0.0),32.0)*0.5;
        FragColor=vec4((a+d*vec3(1.0,0.95,0.85)+s*vec3(1.0))*uOC,1.0);
    }
)GLSL";

const char* outVert=R"GLSL(
    #version 330 core
    layout(location=0) in vec3 aPos;
    layout(location=1) in vec3 aN;
    uniform mat4 uModel,uView,uProj;
    uniform float uThk;
    void main(){gl_Position=uProj*uView*uModel*vec4(aPos+aN*uThk,1.0);}
)GLSL";
const char* outFrag=R"GLSL(
    #version 330 core
    out vec4 FragColor;
    uniform vec3 uOC;
    void main(){FragColor=vec4(uOC,1.0);}
)GLSL";

const char* swpVert=R"GLSL(
    #version 330 core
    layout(location=0) in vec2 aPos;
    layout(location=1) in vec2 aUV;
    out vec2 vUV;
    void main(){gl_Position=vec4(aPos,0.0,1.0);vUV=aUV;}
)GLSL";
const char* swpFrag=R"GLSL(
    #version 330 core
    in vec2 vUV; out vec4 FragColor;
    uniform float uTime; uniform vec3 uSC;
    void main(){
        vec2 uv=vUV-0.5; float d=length(uv);
        if(d>0.48) discard;
        float a=atan(uv.y,uv.x);
        float sw=mod(uTime*0.9,6.2832);
        float df=mod(sw-a+6.2832,6.2832);
        float t=(1.0-smoothstep(0.0,0.9,df))*(1.0-smoothstep(0.0,0.46,d));
        if(t<0.01) discard;
        FragColor=vec4(uSC,t*0.28);
    }
)GLSL";

const char* quadVert=R"GLSL(
    #version 330 core
    layout(location=0) in vec2 aPos;
    layout(location=1) in vec2 aUV;
    out vec2 vUV;
    void main(){gl_Position=vec4(aPos,0.0,1.0);vUV=aUV;}
)GLSL";
const char* quadFrag=R"GLSL(
    #version 330 core
    in vec2 vUV; out vec4 FragColor;
    uniform sampler2D uTex; uniform int uFX; uniform vec2 uRes;
    void main(){
        vec3 c=texture(uTex,vUV).rgb;
        if(uFX==0){FragColor=vec4(c,1.0);}
        else if(uFX==1){
            float l=dot(c,vec3(0.299,0.587,0.114));
            vec3 s=mix(vec3(0.01,0.05,0.02),vec3(0.10,1.00,0.40),l);
            float sc=1.0-0.18*mod(floor(vUV.y*uRes.y),2.0);
            vec2 uc=vUV-0.5; float v=1.0-dot(uc,uc)*2.2;
            FragColor=vec4(s*sc*max(v,0.1),1.0);
        }else if(uFX==2){
            float l=dot(c,vec3(0.299,0.587,0.114));
            FragColor=vec4(0.04,l*1.3+0.04,0.04,1.0);
        }else{
            vec2 px=1.0/uRes;
            float L=dot(texture(uTex,vUV-vec2(px.x,0)).rgb,vec3(1.0/3.0));
            float R=dot(texture(uTex,vUV+vec2(px.x,0)).rgb,vec3(1.0/3.0));
            float U=dot(texture(uTex,vUV+vec2(0,px.y)).rgb,vec3(1.0/3.0));
            float D=dot(texture(uTex,vUV-vec2(0,px.y)).rgb,vec3(1.0/3.0));
            float e=sqrt((R-L)*(R-L)+(U-D)*(U-D))*6.0;
            FragColor=vec4(c+vec3(0.05,0.6,0.2)*clamp(e,0.0,1.0),1.0);
        }
    }
)GLSL";

unsigned int cs(unsigned int t,const char* s){
    unsigned int id=glCreateShader(t);glShaderSource(id,1,&s,NULL);glCompileShader(id);
    int ok;char log[512];glGetShaderiv(id,GL_COMPILE_STATUS,&ok);
    if(!ok){glGetShaderInfoLog(id,512,NULL,log);std::cerr<<log;}return id;
}
unsigned int mkProg(const char* vs,const char* fs){
    unsigned int v=cs(GL_VERTEX_SHADER,vs),f=cs(GL_FRAGMENT_SHADER,fs);
    unsigned int p=glCreateProgram();glAttachShader(p,v);glAttachShader(p,f);glLinkProgram(p);
    glDeleteShader(v);glDeleteShader(f);return p;
}

glm::vec3 camPos(0,5.0f,20.0f),camFront(0,-0.2f,-1),camUp(0,1,0);
float yaw=-90,pitch=-10,lastX=512,lastY=340,fov=50;
bool firstMouse=true;
bool showOutline=false,hostile=false,showSweep=true;
int fxMode=1;
bool oPrev=false,tPrev=false,sPrev=false;
bool f1p=false,f2p=false,f3p=false,f4p=false;

void mouseCB(GLFWwindow* w,double x,double y){
    if(firstMouse){lastX=(float)x;lastY=(float)y;firstMouse=false;}
    float dx=((float)x-lastX)*0.1f,dy=(lastY-(float)y)*0.1f;
    lastX=(float)x;lastY=(float)y;yaw+=dx;pitch+=dy;
    if(pitch>89)pitch=89;if(pitch<-89)pitch=-89;
    glm::vec3 f;
    f.x=cos(glm::radians(yaw))*cos(glm::radians(pitch));
    f.y=sin(glm::radians(pitch));
    f.z=sin(glm::radians(yaw))*cos(glm::radians(pitch));
    camFront=glm::normalize(f);
}
void scrollCB(GLFWwindow* w,double xo,double yo){fov-=(float)yo;if(fov<5)fov=5;if(fov>90)fov=90;}
void onResize(GLFWwindow* w,int W,int H){glViewport(0,0,W,H);}

void processInput(GLFWwindow* w,float dt){
    if(glfwGetKey(w,GLFW_KEY_ESCAPE)==GLFW_PRESS)glfwSetWindowShouldClose(w,true);
    float s=8.0f*dt;
    if(glfwGetKey(w,GLFW_KEY_W)==GLFW_PRESS)camPos+=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_S)==GLFW_PRESS)camPos-=s*camFront;
    if(glfwGetKey(w,GLFW_KEY_A)==GLFW_PRESS)camPos-=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_D)==GLFW_PRESS)camPos+=glm::normalize(glm::cross(camFront,camUp))*s;
    if(glfwGetKey(w,GLFW_KEY_Q)==GLFW_PRESS)camPos.y+=s;
    if(glfwGetKey(w,GLFW_KEY_E)==GLFW_PRESS)camPos.y-=s;
    bool oN=(glfwGetKey(w,GLFW_KEY_O)==GLFW_PRESS);
    bool tN=(glfwGetKey(w,GLFW_KEY_T)==GLFW_PRESS);
    bool sN=(glfwGetKey(w,GLFW_KEY_G)==GLFW_PRESS); // G=sweep (S is fly-back)
    bool k1=(glfwGetKey(w,GLFW_KEY_F1)==GLFW_PRESS);
    bool k2=(glfwGetKey(w,GLFW_KEY_F2)==GLFW_PRESS);
    bool k3=(glfwGetKey(w,GLFW_KEY_F3)==GLFW_PRESS);
    bool k4=(glfwGetKey(w,GLFW_KEY_F4)==GLFW_PRESS);
    if(oN&&!oPrev){showOutline=!showOutline;std::cout<<(showOutline?"Outline ON\n":"Outline OFF\n");}
    if(tN&&!tPrev){hostile=!hostile;std::cout<<(hostile?"HOSTILE\n":"Friendly\n");}
    if(sN&&!sPrev){showSweep=!showSweep;std::cout<<(showSweep?"Sweep ON\n":"Sweep OFF\n");}
    if(k1&&!f1p){fxMode=0;std::cout<<"FX: Normal\n";}
    if(k2&&!f2p){fxMode=1;std::cout<<"FX: Sonar Phosphor\n";}
    if(k3&&!f3p){fxMode=2;std::cout<<"FX: NVG Green\n";}
    if(k4&&!f4p){fxMode=3;std::cout<<"FX: Edge Glow\n";}
    oPrev=oN;tPrev=tN;sPrev=sN;f1p=k1;f2p=k2;f3p=k3;f4p=k4;
}

int main(){
    std::cout<<"\n=== Day 3 Lab 5 — Full Maritime Surveillance Scene ===\n\n";
    std::cout<<"WASD+Q/E=fly | O=outline | T=threat | G=sweep | F1-F4=FX | ESC=quit\n\n";

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR,3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR,3);
    glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);
    GLFWwindow* window=glfwCreateWindow(WIN_W,WIN_H,
        "Surveillance Scene — Lab 5: Full Capstone",NULL,NULL);
    if(!window){glfwTerminate();return-1;}
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window,onResize);
    glfwSetCursorPosCallback(window,mouseCB);
    glfwSetScrollCallback(window,scrollCB);
    glfwSetInputMode(window,GLFW_CURSOR,GLFW_CURSOR_DISABLED);
    glewExperimental=GL_TRUE;glewInit();
    glEnable(GL_DEPTH_TEST);
    std::cout<<"GPU: "<<glGetString(GL_RENDERER)<<"\n\n";

    unsigned int buoyShader=mkProg(buoyVert,buoyFrag);
    unsigned int vShader=mkProg(vesselVert,vesselFrag);
    unsigned int oShader=mkProg(outVert,outFrag);
    unsigned int sShader=mkProg(swpVert,swpFrag);
    unsigned int qShader=mkProg(quadVert,quadFrag);
    std::cout<<"[1] All shaders compiled\n";

    // FBO
    unsigned int fbo,cTex,dRBO;
    glGenFramebuffers(1,&fbo);glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    glGenTextures(1,&cTex);glBindTexture(GL_TEXTURE_2D,cTex);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,WIN_W,WIN_H,0,GL_RGB,GL_UNSIGNED_BYTE,NULL);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,cTex,0);
    glGenRenderbuffers(1,&dRBO);glBindRenderbuffer(GL_RENDERBUFFER,dRBO);
    glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH24_STENCIL8,WIN_W,WIN_H);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_STENCIL_ATTACHMENT,GL_RENDERBUFFER,dRBO);
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE)std::cerr<<"FBO error\n";
    else std::cout<<"[2] FBO complete\n";
    glBindFramebuffer(GL_FRAMEBUFFER,0);

    // Buoy geometry + instance data
    float mk[]={
        0,1,0, 0.5f,0,0.5f, 0,1,0, 0.5f,0,-0.5f, 0,1,0, -0.5f,0,-0.5f,
        0,1,0, -0.5f,0,0.5f, 0,1,0, 0.5f,0,0.5f,
        0,-0.6f,0, 0.5f,0,0.5f, 0,-0.6f,0, 0.5f,0,-0.5f,
        0,-0.6f,0, -0.5f,0,-0.5f, 0,-0.6f,0, -0.5f,0,0.5f, 0,-0.6f,0, 0.5f,0,0.5f
    };
    int mkCnt=sizeof(mk)/(3*sizeof(float));
    const int NINST=500;
    std::mt19937 rng(77);
    std::uniform_real_distribution<float> pd(-18.0f,18.0f);
    std::uniform_int_distribution<int>    td(0,2);
    struct ID{float x,z,t;};
    std::vector<ID> idat(NINST);
    for(auto& d:idat){d.x=pd(rng);d.z=pd(rng);d.t=(float)td(rng);}
    unsigned int gVBO,iVBO,bVAO;
    glGenBuffers(1,&gVBO);glBindBuffer(GL_ARRAY_BUFFER,gVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(mk),mk,GL_STATIC_DRAW);
    glGenBuffers(1,&iVBO);glBindBuffer(GL_ARRAY_BUFFER,iVBO);
    glBufferData(GL_ARRAY_BUFFER,NINST*sizeof(ID),idat.data(),GL_STATIC_DRAW);
    glGenVertexArrays(1,&bVAO);glBindVertexArray(bVAO);
    glBindBuffer(GL_ARRAY_BUFFER,gVBO);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,3*sizeof(float),(void*)0);glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER,iVBO);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,sizeof(ID),(void*)0);glEnableVertexAttribArray(1);glVertexAttribDivisor(1,1);
    glVertexAttribPointer(2,1,GL_FLOAT,GL_FALSE,sizeof(ID),(void*)(2*sizeof(float)));glEnableVertexAttribArray(2);glVertexAttribDivisor(2,1);
    glBindVertexArray(0);
    std::cout<<"[3] Buoy VAO: "<<NINST<<" instanced markers\n";

    // Vessel hull
    float hull[]={
        -1.2f,0.2f,-0.3f,0,1,0,  1.2f,0.2f,-0.3f,0,1,0,  1.2f,0.2f,0.3f,0,1,0,
         1.2f,0.2f,0.3f,0,1,0,  -1.2f,0.2f,0.3f,0,1,0,  -1.2f,0.2f,-0.3f,0,1,0,
        -1.2f,-0.2f,-0.3f,0,-1,0,  1.2f,-0.2f,-0.3f,0,-1,0,  1.2f,-0.2f,0.3f,0,-1,0,
         1.2f,-0.2f,0.3f,0,-1,0,  -1.2f,-0.2f,0.3f,0,-1,0,  -1.2f,-0.2f,-0.3f,0,-1,0,
        -1.2f,-0.2f,0.3f,0,0,1,  1.2f,-0.2f,0.3f,0,0,1,  1.2f,0.2f,0.3f,0,0,1,
         1.2f,0.2f,0.3f,0,0,1,  -1.2f,0.2f,0.3f,0,0,1,  -1.2f,-0.2f,0.3f,0,0,1,
        -1.2f,-0.2f,-0.3f,0,0,-1,  1.2f,-0.2f,-0.3f,0,0,-1,  1.2f,0.2f,-0.3f,0,0,-1,
         1.2f,0.2f,-0.3f,0,0,-1,  -1.2f,0.2f,-0.3f,0,0,-1,  -1.2f,-0.2f,-0.3f,0,0,-1,
         1.2f,-0.2f,-0.3f,1,0,0,  1.2f,-0.2f,0.3f,1,0,0,  1.2f,0.2f,0.3f,1,0,0,
         1.2f,0.2f,0.3f,1,0,0,  1.2f,0.2f,-0.3f,1,0,0,  1.2f,-0.2f,-0.3f,1,0,0,
        -1.2f,-0.2f,-0.3f,-1,0,0,  -1.2f,-0.2f,0.3f,-1,0,0,  -1.2f,0.2f,0.3f,-1,0,0,
        -1.2f,0.2f,0.3f,-1,0,0,  -1.2f,0.2f,-0.3f,-1,0,0,  -1.2f,-0.2f,-0.3f,-1,0,0,
    };
    unsigned int hVAO,hVBO;
    glGenVertexArrays(1,&hVAO);glBindVertexArray(hVAO);
    glGenBuffers(1,&hVBO);glBindBuffer(GL_ARRAY_BUFFER,hVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(hull),hull,GL_STATIC_DRAW);
    glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)0);glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,6*sizeof(float),(void*)(3*sizeof(float)));glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    // Fullscreen quad
    float qd[]={-1,-1,0,0, 1,-1,1,0, 1,1,1,1, 1,1,1,1, -1,1,0,1, -1,-1,0,0};
    unsigned int qVAO,qVBO;
    glGenVertexArrays(1,&qVAO);glBindVertexArray(qVAO);
    glGenBuffers(1,&qVBO);glBindBuffer(GL_ARRAY_BUFFER,qVBO);
    glBufferData(GL_ARRAY_BUFFER,sizeof(qd),qd,GL_STATIC_DRAW);
    glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)0);glEnableVertexAttribArray(0);
    glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,4*sizeof(float),(void*)(2*sizeof(float)));glEnableVertexAttribArray(1);
    glBindVertexArray(0);

    glUseProgram(qShader);
    glUniform1i(glGetUniformLocation(qShader,"uTex"),0);
    glUniform2f(glGetUniformLocation(qShader,"uRes"),(float)WIN_W,(float)WIN_H);
    std::cout<<"[4] All VAOs ready. Full capstone loop starting.\n\n";

    float lastFrame=0;
    while(!glfwWindowShouldClose(window)){
        float now=(float)glfwGetTime();
        float dt=now-lastFrame;lastFrame=now;
        processInput(window,dt);

        glm::vec3 lp(cos(now*0.4f)*5.0f,4.0f,sin(now*0.4f)*5.0f);
        glm::mat4 view=glm::lookAt(camPos,camPos+camFront,camUp);
        glm::mat4 proj=glm::perspective(glm::radians(fov),(float)WIN_W/WIN_H,0.1f,100.0f);
        glm::mat4 model=glm::mat4(1.0f);

        // ── PASS 1: everything into FBO ────────────────────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,fbo);
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_STENCIL_TEST);
        glClearColor(0.02f,0.04f,0.09f,1.0f);
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

        // Buoys (instanced, no stencil)
        glDisable(GL_STENCIL_TEST);
        glUseProgram(buoyShader);
        glUniformMatrix4fv(glGetUniformLocation(buoyShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(buoyShader,"uProj"),1,GL_FALSE,glm::value_ptr(proj));
        glUniform1f(glGetUniformLocation(buoyShader,"uTime"),now);
        glBindVertexArray(bVAO);
        glDrawArraysInstanced(GL_TRIANGLE_STRIP,0,mkCnt,NINST);

        // Vessel (stencil write if outline active)
        if(showOutline){
            glEnable(GL_STENCIL_TEST);
            glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
            glStencilFunc(GL_ALWAYS,1,0xFF);
            glStencilMask(0xFF);
        }
        glm::vec3 oc=hostile?glm::vec3(0.6f,0.1f,0.1f):glm::vec3(0.5f,0.54f,0.58f);
        glUseProgram(vShader);
        glUniformMatrix4fv(glGetUniformLocation(vShader,"uModel"),1,GL_FALSE,glm::value_ptr(model));
        glUniformMatrix4fv(glGetUniformLocation(vShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
        glUniformMatrix4fv(glGetUniformLocation(vShader,"uProj"),1,GL_FALSE,glm::value_ptr(proj));
        glUniform3fv(glGetUniformLocation(vShader,"uLP"),1,glm::value_ptr(lp));
        glUniform3fv(glGetUniformLocation(vShader,"uVP"),1,glm::value_ptr(camPos));
        glUniform3fv(glGetUniformLocation(vShader,"uOC"),1,glm::value_ptr(oc));
        glBindVertexArray(hVAO);
        glDrawArrays(GL_TRIANGLES,0,36);

        // Outline (stencil read)
        if(showOutline){
            glStencilFunc(GL_NOTEQUAL,1,0xFF);
            glStencilMask(0x00);
            glDisable(GL_DEPTH_TEST);
            glm::vec3 outC=hostile?glm::vec3(1.0f,0.1f,0.1f):glm::vec3(0.2f,0.7f,1.0f);
            glUseProgram(oShader);
            glUniformMatrix4fv(glGetUniformLocation(oShader,"uModel"),1,GL_FALSE,glm::value_ptr(model));
            glUniformMatrix4fv(glGetUniformLocation(oShader,"uView"),1,GL_FALSE,glm::value_ptr(view));
            glUniformMatrix4fv(glGetUniformLocation(oShader,"uProj"),1,GL_FALSE,glm::value_ptr(proj));
            glUniform1f(glGetUniformLocation(oShader,"uThk"),0.055f);
            glUniform3fv(glGetUniformLocation(oShader,"uOC"),1,glm::value_ptr(outC));
            glBindVertexArray(hVAO);
            glDrawArrays(GL_TRIANGLES,0,36);
            glStencilMask(0xFF);glDisable(GL_STENCIL_TEST);glEnable(GL_DEPTH_TEST);
        }

        // Sweep (blended, last)
        if(showSweep){
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
            glDepthMask(GL_FALSE);
            glDisable(GL_STENCIL_TEST);
            glm::vec3 sc=hostile?glm::vec3(1.0f,0.15f,0.15f):glm::vec3(0.1f,0.85f,0.3f);
            glUseProgram(sShader);
            glUniform1f(glGetUniformLocation(sShader,"uTime"),now);
            glUniform3fv(glGetUniformLocation(sShader,"uSC"),1,glm::value_ptr(sc));
            glBindVertexArray(qVAO);
            glDrawArrays(GL_TRIANGLES,0,6);
            glDepthMask(GL_TRUE);glDisable(GL_BLEND);
        }

        // ── PASS 2: FBO → screen with post-processing ──────────────────────
        glBindFramebuffer(GL_FRAMEBUFFER,0);
        glDisable(GL_DEPTH_TEST);glDisable(GL_STENCIL_TEST);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(qShader);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D,cTex);
        glUniform1i(glGetUniformLocation(qShader,"uFX"),fxMode);
        glBindVertexArray(qVAO);
        glDrawArrays(GL_TRIANGLES,0,6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1,&bVAO);glDeleteVertexArrays(1,&hVAO);glDeleteVertexArrays(1,&qVAO);
    glDeleteBuffers(1,&gVBO);glDeleteBuffers(1,&iVBO);glDeleteBuffers(1,&hVBO);glDeleteBuffers(1,&qVBO);
    glDeleteTextures(1,&cTex);glDeleteRenderbuffers(1,&dRBO);glDeleteFramebuffers(1,&fbo);
    glDeleteProgram(buoyShader);glDeleteProgram(vShader);glDeleteProgram(oShader);
    glDeleteProgram(sShader);glDeleteProgram(qShader);
    glfwTerminate();
    std::cout<<"\nDay 3 capstone complete. All systems shut down.\n";
    return 0;
}
Expected Terminal Output
=== Day 3 Lab 5 -- Full Maritime Surveillance Scene ===
[1] All shaders compiled
[2] FBO complete
[3] Buoy VAO: 500 instanced markers
[4] All VAOs ready. Full capstone loop starting.
Outline ON
HOSTILE
FX: Sonar Phosphor
Screen: 500 buoys + red vessel + red outline + red sweep -> sonar phosphor
🔬 Experiments
  • Add a second vessel at position (3, 0, 2): Translate a second model matrix. Draw it between buoys and the outline pass. The stencil outline only applies to vessel 1 (stencil writes only for the first hull draw). Shows selected vs unselected objects in one scene.
  • Fly above the entire field at Y=40: Use Q key. Look down. All 500 buoys and the vessel from top-down. The sonar phosphor effect turns the top-down view into a functional sonar display aesthetic.
  • Change NINST from 500 to 2,000: Rebuild. Still smooth at 60 fps. FBO and stencil overhead is per-frame, not per-instance — instancing scales independently of the other systems.
✓ Lab 5 Deliverable — Day 3 Capstone Complete
Show your instructor the complete running scene:
  • 500 instanced buoy markers visible, each bobbing at a unique phase
  • Lit vessel at scene centre with Phong lighting
  • O key: stencil outline appears/disappears cleanly around vessel perimeter
  • Vessel hull visible THROUGH the transparent radar sweep
  • T key: both outline and sweep change colour (blue/green → red for hostile)
  • F2: sonar phosphor wraps entire scene including outline and sweep
  • Full fly camera works in all configurations
  • Can explain the draw order and why each step must be in that position
← All Labs
By Raushan Ranjan (MCT | Educator)
Koenig Original AI-Courseware · Day 3 Complete
Day 4 Labs — Coming Soon →