Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
322AdvancedGraphics/TerrainGeneration.cpp
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1150 lines (953 sloc)
32.9 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <fstream> | |
#include <map> | |
#include <chrono> | |
#include <random> | |
#include <array> | |
#include <glm/glm.hpp> | |
#include <glm/gtc/quaternion.hpp> | |
#include <glm/gtc/matrix_transform.hpp> | |
#include <glm/gtc/type_ptr.hpp> | |
#include <glm/gtc/matrix_inverse.hpp> | |
#include <glm/gtx/rotate_vector.hpp> | |
#include <GL/glew.h> | |
#include <GL/freeglut.h> | |
#include "getBMP.h" | |
#include "Shaders.h" | |
//#include <GL/glext.h> | |
#pragma comment(lib, "glew32.lib") | |
using namespace std; | |
using namespace glm; | |
// Size of the terrain | |
const int MAP_SIZE = 257; // must be power of 2 plus one | |
const float RANDOMNESS = 50.f; | |
const int SCREEN_WIDTH = 1280; | |
const int SCREEN_HEIGHT = 720; | |
struct RGB | |
{ | |
uint8 r, g, b; | |
}; | |
struct RGBA | |
{ | |
uint8 r, g, b, a; | |
}; | |
struct Image | |
{ | |
int sizeX; | |
int sizeY; | |
RGB* data; | |
}; | |
struct ImageRGBA | |
{ | |
int sizeX; | |
int sizeY; | |
RGBA* data; | |
}; | |
struct Vertex | |
{ | |
glm::vec4 coords; | |
glm::vec3 normal; | |
glm::vec2 texCoords; | |
}; | |
struct Matrix4x4 | |
{ | |
float entries[16]; | |
}; | |
static mat4 projMat = mat4(1.0); | |
static const Matrix4x4 IDENTITY_MATRIX4x4 = | |
{ | |
{ | |
1.0, 0.0, 0.0, 0.0, | |
0.0, 1.0, 0.0, 0.0, | |
0.0, 0.0, 1.0, 0.0, | |
0.0, 0.0, 0.0, 1.0 | |
} | |
}; | |
struct Light | |
{ | |
vec4 ambCols; | |
vec4 difCols; | |
vec4 specCols; | |
vec4 direction; | |
}; | |
struct Material | |
{ | |
vec4 ambRefl; | |
vec4 difRefl; | |
vec4 specRefl; | |
vec4 emitCols; | |
float shininess; | |
}; | |
static Light light0 = | |
{ | |
vec4(0.4, 0.4, 0.4, 1.0), // ambient | |
vec4(1.0, 1.0, 1.0, 1.0), // specular | |
vec4(1.0, 1.0, 1.0, 1.0), // diffuse | |
normalize(vec4(0.5, 0.5, 0.0, 0.0)) // direction | |
}; | |
static vector<Material> materials = | |
{ | |
// standard material | |
{ | |
vec4(1.0, 1.0, 1.0, 1.0), // amb | |
vec4(1.0, 1.0, 1.0, 1.0), // diff | |
vec4(0.0, 0.0, 0.0, 1.0), // spec | |
vec4(0.0, 0.0, 0.0, 1.0), // emit | |
1.f | |
}, | |
// shiny material | |
{ | |
vec4(1.0, 1.0, 1.0, 1.0), | |
vec4(1.0, 1.0, 1.0, 1.0), | |
vec4(1.0, 1.0, 1.0, 1.0), | |
vec4(0.0, 0.0, 0.0, 1.0), | |
30.0f | |
}, | |
// emissive material | |
{ | |
vec4(0.0, 0.0, 0.0, 1.0), | |
vec4(0.0, 0.0, 0.0, 1.0), | |
vec4(0.0, 0.0, 0.0, 1.0), | |
vec4(1.0, 1.0, 1.0, 1.0), | |
1.0f | |
} | |
}; | |
enum buffer { TERRAIN_VERTICES, CLOUD_VERTICES, TREE_VERTICES, LEAF_VERTICES }; | |
enum object { TERRAIN, CLOUD, TREE, LEAF }; | |
// Globals | |
static Vertex terrainVertices[MAP_SIZE * MAP_SIZE] = {}; | |
const int numStripsRequired = MAP_SIZE - 1; | |
const int verticesPerStrip = 2 * MAP_SIZE; | |
unsigned int terrainIndexData[numStripsRequired][verticesPerStrip]; | |
unsigned int cloudIndexData[] = { 0, 1, 2, 3 }; | |
vector<vector<int>> treeIndexData; | |
unsigned int leafIndexData[] = { 0, 1, 2,3}; | |
vector<vec3> treePositions; | |
struct BranchEnd | |
{ | |
vec3 pos; | |
vec3 direction; | |
vec3 perpendicular; | |
}; | |
vector<BranchEnd> branchEnds; | |
struct Leaf | |
{ | |
vec3 pos; | |
vec3 initialOrientation; | |
float rotation; | |
vec3 perpendicular; | |
}; | |
vector<Leaf> leaves; | |
static unsigned int | |
shaderProgramId, | |
vertexShaderId, | |
fragmentShaderId, | |
modelMatLoc, | |
viewMatLoc, | |
projMatLoc, | |
buffer[4], | |
vao[4]; | |
unsigned int skyboxVertexShaderId, skyboxFragmentShaderId, skyboxShaderProgramId; | |
unsigned int skyboxVAO, skyboxVBO, cubemapTexture; | |
GLuint groundTextureID, cloudTextureID, treeTextureID; | |
vec3 cameraPos(2.5f, 2.5f, 10.f); | |
glm::quat cameraOrientation; | |
std::map <char, bool> keys; | |
std::map <int, bool> specialKeys; | |
std::mt19937 randomNumberEngine; | |
// Function to read text file, used to read shader files | |
static char* readTextFile(char* aTextFile) | |
{ | |
FILE* filePointer = fopen(aTextFile, "rb"); | |
char* content = NULL; | |
long numVal = 0; | |
fseek(filePointer, 0L, SEEK_END); | |
numVal = ftell( filePointer); | |
fseek(filePointer, 0L, SEEK_SET); | |
content = (char*)malloc((numVal + 1) * sizeof(char)); | |
fread(content, 1, numVal, filePointer); | |
content[numVal] = '\0'; | |
fclose(filePointer); | |
return content; | |
} | |
void squareStep(vector<vector<float>>& heightmap, int size, int x, int y, int distance, float randMax) | |
{ | |
if (x < 0 || x >= size || y < 0 || y >= size) | |
return; | |
int count = 0; | |
float total = 0.f; | |
if (y >= 0 && y < size) | |
{ | |
if (x - distance >= 0) | |
{ | |
++count; | |
total += heightmap[x - distance][y]; | |
} | |
if (x + distance < size) | |
{ | |
++count; | |
total += heightmap[x + distance][y]; | |
} | |
} | |
if (x >= 0 && x < size) | |
{ | |
if (y - distance >= 0) | |
{ | |
++count; | |
total += heightmap[x][y - distance]; | |
} | |
if (y + distance < size) | |
{ | |
++count; | |
total += heightmap[x][y + distance]; | |
} | |
} | |
assert(x >= 0 && x < size && y >= 0 && y < size); | |
std::uniform_real_distribution<float> randomDistribution(-randMax, randMax); | |
heightmap[x][y] = (total / count) + randomDistribution(randomNumberEngine); | |
} | |
void diamondSquare(vector<vector<float>>& heightmap, int size, float randMax) | |
{ | |
int stepSize = size - 1; | |
while (stepSize > 1) | |
{ | |
std::uniform_real_distribution<float> randomDistribution(-randMax, randMax); | |
int halfStepSize = stepSize / 2; | |
// Diamond step - set centre point to average of square corners plus random - creating diamonds | |
for (int z = 0; z < size - 1; z += stepSize) | |
{ | |
for (int x = 0; x < size - 1; x += stepSize) | |
{ | |
float total = heightmap[x][z] | |
+ heightmap[x + stepSize][z] | |
+ heightmap[x][z + stepSize] | |
+ heightmap[x + stepSize][z + stepSize]; | |
float average = total / 4.f; | |
heightmap[x + halfStepSize][z + halfStepSize] = average + randomDistribution(randomNumberEngine); | |
} | |
} | |
// Square step - set midpoints of square edges, creating squares | |
for (int z = 0; z < size - 1; z += stepSize) | |
{ | |
for (int x = 0; x < size - 1; x += stepSize) | |
{ | |
int centreX = x + halfStepSize, centreY = z + halfStepSize; | |
squareStep(heightmap, size, centreX - halfStepSize, centreY, halfStepSize, randMax); | |
squareStep(heightmap, size, centreX + halfStepSize, centreY, halfStepSize, randMax); | |
squareStep(heightmap, size, centreX, centreY - halfStepSize, halfStepSize, randMax); | |
squareStep(heightmap, size, centreX, centreY + halfStepSize, halfStepSize, randMax); | |
} | |
} | |
stepSize /= 2; | |
randMax /= 2.f; | |
} | |
} | |
void setupTerrain() | |
{ | |
// Initialise terrain - set values in the height map to 0 | |
vector<vector<float>> terrain; | |
for (int x = 0; x < MAP_SIZE; x++) | |
{ | |
terrain.push_back(vector<float>(MAP_SIZE)); | |
for (int z = 0; z < MAP_SIZE; z++) | |
{ | |
terrain[x][z] = 0; | |
} | |
} | |
// Set corners to random heights | |
std::uniform_real_distribution<float> randomDistribution(-RANDOMNESS, RANDOMNESS); | |
terrain[0][0] = randomDistribution(randomNumberEngine); | |
terrain[MAP_SIZE-1][0] = randomDistribution(randomNumberEngine); | |
terrain[0][MAP_SIZE - 1] = randomDistribution(randomNumberEngine); | |
terrain[MAP_SIZE - 1][MAP_SIZE - 1] = randomDistribution(randomNumberEngine); | |
// Run diamond square algorithm for terrain | |
diamondSquare(terrain, MAP_SIZE, RANDOMNESS); | |
cameraPos = glm::vec3(MAP_SIZE / 2, 10.f, MAP_SIZE); | |
// Intialise vertex array | |
int i = 0; | |
for (int z = 0; z < MAP_SIZE; z++) | |
{ | |
for (int x = 0; x < MAP_SIZE; x++) | |
{ | |
int texCoordX = (i + z) % 2; | |
// Set the coords (1st 4 elements) | |
terrainVertices[i] = { { (float)x, terrain[x][z], (float)z, 1.0 }, | |
{ 0, 1, 0 }, // normal | |
{ texCoordX, z % 2 } }; // texCoords | |
i++; | |
} | |
} | |
// Now build the index data | |
i = 0; | |
for (int z = 0; z < MAP_SIZE - 1; z++) | |
{ | |
i = z * MAP_SIZE; | |
// Points for lower part of triangle strip - even numbered vertices | |
for (int x = 0; x < MAP_SIZE * 2; x += 2) | |
{ | |
terrainIndexData[z][x] = i; | |
i++; | |
} | |
// Points for upper part of triangle strip | |
for (int x = 1; x < MAP_SIZE * 2 + 1; x += 2) | |
{ | |
terrainIndexData[z][x] = i; | |
i++; | |
} | |
} | |
// Calculate the normals for each quad of the heightmap, where each quad consists of two | |
// triangles.You need to calculate the normal with a counter clockwise ordering of the triangles.The | |
// normal can be calculated by taking the cross product between two edges of your triangle.These can | |
// be stored in a temporary data structure as this is an intermediate step to enable you to calculate the | |
// vertex normal in the next step. | |
vector<vector<vec3>> quadNormals; | |
for (int z = 0; z < MAP_SIZE - 1; z++) | |
{ | |
quadNormals.push_back(vector<vec3>(MAP_SIZE - 1)); | |
for (int x = 0; x < MAP_SIZE - 1; x++) | |
{ | |
vec3 p1 = vec3(terrainVertices[z * MAP_SIZE + x].coords); | |
vec3 p2 = vec3(terrainVertices[(z+1) * MAP_SIZE + x].coords); | |
vec3 p3 = vec3(terrainVertices[z * MAP_SIZE + x + 1].coords); | |
/* Cross product | |
Set Vector U to (Triangle.p2 minus Triangle.p1) | |
Set Vector V to (Triangle.p3 minus Triangle.p1) | |
Set Normal.x to (multiply U.y by V.z) minus (multiply U.z by V.y) | |
Set Normal.y to (multiply U.z by V.x) minus (multiply U.x by V.z) | |
Set Normal.z to (multiply U.x by V.y) minus (multiply U.y by V.x) | |
*/ | |
vec3 u = p2 - p1; | |
vec3 v = p3 - p1; | |
quadNormals[z][x] = glm::normalize(vec3{ | |
u.y * v.z - u.z * v.y, | |
u.z * v.x - u.x * v.z, | |
u.x * v.y - u.y * v.x }); | |
} | |
} | |
// Calculate the normals for each vertex in the heightmap by summing the surrounding triangle | |
// normals and then normalising the final vector. | |
i = 0; | |
for (int z = 0; z < MAP_SIZE; z++) | |
{ | |
for (int x = 0; x < MAP_SIZE; x++) | |
{ | |
vec3 normal(0, 0, 0); | |
// top left | |
if (x > 0 && z > 0) | |
normal += quadNormals[z - 1][x - 1]; | |
// below left | |
if (x > 0 && z < MAP_SIZE - 1) | |
normal += quadNormals[z][x - 1]; | |
// top right | |
if (x < MAP_SIZE - 1 && z > 0) | |
normal += quadNormals[z - 1][x]; | |
// below right | |
if (x < MAP_SIZE - 1 && z < MAP_SIZE - 1) | |
normal += quadNormals[z][x]; | |
terrainVertices[i].normal = normalize(normal); | |
i++; | |
} | |
} | |
// Create an image of random green pixels | |
const int textureSize = 32; | |
std::uniform_int_distribution<int> grassGreenColourDistribution(0, 255); | |
Image texture{ textureSize,textureSize, new RGB[textureSize * textureSize] }; | |
for (int y = 0; y < textureSize; y++) | |
{ | |
for (int x = 0; x < textureSize; x++) | |
{ | |
texture.data[x + y * textureSize].r = 0; | |
texture.data[x + y * textureSize].g = grassGreenColourDistribution(randomNumberEngine); | |
texture.data[x + y * textureSize].b = 0; | |
} | |
} | |
// Create vertex array object (VAO) and vertex buffer object (VBO) and associate data with vertex shader. | |
glGenVertexArrays(1, vao); | |
glGenBuffers(1, buffer); | |
glBindVertexArray(vao[TERRAIN]); | |
glBindBuffer(GL_ARRAY_BUFFER, buffer[TERRAIN_VERTICES]); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(terrainVertices), terrainVertices, GL_STATIC_DRAW); | |
// vertices | |
// index, size, type, normalized, stride, pointer | |
// size: number of components per generic vertex attribute | |
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(terrainVertices[0]), 0); | |
glEnableVertexAttribArray(0); | |
// normals | |
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, sizeof(terrainVertices[0]), (void*)(offsetof(Vertex, normal))); | |
glEnableVertexAttribArray(1); | |
// texCoords | |
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(terrainVertices[0]), (void*)(offsetof(Vertex, texCoords))); | |
glEnableVertexAttribArray(2); | |
glGenTextures(1, &groundTextureID); | |
//// Bind image. | |
glActiveTexture(GL_TEXTURE0); | |
glBindTexture(GL_TEXTURE_2D, groundTextureID); | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, texture.sizeX, texture.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, texture.data); | |
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); | |
glGenerateMipmap(GL_TEXTURE_2D); | |
GLenum error; | |
while ((error = glGetError()) != GL_NO_ERROR) | |
std::cout << error << endl; | |
} | |
void setupClouds() | |
{ | |
vector<vector<float>> cloudTextureData; | |
const int textureSize = 512; | |
for (int y = 0; y < textureSize + 1; y++) | |
{ | |
cloudTextureData.push_back(vector<float>(textureSize + 1)); | |
} | |
diamondSquare(cloudTextureData, textureSize + 1, 500.f); | |
ImageRGBA cloudTexture{ textureSize,textureSize, new RGBA[textureSize * textureSize] }; | |
for (int y = 0; y < textureSize; y++) | |
{ | |
for (int x = 0; x < textureSize; x++) | |
{ | |
int i = x + y * textureSize; | |
int col = std::min<int>(255, std::max<int>(0, (int)cloudTextureData[y][x])); | |
cloudTexture.data[i].r = cloudTexture.data[i].g = cloudTexture.data[i].b = 255; | |
cloudTexture.data[i].a = (uint8)col; | |
} | |
} | |
const int cloudRange = 5000; | |
const int cloudHeight = 500; | |
Vertex cloudVertices[] = { | |
{ vec4(-cloudRange, cloudHeight, cloudRange, 1), vec3(0,-1,0), vec2(0,1) }, | |
{ vec4(-cloudRange, cloudHeight, -cloudRange, 1), vec3(0,-1,0), vec2(0,0) }, | |
{ vec4(cloudRange, cloudHeight, cloudRange, 1), vec3(0,-1,0), vec2(1,1) }, | |
{ vec4(cloudRange, cloudHeight, -cloudRange, 1), vec3(0,-1,0), vec2(0,1) } | |
}; | |
// Create vertex array object (VAO) and vertex buffer object (VBO) and associate data with vertex shader. | |
glGenVertexArrays(1, &vao[CLOUD]); | |
glGenBuffers(1, &buffer[CLOUD]); | |
glBindVertexArray(vao[CLOUD]); | |
glBindBuffer(GL_ARRAY_BUFFER, buffer[CLOUD_VERTICES]); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(cloudVertices), cloudVertices, GL_STATIC_DRAW); | |
// vertices | |
// index, size, type, normalized, stride, pointer | |
// size: number of components per generic vertex attribute | |
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(cloudVertices[0]), 0); | |
glEnableVertexAttribArray(0); | |
// normals | |
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, sizeof(cloudVertices[0]), (void*)(offsetof(Vertex, normal))); | |
glEnableVertexAttribArray(1); | |
// texCoords | |
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(cloudVertices[0]), (void*)(offsetof(Vertex, texCoords))); | |
glEnableVertexAttribArray(2); | |
glGenTextures(1, &cloudTextureID); | |
// Bind image. | |
glActiveTexture(GL_TEXTURE0); | |
glBindTexture(GL_TEXTURE_2D, cloudTextureID); | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, cloudTexture.sizeX, cloudTexture.sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, cloudTexture.data); | |
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); | |
glGenerateMipmap(GL_TEXTURE_2D); | |
GLenum error; | |
while ((error = glGetError()) != GL_NO_ERROR) | |
std::cout << error << endl; | |
} | |
void createBranch(vector<Vertex>& vertices, vec3 treeBase, vec3 start, vec3 branchDirection, float length, float width) | |
{ | |
// Get perpendicular vector from branch direction by doing cross product | |
// with a different vector | |
vec3 perpendicular = glm::cross(branchDirection, branchDirection + vec3(1, 0, 0)); | |
// Get a vector at right angles to the perpendicular vector and the direction vector | |
vec3 right = glm::cross(branchDirection, perpendicular); | |
vec3 p = perpendicular * width; | |
vec3 r = right * width; | |
vec3 d = branchDirection * length; | |
int firstIndex = vertices.size(); | |
// Make vertices | |
vertices.insert(vertices.end(), { | |
// front | |
{ vec4(start + p + r, 1), vec3(0,0,1), vec2(0,0) }, | |
{ vec4(start + p + r + d, 1), vec3(0,0,1), vec2(1,1) }, | |
{ vec4(start + p - r, 1), vec3(0,0,1), vec2(0,1) }, | |
{ vec4(start + p - r + d, 1), vec3(0,0,1), vec2(1,0) }, | |
// back | |
{ vec4(start - p + r, 1), vec3(0,0,1), vec2(0,0) }, | |
{ vec4(start - p - r, 1), vec3(0,0,1), vec2(0,1) }, | |
{ vec4(start - p + r + d, 1), vec3(0,0,1), vec2(1,1) }, | |
{ vec4(start - p - r + d, 1), vec3(0,0,1), vec2(1,0) }, | |
// left | |
{ vec4(start + r + p, 1), vec3(0,0,1), vec2(0,0) }, | |
{ vec4(start + r - p, 1), vec3(0,0,1), vec2(0,1) }, | |
{ vec4(start + r + p + d, 1), vec3(0,0,1), vec2(1,1) }, | |
{ vec4(start + r - p + d, 1), vec3(0,0,1), vec2(1,0) }, | |
// right | |
{ vec4(start - r + p, 1), vec3(0,0,1), vec2(0,0) }, | |
{ vec4(start - r + p + d, 1), vec3(0,0,1), vec2(1,0) }, | |
{ vec4(start - r - p, 1), vec3(0,0,1), vec2(0,1) }, | |
{ vec4(start - r - p + d, 1), vec3(0,0,1), vec2(1,1) }, | |
}); | |
for (int i = 0; i < 4; ++i) | |
treeIndexData.push_back({ firstIndex + i * 4, firstIndex + i * 4 + 1, firstIndex + i * 4 + 2, firstIndex + i * 4 + 3 }); | |
// create branches at end of current branch | |
if (length > 2.5f) | |
{ | |
std::uniform_real_distribution<float> minusOneOneDistribution(-1.f, 1.f); | |
std::uniform_int_distribution<int> numBranchesDistribution(4, 6); | |
for (int i = 0; i < numBranchesDistribution(randomNumberEngine); ++i) | |
{ | |
vec3 direction = normalize(branchDirection + vec3(minusOneOneDistribution(randomNumberEngine), minusOneOneDistribution(randomNumberEngine), minusOneOneDistribution(randomNumberEngine)) * 1.5f); | |
// make sure branch doesn't go too close to ground | |
float branchLength = length / 1.5f; | |
if ((start + direction * branchLength).y < treeBase.y) | |
{ | |
direction.y += 2.f; | |
direction = normalize(direction); | |
} | |
createBranch(vertices, treeBase, start + d, direction, branchLength, width / 1.75f); | |
} | |
} | |
else | |
{ | |
branchEnds.push_back(BranchEnd{ start + d, branchDirection, glm::cross(branchDirection,vec3(1,0,0)) }); | |
} | |
} | |
void setupTrees() | |
{ | |
const float trunkH = 15.f; | |
const float trunkW = 0.5f; | |
const float halfTrunkW = trunkW / 2.f; | |
vec3 start{ 0,0,0 }; | |
vector<Vertex> vertices; | |
vec3 branchDirection{ 0,1,0 }; | |
branchDirection = glm::normalize(branchDirection); | |
createBranch(vertices, start, start, branchDirection, trunkH, trunkW); | |
// Set normals | |
for (int i = 0; i < vertices.size(); i += 4) { | |
vec3 normal = cross(vec3(vertices[i].coords), vec3(vertices[i + 1].coords)); | |
vertices[i].normal = normal; | |
vertices[i+1].normal = normal; | |
vertices[i+2].normal = normal; | |
vertices[i+3].normal = normal; | |
} | |
// Create an image of random brown pixels | |
const int textureSize = 256; | |
std::uniform_int_distribution<int> zero255Distribution(0, 255); | |
Image texture{ textureSize,textureSize, new RGB[textureSize * textureSize] }; | |
for (int y = 0; y < textureSize; y++) | |
{ | |
for (int x = 0; x < textureSize; x++) | |
{ | |
int value = zero255Distribution(randomNumberEngine); | |
texture.data[x + y * textureSize].r = value / 2; | |
texture.data[x + y * textureSize].g = value / 3; | |
texture.data[x + y * textureSize].b = value / 5; | |
} | |
} | |
// Create vertex array object (VAO) and vertex buffer object (VBO) and associate data with vertex shader. | |
glGenVertexArrays(1, &vao[TREE]); | |
glGenBuffers(1, &buffer[TREE]); | |
glBindVertexArray(vao[TREE]); | |
glBindBuffer(GL_ARRAY_BUFFER, buffer[TREE_VERTICES]); | |
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertices[0]), &vertices[0], GL_STATIC_DRAW); | |
// vertices | |
// index, size, type, normalized, stride, pointer | |
// size: number of components per generic vertex attribute | |
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), 0); | |
glEnableVertexAttribArray(0); | |
// normals | |
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, sizeof(vertices[0]), (void*)(offsetof(Vertex, normal))); | |
glEnableVertexAttribArray(1); | |
// texCoords | |
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), (void*)(offsetof(Vertex, texCoords))); | |
glEnableVertexAttribArray(2); | |
glGenTextures(1, &treeTextureID); | |
// Bind image. | |
glActiveTexture(GL_TEXTURE0); | |
glBindTexture(GL_TEXTURE_2D, treeTextureID); | |
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, texture.sizeX, texture.sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, texture.data); | |
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); | |
glGenerateMipmap(GL_TEXTURE_2D); | |
GLenum error; | |
while ((error = glGetError()) != GL_NO_ERROR) | |
std::cout << error << endl; | |
for (int i = 0; i < 3; ++i) | |
{ | |
std::uniform_int_distribution<int> terrainVertexDistribution(0, (MAP_SIZE * MAP_SIZE) - 1); | |
Vertex v = terrainVertices[terrainVertexDistribution(randomNumberEngine)]; | |
treePositions.push_back(vec3(v.coords)); | |
for (BranchEnd& end : branchEnds) | |
{ | |
leaves.push_back({ vec3(v.coords) + end.pos , end.direction, 0,end.perpendicular }); | |
} | |
} | |
} | |
void setupLeaf() | |
{ | |
const float size = 0.8f; | |
Vertex leafVertices[] = { | |
{ vec4(0,0,0, 1), vec3(0,-1,0), vec2(0,1) }, | |
{ vec4(size,0,0, 1), vec3(0,-1,0), vec2(0,0) }, | |
{ vec4(size/2.f,size/2.f,0,1 ), vec3(0,-1,0), vec2(1,1) }, | |
{ vec4(size / 2.f,-size / 2.f,0,1), vec3(0,-1,0), vec2(1,0) }, | |
}; | |
// Create vertex array object (VAO) and vertex buffer object (VBO) and associate data with vertex shader. | |
glGenVertexArrays(1, &vao[LEAF]); | |
glGenBuffers(1, &buffer[LEAF]); | |
glBindVertexArray(vao[LEAF]); | |
glBindBuffer(GL_ARRAY_BUFFER, buffer[LEAF_VERTICES]); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(leafVertices), leafVertices, GL_STATIC_DRAW); | |
// vertices | |
// index, size, type, normalized, stride, pointer | |
// size: number of components per generic vertex attribute | |
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(leafVertices[0]), 0); | |
glEnableVertexAttribArray(0); | |
// normals | |
glVertexAttribPointer(1, 3, GL_FLOAT, GL_TRUE, sizeof(leafVertices[0]), (void*)(offsetof(Vertex, normal))); | |
glEnableVertexAttribArray(1); | |
// texCoords | |
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(leafVertices[0]), (void*)(offsetof(Vertex, texCoords))); | |
glEnableVertexAttribArray(2); | |
GLenum error; | |
while ((error = glGetError()) != GL_NO_ERROR) | |
std::cout << error << endl; | |
} | |
//skybox | |
float skyboxVertices[] = { | |
// positions | |
-1.0f, 1.0f, -1.0f, | |
-1.0f, -1.0f, -1.0f, | |
1.0f, -1.0f, -1.0f, | |
1.0f, -1.0f, -1.0f, | |
1.0f, 1.0f, -1.0f, | |
-1.0f, 1.0f, -1.0f, | |
-1.0f, -1.0f, 1.0f, | |
-1.0f, -1.0f, -1.0f, | |
-1.0f, 1.0f, -1.0f, | |
-1.0f, 1.0f, -1.0f, | |
-1.0f, 1.0f, 1.0f, | |
-1.0f, -1.0f, 1.0f, | |
1.0f, -1.0f, -1.0f, | |
1.0f, -1.0f, 1.0f, | |
1.0f, 1.0f, 1.0f, | |
1.0f, 1.0f, 1.0f, | |
1.0f, 1.0f, -1.0f, | |
1.0f, -1.0f, -1.0f, | |
-1.0f, -1.0f, 1.0f, | |
-1.0f, 1.0f, 1.0f, | |
1.0f, 1.0f, 1.0f, | |
1.0f, 1.0f, 1.0f, | |
1.0f, -1.0f, 1.0f, | |
-1.0f, -1.0f, 1.0f, | |
-1.0f, 1.0f, -1.0f, | |
1.0f, 1.0f, -1.0f, | |
1.0f, 1.0f, 1.0f, | |
1.0f, 1.0f, 1.0f, | |
-1.0f, 1.0f, 1.0f, | |
-1.0f, 1.0f, -1.0f, | |
-1.0f, -1.0f, -1.0f, | |
-1.0f, -1.0f, 1.0f, | |
1.0f, -1.0f, -1.0f, | |
1.0f, -1.0f, -1.0f, | |
-1.0f, -1.0f, 1.0f, | |
1.0f, -1.0f, 1.0f | |
}; | |
unsigned int loadCubemap(vector<std::string> faces) | |
{ | |
unsigned int textureID; | |
glGenTextures(1, &textureID); | |
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); | |
for (unsigned int i = 0; i < faces.size(); i++) | |
{ | |
//unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0); | |
BitMapFile* bmp = getbmp(faces[i]); | |
if (bmp) | |
{ | |
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, bmp->sizeX, bmp->sizeY, 0, GL_RGBA, GL_UNSIGNED_BYTE, bmp->data); | |
//stbi_image_free(data); | |
} | |
else | |
{ | |
std::cout << "Cubemap texture failed to load at path: " << faces[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 setupSkybox(void) | |
{ | |
// Setup skybox shaders | |
skyboxVertexShaderId = setShader("vertex", "skyboxVertexShader.glsl"); | |
skyboxFragmentShaderId = setShader("fragment", "skyboxFragmentShader.glsl"); | |
skyboxShaderProgramId = glCreateProgram(); | |
glAttachShader(skyboxShaderProgramId, skyboxVertexShaderId); | |
glAttachShader(skyboxShaderProgramId, skyboxFragmentShaderId); | |
glLinkProgram(skyboxShaderProgramId); | |
// skybox VAO | |
glGenVertexArrays(1, &skyboxVAO); | |
glGenBuffers(1, &skyboxVBO); | |
glBindVertexArray(skyboxVAO); | |
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW); | |
glEnableVertexAttribArray(0); | |
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); | |
vector<std::string> faces | |
{ | |
"./textures/right.bmp", | |
"./textures/left.bmp", | |
"./textures/bottom.bmp", | |
"./textures/top.bmp", | |
"./textures/front.bmp", | |
"./textures/back.bmp" | |
}; | |
cubemapTexture = loadCubemap(faces); | |
unsigned int skyboxMatLoc = glGetUniformLocation(skyboxShaderProgramId, "skybox"); | |
glUniform1i(skyboxMatLoc, 0); | |
// end of skybox | |
} | |
void checkShader(int shaderId) | |
{ | |
int result; | |
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result); | |
if (result == GL_FALSE) | |
{ | |
cout << "Shader compile error" << endl; | |
GLint maxLength = 0; | |
glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &maxLength); | |
// The maxLength includes the NULL character | |
std::vector<GLchar> errorLog(maxLength); | |
glGetShaderInfoLog(shaderId, maxLength, &maxLength, &errorLog[0]); | |
cout << errorLog.data() << endl; | |
cin.get(); | |
} | |
} | |
void setMaterial(Material material) { | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "material.ambRefl"), 1, &material.ambRefl[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "material.difRefl"), 1, &material.difRefl[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "material.specRefl"), 1, &material.specRefl[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "material.emitCols"), 1, &material.emitCols[0]); | |
glUniform1f(glGetUniformLocation(shaderProgramId, "material.shininess"), material.shininess); | |
} | |
// Initialization routine. | |
void setup(void) | |
{ | |
setupTerrain(); | |
setupClouds(); | |
setupTrees(); | |
setupSkybox(); | |
setupLeaf(); | |
glClearColor(0.4, 1.0, 1.0, 0.0); | |
// Create shader program executable - read, compile and link shaders | |
char* vertexShader = readTextFile("vertexShader.glsl"); | |
vertexShaderId = glCreateShader(GL_VERTEX_SHADER); | |
glShaderSource(vertexShaderId, 1, (const char**)&vertexShader, NULL); | |
glCompileShader(vertexShaderId); | |
checkShader(vertexShaderId); | |
char* fragmentShader = readTextFile("fragmentShader.glsl"); | |
fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER); | |
glShaderSource(fragmentShaderId, 1, (const char**)&fragmentShader, NULL); | |
glCompileShader(fragmentShaderId); | |
checkShader(fragmentShaderId); | |
shaderProgramId = glCreateProgram(); | |
glAttachShader(shaderProgramId, vertexShaderId); | |
glAttachShader(shaderProgramId, fragmentShaderId); | |
glLinkProgram(shaderProgramId); | |
glUseProgram(shaderProgramId); | |
/////////////////////////////////////// | |
// Obtain projection matrix uniform location and set value. | |
projMatLoc = glGetUniformLocation(shaderProgramId, "projMat"); | |
projMat = perspective(radians(60.0), (double)SCREEN_WIDTH / (double)SCREEN_HEIGHT, 0.1, 10000.0); | |
glUniformMatrix4fv(projMatLoc, 1, GL_FALSE, value_ptr(projMat)); | |
// Obtain model + view matrix uniform location | |
modelMatLoc = glGetUniformLocation(shaderProgramId, "modelMat"); | |
viewMatLoc = glGetUniformLocation(shaderProgramId, "viewMat"); | |
glUniformMatrix4fv(modelMatLoc, 1, GL_FALSE, value_ptr(mat4())); | |
/////////////////////////////////////// | |
// Setup light | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "light0.ambCols"), 1, &light0.ambCols[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "light0.difCols"), 1, &light0.difCols[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "light0.specCols"), 1, &light0.specCols[0]); | |
glUniform4fv(glGetUniformLocation(shaderProgramId, "light0.direction"), 1, &light0.direction[0]); | |
/////////////////////////////////////// | |
} | |
// Drawing routine. | |
void drawScene(void) | |
{ | |
//light0.direction = glm::rotate(light0.direction, 0.1f, glm::vec3(0, 1, 0)); | |
//light0.direction = glm::rotateX(light0.direction, 0.001f); | |
//glUniform4fv(glGetUniformLocation(programId, "light0.direction"), 1, &light0.direction[0]); | |
glUniform3fv(glGetUniformLocation(shaderProgramId, "viewPos"), 1, &cameraPos[0]); | |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |
mat4 modelMat = glm::mat4(1.0f); | |
glUniformMatrix4fv(modelMatLoc, 1, GL_FALSE, value_ptr(modelMat)); | |
//Draw terrain | |
setMaterial(materials[0]); | |
glBindTexture(GL_TEXTURE_2D, groundTextureID); | |
glBindVertexArray(vao[TERRAIN]); | |
// For each row - draw the triangle strip | |
for (int i = 0; i < MAP_SIZE - 1; i++) | |
{ | |
glDrawElements(GL_TRIANGLE_STRIP, verticesPerStrip, GL_UNSIGNED_INT, terrainIndexData[i]); | |
} | |
// Skybox | |
{ | |
glUseProgram(skyboxShaderProgramId); | |
glDepthFunc(GL_LEQUAL); // change depth function so depth test passes when values are equal to depth buffer's content | |
glm::vec3 forward = cameraOrientation * glm::vec3(0, 0, -1); | |
glm::vec3 up = cameraOrientation * glm::vec3(0, 1, 0); | |
mat4 viewMat = glm::lookAt(vec3(0, 0, 0), forward, up); | |
viewMat = glm::rotate(viewMat, 3.1415927f, vec3(1, 0, 0)); | |
glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgramId, "view"), 1, GL_FALSE, value_ptr(viewMat)); | |
glUniformMatrix4fv(glGetUniformLocation(skyboxShaderProgramId, "projection"), 1, GL_FALSE, value_ptr(projMat)); | |
glBindVertexArray(skyboxVAO); | |
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); | |
glDrawArrays(GL_TRIANGLES, 0, 36); | |
glBindVertexArray(0); | |
glDepthFunc(GL_LESS); // set depth function back to default | |
glUseProgram(shaderProgramId); // set shader program back to default | |
} | |
// Draw clouds | |
setMaterial(materials[2]); | |
glBindTexture(GL_TEXTURE_2D, cloudTextureID); | |
glBindVertexArray(vao[CLOUD]); | |
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, cloudIndexData); | |
// Draw trees | |
setMaterial(materials[0]); | |
glBindTexture(GL_TEXTURE_2D, treeTextureID); | |
glBindVertexArray(vao[TREE]); | |
for(vec3 treePos : treePositions) | |
{ | |
modelMat = glm::mat4(1.0f); | |
modelMat = glm::translate(modelMat, treePos); | |
//modelMat *= glm::mat4_cast(orientation); | |
glUniformMatrix4fv(modelMatLoc, 1, GL_FALSE, value_ptr(modelMat)); | |
// For each face - draw the triangle strip | |
for (int i = 0; i < treeIndexData.size(); i++) | |
{ | |
glDrawElements(GL_TRIANGLE_STRIP, treeIndexData[i].size(), GL_UNSIGNED_INT, treeIndexData[i].data()); | |
} | |
} | |
// Draw leaves | |
setMaterial(materials[0]); | |
glBindTexture(GL_TEXTURE_2D, groundTextureID); | |
glBindVertexArray(vao[LEAF]); | |
static float r = 0.f; | |
r += 0.01f; | |
for (Leaf& leaf : leaves) | |
{ | |
modelMat = glm::mat4(1.0f); | |
modelMat = glm::translate(modelMat, leaf.pos); | |
quat orientation = quat(leaf.initialOrientation); | |
orientation = glm::rotate(orientation, sinf(r),leaf.perpendicular); | |
modelMat *= glm::mat4_cast(orientation); | |
glUniformMatrix4fv(modelMatLoc, 1, GL_FALSE, value_ptr(modelMat)); | |
glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_INT, leafIndexData); | |
} | |
glFlush(); | |
GLenum error; | |
while ((error = glGetError()) != GL_NO_ERROR) | |
std::cout << error << endl; | |
} | |
// OpenGL window reshape routine. | |
void resize(int w, int h) | |
{ | |
glViewport(0, 0, w, h); | |
} | |
// Keyboard input processing routine. | |
void keyInput(unsigned char key, int x, int y) | |
{ | |
switch (key) | |
{ | |
case 27: | |
exit(0); | |
break; | |
default: | |
break; | |
} | |
} | |
void update() { | |
static auto time = std::chrono::high_resolution_clock::now(); | |
auto newTime = std::chrono::high_resolution_clock::now(); | |
float secondsPassed = std::chrono::duration_cast<std::chrono::duration<float>>(newTime - time).count(); | |
time = newTime; | |
float turningSpeed = 1.f * secondsPassed; | |
if (specialKeys[GLUT_KEY_LEFT]) | |
cameraOrientation = glm::rotate(cameraOrientation, turningSpeed, glm::vec3(0, 1, 0)); | |
if (specialKeys[GLUT_KEY_RIGHT]) | |
cameraOrientation = glm::rotate(cameraOrientation, -turningSpeed, glm::vec3(0, 1, 0)); | |
if (keys['q']) | |
cameraOrientation = glm::rotate(cameraOrientation, turningSpeed, glm::vec3(1, 0, 0)); | |
if (keys['a']) | |
cameraOrientation = glm::rotate(cameraOrientation, -turningSpeed, glm::vec3(1, 0, 0)); | |
if (keys['z']) | |
cameraOrientation = glm::rotate(cameraOrientation, turningSpeed, glm::vec3(0, 0, 1)); | |
if (keys['x']) | |
cameraOrientation = glm::rotate(cameraOrientation, -turningSpeed, glm::vec3(0, 0, 1)); | |
glm::vec3 forward = cameraOrientation * glm::vec3(0, 0, -1); | |
glm::vec3 up = cameraOrientation * glm::vec3(0, 1, 0); | |
float speed = 30.f; | |
if (specialKeys[GLUT_KEY_SHIFT_L]) | |
speed = 60.f; | |
if (specialKeys[GLUT_KEY_UP]) | |
cameraPos += forward * secondsPassed * speed; | |
if (specialKeys[GLUT_KEY_DOWN]) | |
cameraPos -= forward * secondsPassed * speed; | |
mat4 viewMat = glm::lookAt( | |
cameraPos, | |
cameraPos + forward, | |
up); | |
glUniformMatrix4fv(viewMatLoc, 1, GL_FALSE, value_ptr(viewMat)); | |
glutPostRedisplay(); | |
} | |
// Main routine. | |
int main(int argc, char* argv[]) | |
{ | |
glutInit(&argc, argv); | |
// Set the version of OpenGL (4.2) | |
glutInitContextVersion(4, 2); | |
// The core profile excludes all discarded features | |
glutInitContextProfile(GLUT_CORE_PROFILE); | |
// Forward compatibility excludes features marked for deprecation ensuring compatability with future versions | |
glutInitContextFlags(GLUT_FORWARD_COMPATIBLE); | |
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_MULTISAMPLE); | |
glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT); | |
glutInitWindowPosition(100, 100); | |
glutCreateWindow("TerrainGeneration"); | |
// Set OpenGL to render in filled mode | |
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); | |
glEnable(GL_DEPTH_TEST); | |
glEnable(GL_CULL_FACE); | |
glCullFace(GL_BACK); | |
glEnable(GL_BLEND); | |
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |
glutDisplayFunc(drawScene); | |
glutReshapeFunc(resize); | |
glutKeyboardFunc(keyInput); | |
glutIdleFunc(update); | |
glutKeyboardFunc([](unsigned char key, int x, int y) { | |
keys[key] = true; | |
//if we press escape, exit the game | |
if (key == 27) { | |
exit(0); | |
} | |
}); | |
glutKeyboardUpFunc([](unsigned char key, int x, int y) { | |
keys[key] = false; | |
}); | |
glutSpecialFunc([](int key, int x, int y) { | |
specialKeys[key] = true; | |
}); | |
glutSpecialUpFunc([](int key, int x, int y) { | |
specialKeys[key] = false; | |
}); | |
glewExperimental = GL_TRUE; | |
glewInit(); | |
setup(); | |
glutMainLoop(); | |
} |