
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_Window* window = SDL_CreateWindow("Free-Fly Camera", 800, 600, SDL_WINDOW_OPENGL);
SDL_GLContext glContext = SDL_GL_CreateContext(window);
SDL_GetWindowRelativeMouseMode(window); // Lock/hide mouse
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_COLOR_MATERIAL);
glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
glShadeModel(GL_SMOOTH);
GLfloat lightPos[] = { 2.0f, 4.0f, 2.0f, 1.0f };
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
Here we set the object colors and load the object:
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glColor3f(1.0f, 0.6f, 0.3f); // Object color
if (!loadOBJ("base.obj")) {
std::cerr << "Failed to load base.obj\n";
return 1;
}
You can download the obj file here : base.obj
To load the obj file we need to download the library tinyobjloader, it's an header file library to use it you simply need to define TINYOBJLOADER_IMPLEMENTATION before including it.
then to load the Obj we need a function using this library:
bool loadOBJ(const char* path) {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string warn, err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, path);
if (!warn.empty()) std::cout << "WARN: " << warn << std::endl;
if (!err.empty()) std::cerr << "ERR: " << err << std::endl;
if (!ret) return false;
for (const auto& shape : shapes) {
for (const auto& idx : shape.mesh.indices) {
Vertex v{};
v.x = attrib.vertices[3 * idx.vertex_index + 0];
v.y = attrib.vertices[3 * idx.vertex_index + 1];
v.z = attrib.vertices[3 * idx.vertex_index + 2];
if (!attrib.normals.empty() && idx.normal_index >= 0) {
v.nx = attrib.normals[3 * idx.normal_index + 0];
v.ny = attrib.normals[3 * idx.normal_index + 1];
v.nz = attrib.normals[3 * idx.normal_index + 2];
}
model.push_back(v);
}
}
return true;
}
We define the model and the vertex struct like this:
struct Vertex {
float x, y, z; // point
float nx, ny, nz; // normal
};
std::vector<Vertex> model;
The nice things to see the object is to have a camera that can move around, for that we init some variables.
float camX = 0.0f, camY = 0.0f, camZ = 5.0f; // Start outside
float yaw = 0.0f, pitch = 0.0f;
float moveSpeed = 0.1f;
float sensitivity = 0.2f;
Here are all the code inside the main loop.
For the event loop we use the motion of the mouse to orient the camera.
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false;
if (e.type == SDL_EVENT_MOUSE_MOTION) {
yaw += e.motion.xrel * sensitivity;
pitch += e.motion.yrel * sensitivity;
if (pitch > 89.0f) pitch = 89.0f;
if (pitch < -89.0f) pitch = -89.0f;
}
}
The yaw and the pitch are set by the mouse, we now can compute the camera movement and ajust it based on the inputs.
float yawRad = yaw * 3.14159f / 180.0f;
float pitchRad = pitch * 3.14159f / 180.0f;
float forwardX = cosf(pitchRad) * sinf(yawRad);
float forwardY = sinf(pitchRad);
float forwardZ = -cosf(pitchRad) * cosf(yawRad);
float rightX = cosf(yawRad);
float rightZ = sinf(yawRad);
const bool* keys = SDL_GetKeyboardState(nullptr);
if (keys[SDL_SCANCODE_W]) {
camX += forwardX * moveSpeed;
camY += forwardY * moveSpeed;
camZ += forwardZ * moveSpeed;
}
if (keys[SDL_SCANCODE_S]) {
camX -= forwardX * moveSpeed;
camY -= forwardY * moveSpeed;
camZ -= forwardZ * moveSpeed;
}
if (keys[SDL_SCANCODE_A]) {
camX -= rightX * moveSpeed;
camZ += rightZ * moveSpeed;
}
if (keys[SDL_SCANCODE_D]) {
camX += rightX * moveSpeed;
camZ -= rightZ * moveSpeed;
}
glViewport(0, 0, 800, 600);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
perspective(60.0f, 800.0f / 600.0f, 0.1f, 1000.0f);
The perspective function is an implementation of the gluPerspective:
void perspective(float fovY, float aspect, float zNear, float zFar) {
float f = 1.0f / tanf(fovY * 0.5f * 3.14159f / 180.0f);
float m[16] = {
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, (zFar + zNear) / (zNear - zFar), -1,
0, 0, (2 * zFar * zNear) / (zNear - zFar), 0
};
glLoadMatrixf(m);
}
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(-pitch, 1, 0, 0);
glRotatef(-yaw, 0, 1, 0);
glTranslatef(-camX, -camY, -camZ); // move world opposite to camera
drawModel();
The draw model function is simple:
void drawModel() {
glBegin(GL_TRIANGLES);
for (const auto& v : model) {
glNormal3f(v.nx, v.ny, v.nz);
glVertex3f(v.x, v.y, v.z);
}
glEnd();
}
Need another OS ? => Windows, Mac, Linux