// This source code is property of the Computer Graphics and Visualization // chair of the TU Dresden. Do not distribute! // Copyright (C) CGV TU Dresden - All Rights Reserved #include "Viewer.h" #include #include #include #include #include #include "glsl.h" #include "textures.h" #include #include #include #include //using vec4 = Eigen::Vector4f; /* #define Eigen::Vector4f vec4 #define Eigen::Vector3f vec3 #define Eigen::Vector2f vec2 */ constexpr bool REFERENCE = true; constexpr uint32_t PATCH_SIZE = 256; //number of vertices along one side of the terrain patch Viewer::Viewer() : AbstractViewer("CG1 Exercise 2"), terrainPositions(nse::gui::VertexBuffer), terrainIndices(nse::gui::IndexBuffer), offsetBuffer(nse::gui::VertexBuffer), referenceVB(nse::gui::VertexBuffer), referenceIB(nse::gui::IndexBuffer) { LoadShaders(); CreateGeometry(); grass_texture_location = terrainShader.uniform("grass"); rock_texture_location = terrainShader.uniform("rock"); alpha_texture_location = terrainShader.uniform("alpha"); road_texture_location = terrainShader.uniform("road"); road_specular_location = terrainShader.uniform("specular_road"); //Create a texture and framebuffer for the background glGenFramebuffers(1, &backgroundFBO); glGenTextures(1, &backgroundTexture); //Align camera to view a reasonable part of the terrain camera().SetSceneExtent(nse::math::BoundingBox(Eigen::Vector3f(0, 0, 0), Eigen::Vector3f(PATCH_SIZE - 1, 0, PATCH_SIZE - 1))); camera().FocusOnPoint(0.5f * Eigen::Vector3f(PATCH_SIZE - 1, 15, PATCH_SIZE - 1)); camera().Zoom(-30); camera().RotateAroundFocusPointLocal(Eigen::AngleAxisf(-0.5f, Eigen::Vector3f::UnitY()) * Eigen::AngleAxisf(-0.05f, Eigen::Vector3f::UnitX())); camera().FixClippingPlanes(0.1, 1000); } bool Viewer::resizeEvent(const Eigen::Vector2i &) { //Re-generate the texture and FBO for the background glBindFramebuffer(GL_FRAMEBUFFER, backgroundFBO); glBindTexture(GL_TEXTURE_2D, backgroundTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width(), height(), 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, backgroundTexture, 0); auto fboStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER); if (fboStatus != GL_FRAMEBUFFER_COMPLETE) std::cout << "Warning: Background framebuffer is not complete: " << fboStatus << std::endl; glBindFramebuffer(GL_FRAMEBUFFER, 0); return false; } inline std::string loadShaderText(const char *path) { std::ifstream is(path); std::string s_save; if (is.is_open()) { s_save.assign(std::istreambuf_iterator(is), std::istreambuf_iterator()); } else { std::cout << "could not open " << path << std::endl; } is.close(); return s_save; } void Viewer::LoadShaders() { std::string sky_vs = loadShaderText("exercise2/glsl/sky.vert"); std::string sky_fs = loadShaderText("exercise2/glsl/sky.frag"); std::string terrain_vs = loadShaderText("exercise2/glsl/terrain.vert"); std::string terrain_fs = loadShaderText("exercise2/glsl/terrain.frag"); skyShader.init("Sky Shader", sky_vs, sky_fs); terrainShader.init("Terrain Shader", terrain_vs, terrain_fs); } GLuint CreateTexture(const unsigned char *fileData, size_t fileLength, bool repeat = true) { GLuint textureName; int textureWidth, textureHeight, textureChannels; auto pixelData = stbi_load_from_memory(fileData, fileLength, &textureWidth, &textureHeight, &textureChannels, 3); GLenum provided_format; if (textureChannels == 1) { provided_format = GL_R; } else if (textureChannels == 2) { provided_format = GL_RG; } else if (textureChannels == 3) { provided_format = GL_RGB; } else if (textureChannels == 4) { provided_format = GL_RGBA; } glGenTextures(1, &textureName); glBindTexture(GL_TEXTURE_2D, textureName); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0, provided_format, GL_UNSIGNED_BYTE, pixelData); glGenerateMipmap(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); if (repeat) { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); } else { glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } stbi_image_free(pixelData); return textureName; } std::string vec4_to_str(Eigen::Vector4f &v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ", " << v.w() << ")"; return ss.str(); } std::string vec3_to_str(Eigen::Vector3f& v) { std::stringstream ss; ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; return ss.str(); } float length(Eigen::Vector2f v) { return sqrt(v.x() * v.x() + v.y() * v.y()); } float dot(Eigen::Vector2f v1, Eigen::Vector2f v2) { return (v1.x() * v2.x() + v1.y() * v2.y()) / length(v1) * length(v2); } float mix(float f1, float f2, float value) { float difference = f1 - f2; return f1 + difference * value; } //source: https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83 float rand(Eigen::Vector2f c) { double dummy; return 2 * modf(sin(dot(c, Eigen::Vector2f(12.9898f, 78.233f))) * 43758.5453, &dummy) - 1; } Eigen::Vector2f mul(Eigen::Vector2f v1, Eigen::Vector2f v2) { return Eigen::Vector2f(v1.x() * v2.x(), v1.y() * v2.y()); } float perlinNoise(Eigen::Vector2f p) { Eigen::Vector2f ij = Eigen::Vector2f(floor(p.x()), floor(p.y())); Eigen::Vector2f xy = p - ij; xy = (3.0f * mul(xy, xy)) - (2.0f * mul(mul(xy, xy), xy)); //xy = 0.5f * (1.0f - cos(3.1415926 * xy)); float a = rand((ij + Eigen::Vector2f(0.0f, 0.0f))); float b = rand((ij + Eigen::Vector2f(1.0f, 0.0f))); float c = rand((ij + Eigen::Vector2f(0.0f, 1.0f))); float d = rand((ij + Eigen::Vector2f(1.0f, 1.0f))); float x1 = mix(a, b, xy.x()); float x2 = mix(c, d, xy.x()); return mix(x1, x2, xy.y()); } //based on https://www.seedofandromeda.com/blogs/58-procedural-heightmap-terrain-generation float getTerrainHeight(Eigen::Vector2f p) { float total = 0.0; float maxAmplitude = 0.0; float amplitude = 1.0; float frequency = 0.02; for (int i = 0; i < 11; i++) { total += ((1.0 - abs(perlinNoise(p * frequency))) * 2.0 - 1.0) * amplitude; frequency *= 2.0; maxAmplitude += amplitude; amplitude *= 0.45; } return 15 * total / maxAmplitude; } Eigen::Vector3f vec4_to_vec3(Eigen::Vector4f v) { return Eigen::Vector3f(v.x(), v.y(), v.z()); } Eigen::Vector3f calculate_gpu_normal(Eigen::Vector4f pos, int gl_VertexID) { const Eigen::Vector2f offsets[6] = { Eigen::Vector2f(0.0, 0.0), Eigen::Vector2f(0.0, 1.0), Eigen::Vector2f(1.0, 0.0), Eigen::Vector2f(0.0, 1.0), Eigen::Vector2f(1.0, 0.0), Eigen::Vector2f(1.0, 1.0) }; int offset_index = gl_VertexID % 6; Eigen::Vector4f base_vertex = Eigen::Vector4f(pos.x() - offsets[offset_index].x(), pos.y(), pos.z() - offsets[offset_index].y(), pos.w()); // the other 2 points from the triangle int points_index = 0; Eigen::Vector4f points[2]; int iterator_offset = 0; // second triangle offset if (offset_index > 2) { iterator_offset = 3; } for (int i = iterator_offset; i < (3 + iterator_offset); i++) { // skip for current vertex if (i == offset_index) { continue; } Eigen::Vector2f new_pos = Eigen::Vector2f(base_vertex.x(), base_vertex.z()) + offsets[i]; float y = getTerrainHeight(new_pos); points[points_index] = Eigen::Vector4f(new_pos.x(), y, new_pos.y(), base_vertex.w()); points_index++; } // create connection vectors Eigen::Vector3f v1 = vec4_to_vec3(points[0] - pos); Eigen::Vector3f v2 = vec4_to_vec3(points[1] - pos); v1.normalize(); v2.normalize(); Eigen::Vector3f normal = v1.cross(v2).normalized(); return normal; } Eigen::Vector3f calculate_reference_normal(Eigen::Vector4f p1, Eigen::Vector4f p2, Eigen::Vector4f p3) { Eigen::Vector3f v1 = vec4_to_vec3(p1 - p2); Eigen::Vector3f v2 = vec4_to_vec3(p1 - p3); v1.normalize(); v2.normalize(); Eigen::Vector3f normal = v1.cross(v2).normalized(); return normal; } void Viewer::CreateGeometry() { //empty VAO for sky emptyVAO.generate(); //terrain VAO terrainVAO.generate(); terrainVAO.bind(); std::vector positions; std::vector indices; /*Generate positions and indices for a terrain patch with a single triangle strip */ for (int i = 0; i < PATCH_SIZE; i++) { for (int j = 0; j < PATCH_SIZE; j++) { positions.emplace_back((float)j, 0.0f, (float)i, 1.0f); } } // full x direction, we skip x = 255 inside the loop // decrease y direction by 1 int count = (PATCH_SIZE) * (PATCH_SIZE - 1); for (int k = 0; k < count; k++) { // skip creating triangles on last x in row /* int end_of_x = k % (PATCH_SIZE); if (end_of_x == 0) { continue; } */ indices.emplace_back(k); indices.emplace_back(k + PATCH_SIZE); //indices.emplace_back(k + 1); //indices.emplace_back(k + 1 + PATCH_SIZE); } terrainShader.bind(); terrainPositions.uploadData(positions).bindToAttribute("position"); terrainIndices.uploadData(indices.size() * sizeof(uint32_t), indices.data()); referenceVAO.generate(); referenceVAO.bind(); std::vector ref_pos; std::vector ref_ind; for (int i = 0; i < PATCH_SIZE; i++) { for (int j = 0; j < PATCH_SIZE; j++) { ref_pos.emplace_back((float)j, getTerrainHeight(Eigen::Vector2f((float)j, (float)i)), (float)i, 1.0f); } } int index_width = PATCH_SIZE - 1; for (int i = 0; i < index_width; i++) { for (int j = 0; j < index_width; j++) { int p1 = i + (PATCH_SIZE * j); int p2 = i + (PATCH_SIZE * j) + 1; int p3 = i + (PATCH_SIZE * (j + 1)); int p4 = i + (PATCH_SIZE * (j + 1)) + 1; ref_ind.emplace_back(p1); ref_ind.emplace_back(p3); ref_ind.emplace_back(p2); ref_ind.emplace_back(p3); ref_ind.emplace_back(p2); ref_ind.emplace_back(p4); } } // -------------------------------------------------- // ---------------- Debugging Area ------------------ // -------------------------------------------------- /* for (int i = 0; i < ref_ind.size(); i+=6) { struct Point { Eigen::Vector4f position; Eigen::Vector3f normal; }; std::vector gpu_points; std::vector ref_points; for (int j = i; j < i+6; j++) { // gpu { auto position = ref_pos[j]; auto normal = calculate_gpu_normal(position, j); gpu_points.emplace_back(Point { position, normal }); } // reference { int index = j % 6; auto position = ref_pos[j]; Eigen::Vector3f normal; if (index < 3) { normal = calculate_reference_normal(ref_pos[i], ref_pos[i+1], ref_pos[i+2]); } else { normal = calculate_reference_normal(ref_pos[i+3], ref_pos[i+4], ref_pos[i+5]); } ref_points.emplace_back(Point { position, normal }); } } std::string gpu_output = "gpu_points: \n"; std::string ref_output = "ref_points: \n"; for (Point& point : gpu_points) { gpu_output += "\tpos: " + vec4_to_str(point.position) + "\tnormal: " + vec3_to_str(point.normal) + "\n"; } for (Point& point : ref_points) { ref_output += "\tpos: " + vec4_to_str(point.position) + "\tnormal: " + vec3_to_str(point.normal) + "\n"; } std::cout << gpu_output << std::endl; std::cout << ref_output << std::endl; abort(); } */ // -------------------------------------------------- // -------------- Debugging Area End ---------------- // -------------------------------------------------- referenceVB.uploadData(ref_pos) .bindToAttribute("position"); referenceIB.uploadData(ref_ind.size() * sizeof(uint32_t), ref_ind.data()); //textures grassTexture = CreateTexture((unsigned char *)grass_jpg, grass_jpg_size); rockTexture = CreateTexture((unsigned char *)rock_jpg, rock_jpg_size); roadColorTexture = CreateTexture((unsigned char *)roadcolor_jpg, roadcolor_jpg_size); roadNormalMap = CreateTexture((unsigned char *)roadnormals_jpg, roadnormals_jpg_size); roadSpecularMap = CreateTexture((unsigned char *)roadspecular_jpg, roadspecular_jpg_size); alphaMap = CreateTexture((unsigned char *)alpha_jpg, alpha_jpg_size, false); } void Viewer::RenderSky() { Eigen::Matrix4f skyView = view; for (int i = 0; i < 3; ++i) skyView.col(i).normalize(); skyView.col(3).head<3>().setZero(); Eigen::Matrix4f skyMvp = proj * skyView; glDepthMask(GL_FALSE); glEnable(GL_DEPTH_CLAMP); emptyVAO.bind(); skyShader.bind(); skyShader.setUniform("mvp", skyMvp); glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); glDisable(GL_DEPTH_CLAMP); glDepthMask(GL_TRUE); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, backgroundFBO); glBlitFramebuffer(0, 0, width(), height(), 0, 0, width(), height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); } void CalculateViewFrustum(const Eigen::Matrix4f &mvp, Eigen::Vector4f *frustumPlanes, nse::math::BoundingBox &bbox) { frustumPlanes[0] = (mvp.row(3) + mvp.row(0)).transpose(); frustumPlanes[1] = (mvp.row(3) - mvp.row(0)).transpose(); frustumPlanes[2] = (mvp.row(3) + mvp.row(1)).transpose(); frustumPlanes[3] = (mvp.row(3) - mvp.row(1)).transpose(); frustumPlanes[4] = (mvp.row(3) + mvp.row(2)).transpose(); frustumPlanes[5] = (mvp.row(3) - mvp.row(2)).transpose(); Eigen::Matrix4f invMvp = mvp.inverse(); bbox.reset(); for (int x = -1; x <= 1; x += 2) for (int y = -1; y <= 1; y += 2) for (int z = -1; z <= 1; z += 2) { Eigen::Vector4f corner = invMvp * Eigen::Vector4f(x, y, z, 1); corner /= corner.w(); bbox.expand(corner.head<3>()); } } bool IsBoxCompletelyBehindPlane(const Eigen::Vector3f &boxMin, const Eigen::Vector3f &boxMax, const Eigen::Vector4f &plane) { return plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMin.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMin.x(), boxMin.y(), boxMax.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMin.x(), boxMax.y(), boxMin.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMin.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMax.x(), boxMin.y(), boxMax.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0 && plane.dot(Eigen::Vector4f(boxMax.x(), boxMax.y(), boxMin.z(), 1)) < 0; } void Viewer::drawContents() { camera().ComputeCameraMatrices(view, proj); Eigen::Matrix4f mvp = proj * view; Eigen::Vector3f cameraPosition = view.inverse().col(3).head<3>(); int visiblePatches = 0; RenderSky(); //render terrain glEnable(GL_DEPTH_TEST); if (REFERENCE) { referenceVAO.bind(); } else { terrainVAO.bind(); } terrainShader.bind(); terrainShader.setUniform("screenSize", Eigen::Vector2f(width(), height()), false); terrainShader.setUniform("mvp", mvp); terrainShader.setUniform("cameraPos", cameraPosition, false); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, grassTexture); glUniform1i(grass_texture_location, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, rockTexture); glUniform1i(rock_texture_location, 1); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, alphaMap); glUniform1i(alpha_texture_location, 2); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, roadColorTexture); glUniform1i(road_texture_location, 3); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, roadSpecularMap); glUniform1i(road_specular_location, 4); if (REFERENCE) { glDrawElements(GL_TRIANGLES, (PATCH_SIZE - 1) * (PATCH_SIZE - 1) * 6, GL_UNSIGNED_INT, 0); } else { glPrimitiveRestartIndex(PATCH_SIZE * 2); glDrawElements(GL_TRIANGLE_STRIP, (PATCH_SIZE - 1) * PATCH_SIZE * 2, GL_UNSIGNED_INT, 0); } //Render text nvgBeginFrame(mNVGContext, width(), height(), mPixelRatio); std::string text = "Patches visible: " + std::to_string(visiblePatches); nvgText(mNVGContext, 10, 20, text.c_str(), nullptr); nvgEndFrame(mNVGContext); }