main.cpp.
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.
C:\Labs\SurveillanceScene\
├── CMakeLists.txt <-- write once, never change
└── src\
└── main.cpp <-- replace with each lab's complete file
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
)cd C:\Labs\SurveillanceScene cmake -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Release build\Release\SurveillanceScene.exe
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.- ►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
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.
- 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
// ═══════════════════════════════════════════════════════════════════════════
// 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;
}
- 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.7touTime * 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)tovec2(0.30, -0.10). Rebuild — contact moves to new bearing.
- 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
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.- ►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_InstanceIDdrives 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
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;
- 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
// ═══════════════════════════════════════════════════════════════════════════
// 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;
}
- 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.
- 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
- ►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
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.
- 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)
// ═══════════════════════════════════════════════════════════════════════════
// 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;
}
- Change scan-line frequency: In the sonar phosphor shader, change
floor(vUV.y * 680.0)to340.0for thicker lines, or2720.0for 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.
- 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
- ►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
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
- 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
// ═══════════════════════════════════════════════════════════════════════════
// 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;
}
- 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.
- 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 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
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.
- 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
// ═══════════════════════════════════════════════════════════════════════════
// 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;
}
- 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.
- 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
Koenig Original AI-Courseware · Day 3 Complete