/* Simplified OpenGL 4.5 demo * Seth Long, Fall 2020 * Simple demo, C++ version * Note operator overloading for matrix multiplication! * * Linux build: g++ simple_demo.cpp -lglfw -lGLEW -lGL -lGLU */ /* Set this to WINDOWS for Windows, UNIX for Linux, BSD, etc. */ #define UNIX /* This is needed to allow fopen, fread, etc. */ #ifdef WINDOWS #define _CRT_SECURE_NO_WARNINGS #endif /* General-purpose C includes */ #include #include /* General-purpose C++ includes */ #include #include /* Data Structures */ #include #include /* OpenGL Loader */ #ifdef UNIX #include // glad on Windows #else #include #endif /* OpenGL Includes */ #include #include #include #include /* Tiny obj loader - loads Wavefront .obj files */ #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" /* Provides stbi_image to load textures */ #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" using namespace std; using namespace glm; struct globals { float height = 800; float width = 1280; bool shutdown = false; vec3 player_position; float heading = 0.0; float elevation = 0.0; bool forward = false; bool backward = false; bool left = false; bool right = false; }; globals g; vec3 get_lookat_point(int direction = 0, float r = 0.0); void GLAPIENTRY debug_callback(GLenum, GLenum type, GLuint, GLenum severity, GLsizei, const GLchar* message, const void*){ if(severity != GL_DEBUG_SEVERITY_HIGH && severity != GL_DEBUG_SEVERITY_MEDIUM ) return; fprintf( stderr, "GL ERROR MESSAGE: %s type = 0x%x, severity = 0x%x, message = %s\n", ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), type, severity, message ); } /* Remember to free the result! */ char* get_source(const char *filename){ FILE *source_file = fopen(filename, "r"); fseek(source_file, 0, SEEK_END); long length = ftell(source_file); char *source = (char*)malloc(length + 1); rewind(source_file); fread(source, 1, length, source_file); fclose(source_file); source[length] = 0; return source; } class GameObject { public: /* Identifiers for OpenGL resources */ unsigned int v_attrib; unsigned int tc_attrib; unsigned int vp_uniform; unsigned int program; unsigned int vbuf; unsigned int ebuf; unsigned int tex; unsigned int element_count; unsigned int models_buffer; /* Properties not related to OpenGL */ vector models; float spin_rate = 0.01; void add_location(vec3 location); bool init(const char* object_file, const char* texture_file, bool flip_xy = false); void draw(mat4 vp); virtual void movement(float ms); // takes the number of milliseconds since the last run }; class NotRotateObject : public GameObject { void movement(float ms) override {} }; void GameObject::add_location(vec3 location){ models.push_back(translate(mat4(1.0), location)); } void GameObject::movement(float ms){ for(auto &m : models) m = rotate(m, spin_rate, vec3(0, 1, 0)); } vector game_objects; struct vertex { float x, y, z, u, v; }; bool operator==(const vertex& a, const vertex& o){ return a.x == o.x && a.y == o.y && a.z == o.z && a.u == o.u && a.v == o.v; } namespace std { template<> struct hash { size_t operator()(vertex const& v) const { size_t vals[] = {(size_t)(v.x * 100000), (size_t)(v.y * 10000), (size_t)(v.z * 1000), (size_t)(v.u * 100), (size_t)v.v}; size_t result = vals[0]; for(auto v : vals) result = result ^ v; return result; } }; } bool GameObject::init(const char* object_file, const char* texture_file, bool flip_yz){ tinyobj::attrib_t attrib; vector shapes; vector materials; string warn, err; if(!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, object_file)){ printf("Loading failed for: %s\n%s\n", object_file, err.c_str()); return false; } vector vertices; vector elements; unordered_map unique_vertices; for(const auto& shape : shapes){ for(const auto& index : shape.mesh.indices){ vertex new_vertex; new_vertex.x = attrib.vertices[3 * index.vertex_index + 0]; // x coordinate if(flip_yz){ new_vertex.z = attrib.vertices[3 * index.vertex_index + 1]; // y coordinate new_vertex.y = attrib.vertices[3 * index.vertex_index + 2]; // z coordinate } else { new_vertex.y = attrib.vertices[3 * index.vertex_index + 1]; // y coordinate new_vertex.z = attrib.vertices[3 * index.vertex_index + 2]; // z coordinate } new_vertex.u = attrib.texcoords[2 * index.texcoord_index + 0]; // u coordinate new_vertex.v = 1.0f - attrib.texcoords[2 * index.texcoord_index + 1]; // v coordinate if(unique_vertices.count(new_vertex) == 0){ unique_vertices[new_vertex] = (unsigned int)vertices.size(); vertices.push_back(new_vertex); } elements.push_back(unique_vertices[new_vertex]); } } element_count = elements.size(); printf("Element count: %lu\n", elements.size()); // Format: xyzuv glGenBuffers(1, &vbuf); glBindBuffer(GL_SHADER_STORAGE_BUFFER, vbuf); glBufferData(GL_SHADER_STORAGE_BUFFER, 20 * vertices.size(), vertices.data(), GL_STATIC_DRAW); glGenBuffers(1, &ebuf); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf); glBufferData(GL_ELEMENT_ARRAY_BUFFER, 4 * elements.size(), elements.data(), GL_STATIC_DRAW); /* Setup texture here */ int img_width, img_height; unsigned char* image = stbi_load(texture_file, &img_width, &img_height, 0, STBI_rgb); if(!image){ printf("image failed to load: %s\n", texture_file); return false; } else { printf("Image size is %d by %d\n", img_width, img_height); } glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img_width, img_height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); free(image); glGenBuffers(1, &models_buffer); char *source = get_source("vertex_shader.glsl"); puts(source); unsigned int vs_reference = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vs_reference, 1, (const char**)&source, 0); glCompileShader(vs_reference); glGetShaderInfoLog(vs_reference, 2048, NULL, source); puts(source); free(source); int compile_ok; glGetShaderiv(vs_reference, GL_COMPILE_STATUS, &compile_ok); if(!compile_ok){ printf("Vertex shader failed to compile\n"); return false; } source = get_source("fragment_shader.glsl"); puts(source); unsigned int fs_reference = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fs_reference, 1, (const char**)&source, 0); glCompileShader(fs_reference); glGetShaderInfoLog(fs_reference, 2048, NULL, source); puts(source); glGetShaderiv(fs_reference, GL_COMPILE_STATUS, &compile_ok); if(!compile_ok){ printf("Fragment shader failed to compile\n"); return false; } free(source); program = glCreateProgram(); glAttachShader(program, vs_reference); glAttachShader(program, fs_reference); glLinkProgram(program); int link_ok; glGetProgramiv(program, GL_LINK_STATUS, &link_ok); glGetProgramInfoLog(program, 2048, NULL, source); puts(source); if(!link_ok){ printf("Link failure\n"); return false; } v_attrib = glGetAttribLocation(program, "in_vertex"); tc_attrib = glGetAttribLocation(program, "in_texcoord"); vp_uniform = glGetUniformLocation(program, "vp"); return true; } void GameObject::draw(mat4 vp){ static int count; count++; glUseProgram(program); glEnableVertexAttribArray(v_attrib); glBindBuffer(GL_ARRAY_BUFFER, vbuf); glVertexAttribPointer(v_attrib, 3, GL_FLOAT, GL_FALSE, 20, 0); glEnableVertexAttribArray(tc_attrib); glBindBuffer(GL_ARRAY_BUFFER, vbuf); glVertexAttribPointer(tc_attrib, 2, GL_FLOAT, GL_FALSE, 20, (const void*)12); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); glBindBuffer(GL_SHADER_STORAGE_BUFFER, models_buffer); glBufferData(GL_SHADER_STORAGE_BUFFER, models.size() * sizeof(mat4), models.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, models_buffer); /* This is for a specific object again */ glUniformMatrix4fv(vp_uniform, 1, 0, value_ptr(vp)); glDrawElementsInstanced(GL_TRIANGLES, element_count, GL_UNSIGNED_INT, 0, models.size()); } void general_management_function(){ while(!g.shutdown) { if(g.forward) g.player_position = get_lookat_point(0, 0.2); if(g.right) g.player_position = get_lookat_point(1, 0.2); if(g.backward) g.player_position = get_lookat_point(2, 0.2); if(g.left) g.player_position = get_lookat_point(3, 0.2); this_thread::sleep_for(chrono::milliseconds(10)); } } void movement_function() { /* We might need a shutdown procedure that ends this loop */ while(!g.shutdown) { for(auto o : game_objects) o->movement(10); /* TODO: Sleep for 10 ms - the time this iteration took */ this_thread::sleep_for(chrono::milliseconds(10)); } } /* direction: 0 - forward, 1 : right, 2 : backwards, 3 : left */ /* If r is set to 0, elevation is incorporated into the result. * Otherwise, it is ignored, and this function leaves y as 0 */ vec3 get_lookat_point(int direction, float r){ vec3 lap; if(r == 0.0f) { r = cos(g.elevation); lap.y = sin(g.elevation); } else { lap.y = 0; } if(direction == 0){ lap.x = g.player_position.x + r * cos(g.heading); lap.z = g.player_position.z + r * sin(g.heading); } else if (direction == 1){ lap.x = g.player_position.x + r * cos(g.heading + M_PI / 2); lap.z = g.player_position.z + r * sin(g.heading + M_PI / 2); } else if (direction == 2){ lap.x = g.player_position.x - r * cos(g.heading); lap.z = g.player_position.z - r * sin(g.heading); } else if (direction == 3){ lap.x = g.player_position.x + r * cos(g.heading - M_PI / 2); lap.z = g.player_position.z + r * sin(g.heading - M_PI / 2); } return lap; } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){ printf("key %d was pressed\n", key); if(action == GLFW_PRESS) { // down if(key == GLFW_KEY_W) g.forward = true; if(key == GLFW_KEY_S) g.backward = true; if(key == GLFW_KEY_A) g.left = true; if(key == GLFW_KEY_D) g.right = true; } if(action == GLFW_RELEASE) { // UP if(key == GLFW_KEY_W) g.forward = false; if(key == GLFW_KEY_S) g.backward = false; if(key == GLFW_KEY_A) g.left = false; if(key == GLFW_KEY_D) g.right = false; } } void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos){ double dx = g.width/2 - xpos; double dy = g.height/2 - ypos; glfwSetCursorPos(window, g.width / 2, g.height / 2); g.heading -= (dx / 200.0); g.elevation += (dy / 200.0); if(g.elevation > 3.14f/2) g.elevation = 3.14f/2; if(g.elevation < -3.14f/2) g.elevation = -3.14f/2; } void window_size_callback(GLFWwindow* window, int width, int height){ g.width = width; g.height = height; glViewport(0, 0, width, height); } int main(int argc, char ** argv){ glfwInit(); GLFWwindow* window = glfwCreateWindow(g.width, g.height, "Simple OpenGL 4.0+ Demo", 0, 0); glfwMakeContextCurrent(window); // TODO: glad instead of glew #ifdef UNIX glewInit(); #else gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); #endif // During init, enable debug output glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(debug_callback, 0); // Set up callback functions for glfw glfwSetKeyCallback(window, key_callback); glfwSetCursorPosCallback(window, cursor_pos_callback); glfwSetFramebufferSizeCallback(window, window_size_callback); /* Level Loading Area */ g.player_position = vec3(0, 0, 10); /* GameObject *cylinder = new GameObject; if(!cylinder->init("cylinder.obj", "brick.jpg")){ printf("Object failed to load, giving up!\n"); return 1; } for(int i = -8; i < 8; i += 3) cylinder->add_location(vec3(i, 0, 0)); game_objects.push_back(cylinder); */ /* GameObject *cube = new GameObject; cube->spin_rate = 0.05f; if(!cube->init("cube.obj", "brick.jpg")) return 1; for(int i = -8; i < 8; i += 3) cube->add_location(vec3(i, 3, 0)); game_objects.push_back(cube); */ NotRotateObject *chalet = new NotRotateObject; chalet->init("chalet.obj", "chalet.jpg", true); chalet->add_location(vec3(-3, -2, 0)); chalet->add_location(vec3(-3, 3, 0)); chalet->models[0] = scale(chalet->models[0], vec3(3, 3, 3)); chalet->models[1] = scale(chalet->models[1], vec3(.5, .5, .5)); game_objects.push_back(chalet); GameObject *spinchalet = new GameObject; spinchalet->init("chalet.obj", "chalet.jpg", true); spinchalet->add_location(vec3(3, -2, 0)); spinchalet->add_location(vec3(3, 3, 0)); spinchalet->models[0] = scale(spinchalet->models[0], vec3(3, 3, 3)); spinchalet->models[1] = scale(spinchalet->models[1], vec3(.5, .5, .5)); game_objects.push_back(spinchalet); /* Done with level loading */ int count = 0; /* Ready to start! Time to start movement */ thread movement_thread(movement_function); thread general_management_thread(general_management_function); glEnable(GL_DEPTH_TEST); while(!glfwWindowShouldClose(window)){ /* Not for a particular object */ count++; glfwPollEvents(); glClearColor(1.0, 1.0, 1.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT); /* This sets up the camera location, view angle, etc */ mat4 view = lookAt(g.player_position, get_lookat_point(), vec3(0, 1, 0)); mat4 projection = perspective(radians(45.0f), g.width/g.height, 0.1f, 1000.0f); mat4 vp = projection * view; /* Call draw and give it vp */ for(auto o : game_objects) o->draw(vp); /* Not for a particular object */ glfwSwapBuffers(window); // usleep(10000); } g.shutdown = true; movement_thread.join(); general_management_thread.join(); glfwDestroyWindow(window); glfwTerminate(); return 0; }