main.cpp — copy the whole file, build, run.All 5 labs share one project folder: C:\Labs\TacticalDisplay\. The CMakeLists.txt is identical for all labs — you write it once and never change it. Each lab replaces only src\main.cpp. Build once after Lab 1 setup, then just replace main.cpp + rebuild for each subsequent lab.
C:\Labs\TacticalDisplay\
├── CMakeLists.txt ← write once, same for all 5 labs
└── src\
└── main.cpp ← replace with each lab's complete file
cmake_minimum_required(VERSION 3.20)
project(TacticalDisplay)
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(TacticalDisplay src/main.cpp)
target_link_libraries(TacticalDisplay
OpenGL::GL
${GLFW_DIR}/lib-vc2022/glfw3.lib
${GLEW_DIR}/lib/Release/x64/glew32s.lib
)cd C:\Labs\TacticalDisplay cmake -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Release build\Release\TacticalDisplay.exe
Write your lab's main.cpp (or copy the complete file above) → open Developer Command Prompt for VS 2022 → run the 4 build commands above. The CMakeLists.txt and the folder path never change. Only src\main.cpp changes between labs.
- ►1024×768 window titled “Tactical Contact Display — Naval Ops”
- ►Near-black background (R=0.02, G=0.03, B=0.07) — ops-room dark display colour
- ►GPU name + OpenGL version + ready message printed to terminal
- ►ESC closes cleanly, resize maintains correct viewport
- CMakeLists.txt written (use the shared one above)
- GLFW initialised with correct OpenGL 3.3 core hints
- Window created 1024×768 with correct title
- GLEW initialised after making context current
- GPU info printed to terminal on startup
- Near-black tactical background colour set
- ESC closes the window cleanly
- Window resize keeps viewport correct
Try writing main.cpp from memory using what you learned in Demo 2. The 5-step GLFW sequence is always the same: Init → Hints → CreateWindow → MakeContextCurrent → GLEW. Only refer to the complete file below if you get stuck after trying.
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 1 · LAB 1 of 5
// Tactical Contact Display — Display Window Setup
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: TacticalDisplay (use this in CMakeLists.txt)
// FOLDER: C:\Labs\TacticalDisplay\
//
// WHAT THIS LAB DOES:
// Creates the tactical operations display window — dark background,
// correct viewport, working render loop, ESC to quit.
// GPU info printed to terminal to confirm environment is live.
//
// WHAT YOU WILL SEE:
// - Window titled "Tactical Contact Display — Naval Ops" at 1024x768
// - Near-black background (ops-room display colour)
// - Terminal: GPU name + OpenGL version + "Display ready" message
// - ESC closes cleanly
// ═══════════════════════════════════════════════════════════════════════════
// GLEW must always be included BEFORE GLFW — no exceptions
// GLEW_STATIC = static link, no .dll needed at runtime
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ─────────────────────────────────────────────────────────────────────────────
// Called automatically by GLFW whenever the window is resized.
// Keeps the OpenGL drawing area matching the new window dimensions.
// ─────────────────────────────────────────────────────────────────────────────
void onResize(GLFWwindow* window, int width, int height) {
glViewport(0, 0, width, height);
}
// ─────────────────────────────────────────────────────────────────────────────
// Checked every frame inside the render loop.
// If ESC is pressed, signals the window to close on next iteration.
// ─────────────────────────────────────────────────────────────────────────────
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// ─────────────────────────────────────────────────────────────────────────────
// MAIN
// ─────────────────────────────────────────────────────────────────────────────
int main() {
// ── STEP 1: Initialise GLFW ───────────────────────────────────────────────
// Must be the very first GLFW call — sets up its internal state.
if (!glfwInit()) {
std::cerr << "ERROR: GLFW init failed\n";
return -1;
}
// ── STEP 2: Context hints — what kind of OpenGL we want ───────────────────
// MUST be called before glfwCreateWindow — they are like a spec form.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // OpenGL 3.3
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// ── STEP 3: Create the OS window AND the OpenGL context ───────────────────
// 1024x768 = common tactical display resolution
GLFWwindow* window = glfwCreateWindow(
1024, 768,
"Tactical Contact Display — Naval Ops",
NULL, // NULL = windowed (not fullscreen)
NULL // NULL = no shared context
);
if (!window) {
std::cerr << "ERROR: Window creation failed\n";
glfwTerminate();
return -1;
}
// ── STEP 4: Make the context current on this thread ───────────────────────
// All gl* calls from this point forward go to this context.
// CRITICAL: glewInit() will fail silently without a current context.
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, onResize);
// ── STEP 5: Initialise GLEW ───────────────────────────────────────────────
// Loads all OpenGL function pointers from the GPU driver.
// Without this, calling glClear() = crash.
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
std::cerr << "ERROR: GLEW init failed\n";
return -1;
}
// Confirm the environment is working — print GPU info to terminal
std::cout << "\n[ Tactical Contact Display — System Check ]\n";
std::cout << "GPU: " << glGetString(GL_RENDERER) << "\n";
std::cout << "OpenGL Version: " << glGetString(GL_VERSION) << "\n";
std::cout << "GLSL Version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << "\n";
std::cout << "Display ready. Press ESC to shut down.\n\n";
// ── STEP 6: RENDER LOOP ───────────────────────────────────────────────────
// Runs 60 times per second until window is closed.
// Each iteration = one frame on screen.
while (!glfwWindowShouldClose(window)) {
// ① Check for ESC key
processInput(window);
// ② Clear to tactical display dark background
// RGB(5, 8, 18) / 255 = (0.02, 0.03, 0.07) — near-black with blue tint
// Matches real operational display colours (reduces eye strain in dark ops rooms)
glClearColor(0.02f, 0.03f, 0.07f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// ③ Draw contact markers HERE — added from Lab 2 onward
// (Nothing drawn yet — proving the window and loop work first)
// ④ Show completed frame
glfwSwapBuffers(window);
// ⑤ Process OS events — keyboard, mouse, resize
glfwPollEvents();
}
// ── CLEANUP ───────────────────────────────────────────────────────────────
glfwTerminate();
std::cout << "Display shutdown cleanly.\n";
return 0;
}
- Change background: Try
glClearColor(0.0f, 0.1f, 0.05f, 1.0f)→ dark green (submarine sonar display colour). Rebuild. - Comment out glfwPollEvents(): Rebuild → window freezes immediately. Ctrl+C to kill. Proves why polling is mandatory.
- Comment out glfwSwapBuffers(): Rebuild → window stays black even with glClearColor set. Proves double buffering.
- Window opens at 1024×768 with correct title
- Near-black background visible (not pure black — slight blue tint)
- Terminal shows GPU name + version + ready message
- ESC closes without crash
- ►Vertex shader + fragment shader written in GLSL and compiled by the GPU driver
- ►3 contact triangles uploaded to GPU VRAM with VBO (108 bytes = 27 floats)
- ►VAO records the vertex layout (3 floats per vertex, stride=12, offset=0)
- ►All 3 contacts drawn in one
glDrawArrays(GL_TRIANGLES, 0, 9)call - ►Terminal prints 4 confirmation steps: shader ID, bytes uploaded, attribute info
Y=+1.0 (top)
|
▲ Friendly | ▲ Unknown
(0.0, 0.6) | (0.5, 0.3)
|
X=-1 ------+------- X=+1
|
▲ Hostile |
(-0.4,-0.4) |
|
Y=-1.0 (bottom)
Each contact = small upward-pointing triangle
Triangle size: tip at Y+0.06, base at Y-0.06 from centre
All the same dim-white colour in Lab 2 (colours added in Lab 3)
All 27 float values (9 vertices × 3 floats each) for all 3 contacts go into a single float array and one VBO. glDrawArrays(GL_TRIANGLES, 0, 9) draws them all: the GPU reads vertices 0,1,2 as triangle 1, vertices 3,4,5 as triangle 2, vertices 6,7,8 as triangle 3. One draw call = three contacts. We split into separate draw calls in Lab 5 when we need different uniform values per contact.
- Vertex shader written: aPos at location=0, outputs gl_Position
- Fragment shader written: outputs fixed dim-white colour
- compileShader helper written with error checking
- Contact geometry defined: 9 vertices × 3 floats = 27 floats
- VAO bound FIRST, then VBO created and data uploaded
- glVertexAttribPointer: 3 floats, stride=12, offset=0
- Render loop draws all 9 vertices in one call
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 1 · LAB 2 of 5
// Tactical Contact Display — Contact Marker Geometry
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: TacticalDisplay
// FOLDER: C:\Labs\TacticalDisplay\
//
// WHAT THIS LAB ADDS OVER LAB 1:
// - Vertex + Fragment shaders (GLSL) compiled and linked at runtime
// - 3 contact triangle markers uploaded to GPU with VBO + VAO
// - All 3 contacts drawn in the render loop as dim white triangles
// - Terminal prints setup confirmation steps
//
// WHAT YOU WILL SEE:
// - Same dark window as Lab 1
// - 3 small white triangles: top-centre, lower-left, right-centre
// - Terminal: shader compiled, VBO/VAO ready messages
//
// CONTACT POSITIONS (NDC — range -1.0 to +1.0):
// Contact 1 (Friendly): centre (0.0, 0.6) — top-centre
// Contact 2 (Hostile): centre (-0.4,-0.4) — lower-left
// Contact 3 (Unknown): centre (0.5, 0.3) — right-centre
// ═══════════════════════════════════════════════════════════════════════════
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ─────────────────────────────────────────────────────────────────────────────
// VERTEX SHADER
// Runs once per vertex on the GPU.
// Takes aPos (X,Y,Z) from the VBO and places it on screen.
// layout(location=0) MUST match glVertexAttribPointer(0, ...) below.
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
#version 330 core
layout(location = 0) in vec3 aPos;
void main() {
gl_Position = vec4(aPos, 1.0);
}
)GLSL";
// ─────────────────────────────────────────────────────────────────────────────
// FRAGMENT SHADER
// Runs once per pixel the triangle covers.
// Returns dim white for all contacts — we add threat colours in Lab 3.
// ─────────────────────────────────────────────────────────────────────────────
const char* fragSrc = R"GLSL(
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(0.85, 0.85, 0.85, 1.0); // dim white
}
)GLSL";
// ─────────────────────────────────────────────────────────────────────────────
// HELPER: compileShader
// Compiles one shader (vertex or fragment) and returns its GPU-side ID.
// Always checks for errors — GLSL errors only appear at runtime.
// ─────────────────────────────────────────────────────────────────────────────
unsigned int compileShader(unsigned int type, const char* source) {
unsigned int id = glCreateShader(type);
glShaderSource(id, 1, &source, NULL);
glCompileShader(id);
int ok; char log[512];
glGetShaderiv(id, GL_COMPILE_STATUS, &ok);
if (!ok) {
glGetShaderInfoLog(id, 512, NULL, log);
std::cerr << "Shader compile error:\n" << log << "\n";
}
return id;
}
// ─────────────────────────────────────────────────────────────────────────────
// HELPER: createShaderProgram
// Links vertex + fragment into one shader program. Returns program ID.
// ─────────────────────────────────────────────────────────────────────────────
unsigned int createShaderProgram(const char* vSrc, const char* fSrc) {
unsigned int vs = compileShader(GL_VERTEX_SHADER, vSrc);
unsigned int fs = compileShader(GL_FRAGMENT_SHADER, fSrc);
unsigned int prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
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() {
// Window + context setup (same as Lab 1)
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, 768, "Tactical Contact Display — Naval Ops", NULL, NULL);
if (!window) { glfwTerminate(); return -1; }
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, onResize);
glewExperimental = GL_TRUE;
glewInit();
std::cout << "GPU: " << glGetString(GL_RENDERER) << "\n\n";
// ── Compile shader program ────────────────────────────────────────────────
unsigned int shaderProg = createShaderProgram(vertSrc, fragSrc);
std::cout << "[1] Shader program compiled + linked. ID = " << shaderProg << "\n";
// ── Contact geometry ──────────────────────────────────────────────────────
// 3 contacts x 3 vertices x 3 floats (x,y,z) = 27 floats = 108 bytes
// Each contact = a small upward-pointing triangle (tactical icon)
// All vertices use position ONLY — colour added in Lab 3
float contacts[] = {
// X Y Z
// Contact 1 — FRIENDLY vessel (top-centre of display)
0.00f, 0.66f, 0.0f, // tip (top)
-0.05f, 0.54f, 0.0f, // bottom-left
0.05f, 0.54f, 0.0f, // bottom-right
// Contact 2 — HOSTILE vessel (lower-left quadrant)
-0.40f, -0.34f, 0.0f, // tip
-0.45f, -0.46f, 0.0f, // bottom-left
-0.35f, -0.46f, 0.0f, // bottom-right
// Contact 3 — UNKNOWN contact (right-centre area)
0.50f, 0.36f, 0.0f, // tip
0.45f, 0.24f, 0.0f, // bottom-left
0.55f, 0.24f, 0.0f, // bottom-right
};
// 9 vertices × 3 floats = 27 floats × 4 bytes = 108 bytes
// ── VAO + VBO setup ───────────────────────────────────────────────────────
// VAO bind FIRST → then VBO → then glVertexAttribPointer
// (VAO must be recording before you describe the layout)
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO); // ← RECORDING STARTS
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(contacts), contacts, GL_STATIC_DRAW);
std::cout << "[2] " << sizeof(contacts) << " bytes uploaded to GPU VRAM (VBO ID=" << VBO << ")\n";
// Attribute 0: position — 3 floats, stride=12 (3x4), offset=0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
std::cout << "[3] VAO recorded. Attribute 0: 3 floats, stride=12, offset=0\n";
glBindVertexArray(0); // ← RECORDING STOPS
std::cout << "[4] Setup complete. Render loop starting...\n";
std::cout << " ESC = quit\n\n";
// ── RENDER LOOP ───────────────────────────────────────────────────────────
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.02f, 0.03f, 0.07f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Activate shader program
glUseProgram(shaderProg);
// Bind VAO — restores VBO + attribute layout in one call
glBindVertexArray(VAO);
// Draw all 3 contacts: 9 vertices = 3 triangles
// GL_TRIANGLES: every 3 consecutive vertices = 1 filled triangle
glDrawArrays(GL_TRIANGLES, 0, 9);
glfwSwapBuffers(window);
glfwPollEvents();
}
// ── CLEANUP ───────────────────────────────────────────────────────────────
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProg);
glfwTerminate();
return 0;
}
- Comment out glUseProgram(): Black screen. GPU has no shader to run. Restore and verify it works again.
- Comment out glBindVertexArray(VAO): Black screen. GPU has no data to read. Restore.
- Move a contact: Change the X,Y values of Contact 1's vertices (first 9 floats) to move it to a different position on screen.
- Add a 4th contact: Add 3 more vertices after the Unknown contact in the float array and change the draw count from 9 to 12.
- Three small triangles visible: top-centre, lower-left, right-centre
- No shader compile errors in terminal
- Terminal shows the 4 setup confirmation messages
- ESC closes cleanly
- ►Each vertex now has 6 floats: X Y Z R G B (was 3 floats: X Y Z)
- ►Vertex shader receives aColour at location=1 and passes it to fragment shader
- ►Fragment shader uses vColour from vertex shader instead of hardcoded white
- ►VAO records TWO glVertexAttribPointer calls: pos (stride=24, offset=0) and colour (stride=24, offset=12)
- ►Render loop is IDENTICAL to Lab 2 — only the data description changed
Byte: 0 4 8 12 16 20 | 24 28 32 36 40 44 | ...
[X ] [Y ] [Z ] [R ] [G ] [B ] [X ] [Y ] [Z ] [R ] [G ] [B ] ...
|───position───|──colour──| |───position───|──colour──|
Vertex 0 (24 bytes) Vertex 1 (24 bytes)
Stride = 24 bytes (6 floats × 4 bytes) — SAME for both attributes
Position offset = 0 bytes (starts at byte 0 of each vertex)
Colour offset = 12 bytes (starts after 3 position floats: 3 × 4 = 12)
- Vertex shader: aColour at location=1, out vec3 vColour passes colour to fragment shader
- Fragment shader: in vec3 vColour (must match vertex shader name), FragColor = vec4(vColour, 1.0)
- Contact data updated: 9 vertices × 6 floats = 54 floats × 4 bytes = 216 bytes
- Friendly contact (first 3 vertices): all blue (0.20, 0.50, 1.00)
- Hostile contact (next 3 vertices): all red (1.00, 0.20, 0.20)
- Unknown contact (last 3 vertices): all yellow (1.00, 0.85, 0.10)
- Attribute 0 (pos): stride=24, offset=0 — Attribute 1 (colour): stride=24, offset=12
The draw call glDrawArrays(GL_TRIANGLES, 0, 9) is unchanged. The VAO recorded the new 2-attribute layout during setup, so it restores everything with one bind in the loop. This is exactly the VAO power you saw in Demo 4: describe the data once, the draw call never changes.
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 1 · LAB 3 of 5
// Tactical Contact Display — Threat Classification Colours
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: TacticalDisplay
// FOLDER: C:\Labs\TacticalDisplay\
//
// WHAT THIS LAB ADDS OVER LAB 2:
// - Each vertex now carries 6 floats: X Y Z R G B (interleaved VBO)
// - VAO records TWO attributes (position + colour)
// - Vertex shader receives and passes colour to fragment shader
// - GPU auto-interpolates colour between vertices
// - Friendly = BLUE, Hostile = RED, Unknown = YELLOW
//
// KEY CONCEPT — Interleaved stride/offset:
// Stride = 24 bytes (6 floats × 4 bytes per float)
// Position offset = 0 bytes (starts at byte 0)
// Colour offset = 12 bytes (after the 3 position floats)
//
// WHAT YOU WILL SEE:
// - Top-centre triangle: BLUE (friendly)
// - Lower-left triangle: RED (hostile)
// - Right-centre triangle: YELLOW (unknown)
// - Render loop is IDENTICAL to Lab 2 — only data description changed
// ═══════════════════════════════════════════════════════════════════════════
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ─────────────────────────────────────────────────────────────────────────────
// VERTEX SHADER — receives TWO attributes now
// location=0: aPos (x,y,z) — position
// location=1: aColour (r,g,b) — threat colour
// "out vec3 vColour" passes colour to fragment shader (GPU interpolates between vertices)
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColour;
out vec3 vColour; // GPU will interpolate this between the 3 vertices
void main() {
gl_Position = vec4(aPos, 1.0);
vColour = aColour; // pass threat colour through unchanged
}
)GLSL";
// ─────────────────────────────────────────────────────────────────────────────
// FRAGMENT SHADER — receives the interpolated colour from vertex shader
// "in vec3 vColour" MUST match "out vec3 vColour" name in vertex shader
// ─────────────────────────────────────────────────────────────────────────────
const char* fragSrc = R"GLSL(
#version 330 core
in vec3 vColour;
out vec4 FragColor;
void main() {
FragColor = vec4(vColour, 1.0); // RGB from vertex shader, Alpha=1 (fully opaque)
}
)GLSL";
unsigned int compileShader(unsigned int type, const char* source) {
unsigned int id = glCreateShader(type);
glShaderSource(id, 1, &source, 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 << "\n"; }
return id;
}
unsigned int createShaderProgram(const char* vSrc, const char* fSrc) {
unsigned int vs = compileShader(GL_VERTEX_SHADER, vSrc);
unsigned int fs = compileShader(GL_FRAGMENT_SHADER, fSrc);
unsigned int prog = glCreateProgram();
glAttachShader(prog, vs); glAttachShader(prog, fs); glLinkProgram(prog);
glDeleteShader(vs); glDeleteShader(fs);
return prog;
}
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() {
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, 768, "Tactical Contact Display — Naval Ops", 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 shaderProg = createShaderProgram(vertSrc, fragSrc);
std::cout << "[1] Shader compiled. ID = " << shaderProg << "\n";
// ── Contact geometry: X Y Z R G B per vertex (6 floats = 24 bytes) ───────
//
// Memory layout per vertex (24 bytes):
// Byte: 0 4 8 12 16 20
// [X ] [Y ] [Z ] [R ] [G ] [B ]
// |position | |colour |
//
// Stride = 24 (6 × 4), Position offset = 0, Colour offset = 12
float contacts[] = {
// X Y Z R G B
// Contact 1 — FRIENDLY (BLUE)
0.00f, 0.66f, 0.0f, 0.20f, 0.50f, 1.00f, // tip
-0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f, // bottom-left
0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f, // bottom-right
// Contact 2 — HOSTILE (RED)
-0.40f, -0.34f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.45f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.35f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
// Contact 3 — UNKNOWN (YELLOW)
0.50f, 0.36f, 0.0f, 1.00f, 0.85f, 0.10f,
0.45f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
0.55f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
};
// 9 vertices × 6 floats = 54 floats × 4 bytes = 216 bytes
// ── VAO + VBO with TWO attributes ────────────────────────────────────────
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO); // ← RECORDING STARTS
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(contacts), contacts, GL_STATIC_DRAW);
std::cout << "[2] " << sizeof(contacts) << " bytes on GPU (6 floats/vertex)\n";
// Attribute 0: POSITION — 3 floats, stride=24, offset=0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
std::cout << "[3] Attr 0 (position): stride=24, offset=0\n";
// Attribute 1: COLOUR — 3 floats, stride=24, offset=12
// offset=12 because position (3 floats × 4 bytes) takes the first 12 bytes
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
std::cout << "[4] Attr 1 (colour): stride=24, offset=12\n";
glBindVertexArray(0); // ← RECORDING STOPS
std::cout << "[5] Setup complete. Render loop starting...\n";
std::cout << " ESC = quit\n\n";
// ── RENDER LOOP ───────────────────────────────────────────────────────────
// IDENTICAL to Lab 2 render loop.
// Same glDrawArrays(GL_TRIANGLES, 0, 9) call.
// Different output because VAO now has 2 attributes.
// This proves VAO power: describe once, change data not draw calls.
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.02f, 0.03f, 0.07f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProg);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 9); // 9 vertices = 3 contacts
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProg);
glfwTerminate();
return 0;
}
- Top-centre contact = BLUE (friendly vessel)
- Lower-left contact = RED (hostile vessel)
- Right-centre contact = YELLOW (unknown contact)
- Each triangle is a solid uniform colour (no gradient per vertex — all 3 vertices same colour)
- Terminal shows 5 setup messages with correct stride/offset values
- ►Second VAO + VBO + EBO for the display boundary + crosshair grid
- ►8 grid vertices total: 4 boundary corners (vertices 0-3) + 4 crosshair points (vertices 4-7)
- ►EBO index list {0,1,2,3} + GL_LINE_LOOP draws boundary as a closed rectangle outline
- ►GL_LINES draws vertices 4-7 as 2 crosshair line segments (horizontal + vertical)
- ►Render loop now has 3 draw calls: contacts + boundary + crosshair
- ►W key toggles wireframe mode on contact triangles
Vertices 0-3: boundary rectangle corners
0(-0.9, 0.9) ──────────────────── 1(0.9, 0.9)
| |
| tactical display |
| |
3(-0.9,-0.9) ──────────────────── 2(0.9,-0.9)
EBO index list {0, 1, 2, 3} + GL_LINE_LOOP = closed rectangle outline
(draws: 0→1, 1→2, 2→3, 3→0)
Vertices 4-5: horizontal crosshair
4(-0.9, 0.0) ─── vertical centre ─── 5(0.9, 0.0)
Vertices 6-7: vertical crosshair
6(0.0, 0.9)
|
|
7(0.0,-0.9)
GL_LINES with start=4, count=4 draws: 4→5 (horizontal) and 6→7 (vertical)
- Contact VAO + VBO from Lab 3 unchanged and still working
- 8 grid vertices defined (X Y Z R G B format, dim grey colour)
- EBO index list {0,1,2,3} created for boundary rectangle
- Grid VAO + VBO + EBO created and configured
- Boundary drawn with glDrawElements(GL_LINE_LOOP, 4, ...)
- Crosshair drawn with glDrawArrays(GL_LINES, 4, 4)
- W key toggles wireframe on/off
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 1 · LAB 4 of 5
// Tactical Contact Display — Boundary + Grid Lines with EBO
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: TacticalDisplay
// FOLDER: C:\Labs\TacticalDisplay\
//
// WHAT THIS LAB ADDS OVER LAB 3:
// - A second VAO + VBO + EBO for the display boundary + grid lines
// - EBO (Element Buffer Object) stores index list for boundary rectangle
// - GL_LINE_LOOP draws the 4 corners as a closed rectangle outline
// - GL_LINES draws 2 crosshair lines dividing display into quadrants
// - Two separate draw calls per frame: one for contacts, one for grid
// - Press W to toggle wireframe on contacts (to see triangle edges)
//
// WHAT YOU WILL SEE:
// - Blue/red/yellow contact triangles (same as Lab 3)
// - Dim grey rectangle outline around the display boundary
// - Dim horizontal + vertical crosshair lines (quadrant dividers)
// - W key: toggle wireframe
// ═══════════════════════════════════════════════════════════════════════════
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ─────────────────────────────────────────────────────────────────────────────
// VERTEX SHADER — unchanged from Lab 3 (position + colour, 2 attributes)
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColour;
out vec3 vColour;
void main() {
gl_Position = vec4(aPos, 1.0);
vColour = aColour;
}
)GLSL";
// ─────────────────────────────────────────────────────────────────────────────
// FRAGMENT SHADER — unchanged from Lab 3
// ─────────────────────────────────────────────────────────────────────────────
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 type, const char* source) {
unsigned int id = glCreateShader(type);
glShaderSource(id, 1, &source, 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* vSrc, const char* fSrc) {
unsigned int vs = compileShader(GL_VERTEX_SHADER, vSrc);
unsigned int fs = compileShader(GL_FRAGMENT_SHADER, fSrc);
unsigned int prog = glCreateProgram();
glAttachShader(prog, vs); glAttachShader(prog, fs); glLinkProgram(prog);
glDeleteShader(vs); glDeleteShader(fs);
return prog;
}
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() {
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, 768, "Tactical Contact Display — Naval Ops", 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 shaderProg = createShaderProgram(vertSrc, fragSrc);
std::cout << "[1] Shader compiled. ID = " << shaderProg << "\n";
// ── SHAPE 1: Contact markers (from Lab 3, unchanged) ─────────────────────
// Format: X Y Z R G B (6 floats = 24 bytes per vertex)
float contacts[] = {
// X Y Z R G B
// Contact 1 — FRIENDLY (BLUE)
0.00f, 0.66f, 0.0f, 0.20f, 0.50f, 1.00f,
-0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f,
0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f,
// Contact 2 — HOSTILE (RED)
-0.40f, -0.34f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.45f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.35f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
// Contact 3 — UNKNOWN (YELLOW)
0.50f, 0.36f, 0.0f, 1.00f, 0.85f, 0.10f,
0.45f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
0.55f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
};
// Contact VAO + VBO (same as Lab 3)
unsigned int contactVAO, contactVBO;
glGenVertexArrays(1, &contactVAO);
glBindVertexArray(contactVAO);
glGenBuffers(1, &contactVBO);
glBindBuffer(GL_ARRAY_BUFFER, contactVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(contacts), contacts, 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);
std::cout << "[2] Contact VAO+VBO ready\n";
// ── SHAPE 2: Display boundary + crosshair grid ────────────────────────────
//
// 8 vertices total — same format: X Y Z R G B (6 floats, 24 bytes each)
// All grid geometry = dim grey (R=0.22, G=0.28, B=0.36)
//
// Vertices 0-3: 4 corners of boundary rectangle
// Vertex 4-5: horizontal crosshair line (left-mid to right-mid)
// Vertex 6-7: vertical crosshair line (top-mid to bottom-mid)
//
// EBO index list {0,1,2,3} used with GL_LINE_LOOP to draw boundary outline
// Vertices 4-7 used with GL_LINES to draw crosshair (2 line segments)
float gridVerts[] = {
// X Y Z R G B
// Corners 0-3: boundary rectangle
-0.90f, 0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 0: top-left
0.90f, 0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 1: top-right
0.90f, -0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 2: bottom-right
-0.90f, -0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 3: bottom-left
// Vertices 4-5: horizontal crosshair
-0.90f, 0.00f, 0.0f, 0.18f, 0.23f, 0.30f, // 4: left-mid
0.90f, 0.00f, 0.0f, 0.18f, 0.23f, 0.30f, // 5: right-mid
// Vertices 6-7: vertical crosshair
0.00f, 0.90f, 0.0f, 0.18f, 0.23f, 0.30f, // 6: top-mid
0.00f, -0.90f, 0.0f, 0.18f, 0.23f, 0.30f, // 7: bottom-mid
};
// EBO index list for the boundary rectangle
// GL_LINE_LOOP will draw edges: 0→1, 1→2, 2→3, 3→0 (automatically closes)
unsigned int boundaryIdx[] = { 0, 1, 2, 3 };
// Grid VAO + VBO + EBO
unsigned int gridVAO, gridVBO, gridEBO;
glGenVertexArrays(1, &gridVAO);
glBindVertexArray(gridVAO); // ← RECORDING STARTS
glGenBuffers(1, &gridVBO);
glBindBuffer(GL_ARRAY_BUFFER, gridVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(gridVerts), gridVerts, GL_STATIC_DRAW);
glGenBuffers(1, &gridEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gridEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(boundaryIdx), boundaryIdx, GL_STATIC_DRAW);
// Same two attributes as contacts (stride=24, pos at 0, colour at 12)
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); // ← RECORDING STOPS
std::cout << "[3] Grid VAO+VBO+EBO ready (4 boundary corners + 4 crosshair vertices)\n";
std::cout << "[4] Render loop starting. ESC=quit | W=wireframe toggle\n\n";
// ── RENDER LOOP ───────────────────────────────────────────────────────────
bool wireframe = false;
bool wPrev = false;
while (!glfwWindowShouldClose(window)) {
processInput(window);
// W key: toggle wireframe (edge-detect — one toggle per press)
bool wNow = (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS);
if (wNow && !wPrev) {
wireframe = !wireframe;
glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL);
std::cout << (wireframe ? "Wireframe ON\n" : "Wireframe OFF\n");
}
wPrev = wNow;
glClearColor(0.02f, 0.03f, 0.07f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProg);
// ── Draw 1: Contact markers (triangles) ───────────────────────────────
glBindVertexArray(contactVAO);
glDrawArrays(GL_TRIANGLES, 0, 9); // 9 vertices = 3 contacts
// ── Draw 2: Boundary rectangle outline ───────────────────────────────
// GL_LINE_LOOP: connects indexed vertices as a closed line polygon
// 4 indices → 4 edges drawn (0→1, 1→2, 2→3, 3→0)
glBindVertexArray(gridVAO);
glDrawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_INT, 0);
// ── Draw 3: Crosshair lines ───────────────────────────────────────────
// GL_LINES: every pair of vertices = one line segment
// Start at vertex 4 (left-mid), draw 4 vertices = 2 line segments
// Vertices 4,5 = horizontal line | Vertices 6,7 = vertical line
glDrawArrays(GL_LINES, 4, 4);
glfwSwapBuffers(window);
glfwPollEvents();
}
// ── CLEANUP ───────────────────────────────────────────────────────────────
glDeleteVertexArrays(1, &contactVAO);
glDeleteVertexArrays(1, &gridVAO);
glDeleteBuffers(1, &contactVBO);
glDeleteBuffers(1, &gridVBO);
glDeleteBuffers(1, &gridEBO);
glDeleteProgram(shaderProg);
glfwTerminate();
return 0;
}
- Dim grey rectangle outline around the display area
- Horizontal + vertical crosshair dividing display into 4 quadrants
- All 3 colour-coded contact triangles still visible and correct
- Grid lines noticeably dimmer than contact markers
- W key toggles wireframe on contacts
- ►Fragment shader:
uniform float uTimereceives seconds since start each frame - ►Fragment shader:
uniform float uIsHostileactivates pulse logic when 1.0 - ►Pulse logic:
sin(uTime × 4.0)oscillates colour between dark-red and bright-red - ►3 separate draw calls for contacts (so each can get different uIsHostile value)
- ►glGetUniformLocation called once BEFORE loop, cached integer used inside loop
- ►Time value printed to terminal every 60 frames to confirm uniform updates
To give each contact a different uniform value (uIsHostile), we set the uniform then draw, set it again then draw, etc. glDrawArrays(GL_TRIANGLES, 0, 3) draws vertices 0-2 (Contact 1). glDrawArrays(GL_TRIANGLES, 3, 3) draws vertices 3-5 (Contact 2). glDrawArrays(GL_TRIANGLES, 6, 3) draws vertices 6-8 (Contact 3). The VAO is bound once — the start offset tells OpenGL which vertices to read. No data changes. Same VAO, different draw-call offsets.
- Fragment shader has both uTime and uIsHostile uniforms declared
- Fragment shader pulse logic: sin(uTime×4.0) mapped to 0.0–1.0, applied only when uIsHostile > 0.5
- mix(darkRed, brightRed, pulse) correctly oscillates hostile contact colour
- uTimeLoc and uHostileLoc obtained with glGetUniformLocation BEFORE render loop
- glfwGetTime() cast to float and sent via glUniform1f each frame
- Contact 1: glUniform1f(uHostileLoc, 0.0f) then glDrawArrays(... 0, 3)
- Contact 2: glUniform1f(uHostileLoc, 1.0f) then glDrawArrays(... 3, 3)
- Contact 3: glUniform1f(uHostileLoc, 0.0f) then glDrawArrays(... 6, 3)
- Hostile contact visibly pulsing — friendly and unknown stay steady
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 1 · LAB 5 of 5
// Tactical Contact Display — Live Threat Pulse Animation
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: TacticalDisplay
// FOLDER: C:\Labs\TacticalDisplay\
//
// WHAT THIS LAB ADDS OVER LAB 4:
// - uTime uniform: current time (seconds) sent to GPU every frame
// - uIsHostile uniform: 1.0 for hostile contact, 0.0 for others
// - Fragment shader: hostile contact pulses between dark-red and bright-red
// - Contacts drawn in 3 separate draw calls so each can have different uniform values
// - W key: wireframe toggle (from Lab 4)
//
// HOW THE PULSING WORKS:
// sin(uTime × 4.0) oscillates between -1 and +1 (4× normal speed)
// (sin() + 1.0) / 2.0 maps to 0.0 – 1.0
// mix(darkRed, brightRed, 0.0–1.0) = colour oscillates between dark and bright
// Only applied when uIsHostile == 1.0
//
// WHAT YOU WILL SEE:
// - Friendly (blue triangle): steady — no pulse
// - HOSTILE (red triangle): visibly pulsing brighter/darker — warning pattern
// - Unknown (yellow triangle): steady — no pulse
// - Boundary + crosshair: steady dim grey (same as Lab 4)
// - Terminal: time printed every 60 frames to confirm uniform is updating
// ═══════════════════════════════════════════════════════════════════════════
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
// ─────────────────────────────────────────────────────────────────────────────
// VERTEX SHADER — unchanged from Lab 4
// ─────────────────────────────────────────────────────────────────────────────
const char* vertSrc = R"GLSL(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aColour;
out vec3 vColour;
void main() {
gl_Position = vec4(aPos, 1.0);
vColour = aColour;
}
)GLSL";
// ─────────────────────────────────────────────────────────────────────────────
// FRAGMENT SHADER — NEW: uses uTime and uIsHostile uniforms
//
// uniform float uTime → seconds since program start, sent every frame
// uniform float uIsHostile → 1.0 = this is hostile, 0.0 = not hostile
//
// if uIsHostile == 1.0: override colour with pulsing red effect
// else: use vColour from vertex shader unchanged
// ─────────────────────────────────────────────────────────────────────────────
const char* fragSrc = R"GLSL(
#version 330 core
in vec3 vColour;
out vec4 FragColor;
uniform float uTime; // sent from C++ every frame via glUniform1f
uniform float uIsHostile; // 1.0 = hostile contact, 0.0 = not hostile
void main() {
vec3 col = vColour; // start with the colour from vertex shader
if (uIsHostile > 0.5) {
// pulse: sin oscillates -1 to +1 → map to 0.0–1.0 for brightness
// multiply by 4.0 for faster warning pulse rate
float pulse = (sin(uTime * 4.0) + 1.0) / 2.0;
// mix(a, b, t) = a*(1-t) + b*t
// at pulse=0.0: dark red (0.30, 0.04, 0.04)
// at pulse=1.0: bright red (1.00, 0.18, 0.18)
col = mix(vec3(0.30, 0.04, 0.04), vec3(1.00, 0.18, 0.18), pulse);
}
FragColor = vec4(col, 1.0);
}
)GLSL";
unsigned int compileShader(unsigned int type, const char* source) {
unsigned int id = glCreateShader(type);
glShaderSource(id, 1, &source, 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* vSrc, const char* fSrc) {
unsigned int vs = compileShader(GL_VERTEX_SHADER, vSrc);
unsigned int fs = compileShader(GL_FRAGMENT_SHADER, fSrc);
unsigned int prog = glCreateProgram();
glAttachShader(prog, vs); glAttachShader(prog, fs); glLinkProgram(prog);
glDeleteShader(vs); glDeleteShader(fs);
return prog;
}
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() {
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, 768, "Tactical Contact Display — Naval Ops", 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 shaderProg = createShaderProgram(vertSrc, fragSrc);
std::cout << "[1] Shader compiled. ID = " << shaderProg << "\n";
// ── Contact markers (format: X Y Z R G B — same as Labs 3 and 4) ─────────
float contacts[] = {
// X Y Z R G B
// Contact 1 — FRIENDLY (BLUE)
0.00f, 0.66f, 0.0f, 0.20f, 0.50f, 1.00f,
-0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f,
0.05f, 0.54f, 0.0f, 0.20f, 0.50f, 1.00f,
// Contact 2 — HOSTILE (RED — but fragment shader overrides with pulse)
-0.40f, -0.34f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.45f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
-0.35f, -0.46f, 0.0f, 1.00f, 0.20f, 0.20f,
// Contact 3 — UNKNOWN (YELLOW)
0.50f, 0.36f, 0.0f, 1.00f, 0.85f, 0.10f,
0.45f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
0.55f, 0.24f, 0.0f, 1.00f, 0.85f, 0.10f,
};
unsigned int contactVAO, contactVBO;
glGenVertexArrays(1, &contactVAO);
glBindVertexArray(contactVAO);
glGenBuffers(1, &contactVBO);
glBindBuffer(GL_ARRAY_BUFFER, contactVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(contacts), contacts, 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);
std::cout << "[2] Contact VAO+VBO ready\n";
// ── Boundary + crosshair grid (same as Lab 4) ─────────────────────────────
float gridVerts[] = {
// X Y Z R G B
-0.90f, 0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 0: boundary top-left
0.90f, 0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 1: boundary top-right
0.90f, -0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 2: boundary bottom-right
-0.90f, -0.90f, 0.0f, 0.22f, 0.28f, 0.36f, // 3: boundary bottom-left
-0.90f, 0.00f, 0.0f, 0.18f, 0.23f, 0.30f, // 4: crosshair left-mid
0.90f, 0.00f, 0.0f, 0.18f, 0.23f, 0.30f, // 5: crosshair right-mid
0.00f, 0.90f, 0.0f, 0.18f, 0.23f, 0.30f, // 6: crosshair top-mid
0.00f, -0.90f, 0.0f, 0.18f, 0.23f, 0.30f, // 7: crosshair bottom-mid
};
unsigned int boundaryIdx[] = { 0, 1, 2, 3 };
unsigned int gridVAO, gridVBO, gridEBO;
glGenVertexArrays(1, &gridVAO);
glBindVertexArray(gridVAO);
glGenBuffers(1, &gridVBO);
glBindBuffer(GL_ARRAY_BUFFER, gridVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(gridVerts), gridVerts, GL_STATIC_DRAW);
glGenBuffers(1, &gridEBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gridEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(boundaryIdx), boundaryIdx, 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);
std::cout << "[3] Grid VAO+VBO+EBO ready\n";
// ── Get uniform locations BEFORE the loop (call once, cache the ID) ───────
// glGetUniformLocation is slow — never call inside the render loop
int uTimeLoc = glGetUniformLocation(shaderProg, "uTime");
int uHostileLoc = glGetUniformLocation(shaderProg, "uIsHostile");
std::cout << "[4] uTime location = " << uTimeLoc
<< " | uIsHostile location = " << uHostileLoc << "\n";
std::cout << "[5] Render loop starting. ESC=quit | W=wireframe\n\n";
// ── RENDER LOOP ───────────────────────────────────────────────────────────
bool wireframe = false;
bool wPrev = false;
int frameCount = 0;
while (!glfwWindowShouldClose(window)) {
processInput(window);
// W key wireframe toggle
bool wNow = (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS);
if (wNow && !wPrev) {
wireframe = !wireframe;
glPolygonMode(GL_FRONT_AND_BACK, wireframe ? GL_LINE : GL_FILL);
std::cout << (wireframe ? "Wireframe ON\n" : "Wireframe OFF\n");
}
wPrev = wNow;
glClearColor(0.02f, 0.03f, 0.07f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProg);
// Send current time to shader every frame
float t = (float)glfwGetTime();
glUniform1f(uTimeLoc, t);
// Print time to terminal every 60 frames — confirm uniform updates
frameCount++;
if (frameCount % 60 == 0) {
std::cout << "Frame " << frameCount << ": uTime = " << t << "s\n";
}
glBindVertexArray(contactVAO);
// ── Draw Contact 1: FRIENDLY — uIsHostile=0.0, no pulse ──────────────
glUniform1f(uHostileLoc, 0.0f);
glDrawArrays(GL_TRIANGLES, 0, 3); // vertices 0,1,2
// ── Draw Contact 2: HOSTILE — uIsHostile=1.0, pulse ACTIVE ───────────
glUniform1f(uHostileLoc, 1.0f);
glDrawArrays(GL_TRIANGLES, 3, 3); // vertices 3,4,5
// ── Draw Contact 3: UNKNOWN — uIsHostile=0.0, no pulse ───────────────
glUniform1f(uHostileLoc, 0.0f);
glDrawArrays(GL_TRIANGLES, 6, 3); // vertices 6,7,8
// ── Draw boundary + crosshair (not hostile, no pulse) ─────────────────
glUniform1f(uHostileLoc, 0.0f);
glBindVertexArray(gridVAO);
glDrawElements(GL_LINE_LOOP, 4, GL_UNSIGNED_INT, 0); // boundary outline
glDrawArrays(GL_LINES, 4, 4); // crosshair lines
glfwSwapBuffers(window);
glfwPollEvents();
}
// ── CLEANUP ───────────────────────────────────────────────────────────────
glDeleteVertexArrays(1, &contactVAO);
glDeleteVertexArrays(1, &gridVAO);
glDeleteBuffers(1, &contactVBO);
glDeleteBuffers(1, &gridVBO);
glDeleteBuffers(1, &gridEBO);
glDeleteProgram(shaderProg);
glfwTerminate();
std::cout << "\nTactical display shutdown. Day 1 complete.\n";
return 0;
}
- Add a 4th contact (missile): Add 3 more vertices to contacts[] (green colour, any position). Add a 4th draw call with uIsHostile=1.0 but faster pulse (change 4.0 to 8.0 in the shader). You'll need to pass uPulseSpeed as a second uniform or hardcode differently.
- Animate contact position: Add
uniform float uOffsetto vertex shader. Apply to X position:gl_Position = vec4(aPos.x + uOffset, aPos.y, aPos.z, 1.0). Sendsin(t) * 0.3feach frame — hostile contact drifts left and right. - Make unknown flash: Set uIsHostile=0.5 for the unknown contact. In the fragment shader, check
uIsHostile > 0.3 && uIsHostile < 0.7and apply a slower yellow pulse.
- FRIENDLY contact (blue triangle) — steady, does not pulse
- HOSTILE contact (red triangle) — visibly pulsing dark-red to bright-red continuously
- UNKNOWN contact (yellow triangle) — steady, does not pulse
- Display boundary rectangle (dim grey, drawn as a line outline)
- Crosshair grid lines dividing display into quadrants
- Animation runs smoothly at 60fps — no flicker
- Terminal prints time value every 60 frames
- Can explain: what a VBO is, what a VAO records, what a uniform does
Koenig Original AI-Courseware · Day 1 Complete