#define PGL_PREFIX_TYPES #define PORTABLEGL_IMPLEMENTATION #include #define STB_IMAGE_IMPLEMENTATION #include #include #include #include #include #include #include #define SDL_MAIN_HANDLED #include using namespace glm; struct My_Uniforms { mat4 model; mat4 view; mat4 projection; GLuint tex; }; void setup_context(); void cleanup(); bool handle_events(); unsigned int loadTexture(const char *path); void stencil_vs(float* vs_output, pgl_vec4* vertex_attribs, Shader_Builtins* builtins, void* uniforms); void stencil_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms); void stencil_single_color_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms); // settings unsigned int scr_width = 640; unsigned int scr_height = 480; // camera Camera camera(vec3(0.0f, 0.0f, 3.0f)); // timing float deltaTime = 0.0f; float lastFrame = 0.0f; SDL_Window* window; SDL_Renderer* ren; SDL_Texture* tex; pix_t* bbufpix; glContext the_Context; My_Uniforms uniforms; int main() { // SDL2 and PortableGL: initialize and configure // ------------------------------ setup_context(); // tell SDL to capture our mouse SDL_SetRelativeMouseMode(SDL_TRUE); // configure global opengl state // ----------------------------- glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // Create our shader programs and set uniform pointer for each // ----------------------------------------------------------- GLenum smooth[] = { PGL_SMOOTH2 }; GLuint shader = pglCreateProgram(stencil_vs, stencil_fs, 2, smooth, GL_FALSE); glUseProgram(shader); pglSetUniform(&uniforms); // NOTE(rswinkle): This is really inefficient interpolating texcords we don't use but I'm doing // it for the same reason I don't combine model,view,projection client side, to // have a more direct port...maybe I'll go through all these later making small // obvious optimizations GLuint shaderSingleColor = pglCreateProgram(stencil_vs, stencil_single_color_fs, 2, smooth, GL_FALSE); glUseProgram(shaderSingleColor); pglSetUniform(&uniforms); // set up vertex data (and buffer(s)) and configure vertex attributes // ------------------------------------------------------------------ float cubeVertices[] = { // positions // texture Coords -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f }; float planeVertices[] = { // positions // texture Coords (note we set these higher than 1 (together with GL_REPEAT as texture wrapping mode). this will cause the floor texture to repeat) 5.0f, -0.5f, 5.0f, 2.0f, 0.0f, -5.0f, -0.5f, 5.0f, 0.0f, 0.0f, -5.0f, -0.5f, -5.0f, 0.0f, 2.0f, 5.0f, -0.5f, 5.0f, 2.0f, 0.0f, -5.0f, -0.5f, -5.0f, 0.0f, 2.0f, 5.0f, -0.5f, -5.0f, 2.0f, 2.0f }; // cube VAO unsigned int cubeVAO, cubeVBO; glGenVertexArrays(1, &cubeVAO); glGenBuffers(1, &cubeVBO); glBindVertexArray(cubeVAO); glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &cubeVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0); glEnableVertexAttribArray(1); pglVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 3 * sizeof(float)); glBindVertexArray(0); // plane VAO unsigned int planeVAO, planeVBO; glGenVertexArrays(1, &planeVAO); glGenBuffers(1, &planeVBO); glBindVertexArray(planeVAO); glBindBuffer(GL_ARRAY_BUFFER, planeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), &planeVertices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0); glEnableVertexAttribArray(1); pglVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 3 * sizeof(float)); glBindVertexArray(0); // load textures // ------------- unsigned int cubeTexture = loadTexture(FileSystem::getPath("resources/textures/marble.jpg").c_str()); unsigned int floorTexture = loadTexture(FileSystem::getPath("resources/textures/metal.png").c_str()); // render loop // ----------- while (true) { // per-frame time logic // -------------------- int currentFrame = SDL_GetTicks(); deltaTime = (currentFrame - lastFrame)/1000.0f; lastFrame = currentFrame; // input // ----- if (handle_events()) break; // render // ------ glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // don't forget to clear the stencil buffer! // set uniforms // glUseProgram(shaderSingleColor); // No need since both shaders use the same uniform pointer mat4 model = mat4(1.0f); uniforms.view = camera.GetViewMatrix(); uniforms.projection = perspective(radians(camera.Zoom), (float)scr_width / (float)scr_height, 0.1f, 100.0f); glUseProgram(shader); // draw floor as normal, but don't write the floor to the stencil buffer, we only care about the containers. We set its mask to 0x00 to not write to the stencil buffer. glStencilMask(0x00); // floor glBindVertexArray(planeVAO); uniforms.tex = floorTexture; uniforms.model = mat4(1.0f); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); // 1st. render pass, draw objects as normal, writing to the stencil buffer // -------------------------------------------------------------------- glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilMask(0xFF); // cubes glBindVertexArray(cubeVAO); uniforms.tex = cubeTexture; uniforms.model = translate(model, vec3(-1.0f, 0.0f, -1.0f)); glDrawArrays(GL_TRIANGLES, 0, 36); model = mat4(1.0f); uniforms.model = translate(model, vec3(2.0f, 0.0f, 0.0f)); glDrawArrays(GL_TRIANGLES, 0, 36); // 2nd. render pass: now draw slightly scaled versions of the objects, this time disabling stencil writing. // Because the stencil buffer is now filled with several 1s. The parts of the buffer that are 1 are not drawn, thus only drawing // the objects' size differences, making it look like borders. // ----------------------------------------------------------------------------------------------------------------------------- glStencilFunc(GL_NOTEQUAL, 1, 0xFF); glStencilMask(0x00); glDisable(GL_DEPTH_TEST); glUseProgram(shaderSingleColor); float scale = 1.1f; // cubes glBindVertexArray(cubeVAO); glBindTexture(GL_TEXTURE_2D, cubeTexture); model = mat4(1.0f); model = translate(model, glm::vec3(-1.0f, 0.0f, -1.0f)); uniforms.model = glm::scale(model, glm::vec3(scale, scale, scale)); glDrawArrays(GL_TRIANGLES, 0, 36); model = mat4(1.0f); model = translate(model, glm::vec3(2.0f, 0.0f, 0.0f)); uniforms.model = glm::scale(model, glm::vec3(scale, scale, scale)); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); glStencilMask(0xFF); glStencilFunc(GL_ALWAYS, 0, 0xFF); glEnable(GL_DEPTH_TEST); // SDL2: blit texture (ie latest rendered frame) to screen // ------------------------------------------------------------------------------- SDL_UpdateTexture(tex, NULL, bbufpix, scr_width * sizeof(pix_t)); SDL_RenderCopy(ren, tex, NULL, NULL); SDL_RenderPresent(ren); } // optional: de-allocate all resources once they've outlived their purpose: // ------------------------------------------------------------------------ glDeleteVertexArrays(1, &cubeVAO); glDeleteVertexArrays(1, &planeVAO); glDeleteBuffers(1, &cubeVBO); glDeleteBuffers(1, &planeVBO); // SDL and PortableGL cleanup // ------------------------------------------------------------------ cleanup(); return 0; } // process all input: Poll for events reacting to ones we care about // --------------------------------------------------------------------------------------------------------- bool handle_events() { SDL_Event event; SDL_Scancode sc; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: return true; case SDL_KEYDOWN: sc = event.key.keysym.scancode; switch (sc) { case SDL_SCANCODE_ESCAPE: return true; default: ; } break; //sdl_keydown case SDL_WINDOWEVENT: switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: scr_width = event.window.data1; scr_height = event.window.data2; pglResizeFramebuffer(scr_width, scr_height); bbufpix = (pix_t*)pglGetBackBuffer(); glViewport(0, 0, scr_width, scr_height); SDL_DestroyTexture(tex); tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_width, scr_height); break; } break; case SDL_MOUSEMOTION: { float dx = event.motion.xrel; float dy = -event.motion.yrel; // reversed since y coordinates go from bottom to top camera.ProcessMouseMovement(dx, dy); } break; case SDL_MOUSEWHEEL: camera.ProcessMouseScroll(event.wheel.y); break; } } const Uint8 *state = SDL_GetKeyboardState(NULL); if (state[SDL_SCANCODE_W]) { camera.ProcessKeyboard(FORWARD, deltaTime); } if (state[SDL_SCANCODE_S]) { camera.ProcessKeyboard(BACKWARD, deltaTime); } if (state[SDL_SCANCODE_A]) { camera.ProcessKeyboard(LEFT, deltaTime); } if (state[SDL_SCANCODE_D]) { camera.ProcessKeyboard(RIGHT, deltaTime); } return false; } /* // glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) { // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. glViewport(0, 0, width, height); } */ void setup_context() { // Initialize SDL2 and create window SDL_SetMainReady(); if (SDL_Init(SDL_INIT_VIDEO)) { std::cout << "SDL_Init error: " << SDL_GetError() << "\n"; exit(0); } window = SDL_CreateWindow("LearnPortablGL", 100, 100, scr_width, scr_height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); if (!window) { std::cerr << "Failed to create window\n"; SDL_Quit(); exit(0); } // Create Software Renderer and texture ren = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); tex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, scr_width, scr_height); // Initialize and set PGL context if (!init_glContext(&the_Context, &bbufpix, scr_width, scr_height)) { puts("Failed to initialize glContext"); exit(0); } set_glContext(&the_Context); } void stencil_vs(float* vs_output, pgl_vec4* vertex_attribs, Shader_Builtins* builtins, void* uniforms) { My_Uniforms* u = (My_Uniforms*)uniforms; mat4 model = u->model; mat4 view = u->view; mat4 projection = u->projection; vec4 aPos = ((vec4*)vertex_attribs)[0]; vec2 aTexCoords = vec2(((vec4*)vertex_attribs)[1]); // Texcoords = aTexCoord *(vec2*)&vs_output[0] = aTexCoords; *(vec4*)&builtins->gl_Position = projection * view * model * aPos; } void stencil_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms) { My_Uniforms* u = (My_Uniforms*)uniforms; vec2 TexCoords = *(vec2*)&fs_input[0]; builtins->gl_FragColor = texture2D(u->tex, TexCoords.x, TexCoords.y); } void stencil_single_color_fs(float* fs_input, Shader_Builtins* builtins, void* uniforms) { *(vec4*)&builtins->gl_FragColor = vec4(0.04, 0.28, 0.26, 1.0); } void cleanup() { free_glContext(&the_Context); SDL_DestroyTexture(tex); SDL_DestroyRenderer(ren); SDL_DestroyWindow(window); SDL_Quit(); } // utility function for loading a 2D texture from file // --------------------------------------------------- unsigned int loadTexture(char const * path) { unsigned int textureID; glGenTextures(1, &textureID); int width, height, nrComponents; unsigned char *data = stbi_load(path, &width, &height, &nrComponents, STBI_rgb_alpha); if (data) { GLenum format; /* if (nrComponents == 1) format = GL_RED; else if (nrComponents == 3) format = GL_RGB; else if (nrComponents == 4) format = GL_RGBA; */ format = GL_RGBA; glBindTexture(GL_TEXTURE_2D, textureID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); 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_image_free(data); } else { std::cout << "Texture failed to load at path: " << path << std::endl; stbi_image_free(data); } return textureID; }