/* Simplified OpenGL 4.5 demo * Seth Long, Fall 2020 * This is a *very* short demo which displays a triangle * Many of these functions can fail, and return error values * I doubt it can be done much shorter without leaving vertices in only main memory or some such */ #include #include #include #include #include #include #include #include #include #include #include #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include #include #include "tiny_obj_loader.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "scolor.hpp" #include "util.h" using namespace std; using namespace glm; char general_buffer[GBLEN]; float height = 1200; float width = 100; int frame_count = 0; void GLAPIENTRY MessageCallback( 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 CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ), type, severity, message ); } void resize(GLFWwindow*, int new_width, int new_height){ width = new_width; height = new_height; printf("Window resized, now %f by %f\n", width, height); glViewport(0, 0, width, height); } struct Vertex { vec3 pos; vec3 normal; vec2 texCoord; bool operator==(const Vertex& other) const { return pos == other.pos && texCoord == other.texCoord && normal == other.normal; } };// Can't use __attribute__((packed)) due to non-POD vec2 and vec3 namespace std { template<> struct hash { size_t operator()(Vertex const& vertex) const { return hash()(vertex.pos) ^ (hash()(vertex.texCoord) << 1) ^ (hash()(vertex.normal) << 2); } }; } class LoadedObject { public: GLuint ebuf, tvnbuf; size_t element_count; void load_object(const char* filename){ tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string warn, err; vector vertices; vector indices; if(!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename)) throw std::runtime_error(err); unordered_map uniqueVertices = {}; for(const auto& shape : shapes){ for(const auto& index : shape.mesh.indices){ Vertex vertex = {}; vertex.pos = { attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2] }; vertex.texCoord = { attrib.texcoords[2 * index.texcoord_index + 0], 1.0 - attrib.texcoords[2 * index.texcoord_index + 1] }; vertex.normal = { attrib.normals[3 * index.normal_index], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2] }; if(uniqueVertices.count(vertex) == 0){ uniqueVertices[vertex] = (uint32_t)vertices.size(); vertices.push_back(vertex); } indices.push_back(uniqueVertices[vertex]); } } glGenBuffers(1, &tvnbuf); glBindBuffer(GL_SHADER_STORAGE_BUFFER, tvnbuf); glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(struct Vertex) * vertices.size(), vertices.data(), GL_STATIC_DRAW); std::cout << "Loaded " << vertices.size() << " Vertices\n"; glGenBuffers(1, &ebuf); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * indices.size(), indices.data(), GL_STATIC_DRAW); element_count = indices.size(); std::cout << "Loaded " << indices.size() << " Indexes\n"; } int img_width, img_height; GLuint tex; int load_texture(const char* file){ unsigned char* image = stbi_load(file, &img_width, &img_height, 0, STBI_rgb); if(!image){ puts(RED("No image, giving up!").c_str()); return 1; } else printf(PURPLE("Image size: %d by %d\n").c_str(), 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); return 0; } /* Note here: * There's no really good way to let the user change the program a lot in LoadedObject * We could add parameters for the shader names * That would help, but they would need to support the same attributes * So the shader couldn't vary all that much * Probably a derived class using a different shader should inherit from this, and override load_program */ unsigned int v_attrib; unsigned int n_attrib; unsigned int t_attrib; unsigned int mvp_uniform; unsigned int models_buffer; unsigned int factor_uniform; GLuint visual_program; int load_program(){ visual_program = make_program("vertex_shader.glsl", 0, 0, 0, "fragment_shader.glsl"); if(!visual_program) return 1; v_attrib = glGetAttribLocation(visual_program, "in_vertex"); n_attrib = glGetAttribLocation(visual_program, "in_normal"); t_attrib = glGetAttribLocation(visual_program, "in_texcoord"); mvp_uniform = glGetUniformLocation(visual_program, "mvp"); factor_uniform = glGetUniformLocation(visual_program, "factor"); glGenBuffers(1, &models_buffer); return 0; } vector locations; void add_instance(float x, float y, float z){ locations.push_back(vec3(x, y, z)); } void add_instance(vec3 spot){ locations.push_back(spot); } /* Again: Very tied to the default shaders */ void draw(mat4 vp){ /* Note about this soon-to-be loop: Updating the model matrices is a really application * for another thread. OpenGL threading is tricky, but I'm sure you can contrive * to copy them to the card. For Vulkan, you'd us a different command queue for * the model copying that's not the drawing queue. */ vector models; models.reserve(locations.size()); for(vec3 l : locations){ mat4 new_model = mat4(1.0f); new_model = translate(new_model, l); new_model = rotate(new_model, frame_count/100.0f, l); models.push_back(new_model); } glUseProgram(visual_program); glBindBuffer(GL_SHADER_STORAGE_BUFFER, models_buffer); glBufferData(GL_SHADER_STORAGE_BUFFER, models.size() * sizeof(mat4), models.data(), GL_STATIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, models_buffer); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex); // vertices glEnableVertexAttribArray(v_attrib); glBindBuffer(GL_ARRAY_BUFFER, tvnbuf); glVertexAttribPointer(v_attrib, 3, GL_FLOAT, GL_FALSE, 32, 0); // texture coordinates glEnableVertexAttribArray(t_attrib); glBindBuffer(GL_ARRAY_BUFFER, tvnbuf); glVertexAttribPointer(t_attrib, 2, GL_FLOAT, GL_FALSE, 32, (void*)24); // normal vectors glEnableVertexAttribArray(n_attrib); glBindBuffer(GL_ARRAY_BUFFER, tvnbuf); glVertexAttribPointer(n_attrib, 3, GL_FLOAT, GL_FALSE, 32, (void*)12); // Edges3 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebuf); glUniformMatrix4fv(mvp_uniform, 1, 0, value_ptr(vp)); glUniform1f(factor_uniform, -cos(frame_count/100.0f)); glDrawElementsInstanced(GL_TRIANGLES, element_count, GL_UNSIGNED_INT, 0, models.size()); } }; int main(int , char ** ){ glfwInit(); GLFWwindow* window = glfwCreateWindow(width, height, "Simple OpenGL 4.0+ Demo", 0, 0); glfwSetFramebufferSizeCallback(window, resize); glfwMakeContextCurrent(window); glewInit(); // During init, enable debug output glEnable ( GL_DEBUG_OUTPUT ); glDebugMessageCallback( MessageCallback, 0 ); // Load object LoadedObject torus; torus.load_object("torus.obj"); torus.load_texture("brick.jpg"); torus.add_instance(-2, 2, 0); torus.add_instance(2, 2, 0); torus.load_program(); LoadedObject monkey; monkey.load_object("monkey.obj"); monkey.load_texture("brick.jpg"); monkey.add_instance(-2, -2, 0); monkey.add_instance(2, -2, 0); monkey.load_program(); glEnable(GL_DEPTH_TEST); while(!glfwWindowShouldClose(window)){ frame_count++; // Do the visual rendering bit glfwPollEvents(); glClearColor(0, 0, 0, 1.0); glClear(GL_COLOR_BUFFER_BIT); glClear(GL_DEPTH_BUFFER_BIT); mat4 view = lookAt(vec3(0.0f, 0.0f, 8.0f), vec3(0, 0, 0), vec3(0, 1, 0)); mat4 projection = perspective(45.0f, width/height, 0.1f, 1000.0f); mat4 vp = projection * view; torus.draw(vp); monkey.draw(vp); glfwSwapBuffers(window); } glfwDestroyWindow(window); glfwTerminate(); } // g++ load_object.cpp -lGL -lglfw -lGLEW