main.cpp — copy the whole file, build, run.All 3 labs share one project folder: C:\Labs\VesselBriefing\. 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\VesselBriefing\
├── CMakeLists.txt ← write once, same for all 3 labs
├── metal_plate.jpg ← texture for Lab 3 (from C:\Resources\textures\)
└── src\
└── main.cpp ← replace with each lab's complete file
cmake_minimum_required(VERSION 3.20)
project(VesselBriefing)
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(VesselBriefing src/main.cpp)
target_link_libraries(VesselBriefing
OpenGL::GL
${GLFW_DIR}/lib-vc2022/glfw3.lib
${GLEW_DIR}/lib/Release/x64/glew32.lib
)cd C:\Labs\VesselBriefing cmake -B build -G "Visual Studio 17 2022" -A x64 cmake --build build --config Release build\Release\VesselBriefing.exe
Write your lab's main.cpp (or copy the complete file below) → open Developer Command Prompt for VS 2022 → run the 4 build commands above. The CMakeLists.txt and folder path never change. Only src\main.cpp changes between labs.
- ►36-vertex cube mesh (6 faces × 2 triangles) with position + normal per vertex
- ►Model matrix: slow Y-axis rotation (12°/sec) for 360° visual identification
- ►Fixed briefing camera: above-right angle at vec3(2, 1.5, 3) looking at origin
- ►Perspective projection 45° FOV — realistic depth appearance
- ►Depth test enabled — near faces correctly cover far faces
OpenGL draws triangles, not quads. Each face = 2 triangles × 3 vertices = 6. Six faces = 36 total. We cannot share vertices across faces because each face has a different outward normal — the same corner vertex needs a different normal depending on which face it belongs to. So we duplicate all 4 corners per face.
Local coords World coords Camera/View Clip coords
(vertex data) ×M (after model) ×V (lookAt space) ×P (gl_Position)
aPos ────────→ FragPos ──────────→ view space ──────→ gl_Position
uModel uView uProj
gl_Position = uProj * uView * uModel * vec4(aPos, 1.0)
uModel: translate / rotate / scale the vessel
uView: glm::lookAt(eye, centre, up) — camera position
uProj: glm::perspective(45deg, aspect, 0.1, 100.0) — frustum
- 36-vertex cube data declared (position + normal, 6 floats per vertex)
- VAO + VBO uploaded with stride 6, attribute 0 = pos, attribute 1 = normal
- Vertex shader: MVP applied, FragPos and Normal passed out for Lab 2
- Fragment shader: solid grey colour (all lighting deferred to Lab 2)
- glEnable(GL_DEPTH_TEST) called, glClear includes GL_DEPTH_BUFFER_BIT
- Model matrix rotates 12 degrees per second around Y
- Camera at (2, 1.5, 3) looking at origin, Y-up
- Perspective 45°, near 0.1, far 100
Try writing main.cpp from memory using what you learned in Demos 7 and 8. The vertex shader MVP line and the glm::lookAt call are the key pieces. Only look at the complete file below if you are stuck after a genuine attempt.
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 2 · LAB 1 of 3
// 3D Vessel Briefing System — Place a 3D Shape in World Space
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// PROJECT NAME: VesselBriefing
// FOLDER: C:\Labs\VesselBriefing\
//
// WHAT THIS LAB DOES:
// Renders a rotating 3D cube (vessel silhouette) using the full MVP matrix
// pipeline. Depth testing ensures correct face ordering. The vertex shader
// already outputs FragPos and Normal so Lab 2 can add lighting without
// changing the vertex shader.
//
// WHAT YOU WILL SEE:
// - Grey cube rotating slowly on Y axis, camera from above-right
// - Near faces correctly cover far faces (depth test working)
// - Perspective gives realistic 3D depth appearance
// ═══════════════════════════════════════════════════════════════════════════
#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>
const int W = 900, H = 600;
// 36 vertices: 6 faces x 2 triangles x 3 vertices
// Format per vertex: x, y, z (position) nx, ny, nz (outward normal)
float cubeVerts[] = {
// FRONT (+Z)
-0.5f,-0.5f, 0.5f, 0,0,1, 0.5f,-0.5f, 0.5f, 0,0,1, 0.5f, 0.5f, 0.5f, 0,0,1,
0.5f, 0.5f, 0.5f, 0,0,1, -0.5f, 0.5f, 0.5f, 0,0,1, -0.5f,-0.5f, 0.5f, 0,0,1,
// BACK (-Z)
0.5f,-0.5f,-0.5f, 0,0,-1, -0.5f,-0.5f,-0.5f, 0,0,-1, -0.5f, 0.5f,-0.5f, 0,0,-1,
-0.5f, 0.5f,-0.5f, 0,0,-1, 0.5f, 0.5f,-0.5f, 0,0,-1, 0.5f,-0.5f,-0.5f, 0,0,-1,
// LEFT (-X)
-0.5f,-0.5f,-0.5f, -1,0,0, -0.5f,-0.5f, 0.5f, -1,0,0, -0.5f, 0.5f, 0.5f, -1,0,0,
-0.5f, 0.5f, 0.5f, -1,0,0, -0.5f, 0.5f,-0.5f, -1,0,0, -0.5f,-0.5f,-0.5f, -1,0,0,
// RIGHT (+X)
0.5f,-0.5f, 0.5f, 1,0,0, 0.5f,-0.5f,-0.5f, 1,0,0, 0.5f, 0.5f,-0.5f, 1,0,0,
0.5f, 0.5f,-0.5f, 1,0,0, 0.5f, 0.5f, 0.5f, 1,0,0, 0.5f,-0.5f, 0.5f, 1,0,0,
// TOP (+Y)
-0.5f, 0.5f, 0.5f, 0,1,0, 0.5f, 0.5f, 0.5f, 0,1,0, 0.5f, 0.5f,-0.5f, 0,1,0,
0.5f, 0.5f,-0.5f, 0,1,0, -0.5f, 0.5f,-0.5f, 0,1,0, -0.5f, 0.5f, 0.5f, 0,1,0,
// BOTTOM (-Y)
-0.5f,-0.5f,-0.5f, 0,-1,0, 0.5f,-0.5f,-0.5f, 0,-1,0, 0.5f,-0.5f, 0.5f, 0,-1,0,
0.5f,-0.5f, 0.5f, 0,-1,0, -0.5f,-0.5f, 0.5f, 0,-1,0, -0.5f,-0.5f,-0.5f, 0,-1,0,
};
// Vertex shader: MVP pipeline + pass FragPos and Normal for Lab 2 lighting
const char* vertSrc = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 uModel, uView, uProj;
void main() {
FragPos = vec3(uModel * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(uModel))) * aNormal;
gl_Position = uProj * uView * vec4(FragPos, 1.0);
}
)";
// Fragment shader: solid grey — lighting added in Lab 2 without changing vert shader
const char* fragSrc = R"(
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
void main() {
// Simple flat grey — Phong lighting replaces this in Lab 2
FragColor = vec4(0.5, 0.55, 0.6, 1.0); // navy grey
}
)";
static unsigned int makeShader(const char* vs, const char* fs) {
auto compile = [](GLenum t, const char* src) {
unsigned int s = glCreateShader(t);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
int ok; glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) { char buf[512]; glGetShaderInfoLog(s,512,nullptr,buf); std::cerr << buf << "\n"; }
return s;
};
unsigned int v = compile(GL_VERTEX_SHADER, vs), f = compile(GL_FRAGMENT_SHADER, fs);
unsigned int p = glCreateProgram();
glAttachShader(p, v); glAttachShader(p, f); glLinkProgram(p);
glDeleteShader(v); glDeleteShader(f);
return p;
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* win = glfwCreateWindow(W, H, "Vessel Briefing System — Lab 1", nullptr, nullptr);
glfwMakeContextCurrent(win);
glewExperimental = GL_TRUE; glewInit();
glViewport(0, 0, W, H);
glfwSetFramebufferSizeCallback(win, [](GLFWwindow*, int w, int h){ glViewport(0,0,w,h); });
std::cout << "GPU: " << glGetString(GL_RENDERER) << "\n";
std::cout << "OpenGL: " << glGetString(GL_VERSION) << "\n";
std::cout << "Lab 1 ready. ESC=quit.\n";
// Upload cube to GPU
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVerts), cubeVerts, GL_STATIC_DRAW);
// Attribute 0: position (3 floats, stride 6, offset 0)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// Attribute 1: normal (3 floats, stride 6, offset 3)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
unsigned int shader = makeShader(vertSrc, fragSrc);
glEnable(GL_DEPTH_TEST);
// Fixed projection and view — set once before loop
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)W/H, 0.1f, 100.0f);
glm::mat4 view = glm::lookAt(
glm::vec3(2.0f, 1.5f, 3.0f), // camera: above-right briefing angle
glm::vec3(0.0f, 0.0f, 0.0f), // looking at vessel centre
glm::vec3(0.0f, 1.0f, 0.0f) // Y is up
);
glUseProgram(shader);
glUniformMatrix4fv(glGetUniformLocation(shader, "uProj"), 1, GL_FALSE, glm::value_ptr(proj));
glUniformMatrix4fv(glGetUniformLocation(shader, "uView"), 1, GL_FALSE, glm::value_ptr(view));
while (!glfwWindowShouldClose(win)) {
if (glfwGetKey(win, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(win, true);
// Clear BOTH colour and depth buffers each frame
glClearColor(0.04f, 0.06f, 0.10f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Model matrix: rotate 12 degrees per second on Y axis
float t = (float)glfwGetTime();
glm::mat4 model = glm::rotate(glm::mat4(1.0f),
glm::radians(t * 12.0f), glm::vec3(0, 1, 0));
glUniformMatrix4fv(glGetUniformLocation(shader, "uModel"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(win); glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO);
glDeleteProgram(shader);
glfwTerminate();
return 0;
}- Comment out glEnable(GL_DEPTH_TEST): The cube faces flicker as it rotates — far faces bleed through near faces. This is why depth testing is mandatory for any 3D scene.
- Change the rotation speed: Replace
t * 12.0fwitht * 60.0ffor fast spin, ort * 3.0ffor slow presentation rotation. The 12 deg/sec rate is chosen for slow identification viewing. - Move the camera: Change
glm::vec3(2.0f, 1.5f, 3.0f)toglm::vec3(0, 5, 0)for top-down view. Tryvec3(5, 0, 0)for a pure side view. Notice how the perspective changes what you see.
- Cube rotates continuously at ~12 degrees per second
- Depth test: far faces hidden behind near faces — no bleed-through
- Perspective: far edges visibly smaller than near edges
- ESC closes cleanly
- ►Light position fixed at (3, 3, 3) — upper-front-right corner
- ►Ambient strength 0.1 — dark surfaces dim but not black
- ►Diffuse:
dot(norm, lightDir)— surface angle determines brightness - ►Specular: Blinn-Phong half-vector, shininess 64 — moderate metallic sheen
- ►Object colour navy grey vec3(0.5, 0.55, 0.6)
- ►Vertex shader: unchanged from Lab 1 (FragPos and Normal already in output)
If you scale the vessel non-uniformly (e.g. stretch it), the model matrix distorts the normals — a normal that was perpendicular to the surface would no longer be perpendicular after the same transform. The normal matrix (transpose(inverse(uModel))) corrects this distortion. For uniform scaling or rotation-only models it produces the same result, but it is correct practice for all cases.
- Lab 1 folder copied to VesselBriefing_L2 (or replace main.cpp in same folder)
- Fragment shader: ambient + diffuse + specular computation
- Uniforms: uLightPos, uViewPos, uObjectColor, uLightColor, uAmbientStr, uShininess
- uViewPos matches the eye position used in glm::lookAt (2, 1.5, 3)
- Lit face bright, dark face dim (not black), specular highlight visible
- Experiment: change uShininess to 8 (wide glow) and 256 (tiny dot)
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 2 · LAB 2 of 3
// 3D Vessel Briefing System — Phong Lighting on the Hull
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// WHAT THIS LAB ADDS OVER LAB 1:
// Full Phong lighting in the fragment shader. Vertex shader UNCHANGED.
// Light at (3,3,3). Ambient 0.1. Shininess 64. Navy grey vessel colour.
// ═══════════════════════════════════════════════════════════════════════════
#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>
const int W = 900, H = 600;
// Same cube vertex data as Lab 1 (position + normal, 6 floats per vertex)
float cubeVerts[] = {
-0.5f,-0.5f, 0.5f, 0,0,1, 0.5f,-0.5f, 0.5f, 0,0,1, 0.5f, 0.5f, 0.5f, 0,0,1,
0.5f, 0.5f, 0.5f, 0,0,1, -0.5f, 0.5f, 0.5f, 0,0,1, -0.5f,-0.5f, 0.5f, 0,0,1,
0.5f,-0.5f,-0.5f, 0,0,-1, -0.5f,-0.5f,-0.5f, 0,0,-1, -0.5f, 0.5f,-0.5f, 0,0,-1,
-0.5f, 0.5f,-0.5f, 0,0,-1, 0.5f, 0.5f,-0.5f, 0,0,-1, 0.5f,-0.5f,-0.5f, 0,0,-1,
-0.5f,-0.5f,-0.5f, -1,0,0, -0.5f,-0.5f, 0.5f, -1,0,0, -0.5f, 0.5f, 0.5f, -1,0,0,
-0.5f, 0.5f, 0.5f, -1,0,0, -0.5f, 0.5f,-0.5f, -1,0,0, -0.5f,-0.5f,-0.5f, -1,0,0,
0.5f,-0.5f, 0.5f, 1,0,0, 0.5f,-0.5f,-0.5f, 1,0,0, 0.5f, 0.5f,-0.5f, 1,0,0,
0.5f, 0.5f,-0.5f, 1,0,0, 0.5f, 0.5f, 0.5f, 1,0,0, 0.5f,-0.5f, 0.5f, 1,0,0,
-0.5f, 0.5f, 0.5f, 0,1,0, 0.5f, 0.5f, 0.5f, 0,1,0, 0.5f, 0.5f,-0.5f, 0,1,0,
0.5f, 0.5f,-0.5f, 0,1,0, -0.5f, 0.5f,-0.5f, 0,1,0, -0.5f, 0.5f, 0.5f, 0,1,0,
-0.5f,-0.5f,-0.5f, 0,-1,0, 0.5f,-0.5f,-0.5f, 0,-1,0, 0.5f,-0.5f, 0.5f, 0,-1,0,
0.5f,-0.5f, 0.5f, 0,-1,0, -0.5f,-0.5f, 0.5f, 0,-1,0, -0.5f,-0.5f,-0.5f, 0,-1,0,
};
// Vertex shader: UNCHANGED from Lab 1
const char* vertSrc = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 uModel, uView, uProj;
void main() {
FragPos = vec3(uModel * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(uModel))) * aNormal;
gl_Position = uProj * uView * vec4(FragPos, 1.0);
}
)";
// Fragment shader: full Phong lighting — ONLY change from Lab 1
const char* fragSrc = R"(
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;
uniform vec3 uLightPos; // (3,3,3) — upper front right
uniform vec3 uViewPos; // camera eye — must match lookAt eye
uniform vec3 uObjectColor; // navy grey (0.5, 0.55, 0.6)
uniform vec3 uLightColor; // white (1, 1, 1)
uniform float uAmbientStr; // 0.1
uniform float uShininess; // 64.0
void main() {
// AMBIENT — base visibility on dark side
vec3 ambient = uAmbientStr * uLightColor;
// DIFFUSE — surface angle to light
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(uLightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * uLightColor;
// SPECULAR — Blinn-Phong half vector
vec3 viewDir = normalize(uViewPos - FragPos);
vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfDir), 0.0), uShininess);
vec3 specular = 0.5 * spec * uLightColor;
vec3 result = (ambient + diffuse + specular) * uObjectColor;
FragColor = vec4(result, 1.0);
}
)";
static unsigned int makeShader(const char* vs, const char* fs) {
auto compile = [](GLenum t, const char* src) {
unsigned int s = glCreateShader(t);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
int ok; glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) { char buf[512]; glGetShaderInfoLog(s,512,nullptr,buf); std::cerr << buf << "\n"; }
return s;
};
unsigned int v = compile(GL_VERTEX_SHADER, vs), f = compile(GL_FRAGMENT_SHADER, fs);
unsigned int p = glCreateProgram();
glAttachShader(p, v); glAttachShader(p, f); glLinkProgram(p);
glDeleteShader(v); glDeleteShader(f);
return p;
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* win = glfwCreateWindow(W, H, "Vessel Briefing System — Lab 2: Phong Lighting", nullptr, nullptr);
glfwMakeContextCurrent(win);
glewExperimental = GL_TRUE; glewInit();
glViewport(0, 0, W, H);
glfwSetFramebufferSizeCallback(win, [](GLFWwindow*, int w, int h){ glViewport(0,0,w,h); });
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVerts), cubeVerts, 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);
unsigned int shader = makeShader(vertSrc, fragSrc);
glEnable(GL_DEPTH_TEST);
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)W/H, 0.1f, 100.0f);
glm::vec3 camPos(2.0f, 1.5f, 3.0f);
glm::mat4 view = glm::lookAt(camPos, glm::vec3(0.0f), glm::vec3(0,1,0));
glUseProgram(shader);
glUniformMatrix4fv(glGetUniformLocation(shader, "uProj"), 1, GL_FALSE, glm::value_ptr(proj));
glUniformMatrix4fv(glGetUniformLocation(shader, "uView"), 1, GL_FALSE, glm::value_ptr(view));
// Lighting uniforms — set once (constant for this scene)
glUniform3f(glGetUniformLocation(shader, "uLightPos"), 3.0f, 3.0f, 3.0f);
glUniform3f(glGetUniformLocation(shader, "uViewPos"), camPos.x, camPos.y, camPos.z);
glUniform3f(glGetUniformLocation(shader, "uObjectColor"), 0.5f, 0.55f, 0.6f); // navy grey
glUniform3f(glGetUniformLocation(shader, "uLightColor"), 1.0f, 1.0f, 1.0f);
glUniform1f(glGetUniformLocation(shader, "uAmbientStr"), 0.1f);
glUniform1f(glGetUniformLocation(shader, "uShininess"), 64.0f);
std::cout << "Lab 2: Phong lighting active. ESC=quit.\n";
std::cout << "Experiment: change uShininess (8=glow, 256=tight dot)\n";
while (!glfwWindowShouldClose(win)) {
if (glfwGetKey(win, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(win, true);
glClearColor(0.04f, 0.06f, 0.10f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float t = (float)glfwGetTime();
glm::mat4 model = glm::rotate(glm::mat4(1.0f),
glm::radians(t * 12.0f), glm::vec3(0, 1, 0));
glUniformMatrix4fv(glGetUniformLocation(shader, "uModel"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(win); glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO);
glDeleteProgram(shader);
glfwTerminate();
return 0;
}- Change uShininess to 8: The specular highlight spreads across a large portion of the face. Change to 256: it becomes a tiny bright dot. Shininess controls how "polished" the surface appears — lower = matte/rough, higher = mirror-like.
- Set uAmbientStr to 0.0: The dark side of the cube goes completely black. Set it to 0.5: the whole cube is uniformly bright. The 0.1 value is the standard for indoor lighting that is realistic but preserves shape readability.
- Move the light: Change uLightPos to (0, 5, 0) for overhead lighting. To (-3, 0, 0) for side lighting. Observe which faces become the bright face and which face goes dark.
- Dark faces dim but not black — ambient working
- Faces brighten as they rotate toward the light — diffuse working
- Specular highlight visible on the most lit face
- Can change shininess and demonstrate the difference
- ►Vertex data extended from 6 to 8 floats per vertex: position + normal + UV
- ►Attribute 2: UV coords (2 floats, stride 8, offset 6)
- ►stb_image loads metal_plate.jpg, glTexImage2D uploads to GPU
- ►Vertex shader: passes UV to fragment shader via
out vec2 vTexCoord - ►Fragment shader:
result = (ambient + diffuse + specular) * texColor
The Phong calculation produces a scalar value: how much light reaches this surface point (0.0 = none, 1.0 = full). The texture provides the surface colour: what colour the material is when fully lit. Multiplying them gives you the final colour: the material's colour, darkened or brightened by the amount of light. This is the standard PBR-adjacent approach used in all real-time rendering.
Add #define STB_IMAGE_IMPLEMENTATION before #include "stb_image.h" in exactly one source file. The header is at C:\Resources\headers\stb_image.h on the VM — copy it to your project's src\ folder and add the include path to CMakeLists if needed.
- Vertex data rebuilt with 8 floats per vertex (pos + normal + UV)
- Attribute 2 added: UV, stride 8, offset 6
- stb_image.h included and STB_IMAGE_IMPLEMENTATION defined
- Texture loaded from metal_plate.jpg, uploaded with glTexImage2D
- Vertex shader: aTexCoord in, vTexCoord out
// ═══════════════════════════════════════════════════════════════════════════
// RR GRAPHICS LAB — DAY 2 · LAB 3 of 3 [CAPSTONE]
// 3D Vessel Briefing System — Texture + Phong = Full Final Scene
// By Raushan Ranjan (MCT) | Koenig Original AI-Courseware
//
// WHAT THIS LAB ADDS OVER LAB 2:
// - Vertex data: 8 floats/vertex (pos + normal + UV)
// - stb_image loads metal_plate.jpg
// - Fragment shader multiplies Phong result by texture colour
// - All Day 2 concepts active simultaneously
//
// REQUIRES: metal_plate.jpg in the same folder as the .exe
// stb_image.h in src/ (from C:\Resources\headers\)
// ═══════════════════════════════════════════════════════════════════════════
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#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>
const int W = 900, H = 600;
// 8 floats per vertex: x,y,z nx,ny,nz u,v
// UV 0,0=bottom-left 1,0=bottom-right 1,1=top-right 0,1=top-left
float cubeVerts[] = {
// FRONT (+Z)
-0.5f,-0.5f, 0.5f, 0,0,1, 0.0f,0.0f,
0.5f,-0.5f, 0.5f, 0,0,1, 1.0f,0.0f,
0.5f, 0.5f, 0.5f, 0,0,1, 1.0f,1.0f,
0.5f, 0.5f, 0.5f, 0,0,1, 1.0f,1.0f,
-0.5f, 0.5f, 0.5f, 0,0,1, 0.0f,1.0f,
-0.5f,-0.5f, 0.5f, 0,0,1, 0.0f,0.0f,
// BACK (-Z)
0.5f,-0.5f,-0.5f, 0,0,-1, 0.0f,0.0f,
-0.5f,-0.5f,-0.5f, 0,0,-1, 1.0f,0.0f,
-0.5f, 0.5f,-0.5f, 0,0,-1, 1.0f,1.0f,
-0.5f, 0.5f,-0.5f, 0,0,-1, 1.0f,1.0f,
0.5f, 0.5f,-0.5f, 0,0,-1, 0.0f,1.0f,
0.5f,-0.5f,-0.5f, 0,0,-1, 0.0f,0.0f,
// LEFT (-X)
-0.5f,-0.5f,-0.5f, -1,0,0, 0.0f,0.0f,
-0.5f,-0.5f, 0.5f, -1,0,0, 1.0f,0.0f,
-0.5f, 0.5f, 0.5f, -1,0,0, 1.0f,1.0f,
-0.5f, 0.5f, 0.5f, -1,0,0, 1.0f,1.0f,
-0.5f, 0.5f,-0.5f, -1,0,0, 0.0f,1.0f,
-0.5f,-0.5f,-0.5f, -1,0,0, 0.0f,0.0f,
// RIGHT (+X)
0.5f,-0.5f, 0.5f, 1,0,0, 0.0f,0.0f,
0.5f,-0.5f,-0.5f, 1,0,0, 1.0f,0.0f,
0.5f, 0.5f,-0.5f, 1,0,0, 1.0f,1.0f,
0.5f, 0.5f,-0.5f, 1,0,0, 1.0f,1.0f,
0.5f, 0.5f, 0.5f, 1,0,0, 0.0f,1.0f,
0.5f,-0.5f, 0.5f, 1,0,0, 0.0f,0.0f,
// TOP (+Y)
-0.5f, 0.5f, 0.5f, 0,1,0, 0.0f,0.0f,
0.5f, 0.5f, 0.5f, 0,1,0, 1.0f,0.0f,
0.5f, 0.5f,-0.5f, 0,1,0, 1.0f,1.0f,
0.5f, 0.5f,-0.5f, 0,1,0, 1.0f,1.0f,
-0.5f, 0.5f,-0.5f, 0,1,0, 0.0f,1.0f,
-0.5f, 0.5f, 0.5f, 0,1,0, 0.0f,0.0f,
// BOTTOM (-Y)
-0.5f,-0.5f,-0.5f, 0,-1,0, 0.0f,0.0f,
0.5f,-0.5f,-0.5f, 0,-1,0, 1.0f,0.0f,
0.5f,-0.5f, 0.5f, 0,-1,0, 1.0f,1.0f,
0.5f,-0.5f, 0.5f, 0,-1,0, 1.0f,1.0f,
-0.5f,-0.5f, 0.5f, 0,-1,0, 0.0f,1.0f,
-0.5f,-0.5f,-0.5f, 0,-1,0, 0.0f,0.0f,
};
// Vertex shader: adds UV passthrough
const char* vertSrc = R"(
#version 330 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;
out vec3 FragPos;
out vec3 Normal;
out vec2 vTexCoord;
uniform mat4 uModel, uView, uProj;
void main() {
FragPos = vec3(uModel * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(uModel))) * aNormal;
vTexCoord = aTexCoord;
gl_Position = uProj * uView * vec4(FragPos, 1.0);
}
)";
// Fragment shader: Phong lighting multiplied by texture colour
const char* fragSrc = R"(
#version 330 core
in vec3 FragPos;
in vec3 Normal;
in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D uTexture;
uniform vec3 uLightPos, uViewPos, uLightColor;
uniform float uAmbientStr, uShininess;
void main() {
// Sample texture — this replaces the hardcoded object colour from Lab 2
vec3 texColor = texture(uTexture, vTexCoord).rgb;
vec3 ambient = uAmbientStr * uLightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(uLightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * uLightColor;
vec3 viewDir = normalize(uViewPos - FragPos);
vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(norm, halfDir), 0.0), uShininess);
vec3 specular = 0.5 * spec * uLightColor;
// KEY LINE: Phong tells us how much light, texture tells us the colour
vec3 result = (ambient + diffuse + specular) * texColor;
FragColor = vec4(result, 1.0);
}
)";
static unsigned int makeShader(const char* vs, const char* fs) {
auto compile = [](GLenum t, const char* src) {
unsigned int s = glCreateShader(t);
glShaderSource(s, 1, &src, nullptr);
glCompileShader(s);
int ok; glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
if (!ok) { char buf[512]; glGetShaderInfoLog(s,512,nullptr,buf); std::cerr << buf << "\n"; }
return s;
};
unsigned int v = compile(GL_VERTEX_SHADER, vs), f = compile(GL_FRAGMENT_SHADER, fs);
unsigned int p = glCreateProgram();
glAttachShader(p, v); glAttachShader(p, f); glLinkProgram(p);
glDeleteShader(v); glDeleteShader(f);
return p;
}
static unsigned int loadTexture(const char* path) {
unsigned int tex; glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
stbi_set_flip_vertically_on_load(true);
int w,h,ch; unsigned char* data = stbi_load(path, &w, &h, &ch, 0);
if (data) {
GLenum fmt = (ch == 4) ? GL_RGBA : GL_RGB;
glTexImage2D(GL_TEXTURE_2D, 0, fmt, w, h, 0, fmt, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
std::cout << "Texture loaded: " << path << " (" << w << "x" << h << ")\n";
} else {
std::cerr << "WARNING: Could not load " << path << " — using solid grey fallback\n";
// 1x1 grey pixel as fallback so the programme still runs
unsigned char grey[] = {128, 130, 140};
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, grey);
}
stbi_image_free(data);
return tex;
}
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* win = glfwCreateWindow(W, H, "Vessel Briefing System — Lab 3: Textured", nullptr, nullptr);
glfwMakeContextCurrent(win);
glewExperimental = GL_TRUE; glewInit();
glViewport(0, 0, W, H);
glfwSetFramebufferSizeCallback(win, [](GLFWwindow*, int w, int h){ glViewport(0,0,w,h); });
unsigned int VAO, VBO;
glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVerts), cubeVerts, GL_STATIC_DRAW);
// stride = 8 floats (pos3 + normal3 + uv2)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(3*sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void*)(6*sizeof(float)));
glEnableVertexAttribArray(2);
unsigned int shader = makeShader(vertSrc, fragSrc);
unsigned int tex = loadTexture("metal_plate.jpg");
glEnable(GL_DEPTH_TEST);
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)W/H, 0.1f, 100.0f);
glm::vec3 camPos(2.0f, 1.5f, 3.0f);
glm::mat4 view = glm::lookAt(camPos, glm::vec3(0.0f), glm::vec3(0,1,0));
glUseProgram(shader);
glUniformMatrix4fv(glGetUniformLocation(shader, "uProj"), 1, GL_FALSE, glm::value_ptr(proj));
glUniformMatrix4fv(glGetUniformLocation(shader, "uView"), 1, GL_FALSE, glm::value_ptr(view));
glUniform3f(glGetUniformLocation(shader, "uLightPos"), 3.0f, 3.0f, 3.0f);
glUniform3f(glGetUniformLocation(shader, "uViewPos"), camPos.x, camPos.y, camPos.z);
glUniform3f(glGetUniformLocation(shader, "uLightColor"), 1.0f, 1.0f, 1.0f);
glUniform1f(glGetUniformLocation(shader, "uAmbientStr"), 0.15f); // slightly brighter for texture
glUniform1f(glGetUniformLocation(shader, "uShininess"), 64.0f);
glUniform1i(glGetUniformLocation(shader, "uTexture"), 0); // texture unit 0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
std::cout << "Lab 3 capstone — all Day 2 concepts active. ESC=quit.\n";
while (!glfwWindowShouldClose(win)) {
if (glfwGetKey(win, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(win, true);
glClearColor(0.04f, 0.06f, 0.10f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
float t = (float)glfwGetTime();
glm::mat4 model = glm::rotate(glm::mat4(1.0f),
glm::radians(t * 12.0f), glm::vec3(0, 1, 0));
glUniformMatrix4fv(glGetUniformLocation(shader, "uModel"), 1, GL_FALSE, glm::value_ptr(model));
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 36);
glfwSwapBuffers(win); glfwPollEvents();
}
glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO);
glDeleteTextures(1, &tex);
glDeleteProgram(shader);
glfwTerminate();
return 0;
}- Change the texture file: Replace
metal_plate.jpgwith any image file — rename it and update the path. A checkerboard pattern or hull camo makes the UV mapping immediately visible on all 6 faces. - Disable texture — restore Lab 2: In the fragment shader, replace
texture(uTexture, vTexCoord).rgbwithvec3(0.5, 0.55, 0.6). The result is identical to Lab 2. This proves the only change between labs was the texture line. - Add UV scaling: Change
vTexCoord = aTexCoordtovTexCoord = aTexCoord * 2.0in the vertex shader. The texture tiles twice per face. Change to 0.5 and the texture zooms in. This demonstrates UV scaling without touching C++ code.
- Texture maps correctly across all 6 cube faces
- Phong lighting still active — lit faces brighter than dark faces
- Rotation smooth — no jitter or flickering
- Can explain to instructor: what the uModel does, what glm::lookAt does, and what the final multiply line in the fragment shader does
Koenig Original AI-Courseware · Day 2 Labs Complete