From fc6b24fb3d4ccdb01881c8bfd10f4c48830603d2 Mon Sep 17 00:00:00 2001 From: Matraf Date: Sun, 23 Jan 2022 19:19:09 +0100 Subject: [PATCH] no i chuj --- .../56a7f5f31b1538028f7329a2820dd5/main.cpp | 555 ------------------ grafika_projekt/grafika_projekt.vcxproj | 4 +- .../grafika_projekt.vcxproj.filters | 6 + grafika_projekt/shaders/shader_tex.frag | 1 - grafika_projekt/shaders/terrainShader.frag | 36 ++ grafika_projekt/shaders/terrainShader.vert | 28 + grafika_projekt/src/RawModel.cpp | 8 + grafika_projekt/src/RawModel.h | 2 + grafika_projekt/src/Terrain.cpp | 51 +- grafika_projekt/src/Terrain.h | 14 +- grafika_projekt/src/TerrainRenderer.cpp | 56 ++ grafika_projekt/src/TerrainRenderer.h | 36 ++ grafika_projekt/src/main.cpp | 23 +- grafika_projekt/textures/terrain.png | Bin 0 -> 31251 bytes 14 files changed, 247 insertions(+), 573 deletions(-) delete mode 100644 enc_temp_folder/56a7f5f31b1538028f7329a2820dd5/main.cpp create mode 100644 grafika_projekt/shaders/terrainShader.frag create mode 100644 grafika_projekt/shaders/terrainShader.vert create mode 100644 grafika_projekt/src/TerrainRenderer.cpp create mode 100644 grafika_projekt/src/TerrainRenderer.h create mode 100644 grafika_projekt/textures/terrain.png diff --git a/enc_temp_folder/56a7f5f31b1538028f7329a2820dd5/main.cpp b/enc_temp_folder/56a7f5f31b1538028f7329a2820dd5/main.cpp deleted file mode 100644 index f4cb614..0000000 --- a/enc_temp_folder/56a7f5f31b1538028f7329a2820dd5/main.cpp +++ /dev/null @@ -1,555 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#include "glew.h" -#include "freeglut.h" -#include "glm.hpp" -#include "ext.hpp" -#include -#include -#include -#include -#include "Shader_Loader.h" -#include "Render_Utils.h" -#include "Texture.h" -#include "Camera.h" -#include "SOIL/stb_image_aug.h" - -GLuint skyboxProgram, skyboxBuffer; -GLuint bubbleProgram; -GLuint programColor; -GLuint programTexture; - -GLuint textureSubmarine; -GLuint textureBubble; -GLuint textureFish; - -unsigned int cubemapTexture, skyboxVAO; -unsigned int cubeVAO, cubeVBO; - -float skyboxVerticeParameter = 50.0f; -float skyboxBoundary = 48.0f; - -std::vector bubbleArray[300]; -float old_x, old_y = -1; -glm::vec3 cursorDiff; -glm::vec3 lightDir = glm::normalize(glm::vec3(0.0f, skyboxVerticeParameter, 0.0f)); -glm::vec3 cameraPos = glm::vec3(0, 0, 0); -glm::vec3 oldCameraPos = glm::vec3(0, 0, 5); - -glm::vec3 cameraDir; // Wektor "do przodu" kamery -glm::vec3 cameraSide; // Wektor "w bok" kamery -float cameraAngle = 0; - -glm::quat rotation = glm::quat(1, 0, 0, 0); - -glm::mat4 cameraMatrix, perspectiveMatrix; - -Core::Shader_Loader shaderLoader; -Core::RenderContext submarineContext; -Core::RenderContext fishContext; -Core::RenderContext bubbleContext; - -std::vector fishKeyPoints({ -glm::vec3(-18.0f, -10.0f, -10.0f), -glm::vec3(-10.0f, -5.0f, -12.0f), -glm::vec3(8.0f, -3.0f, -3.0f), -glm::vec3(5.0f, 0.0f, 3.0f), -glm::vec3(3.0f, 2.0f, 4.0f), -glm::vec3(8.0f, 5.0f, 9.0f), -glm::vec3(14.0f, 6.0f, 15.0f), -glm::vec3(15.0f, 12.0f, 12.0f), -glm::vec3(10.0f, 17.0f, 15.0f), -glm::vec3(5.0f, 10.0f, 7.0f), -glm::vec3(-1.0f, 4.0f, 8.0f), -glm::vec3(-8.0f, 0.0f, 3.0f), -glm::vec3(-12.0f, -6.0f, -3.0f), -glm::vec3(-15.0f, -8.0f, -6.0f), -glm::vec3(-18.0f, -10.0f, -10.0f), - }); - -std::vector keyRotation; - -std::vector fish; - -std::string skyboxTextures[6] = { - "models/skybox/right.jpg", - "models/skybox/left.jpg", - "models/skybox/top.jpg", - "models/skybox/bottom.jpg", - "models/skybox/front.jpg", - "models/skybox/back.jpg" -}; - - - -float cubeVertices[] = { - // positions // normals - -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, - - -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, - - -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, - -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, - - 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, - 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, - - -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, - 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, - 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, - 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, - -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, - -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, - - -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, - 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, - -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, - -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f -}; - -float skyboxVertices[] = { - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, skyboxVerticeParameter, - -skyboxVerticeParameter, skyboxVerticeParameter, -skyboxVerticeParameter, - - -skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, -skyboxVerticeParameter, - -skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter, - skyboxVerticeParameter, -skyboxVerticeParameter, skyboxVerticeParameter -}; - - -bool isInBoundaries(glm::vec3 nextPosition) { - return nextPosition.z > -skyboxBoundary && nextPosition.z < skyboxBoundary&& nextPosition.y > -skyboxBoundary && - nextPosition.y < skyboxBoundary&& nextPosition.x < skyboxBoundary&& nextPosition.x > -skyboxBoundary; -} - - -std::random_device rd; // obtain a random number from hardware -std::mt19937 gen(rd()); // seed the generator -std::uniform_int_distribution<> distr(-skyboxVerticeParameter, skyboxVerticeParameter); // define the range - -std::vector genBubbleKeyPoints() { - float random1 = distr(gen); - float random2 = distr(gen); - std::vector bubbleKeyPoints({ - glm::vec3(random1 , -skyboxVerticeParameter, random2), - glm::vec3(random1 , skyboxVerticeParameter, random2) - } - ); - return bubbleKeyPoints; -}; - -void generateBubbleArray() { - - for (int i = 0; i < 300; i++) { - bubbleArray[i] = genBubbleKeyPoints(); - } -} - -void keyboard(unsigned char key, int x, int y) -{ - float angleSpeed = 10.f; - float moveSpeed = 1.0f; - glm::vec3 nextPosition; - switch (key) - { - case 'z': cursorDiff.z -= angleSpeed; break; - case 'x': cursorDiff.z += angleSpeed; break; - case 'w': - nextPosition = cameraPos + (cameraDir * moveSpeed); - if (isInBoundaries(nextPosition)) { - cameraPos = nextPosition; - } - break; - case 's': - nextPosition = cameraPos - (cameraDir * moveSpeed); - if (isInBoundaries(nextPosition)) { - cameraPos = nextPosition; - } - break; - case 'd': - nextPosition = cameraPos + (cameraSide * moveSpeed); - if (isInBoundaries(nextPosition)) { - cameraPos = nextPosition; - } - break; - case 'a': - nextPosition = cameraPos - (cameraSide * moveSpeed); - if (isInBoundaries(nextPosition)) { - cameraPos = nextPosition; - } - break; - } -} - -void mouse(int x, int y) -{ - if (old_x >= 0) { - cursorDiff.x = x - old_x; - cursorDiff.y = y - old_y; - } - old_x = x; - old_y = y; - - if (x < 100 || x > 800 - 100) { //you can use values other than 100 for the screen edges if you like, kind of seems to depend on your mouse sensitivity for what ends up working best - old_x = 800 / 2; //centers the last known position, this way there isn't an odd jump with your cam as it resets - old_y = 800 / 2; - glutWarpPointer(800 / 2, 800 / 2); //centers the cursor - } - else if (y < 100 || y > 800 - 100) { - old_x = 800 / 2; - old_y = 800 / 2; - glutWarpPointer(800 / 2, 800 / 2); - } -} - -glm::mat4 createCameraMatrix() -{ - glm::quat rotation_x = glm::angleAxis(cursorDiff.y * 0.03f, glm::vec3(1, 0, 0)); - cursorDiff.y = 0; - glm::quat rotation_y = glm::angleAxis(cursorDiff.x * 0.03f, glm::vec3(0, 1, 0)); - cursorDiff.x = 0; - glm::quat rotation_z = glm::angleAxis(cursorDiff.z * 0.03f, glm::vec3(0, 0, 1)); - cursorDiff.z = 0; - - glm::quat rotationChange = rotation_x * rotation_y * rotation_z; - rotation = glm::normalize(rotationChange * rotation); - - cameraDir = glm::inverse(rotation) * glm::vec3(0, 0, -1); - cameraSide = glm::inverse(rotation) * glm::vec3(1, 0, 0); - - return Core::createViewMatrixQuat(cameraPos, rotation); -} - -std::vector changeKeyPoints(std::vector keyPoints, glm::vec3 toChange) { - std::vector result; - int size = keyPoints.size(); - glm::vec3 change; - - for (int i = 0; i < size; i++) { - change.x = keyPoints[i].x + toChange.x; - change.y = keyPoints[i].y + toChange.y; - change.z = keyPoints[i].z + toChange.z; - result.push_back(change); - } - - return result; -} - -glm::mat4 animationMatrix(float time, glm::vec3 change, std::vector keyPoints, glm::vec3 scaleValue, float speed) { - - time = time * speed; - std::vector distances; - std::vector newKeyPoints = changeKeyPoints(keyPoints, change); - float timeStep = 0; - for (int i = 0; i < keyPoints.size() - 1; i++) { - timeStep += (keyPoints[i] - keyPoints[i + 1]).length(); - distances.push_back((keyPoints[i] - keyPoints[i + 1]).length()); - } - time = fmod(time, timeStep); - - //index of first keyPoint - int index = 0; - - while (distances[index] <= time) { - time = time - distances[index]; - index += 1; - } - - float t = time / distances[index]; - - int size = keyPoints.size(); - int rotationSize = keyRotation.size(); - - glm::vec3 pos = glm::catmullRom(newKeyPoints[std::max(0, (index - 1) % size)], newKeyPoints[(index) % size], newKeyPoints[(index + 1) % size], newKeyPoints[(index + 2) % size], t); - - glm::quat divideByFour = glm::quat(0.25f, 0.25f, 0.25f, 0.25f); - auto a1 = keyRotation[index % rotationSize] * glm::exp(-(glm::log(glm::inverse(keyRotation[index % rotationSize]) * keyRotation[std::max(0, (index - 1) % rotationSize)]) + glm::log(glm::inverse(keyRotation[index % rotationSize]) * keyRotation[(index + 1) % rotationSize])) * divideByFour); - - auto a2 = keyRotation[(index + 1) % rotationSize] * glm::exp(-(glm::log(glm::inverse(keyRotation[(index + 1) % rotationSize]) * keyRotation[index % rotationSize]) + glm::log(glm::inverse(keyRotation[(index + 1) % rotationSize]) * keyRotation[(index + 2) % rotationSize])) * divideByFour); - - auto animationRotation = glm::squad(keyRotation[index % rotationSize], keyRotation[(index + 1) % rotationSize], a1, a2, t); - - glm::mat4 result = glm::translate(pos) * glm::scale(glm::vec3(scaleValue)) * glm::mat4_cast(animationRotation); - - return result; -} - -void drawObjectTexture(Core::RenderContext context, glm::mat4 modelMatrix, GLuint textureId, GLuint program) -{ - glUseProgram(program); - - glUniform3f(glGetUniformLocation(program, "lightDir"), lightDir.x, lightDir.y, lightDir.z); - Core::SetActiveTexture(textureId, "textureSampler", program, 0); - - glm::mat4 transformation = perspectiveMatrix * cameraMatrix * modelMatrix; - glUniformMatrix4fv(glGetUniformLocation(program, "modelViewProjectionMatrix"), 1, GL_FALSE, (float*)&transformation); - glUniformMatrix4fv(glGetUniformLocation(program, "modelMatrix"), 1, GL_FALSE, (float*)&modelMatrix); - - Core::DrawContext(context); - - glUseProgram(0); -} - - -void renderScene() -{ - cameraMatrix = createCameraMatrix(); - perspectiveMatrix = Core::createPerspectiveMatrix(); - - glClearColor(0.219f, 0.407f, 0.658f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - float time = glutGet(GLUT_ELAPSED_TIME) / 1000.f; - glUseProgram(skyboxProgram); - glUniform1i(glGetUniformLocation(skyboxProgram, "skybox"), 0); - glm::mat4 transformation = perspectiveMatrix * cameraMatrix; - glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projectionViewMatrix"), 1, GL_FALSE, (float*)&transformation); - glBindVertexArray(skyboxVAO); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); - glDrawArrays(GL_TRIANGLES, 0, 36); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - glm::mat4 submarineInitialTransformation = glm::translate(glm::vec3(0, -0.5, -0.4)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.25f)); - glm::mat4 submarineModelMatrix = glm::translate(cameraPos + cameraDir) * glm::mat4_cast(glm::inverse(rotation)) * submarineInitialTransformation; - - glm::mat4 bubbleInitialTransformation = glm::translate(glm::vec3(0, -0.5, -0.4)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.5f)); - - glm::vec3 change1 = glm::vec3(0, 3, 0); - glm::vec3 change2 = glm::vec3(0, 0, 0); - glm::vec3 change3 = glm::vec3(3, 0, 0); - glm::vec3 change4 = glm::vec3(0, 2, 1); - - glm::vec3 change0 = glm::vec3(0, 0, 0); - - for (int j = 0; j < 100; j++) { - drawObjectTexture(bubbleContext, animationMatrix(time + j, change0, bubbleArray[j], glm::vec3(0.04f), 0.2f), cubemapTexture, bubbleProgram); - } - - for (int i = 0; i < 5; i++) { - if (time > -10) { - drawObjectTexture(fishContext, animationMatrix(time + 15, change1, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); - drawObjectTexture(fishContext, animationMatrix(time + 15, change2, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); - drawObjectTexture(fishContext, animationMatrix(time + 15, change3, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); - drawObjectTexture(fishContext, animationMatrix(time + 15, change4, fishKeyPoints, glm::vec3(0.25f), 1.f), textureFish, programTexture); - - time -= 6; - } - } - //drawObjectTexture(bubbleContext, submarineInitialTransformation, cubemapTexture, bubbleProgram); - drawObjectTexture(submarineContext, submarineModelMatrix, textureSubmarine, programTexture); - glutSwapBuffers(); -} - -void loadModelToContext(std::string path, Core::RenderContext& context) -{ - Assimp::Importer import; - const aiScene* scene = import.ReadFile(path, aiProcess_Triangulate | aiProcess_CalcTangentSpace); - - if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) - { - std::cout << "ERROR::ASSIMP::" << import.GetErrorString() << std::endl; - return; - } - context.initFromAssimpMesh(scene->mMeshes[0]); -} - -unsigned int loadCubemap() -{ - unsigned int textureID; - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); - - int width, height, nrChannels; - for (unsigned int i = 0; i < 6; i++) - { - unsigned char* data = stbi_load(skyboxTextures[i].c_str(), &width, &height, &nrChannels, STBI_rgb_alpha); - if (data) - { - glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, - 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data - ); - stbi_image_free(data); - } - else - { - std::cout << stbi_failure_reason() << std::endl; - std::cout << "Cubemap tex failed to load at path: " << skyboxTextures[i] << std::endl; - stbi_image_free(data); - } - } - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - - return textureID; -} - -void initSkybox() -{ - glGenVertexArrays(1, &skyboxVAO); - glBindVertexArray(skyboxVAO); - - glGenBuffers(1, &skyboxBuffer); - glBindBuffer(GL_ARRAY_BUFFER, skyboxBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW); - - GLuint vPosition = glGetAttribLocation(skyboxProgram, "aPos"); - glEnableVertexAttribArray(vPosition); - - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(skyboxVertices), skyboxVertices); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); -} - -void initKeyRotation() { - glm::vec3 oldDirection = glm::vec3(0, 0, 1); - glm::quat oldRotationCamera = glm::quat(1, 0, 0, 0); - glm::vec3 direction; - glm::quat rotation; - for (int i = 0; i < fishKeyPoints.size() - 1; i++) { - //3.1 - direction = glm::normalize(fishKeyPoints[i + 1] - fishKeyPoints[i]); - //3.2 - rotation = glm::normalize(glm::rotationCamera(oldDirection, direction) * oldRotationCamera); - //3.3 - keyRotation.push_back(rotation); - //3.4 - oldDirection = direction; - oldRotationCamera = rotation; - } - keyRotation.push_back(glm::quat(1, 0, 0, 0)); -} - -void initCube() -{ - 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, 6 * sizeof(float), (void*)0); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float))); -} - -void init() -{ - glEnable(GL_DEPTH_TEST); - programColor = shaderLoader.CreateProgram((char*)"shaders/shader_color.vert", (char*)"shaders/shader_color.frag"); - programTexture = shaderLoader.CreateProgram((char*)"shaders/shader_tex.vert", (char*)"shaders/shader_tex.frag"); - skyboxProgram = shaderLoader.CreateProgram((char*)"shaders/skybox.vert", (char*)"shaders/skybox.frag"); - bubbleProgram = shaderLoader.CreateProgram((char*)"shaders/bubble.vert", (char*)"shaders/bubble.frag"); - cubemapTexture = loadCubemap(); - - loadModelToContext("models/submarine.obj", submarineContext); - textureSubmarine = Core::LoadTexture("textures/submarine.png"); - - loadModelToContext("models/fish.obj", fishContext); - textureFish = Core::LoadTexture("textures/fish.png"); - - initKeyRotation(); - loadModelToContext("models/submarine.obj", submarineContext); - textureSubmarine = Core::LoadTexture("textures/submarine.png"); - loadModelToContext("models/sphere.obj", bubbleContext); - textureBubble = Core::LoadTexture("textures/bubble.png"); - generateBubbleArray(); - initCube(); - initSkybox(); - -} - -void shutdown() -{ - shaderLoader.DeleteProgram(programColor); - shaderLoader.DeleteProgram(programTexture); - shaderLoader.DeleteProgram(skyboxProgram); - shaderLoader.DeleteProgram(bubbleProgram); - -} - -void idle() -{ - glutPostRedisplay(); -} - - -int main(int argc, char** argv) -{ - glutInit(&argc, argv); - glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA); - glutInitWindowPosition(200, 200); - glutInitWindowSize(800, 800); - glutCreateWindow("Water and shit"); - glewInit(); - - init(); - glutKeyboardFunc(keyboard); - glutPassiveMotionFunc(mouse); - glutDisplayFunc(renderScene); - glutIdleFunc(idle); - - glutSetCursor(GLUT_CURSOR_NONE); - glutMainLoop(); - shutdown(); - return 0; -} \ No newline at end of file diff --git a/grafika_projekt/grafika_projekt.vcxproj b/grafika_projekt/grafika_projekt.vcxproj index 30a7fe1..05618f6 100644 --- a/grafika_projekt/grafika_projekt.vcxproj +++ b/grafika_projekt/grafika_projekt.vcxproj @@ -29,7 +29,7 @@ Application true - v142 + v143 Unicode @@ -154,6 +154,7 @@ + @@ -170,6 +171,7 @@ + diff --git a/grafika_projekt/grafika_projekt.vcxproj.filters b/grafika_projekt/grafika_projekt.vcxproj.filters index 5993483..36eed1b 100644 --- a/grafika_projekt/grafika_projekt.vcxproj.filters +++ b/grafika_projekt/grafika_projekt.vcxproj.filters @@ -51,6 +51,9 @@ Source Files + + Source Files + @@ -95,6 +98,9 @@ Header Files + + Header Files + diff --git a/grafika_projekt/shaders/shader_tex.frag b/grafika_projekt/shaders/shader_tex.frag index b817324..a105e41 100644 --- a/grafika_projekt/shaders/shader_tex.frag +++ b/grafika_projekt/shaders/shader_tex.frag @@ -2,7 +2,6 @@ uniform sampler2D textureSampler; uniform vec3 lightDir; -uniform vec3 lightPos; uniform vec3 cameraPos; uniform vec3 objectColor; diff --git a/grafika_projekt/shaders/terrainShader.frag b/grafika_projekt/shaders/terrainShader.frag new file mode 100644 index 0000000..4a15458 --- /dev/null +++ b/grafika_projekt/shaders/terrainShader.frag @@ -0,0 +1,36 @@ +#version 400 core + +in vec2 pass_textureCoordinates; +in vec3 surfaceNormal; +in vec3 toLightVector; +in vec3 toCameraVector; + +out vec4 out_Color; + +uniform sampler2D modelTexture; + +uniform float shineDamper; +uniform float reflectivity; + +void main(void){ + + vec3 unitNormal = normalize(surfaceNormal); + vec3 unitLightVector = normalize(toLightVector); + + float nDotl = dot(unitNormal,unitLightVector); + float brightness = max(nDotl,0.2); + vec3 diffuse = brightness * vec3(1.0, 1.0, 1.0); //Tu była zmiana + + vec3 unitVectorToCamera = normalize(toCameraVector); + vec3 lightDirection = -unitLightVector; + vec3 reflectedLightDirection = reflect(lightDirection,unitNormal); + + float specularFactor = dot(reflectedLightDirection , unitVectorToCamera); + specularFactor = max(specularFactor,0.0); + float dampedFactor = pow(specularFactor,shineDamper); + vec3 finalSpecular = dampedFactor * reflectivity * vec3(1.0, 1.0, 1.0); //Tu była zmiana + + + out_Color = vec4(diffuse,1.0) * texture(modelTexture,pass_textureCoordinates) + vec4(finalSpecular,1.0); + +} \ No newline at end of file diff --git a/grafika_projekt/shaders/terrainShader.vert b/grafika_projekt/shaders/terrainShader.vert new file mode 100644 index 0000000..d806974 --- /dev/null +++ b/grafika_projekt/shaders/terrainShader.vert @@ -0,0 +1,28 @@ +#version 400 core + +in vec3 position; +in vec2 textureCoordinates; +in vec3 normal; + +out vec2 pass_textureCoordinates; +out vec3 surfaceNormal; +out vec3 toLightVector; +out vec3 toCameraVector; + +uniform mat4 transformationMatrix; +uniform mat4 projectionMatrix; +uniform mat4 viewMatrix; +uniform vec3 lightDir; + +void main(void){ + + vec4 worldPosition = transformationMatrix * vec4(position,1.0); + gl_Position = projectionMatrix * viewMatrix * worldPosition; + pass_textureCoordinates = textureCoordinates * 40.0; + + surfaceNormal = (transformationMatrix * vec4(normal,0.0)).xyz; + toLightVector = lightDir - worldPosition.xyz; + toCameraVector = (inverse(viewMatrix) * vec4(0.0,0.0,0.0,1.0)).xyz - worldPosition.xyz; + + +} \ No newline at end of file diff --git a/grafika_projekt/src/RawModel.cpp b/grafika_projekt/src/RawModel.cpp index bc421d2..5eefdba 100644 --- a/grafika_projekt/src/RawModel.cpp +++ b/grafika_projekt/src/RawModel.cpp @@ -3,4 +3,12 @@ RawModel::RawModel(int vaoID, int vertexCount) { this->vaoID = vaoID; this->vertexCount = vertexCount; +} + +int RawModel::getVertexCount() { + return vertexCount; +} + +int RawModel::getVaoID() { + return vaoID; } \ No newline at end of file diff --git a/grafika_projekt/src/RawModel.h b/grafika_projekt/src/RawModel.h index 1134387..8a13b0d 100644 --- a/grafika_projekt/src/RawModel.h +++ b/grafika_projekt/src/RawModel.h @@ -4,6 +4,8 @@ class RawModel public: RawModel() = default; RawModel(int vaoID, int vertexCount); + int getVertexCount(); + int getVaoID(); private: int vaoID; int vertexCount; diff --git a/grafika_projekt/src/Terrain.cpp b/grafika_projekt/src/Terrain.cpp index bb42d32..35e7389 100644 --- a/grafika_projekt/src/Terrain.cpp +++ b/grafika_projekt/src/Terrain.cpp @@ -1,18 +1,19 @@ #include "Terrain.h" #include "glew.h" #include "freeglut.h" +#include "glm.hpp" - -const float Terrain::SIZE = 800.f; +const float Terrain::SIZE = 400.f; const int Terrain::VERTEX_COUNT = 128; const int Terrain::COUNT = Terrain::VERTEX_COUNT * Terrain::VERTEX_COUNT; -Terrain::Terrain(int gridX, int gridZ, GLuint textureID) { +Terrain::Terrain(int gridX, int gridZ, GLuint textureID, HeightGenerator heightGenerator) { x = gridX * SIZE; z = gridZ * SIZE; texture = textureID; model = generateTerrain(); + this->heightGenerator = heightGenerator; } RawModel Terrain::generateTerrain() { @@ -26,17 +27,18 @@ RawModel Terrain::generateTerrain() { vertices[vertexPointer * 3] = float(j) / float(VERTEX_COUNT - 1) * SIZE; vertices[vertexPointer * 3 + 1] = 0; vertices[vertexPointer * 3 + 2] = float(i) / float(VERTEX_COUNT - 1) * SIZE; - normals[vertexPointer * 3] = 0; - normals[vertexPointer * 3 + 1] = 1; - normals[vertexPointer * 3 + 2] = 0; - textureCoords[vertexPointer * 2] = float(j) / float(VERTEX_COUNT - 1) * SIZE; - textureCoords[vertexPointer * 2 + 1] = float(i) / float(VERTEX_COUNT - 1) * SIZE; + glm::vec3 normal = calculateNormal(j, i); + normals[vertexPointer * 3] = normal.x; + normals[vertexPointer * 3 + 1] = normal.y; + normals[vertexPointer * 3 + 2] = normal.z; + textureCoords[vertexPointer * 2] = float(j) / float(VERTEX_COUNT - 1);// *SIZE; + textureCoords[vertexPointer * 2 + 1] = float(i) / float(VERTEX_COUNT - 1);// *SIZE; vertexPointer++; } } int pointer = 0; - for (int gz = 0; gz < VERTEX_COUNT; gz++) { - for (int gx = 0; gx < VERTEX_COUNT; gx++) { + for (int gz = 0; gz < VERTEX_COUNT - 1; gz++) { + for (int gx = 0; gx < VERTEX_COUNT - 1; gx++) { int topLeft = gz * VERTEX_COUNT + gx; int topRight = topLeft + 1; int bottomLeft = (gz + 1) * VERTEX_COUNT + gx; @@ -71,3 +73,32 @@ void Terrain::storeDataInAttributeList(int attributeNumber, int coordinateSize, glVertexAttribPointer(attributeNumber, coordinateSize, GL_FLOAT, false, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, 0); } + +RawModel Terrain::getModel() { + return model; +} + +GLuint Terrain::getTexture() { + return texture; +} + +glm::vec3 Terrain::calculateNormal(int x, int z) { + float heightL = getHeight(x - 1, z); + float heightR = getHeight(x + 1, z); + float heightD = getHeight(x, z - 1); + float heightU = getHeight(x, z + 1); + glm::vec3 normal = glm::vec3(heightL - heightR, 2.f, heightD - heightU); + return glm::normalize(normal); +} + +float Terrain::getHeight(int x, int z) { + return heightGenerator.generateHeight(x, z); +} + +int Terrain::getX() { + return x; +} + +int Terrain::getZ() { + return z; +} \ No newline at end of file diff --git a/grafika_projekt/src/Terrain.h b/grafika_projekt/src/Terrain.h index 0eaa922..ad30090 100644 --- a/grafika_projekt/src/Terrain.h +++ b/grafika_projekt/src/Terrain.h @@ -1,19 +1,31 @@ #pragma once #include "RawModel.h" +#include "glew.h" +#include "freeglut.h" +#include "glm.hpp" +#include "HeightGenerator.h" class Terrain { public: Terrain() = default; - Terrain(int gridX, int gridZ, GLuint textureID); + Terrain(int gridX, int gridZ, GLuint textureID, HeightGenerator heightGenerator); + RawModel getModel(); + GLuint getTexture(); + int getX(); + int getZ(); + private: static const float SIZE; static const int VERTEX_COUNT; static const int COUNT; float x; float z; + HeightGenerator heightGenerator; GLuint texture; RawModel model; RawModel generateTerrain(); void storeDataInAttributeList(int attributeNumber, int coordinateSize, float data[]); + glm::vec3 calculateNormal(int x, int z); + float getHeight(int x, int z); }; diff --git a/grafika_projekt/src/TerrainRenderer.cpp b/grafika_projekt/src/TerrainRenderer.cpp new file mode 100644 index 0000000..6c1f51e --- /dev/null +++ b/grafika_projekt/src/TerrainRenderer.cpp @@ -0,0 +1,56 @@ +#include "TerrainRenderer.h" +#include "glew.h" +#include "freeglut.h" +#include "glm.hpp" + +TerrainRenderer::TerrainRenderer(GLuint terrainProgram, + glm::mat4 projectionMatrix, + Terrain terrain, + glm::mat4 transformationMatrix, + glm::mat4 viewMatrix, + glm::vec3 lightDir, +// glm::vec3 lightColour, + float shineDamper, + float reflectivity) { + + this->terrainProgram = terrainProgram; + this->projectionMatrix = projectionMatrix; + this->terrain = terrain; + this->transformationMatrix = transformationMatrix; + this->viewMatrix = viewMatrix; + this->lightDir = lightDir; +// this->lightColour = lightColour; + this->shineDamper = shineDamper; + this->reflectivity = reflectivity; +} + +void TerrainRenderer::render() { + initShader(); + prepareTerrain(); + glDrawElements(GL_TRIANGLES, terrain.getModel().getVertexCount(), GL_UNSIGNED_INT, 0); +} + +void TerrainRenderer::initShader() { + glUseProgram(terrainProgram); + glBindAttribLocation(terrainProgram, 0, "position"); + glBindAttribLocation(terrainProgram, 1, "textureCoordinates"); + glBindAttribLocation(terrainProgram, 2, "normal"); + glUniformMatrix4fv(glGetUniformLocation(terrainProgram, "transformationMatrix"), 1, GL_FALSE, (float*) &transformationMatrix); + glUniformMatrix4fv(glGetUniformLocation(terrainProgram, "projectionMatrix"), 2, GL_FALSE, (float*) &projectionMatrix); + glUniformMatrix4fv(glGetUniformLocation(terrainProgram, "viewMatrix"), 3, GL_FALSE, (float*) &viewMatrix); + glUniform3f(glGetUniformLocation(terrainProgram, "lightDir"), lightDir.x, lightDir.y, lightDir.z); +// glUniform3f(glGetUniformLocation(terrainProgram, "lightColour"), lightColour.x, lightColour.y, lightColour.z); + glUniform1f(glGetUniformLocation(terrainProgram, "shineDamper"), shineDamper); + glUniform1f(glGetUniformLocation(terrainProgram, "reflectivity"), reflectivity); + glUseProgram(0); +} + +void TerrainRenderer::prepareTerrain() { + RawModel rawModel = terrain.getModel(); + glBindVertexArray(rawModel.getVaoID()); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, terrain.getTexture()); +} \ No newline at end of file diff --git a/grafika_projekt/src/TerrainRenderer.h b/grafika_projekt/src/TerrainRenderer.h new file mode 100644 index 0000000..e2b03f3 --- /dev/null +++ b/grafika_projekt/src/TerrainRenderer.h @@ -0,0 +1,36 @@ +#pragma once +#include "Terrain.h" +#include "glm.hpp" +#include "glew.h" + +class TerrainRenderer +{ +public: + TerrainRenderer() = default; + TerrainRenderer(GLuint terrainProgram, + glm::mat4 projectionMatrix, + Terrain terrain, + glm::mat4 transformationMatrix, + glm::mat4 viewMatrix, + glm::vec3 lightDir, +// glm::vec3 lightColour, + float shineDamper, + float reflectivity); + + void render(); + +private: + void prepareTerrain(); + void initShader(); + + GLuint terrainProgram; + glm::mat4 projectionMatrix; + Terrain terrain; + glm::mat4 transformationMatrix; + glm::mat4 viewMatrix; + glm::vec3 lightDir; +// glm::vec3 lightColour; + float shineDamper; + float reflectivity; +}; + diff --git a/grafika_projekt/src/main.cpp b/grafika_projekt/src/main.cpp index 72842f9..352ed14 100644 --- a/grafika_projekt/src/main.cpp +++ b/grafika_projekt/src/main.cpp @@ -14,11 +14,14 @@ #include "SOIL/stb_image_aug.h" #include "HeightGenerator.h" #include "Terrain.h" +#include "TerrainRenderer.h" GLuint skyboxProgram, skyboxBuffer; GLuint bubbleProgram; GLuint programTexture; +GLuint terrainProgram; + GLuint textureSubmarine; GLuint textureBubble; GLuint textureFish; @@ -27,7 +30,7 @@ unsigned int cubemapTexture, skyboxVAO; unsigned int cubeVAO, cubeVBO; float skyboxVerticeParameter = 50.0f; -float skyboxBoundary = 48.0f; +float skyboxBoundary = 480.0f; std::vector bubbleArray[300]; float old_x, old_y = -1; @@ -49,6 +52,9 @@ Core::RenderContext submarineContext; Core::RenderContext fishContext; Core::RenderContext bubbleContext; +GLuint textureID; +HeightGenerator heightGenerator; + std::vector fishKeyPoints({ glm::vec3(-18.0f, -10.0f, -10.0f), glm::vec3(-10.0f, -5.0f, -12.0f), @@ -345,7 +351,7 @@ void drawObjectTexture(Core::RenderContext context, glm::mat4 modelMatrix, GLuin void renderScene() -{ +{ cameraMatrix = createCameraMatrix(); perspectiveMatrix = Core::createPerspectiveMatrix(); @@ -359,12 +365,14 @@ void renderScene() glUniformMatrix4fv(glGetUniformLocation(skyboxProgram, "projectionViewMatrix"), 1, GL_FALSE, (float*)&transformation); glBindVertexArray(skyboxVAO); glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); glDrawArrays(GL_TRIANGLES, 0, 36); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glm::mat4 submarineInitialTransformation = glm::translate(glm::vec3(0, -0.5, -0.4)) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)) * glm::scale(glm::vec3(0.25f)); glm::mat4 submarineModelMatrix = glm::translate(cameraPos + cameraDir) * glm::mat4_cast(glm::inverse(rotation)) * submarineInitialTransformation; @@ -497,6 +505,8 @@ void init() programTexture = shaderLoader.CreateProgram((char*)"shaders/shader_tex.vert", (char*)"shaders/shader_tex.frag"); skyboxProgram = shaderLoader.CreateProgram((char*)"shaders/skybox.vert", (char*)"shaders/skybox.frag"); bubbleProgram = shaderLoader.CreateProgram((char*)"shaders/bubble.vert", (char*)"shaders/bubble.frag"); + terrainProgram = shaderLoader.CreateProgram((char*)"shaders/terrainShader.vert", (char*)"shaders/terrainShader.frag"); + cubemapTexture = loadCubemap(); loadModelToContext("models/submarine.obj", submarineContext); @@ -514,9 +524,12 @@ void init() initCube(); initSkybox(); - //GLuint textureID = Core::LoadTexture('textura_terrain'); - //Terrain terrain = new Terrain(0, 0, textureID); - + textureID = Core::LoadTexture("textures/terrain.png"); + + Terrain terrain(0, 0, textureID, heightGenerator); + glm::mat4 terrainTransformation = glm::translate(glm::vec3(terrain.getX(), 0, terrain.getZ())) * glm::rotate(glm::radians(180.0f), glm::vec3(0, 1, 0)); + TerrainRenderer terrainRenderer(terrainProgram, perspectiveMatrix, terrain, terrainTransformation, -cameraMatrix, lightDir, 1.f, 0.f); + terrainRenderer.render(); } void shutdown() diff --git a/grafika_projekt/textures/terrain.png b/grafika_projekt/textures/terrain.png new file mode 100644 index 0000000000000000000000000000000000000000..db2f30d230d38d694f231df71dc460c2301147f6 GIT binary patch literal 31251 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 LNkvXXu0mjf4ba|H literal 0 HcmV?d00001