From 16fba2241e16eef9bd9afa374f0fefc9d40b5f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A4=E9=BE=99?= <68932014+12gch138@users.noreply.github.com> Date: Mon, 19 Dec 2022 12:06:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=B0=E5=BD=A2=E5=9B=BE=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FinalProject/sceneviewer.cpp | 28 +++++----- FinalProject/terrain.cpp | 101 +++++++++++++++++++++-------------- FinalProject/terrain.h | 6 ++- terrain/grass.jpg | Bin 8097 -> 8006 bytes 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/FinalProject/sceneviewer.cpp b/FinalProject/sceneviewer.cpp index 7430ccd..035249f 100644 --- a/FinalProject/sceneviewer.cpp +++ b/FinalProject/sceneviewer.cpp @@ -117,27 +117,27 @@ void SceneViewer::paintGL() { ter->render(); terrainShader.unbind(); - //_shaderProgram.bind(); + _shaderProgram.bind(); - //// Set view and projection matrices - //_shaderProgram.setUniform("view", view); - //_shaderProgram.setUniform("projection", projection); + // Set view and projection matrices + _shaderProgram.setUniform("view", view); + _shaderProgram.setUniform("projection", projection); - //for (auto object : _objects) { - // object.render(_shaderProgram); - //} + for (auto object : _objects) { + object.render(_shaderProgram); + } - //_shaderProgram.unbind(); + _shaderProgram.unbind(); - //skyshader.bind(); - //view = glm::mat4(glm::mat3(view)); - //skyshader.setUniform("view", view); - //skyshader.setUniform("projection", projection); - //sky->render(); - //skyshader.unbind(); + skyshader.bind(); + view = glm::mat4(glm::mat3(view)); + skyshader.setUniform("view", view); + skyshader.setUniform("projection", projection); + sky->render(); + skyshader.unbind(); } diff --git a/FinalProject/terrain.cpp b/FinalProject/terrain.cpp index 1fe9dbb..fe88b72 100644 --- a/FinalProject/terrain.cpp +++ b/FinalProject/terrain.cpp @@ -8,16 +8,16 @@ Terrain::Terrain(std::string path){ - stbi_set_flip_vertically_on_load(false); + stbi_set_flip_vertically_on_load(true); - unsigned char* data = stbi_load((path + "/heightmap.png").c_str(), &width, &height, &nrChannels, 0); + unsigned char* data = stbi_load("D:/ProgrammingFile/LearnOpenGL/src/8.guest/2021/3.tessellation/terrain_gpu_dist/resources/heightmaps/iceland_heightmap.png", &width, &height, &nrChannels, 0); - - float yScale = 64.0f / 256.0f, yShift = 16.0f; + int rez = 1; unsigned bytePerPixel = nrChannels; for (int i = 0; i < height; i++) { + std::vector temp; for (int j = 0; j < width; j++) { unsigned char* pixelOffset = data + (j + width * i) * bytePerPixel; @@ -27,7 +27,11 @@ Terrain::Terrain(std::string path){ vertices.push_back(-height / 2.0f + height * i / (float)height); // vx vertices.push_back((int)y * yScale - yShift); // vy vertices.push_back(-width / 2.0f + width * j / (float)width); // vz + vertices.push_back((float)i); + vertices.push_back((float)j); + temp.push_back((int)y * yScale - yShift); } + Point.push_back(temp); } stbi_image_free(data); @@ -53,8 +57,11 @@ Terrain::Terrain(std::string path){ OPENGL_EXTRA_FUNCTIONS->glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), &vertices[0], GL_STATIC_DRAW); // position attribute - OPENGL_EXTRA_FUNCTIONS->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + OPENGL_EXTRA_FUNCTIONS->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); OPENGL_EXTRA_FUNCTIONS->glEnableVertexAttribArray(0); + // texCoord attribute + OPENGL_EXTRA_FUNCTIONS->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(sizeof(float) * 3)); + OPENGL_EXTRA_FUNCTIONS->glEnableVertexAttribArray(1); OPENGL_EXTRA_FUNCTIONS->glGenBuffers(1, &terrainIBO); OPENGL_EXTRA_FUNCTIONS->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, terrainIBO); @@ -62,7 +69,7 @@ Terrain::Terrain(std::string path){ //textureID = loadTexture2(texName, GL_REPEAT, GL_REPEAT, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR); - tex = loadTexture(path + "/white.jpg"); + tex = loadTexture(path + "/grass.jpg"); } @@ -127,48 +134,62 @@ void Terrain::render() { } -int Terrain::if_under_terrain(glm::vec3 point) { +float Terrain::GetHeight(float px, float pz) { + float fx = px + height / 2; + float fz = pz + width / 2; - int greater_x = ceil(point.x); - int greater_y = ceil(point.y); - int less_x = floor(point.x); - int less_y = floor(point.y); + int x = ((int)fx) % height; + int z = ((int)fz) % width; + int gx = (x + 1) % height; + int gz = (z + 1) % width; - float great_z = Point[greater_x][greater_y]; - float less_z = Point[less_x][less_y]; - - float z = (point.x - less_x) * (great_z - less_z) + less_z; - - if (fabs(z - point.z) < 1e-2) { - return 2; - } - if (z > point.z) { - return 1; - } - else - { - return -1; - } + float ans = (x - fx) * (Point[gx][gz] - Point[x][z]) + Point[x][z]; + return ans; } -Vertex Terrain::hitPoint(glm::vec3 orig, glm::vec3 dir) { +glm::vec3 Terrain::GetNormal(glm::vec3 pos) { + float fx = pos.x; + float fz = pos.z; - glm::vec3 step = glm::normalize(dir); + glm::vec3 point1(fx - 1, GetHeight(fx - 1, fz - 1), fz - 1); + glm::vec3 point2(fx + 1, GetHeight(fx + 1, fz + 1), fz + 1); - int flag = if_under_terrain(orig); + glm::vec3 l1 = pos - point1; + glm::vec3 l2 = point2 - point1; + glm::vec3 ans = glm::normalize(glm::cross(l1, l2)); + return ans; +} - glm::vec3 right = orig; - while (true) { - right += step; - int temp = if_under_terrain(right); - if (temp == 0) { - return Vertex(glm::vec3(0.0f)); - } - if (flag * temp == -1 || temp == 2) { - glm::vec4 ans = Model * glm::vec4(right,1.0f); - return Vertex(glm::vec3(ans)); - } +void Terrain::hitPoint(glm::vec3 orig, glm::vec3 dir) { + // A good ray step is half of the blockScale + glm::vec3 rayStep = dir * (float)width * 0.25f; + glm::vec3 rayStartPosition = orig; + + // Linear search - Loop until find a point inside and outside the terrain Vector3 + glm::vec3 lastRayPosition = orig; + orig += rayStep; + float map_height = GetHeight(orig.x,orig.z); + while (orig.y > map_height) + { + lastRayPosition = orig; + orig += rayStep; + map_height = GetHeight(orig.x, orig.z); } + glm::vec3 startPosition = lastRayPosition; + glm::vec3 endPosition = orig; + + // Binary search with 32 steps. Try to find the exact collision point + for (int i = 0; i < 32; i++) + { + // Binary search pass + glm::vec3 middlePoint = (startPosition + endPosition) * 0.5f; + if (middlePoint.y < height) + endPosition = middlePoint; + else + startPosition = middlePoint; + } + glm::vec3 position = (startPosition + endPosition) * 0.5f; + glm::vec3 normal = GetNormal(position); } \ No newline at end of file diff --git a/FinalProject/terrain.h b/FinalProject/terrain.h index 9ba5837..88e29f1 100644 --- a/FinalProject/terrain.h +++ b/FinalProject/terrain.h @@ -12,16 +12,18 @@ private: std::vector indices; unsigned int terrainVAO, terrainVBO, terrainIBO; std::vector> Point; - int if_under_terrain(glm::vec3 point); + float GetHeight(float px, float pz); + glm::vec3 GetNormal(glm::vec3 pos); int width, height, nrChannels; int numStrips, numTrisPerStrip; int NUM_PATCH_PTS = 4; unsigned rez = 20; + float yScale = 64.0f / 256.0f, yShift = 30.0f; public: unsigned int tex; Terrain(std::string path); void render(); unsigned int loadTexture(std::string path); - Vertex hitPoint(glm::vec3 orig, glm::vec3 dir); + void hitPoint(glm::vec3 orig, glm::vec3 dir); glm::mat4 Model = glm::mat4(1.0f); }; \ No newline at end of file diff --git a/terrain/grass.jpg b/terrain/grass.jpg index 70282817a29a61583a8eb5ecbc92c3d4792da8c6..2fe365273d8308b7f78ee0d6b9fe9df0f16511b4 100644 GIT binary patch delta 7811 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 Gm;N8Y<{)MO delta 7886 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