Broken
This commit is contained in:
parent
7e5261047e
commit
58962e52fc
3
cpp-voxel-engine/.vscode/settings.json
vendored
3
cpp-voxel-engine/.vscode/settings.json
vendored
@ -4,6 +4,7 @@
|
|||||||
"*.js": "javascript",
|
"*.js": "javascript",
|
||||||
"*.c": "c",
|
"*.c": "c",
|
||||||
"*.scene": "yaml",
|
"*.scene": "yaml",
|
||||||
"vector": "cpp"
|
"vector": "cpp",
|
||||||
|
"cmath": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,208 +1,569 @@
|
|||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include "VoxelGame.h"
|
#include "VoxelGame.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <GL/glew.h>
|
|
||||||
#include <GL/glu.h>
|
|
||||||
#include "stb_image.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <unordered_set>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
#include "stb_image.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "GreedyMesher.h"
|
|
||||||
|
|
||||||
namespace {
|
#include <GLFW/glfw3.h>
|
||||||
// --- Perlin Noise Helpers ---
|
//----------------------------------------------------------------------
|
||||||
float fade_(float t) {
|
// Perlin noise
|
||||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
||||||
}
|
static std::vector<int> perm,p;
|
||||||
float lerp_(float a, float b, float t) {
|
static std::once_flag initFlag;
|
||||||
return a + t * (b - a);
|
static void initPerlin(){
|
||||||
}
|
perm.resize(256);
|
||||||
float grad_(int hash, float x, float y) {
|
std::iota(perm.begin(),perm.end(),0);
|
||||||
int h = hash & 7;
|
std::mt19937 gen(1337);
|
||||||
float u = h < 4 ? x : y;
|
std::shuffle(perm.begin(),perm.end(),gen);
|
||||||
float v = h < 4 ? y : x;
|
p.resize(512);
|
||||||
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
|
for(int i=0;i<512;++i) p[i]=perm[i&255];
|
||||||
}
|
|
||||||
float perlin(float x, float y) {
|
|
||||||
static int p[512];
|
|
||||||
static bool init = false;
|
|
||||||
if (!init) {
|
|
||||||
int perm[256] = {
|
|
||||||
151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,
|
|
||||||
140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,
|
|
||||||
247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,
|
|
||||||
57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,
|
|
||||||
74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,
|
|
||||||
60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,
|
|
||||||
65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,
|
|
||||||
200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,
|
|
||||||
52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,
|
|
||||||
207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,
|
|
||||||
119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,
|
|
||||||
129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,
|
|
||||||
218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,
|
|
||||||
81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,
|
|
||||||
184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,
|
|
||||||
222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
|
|
||||||
};
|
|
||||||
for (int i = 0; i < 256; i++) {
|
|
||||||
p[i] = perm[i];
|
|
||||||
p[i + 256] = perm[i];
|
|
||||||
}
|
|
||||||
init = true;
|
|
||||||
}
|
|
||||||
int X = int(floor(x)) & 255;
|
|
||||||
int Y = int(floor(y)) & 255;
|
|
||||||
x -= floor(x);
|
|
||||||
y -= floor(y);
|
|
||||||
float u = fade_(x);
|
|
||||||
float v = fade_(y);
|
|
||||||
int A = p[X] + Y, B = p[X + 1] + Y;
|
|
||||||
return lerp_( v,
|
|
||||||
lerp_( u, grad_(p[A], x, y), grad_(p[A+1], x-1, y) ),
|
|
||||||
lerp_( u, grad_(p[B], x, y-1), grad_(p[B+1], x-1, y-1) )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
static inline float fade(float t){ return t*t*t*(t*(t*6-15)+10); }
|
||||||
// Helper to safely query voxel (returns 0 if out of bounds)
|
static inline float lerp_(float a,float b,float t){ return a+t*(b-a); }
|
||||||
inline int voxelAt(const Chunk& c, int x, int y, int z) {
|
static inline float grad(int h,float x,float y,float z){
|
||||||
if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0;
|
h&=15; float u=h<8?x:y; float v=h<4?y:(h==12||h==14?x:z);
|
||||||
|
return ((h&1)?-u:u)+((h&2)?-v:v);
|
||||||
|
}
|
||||||
|
static float perlin(float x,float y,float z){
|
||||||
|
std::call_once(initFlag,initPerlin);
|
||||||
|
int X=int(floor(x))&255, Y=int(floor(y))&255, Z=int(floor(z))&255;
|
||||||
|
x-=floor(x); y-=floor(y); z-=floor(z);
|
||||||
|
float u=fade(x), v=fade(y), w=fade(z);
|
||||||
|
int A=p[X]+Y, AA=p[A]+Z, AB=p[A+1]+Z;
|
||||||
|
int B=p[X+1]+Y, BA=p[B]+Z, BB=p[B+1]+Z;
|
||||||
|
return lerp_(
|
||||||
|
lerp_( lerp_(grad(p[AA],x,y,z), grad(p[BA],x-1,y,z), u),
|
||||||
|
lerp_(grad(p[AB],x,y-1,z), grad(p[BB],x-1,y-1,z), u), v),
|
||||||
|
lerp_( lerp_(grad(p[AA+1],x,y,z-1), grad(p[BA+1],x-1,y,z-1), u),
|
||||||
|
lerp_(grad(p[AB+1],x,y-1,z-1), grad(p[BB+1],x-1,y-1,z-1), u), v),
|
||||||
|
w
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static inline int voxelAt(const Chunk &c,int x,int y,int z){
|
||||||
|
if(x<0||y<0||z<0||x>=CHUNK_SIZE||y>=CHUNK_HEIGHT||z>=CHUNK_SIZE) return 0;
|
||||||
return c.voxels[x][y][z];
|
return c.voxels[x][y][z];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Greedy mesh with normals
|
||||||
|
|
||||||
VoxelGame::VoxelGame()
|
void VoxelGame::greedyMesh(const Chunk &chunk,
|
||||||
: textureGrass(0), textureDirt(0), textureWood(0),
|
std::vector<Vertex> &vertices,
|
||||||
totalChunksLoaded(0), totalChunksEverLoaded(0)
|
std::vector<uint32_t> &indices)
|
||||||
{
|
{
|
||||||
}
|
const int dims[3]={CHUNK_SIZE,CHUNK_HEIGHT,CHUNK_SIZE};
|
||||||
|
for(int d=0;d<3;d++){
|
||||||
VoxelGame::~VoxelGame() {
|
int u=(d+1)%3, v=(d+2)%3;
|
||||||
glDeleteTextures(1, &textureGrass);
|
int x[3]={0,0,0}, q[3]={0,0,0}; q[d]=1;
|
||||||
glDeleteTextures(1, &textureDirt);
|
for(x[d]=-1;x[d]<dims[d];){
|
||||||
glDeleteTextures(1, &textureWood);
|
std::vector<int> mask(dims[u]*dims[v],0);
|
||||||
}
|
for(x[v]=0;x[v]<dims[v];++x[v]){
|
||||||
|
for(x[u]=0;x[u]<dims[u];++x[u]){
|
||||||
|
int a=0,b=0;
|
||||||
bool VoxelGame::init() {
|
int p0[3]={x[0],x[1],x[2]};
|
||||||
if (!loadTextures()) return false;
|
int p1[3]={x[0]+q[0],x[1]+q[1],x[2]+q[2]};
|
||||||
// Initial camera position
|
if(x[d]>=0) a=voxelAt(chunk,p0[0],p0[1],p0[2]);
|
||||||
float cam[3] = {32,32,80};
|
if(x[d]<dims[d]-1) b=voxelAt(chunk,p1[0],p1[1],p1[2]);
|
||||||
updateChunks(cam);
|
mask[x[u]+dims[u]*x[v]] = ((a!=0)!=(b!=0)) ? ((a!=0)?a:-b) : 0;
|
||||||
// Build mesh for each chunk
|
}
|
||||||
for (auto &p : chunks) {
|
}
|
||||||
p.second.mesh = GreedyMesher::mesh(p.second.voxels);
|
++x[d];
|
||||||
|
for(int j=0;j<dims[v];j++){
|
||||||
|
for(int i=0;i<dims[u];){
|
||||||
|
int c=mask[i+dims[u]*j];
|
||||||
|
if(!c){ ++i; continue; }
|
||||||
|
int w=1; while(i+w<dims[u] && mask[i+w+dims[u]*j]==c) ++w;
|
||||||
|
int h=1; bool done=false;
|
||||||
|
while(j+h<dims[v]){
|
||||||
|
for(int k=0;k<w;k++)
|
||||||
|
if(mask[i+k+dims[u]*(j+h)]!=c){ done=true; break; }
|
||||||
|
if(done) break;
|
||||||
|
++h;
|
||||||
|
}
|
||||||
|
int du[3]={0,0,0}, dv[3]={0,0,0};
|
||||||
|
du[u]=w; dv[v]=h;
|
||||||
|
int pos[3]={0,0,0}; pos[d]=x[d]; pos[u]=i; pos[v]=j;
|
||||||
|
float fx=pos[0], fy=pos[1], fz=pos[2];
|
||||||
|
float px[4]={fx, fx+du[0], fx+du[0]+dv[0], fx+dv[0]};
|
||||||
|
float py[4]={fy, fy+du[1], fy+du[1]+dv[1], fy+dv[1]};
|
||||||
|
float pz[4]={fz, fz+du[2], fz+du[2]+dv[2], fz+dv[2]};
|
||||||
|
glm::vec3 U={px[1]-px[0],py[1]-py[0],pz[1]-pz[0]};
|
||||||
|
glm::vec3 V={px[3]-px[0],py[3]-py[0],pz[3]-pz[0]};
|
||||||
|
glm::vec3 N=glm::normalize(glm::cross(U,V));
|
||||||
|
float duv=float(w), dvv=float(h);
|
||||||
|
Vertex v0{{px[0],py[0],pz[0]},N,{0,0}};
|
||||||
|
Vertex v1{{px[1],py[1],pz[1]},N,{duv,0}};
|
||||||
|
Vertex v2{{px[2],py[2],pz[2]},N,{duv,dvv}};
|
||||||
|
Vertex v3{{px[3],py[3],pz[3]},N,{0,dvv}};
|
||||||
|
if(c<0){ std::swap(v0,v1); std::swap(v2,v3); }
|
||||||
|
uint32_t base=vertices.size();
|
||||||
|
vertices.insert(vertices.end(),{v0,v1,v2,v3});
|
||||||
|
indices.insert(indices.end(),{base,base+1,base+2,base,base+2,base+3});
|
||||||
|
lastFaceCount++;
|
||||||
|
for(int l=0;l<h;++l)
|
||||||
|
for(int k=0;k<w;++k)
|
||||||
|
mask[i+k+dims[u]*(j+l)] = 0;
|
||||||
|
i+=w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// Constructor / Destructor
|
||||||
|
|
||||||
|
VoxelGame::VoxelGame(): cameraPos(32.0f,32.0f,80.0f) {
|
||||||
|
for(int i=0;i<4;i++)
|
||||||
|
workers.emplace_back(&VoxelGame::workerLoop,this);
|
||||||
|
}
|
||||||
|
|
||||||
|
VoxelGame::~VoxelGame(){
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(queueMutex);
|
||||||
|
stopWorkers=true;
|
||||||
|
}
|
||||||
|
queueCV.notify_all();
|
||||||
|
for(auto &t:workers) t.join();
|
||||||
|
|
||||||
|
glDeleteTextures(1,&textureDirt);
|
||||||
|
glDeleteTextures(1,&textureGrass);
|
||||||
|
glDeleteTextures(1,&textureWood);
|
||||||
|
glDeleteFramebuffers(NUM_CASCADES,depthFBO);
|
||||||
|
glDeleteTextures(NUM_CASCADES,depthTex);
|
||||||
|
glDeleteProgram(depthProg);
|
||||||
|
glDeleteProgram(mainProg);
|
||||||
|
for(auto &pr:meshes) destroyMesh(pr.second.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
bool VoxelGame::init(int w,int h){
|
||||||
|
screenW=w; screenH=h;
|
||||||
|
if(!loadTextures()||!loadShaders()) return false;
|
||||||
|
initShadowMaps();
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glEnable(GL_TEXTURE_2D);
|
glEnable(GL_CULL_FACE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VoxelGame::loadTextures() {
|
//----------------------------------------------------------------------
|
||||||
int w,h,n;
|
|
||||||
unsigned char* data;
|
bool VoxelGame::loadTextures(){
|
||||||
data = stbi_load("grass.jpg",&w,&h,&n,4);
|
auto load=[&](const char*f,GLuint &t){
|
||||||
if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; }
|
int W,H,N; unsigned char*d=stbi_load(f,&W,&H,&N,4);
|
||||||
glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass);
|
if(!d) return false;
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
|
glGenTextures(1,&t); glBindTexture(GL_TEXTURE_2D,t);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
|
||||||
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,W,H,0,GL_RGBA,GL_UNSIGNED_BYTE,d);
|
||||||
stbi_image_free(data);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
data = stbi_load("dirt.jpg",&w,&h,&n,4);
|
stbi_image_free(d);
|
||||||
if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; }
|
return true;
|
||||||
glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt);
|
};
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
|
return load("dirt.jpg",textureDirt)
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
|
&& load("grass.jpg",textureGrass)
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
|
&& load("wood.png",textureWood);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
}
|
||||||
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
|
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
//----------------------------------------------------------------------
|
||||||
stbi_image_free(data);
|
|
||||||
data = stbi_load("wood.png",&w,&h,&n,4);
|
bool VoxelGame::loadShaders(){
|
||||||
if (!data) { std::cerr<<"wood.png load fail\n"; return false; }
|
auto compile=[&](const char*s,GLenum t){
|
||||||
glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood);
|
GLuint sh=glCreateShader(t);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
|
glShaderSource(sh,1,&s,nullptr);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
|
glCompileShader(sh);
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
|
return sh;
|
||||||
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
|
};
|
||||||
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
|
// depth shader
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
const char* dvsrc = R"(
|
||||||
stbi_image_free(data);
|
#version 330 core
|
||||||
|
layout(location=0) in vec3 aPos;
|
||||||
|
uniform mat4 uLightSpace,uModel;
|
||||||
|
void main(){ gl_Position=uLightSpace*uModel*vec4(aPos,1); }
|
||||||
|
)", *dfsrc = R"(
|
||||||
|
#version 330 core
|
||||||
|
void main(){}
|
||||||
|
)";
|
||||||
|
GLuint dv=compile(dvsrc,GL_VERTEX_SHADER),
|
||||||
|
df=compile(dfsrc,GL_FRAGMENT_SHADER);
|
||||||
|
depthProg=glCreateProgram();
|
||||||
|
glAttachShader(depthProg,dv); glAttachShader(depthProg,df);
|
||||||
|
glLinkProgram(depthProg);
|
||||||
|
glDeleteShader(dv); glDeleteShader(df);
|
||||||
|
|
||||||
|
// scene shader
|
||||||
|
const char* svsrc = R"(
|
||||||
|
#version 330 core
|
||||||
|
layout(location=0) in vec3 aPos;
|
||||||
|
layout(location=1) in vec3 aNorm;
|
||||||
|
layout(location=2) in vec2 aUV;
|
||||||
|
uniform mat4 uModel,uView,uProj,uLightSpace[3];
|
||||||
|
out vec3 FragPos,Normal;
|
||||||
|
out vec2 UV;
|
||||||
|
out vec4 FragPosLight[3];
|
||||||
|
void main(){
|
||||||
|
FragPos = vec3(uModel*vec4(aPos,1));
|
||||||
|
Normal = mat3(transpose(inverse(uModel)))*aNorm;
|
||||||
|
UV = aUV;
|
||||||
|
for(int i=0;i<3;i++)
|
||||||
|
FragPosLight[i] = uLightSpace[i]*vec4(FragPos,1);
|
||||||
|
gl_Position = uProj*uView*vec4(FragPos,1);
|
||||||
|
}
|
||||||
|
)", *sfsrc = R"(
|
||||||
|
#version 330 core
|
||||||
|
in vec3 FragPos,Normal;
|
||||||
|
in vec2 UV;
|
||||||
|
in vec4 FragPosLight[3];
|
||||||
|
uniform sampler2D uTexDirt,uTexGrass;
|
||||||
|
uniform sampler2DShadow uShadowMap[3];
|
||||||
|
uniform float cascadeSplit[3];
|
||||||
|
uniform vec3 lightDir,viewPos;
|
||||||
|
float CalcShadow(int idx){
|
||||||
|
vec3 proj = FragPosLight[idx].xyz/FragPosLight[idx].w;
|
||||||
|
proj = proj*0.5+0.5;
|
||||||
|
float bias=0.005,sum=0;
|
||||||
|
for(int x=-1;x<=1;x++)for(int y=-1;y<=1;y++)
|
||||||
|
sum += texture(uShadowMap[idx], proj.xy+vec2(x,y)*0.001, proj.z-bias);
|
||||||
|
sum/=9; return proj.z>1?1:sum;
|
||||||
|
}
|
||||||
|
out vec4 FragColor;
|
||||||
|
void main(){
|
||||||
|
float ndl = max(dot(normalize(Normal),-lightDir),0);
|
||||||
|
vec3 base = Normal.y>0.9?texture(uTexGrass,UV).rgb
|
||||||
|
:texture(uTexDirt,UV).rgb;
|
||||||
|
float d = length(viewPos-FragPos);
|
||||||
|
int idx = d>cascadeSplit[1]?2:d>cascadeSplit[0]?1:0;
|
||||||
|
float sh=CalcShadow(idx);
|
||||||
|
vec3 amb=0.2*base, dif=(1-sh)*ndl*base;
|
||||||
|
FragColor=vec4(amb+dif,1);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
GLuint sv=compile(svsrc,GL_VERTEX_SHADER),
|
||||||
|
sf=compile(sfsrc,GL_FRAGMENT_SHADER);
|
||||||
|
mainProg=glCreateProgram();
|
||||||
|
glAttachShader(mainProg,sv); glAttachShader(mainProg,sf);
|
||||||
|
glLinkProgram(mainProg);
|
||||||
|
glDeleteShader(sv); glDeleteShader(sf);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chunk VoxelGame::generateChunk(int cx,int cz) {
|
//----------------------------------------------------------------------
|
||||||
Chunk c; c.x=cx; c.z=cz;
|
|
||||||
c.voxels.assign(16, std::vector<std::vector<int>>(16, std::vector<int>(16,0)));
|
void VoxelGame::initShadowMaps(){
|
||||||
float scale=0.1f;
|
glGenFramebuffers(NUM_CASCADES,depthFBO);
|
||||||
for(int x=0;x<16;x++)for(int z=0;z<16;z++){
|
glGenTextures(NUM_CASCADES,depthTex);
|
||||||
float wx = cx*16 + x, wz = cz*16 + z;
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
float n = perlin(wx*scale, wz*scale);
|
glBindTexture(GL_TEXTURE_2D,depthTex[i]);
|
||||||
int h = int(((n+1)*0.5f)*16);
|
glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,
|
||||||
h = std::clamp(h,0,16);
|
SHADOW_MAP_SIZE,SHADOW_MAP_SIZE,
|
||||||
for(int y=0;y<h;y++){
|
0,GL_DEPTH_COMPONENT,GL_FLOAT,nullptr);
|
||||||
c.voxels[x][y][z] = (y==h-1?1:2);
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_BORDER);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_BORDER);
|
||||||
|
float bc[4]={1,1,1,1};
|
||||||
|
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR,bc);
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,depthFBO[i]);
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,
|
||||||
|
GL_TEXTURE_2D,depthTex[i],0);
|
||||||
|
glDrawBuffer(GL_NONE);
|
||||||
|
glReadBuffer(GL_NONE);
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,0);
|
||||||
|
|
||||||
|
float near=1.0f, far=200.0f, lambda=0.5f;
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
float si=(i+1)/(float)NUM_CASCADES;
|
||||||
|
float log = near*std::pow(far/near,si);
|
||||||
|
float uni = near + (far-near)*si;
|
||||||
|
cascadeSplits[i] = lambda*log + (1-lambda)*uni;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Chunk VoxelGame::generateChunk(int cx, int cz) {
|
||||||
|
Chunk c;
|
||||||
|
c.x = cx;
|
||||||
|
c.z = cz;
|
||||||
|
c.voxels.assign(CHUNK_SIZE,
|
||||||
|
std::vector<std::vector<int>>(CHUNK_HEIGHT,
|
||||||
|
std::vector<int>(CHUNK_SIZE, 0)));
|
||||||
|
|
||||||
|
const float scale = 0.05f; // controls feature size
|
||||||
|
for (int x = 0; x < CHUNK_SIZE; ++x) {
|
||||||
|
for (int z = 0; z < CHUNK_SIZE; ++z) {
|
||||||
|
// world‐space coords
|
||||||
|
float wx = (cx * CHUNK_SIZE + x) * scale;
|
||||||
|
float wz = (cz * CHUNK_SIZE + z) * scale;
|
||||||
|
// perlin → [-1,1] → [0,1]
|
||||||
|
float n = (perlin(wx, 0.0f, wz) + 1.0f) * 0.5f;
|
||||||
|
// height [1,CHUNK_HEIGHT]
|
||||||
|
int h = 1 + int(n * (CHUNK_HEIGHT - 1));
|
||||||
|
h = std::clamp(h, 1, CHUNK_HEIGHT);
|
||||||
|
// fill from y=0 up to y<h
|
||||||
|
for (int y = 0; y < h; ++y) {
|
||||||
|
// grass at top, dirt below
|
||||||
|
c.voxels[x][y][z] = (y == h - 1 ? 1 : 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelGame::updateChunks(const float cam[3]) {
|
|
||||||
int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16));
|
void VoxelGame::update(float dt, glm::vec3 const& camPos){
|
||||||
int r=2;
|
lastDeltaTime=dt;
|
||||||
std::unordered_set<ChunkKey,ChunkKeyHash> keep;
|
fps=1.0f/dt;
|
||||||
for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){
|
frameTimeSum+=dt; frameCount++;
|
||||||
ChunkKey key{cx+dx,cz+dz};
|
|
||||||
keep.insert(key);
|
int old=totalChunksEverLoaded;
|
||||||
if(chunks.find(key)==chunks.end()){
|
double t0=glfwGetTime();
|
||||||
Chunk nc = generateChunk(key.x,key.z);
|
updateChunks(camPos);
|
||||||
nc.mesh = GreedyMesher::mesh(nc.voxels);
|
double t1=glfwGetTime();
|
||||||
chunks[key]=std::move(nc);
|
lastChunkGenTime=t1-t0;
|
||||||
totalChunksEverLoaded++;
|
chunkGenCount=totalChunksEverLoaded-old;
|
||||||
|
totalChunkGenTime+=lastChunkGenTime*chunkGenCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Called each frame to load/unload chunks around the camera and enqueue new ones for meshing.
|
||||||
|
void VoxelGame::updateChunks(glm::vec3 const& camPos) {
|
||||||
|
int cx = int(std::floor(camPos.x / CHUNK_SIZE));
|
||||||
|
int cz = int(std::floor(camPos.z / CHUNK_SIZE));
|
||||||
|
int r = renderDistance;
|
||||||
|
|
||||||
|
// Track which chunks to keep
|
||||||
|
std::unordered_map<ChunkKey,bool,ChunkKeyHash> keep;
|
||||||
|
for (int dx = -r; dx <= r; ++dx) {
|
||||||
|
for (int dz = -r; dz <= r; ++dz) {
|
||||||
|
ChunkKey k{cx + dx, cz + dz};
|
||||||
|
keep[k] = true;
|
||||||
|
// If not already generated, create and enqueue for meshing
|
||||||
|
if (!chunks.count(k)) {
|
||||||
|
chunks[k] = generateChunk(k.x, k.z);
|
||||||
|
++totalChunksEverLoaded;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(queueMutex);
|
||||||
|
toBuild.push(k);
|
||||||
|
}
|
||||||
|
queueCV.notify_one();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for(auto it=chunks.begin();it!=chunks.end();){
|
|
||||||
if(!keep.count(it->first)) it=chunks.erase(it);
|
// Unload chunks that are out of range
|
||||||
else ++it;
|
for (auto it = chunks.begin(); it != chunks.end(); ) {
|
||||||
|
if (!keep.count(it->first)) {
|
||||||
|
// Also destroy its GPU mesh if present
|
||||||
|
if (meshes.count(it->first)) {
|
||||||
|
destroyMesh(meshes[it->first].get());
|
||||||
|
meshes.erase(it->first);
|
||||||
|
}
|
||||||
|
it = chunks.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
totalChunksLoaded = chunks.size();
|
|
||||||
|
totalChunksLoaded = int(chunks.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoxelGame::update(float dt,const float cam[3]){
|
//----------------------------------------------------------------------------
|
||||||
updateChunks(cam);
|
// Worker thread: copies chunk data, runs greedyMesh on CPU, then enqueues RawMesh for GPU upload.
|
||||||
}
|
void VoxelGame::workerLoop() {
|
||||||
|
while (true) {
|
||||||
|
ChunkKey key;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(queueMutex);
|
||||||
|
queueCV.wait(lk, [&]{ return stopWorkers || !toBuild.empty(); });
|
||||||
|
if (stopWorkers && toBuild.empty()) return;
|
||||||
|
key = toBuild.front();
|
||||||
|
toBuild.pop();
|
||||||
|
}
|
||||||
|
|
||||||
void VoxelGame::render(){
|
// Copy voxel data under lock
|
||||||
for(auto &p:chunks){
|
Chunk chunkCopy;
|
||||||
float ox = p.first.x*16, oz = p.first.z*16;
|
{
|
||||||
for(auto &q:p.second.mesh){
|
std::lock_guard<std::mutex> lk(queueMutex);
|
||||||
// shift by chunk offset
|
auto it = chunks.find(key);
|
||||||
float v0[3]={q.x+ox,q.y,q.z+oz},
|
if (it == chunks.end()) continue;
|
||||||
v1[3]={q.x+q.du[0]+ox,q.y+q.du[1],q.z+q.du[2]+oz},
|
chunkCopy = it->second;
|
||||||
v2[3]={q.x+q.du[0]+q.dv[0]+ox,q.y+q.du[1]+q.dv[1],q.z+q.du[2]+q.dv[2]+oz},
|
}
|
||||||
v3[3]={q.x+q.dv[0]+ox,q.y+q.dv[1],q.z+q.dv[2]+oz};
|
|
||||||
unsigned int tex = (q.textureID==1?textureGrass:(q.textureID==2?textureDirt:textureWood));
|
// Phase 1: CPU‐side greedy meshing
|
||||||
glBindTexture(GL_TEXTURE_2D,tex);
|
RawMesh rm;
|
||||||
glBegin(GL_QUADS);
|
rm.key = key;
|
||||||
glTexCoord2f(0,0); glVertex3fv(v0);
|
greedyMesh(chunkCopy, rm.verts, rm.indices);
|
||||||
glTexCoord2f(1,0); glVertex3fv(v1);
|
|
||||||
glTexCoord2f(1,1); glVertex3fv(v2);
|
// Enqueue for main‐thread GPU upload
|
||||||
glTexCoord2f(0,1); glVertex3fv(v3);
|
{
|
||||||
glEnd();
|
std::lock_guard<std::mutex> lk(queueMutex);
|
||||||
|
readyToUpload.push(std::move(rm));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
// computeLightSpace: full frustum corners approach
|
||||||
|
|
||||||
|
static void computeLightSpace(glm::mat4 const& view, glm::mat4 const& proj,
|
||||||
|
glm::vec3 const& lightDir,
|
||||||
|
glm::mat4 out[3],
|
||||||
|
float splits[3])
|
||||||
|
{
|
||||||
|
glm::mat4 inv = glm::inverse(proj * view);
|
||||||
|
glm::vec4 corners[8] = {
|
||||||
|
{-1,-1,-1,1},{ 1,-1,-1,1},{ 1, 1,-1,1},{-1, 1,-1,1},
|
||||||
|
{-1,-1, 1,1},{ 1,-1, 1,1},{ 1, 1, 1,1},{-1, 1, 1,1}
|
||||||
|
};
|
||||||
|
for(int i=0;i<3;i++){
|
||||||
|
float near = (i==0? 1.0f : splits[i-1]);
|
||||||
|
float far = splits[i];
|
||||||
|
std::vector<glm::vec4> frustCorners;
|
||||||
|
for(int c=0;c<8;c++){
|
||||||
|
glm::vec4 pt = inv * corners[c];
|
||||||
|
pt /= pt.w;
|
||||||
|
frustCorners.push_back(pt);
|
||||||
|
}
|
||||||
|
// light view
|
||||||
|
glm::mat4 lightView = glm::lookAt(-lightDir*100.0f, glm::vec3(0), glm::vec3(0,1,0));
|
||||||
|
// compute AABB
|
||||||
|
glm::vec3 mn(FLT_MAX), mx(-FLT_MAX);
|
||||||
|
for(auto &pt: frustCorners){
|
||||||
|
glm::vec4 lp = lightView * pt;
|
||||||
|
mn = glm::min(mn, glm::vec3(lp));
|
||||||
|
mx = glm::max(mx, glm::vec3(lp));
|
||||||
|
}
|
||||||
|
out[i] = glm::ortho(mn.x,mx.x,mn.y,mx.y,-mx.z-50.0f,-mn.z+50.0f) * lightView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void VoxelGame::render(glm::mat4 const& view, glm::mat4 const& proj){
|
||||||
|
computeLightSpace(view,proj,lightDir,lightSpaceMat,cascadeSplits);
|
||||||
|
|
||||||
|
// 1) Depth passes
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
glViewport(0,0,SHADOW_MAP_SIZE,SHADOW_MAP_SIZE);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,depthFBO[i]);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
glUseProgram(depthProg);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(depthProg,"uLightSpace"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
|
||||||
|
renderScene(depthProg);
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,0);
|
||||||
|
|
||||||
|
// 2) Main pass
|
||||||
|
glViewport(0,0,screenW,screenH);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
|
||||||
|
glUseProgram(mainProg);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,"uView"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(view));
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,"uProj"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(proj));
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
char buf[32]; sprintf(buf,"uLightSpace[%d]",i);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,buf),
|
||||||
|
1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
|
||||||
|
}
|
||||||
|
glUniform1fv(glGetUniformLocation(mainProg,"cascadeSplit"),
|
||||||
|
NUM_CASCADES,cascadeSplits);
|
||||||
|
glUniform3fv(glGetUniformLocation(mainProg,"lightDir"),
|
||||||
|
1,glm::value_ptr(lightDir));
|
||||||
|
glUniform3fv(glGetUniformLocation(mainProg,"viewPos"),
|
||||||
|
1,glm::value_ptr(cameraPos));
|
||||||
|
|
||||||
|
// bind textures
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,textureDirt);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,"uTexDirt"),0);
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,textureGrass);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,"uTexGrass"),1);
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
glActiveTexture(GL_TEXTURE2+i);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,depthTex[i]);
|
||||||
|
char buf[32]; sprintf(buf,"uShadowMap[%d]",i);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,buf),2+i);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderScene(mainProg);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
|
void VoxelGame::renderScene(GLuint prog){
|
||||||
|
for(auto &pr:meshes){
|
||||||
|
glm::mat4 model = glm::translate(glm::mat4(1.0f),
|
||||||
|
glm::vec3(pr.first.x*16.0f,0,pr.first.z*16.0f));
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(prog,"uModel"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(model));
|
||||||
|
ChunkMesh*m=pr.second.get();
|
||||||
|
glBindVertexArray(m->vao);
|
||||||
|
glDrawElements(GL_TRIANGLES,m->indexCount,GL_UNSIGNED_INT,0);
|
||||||
|
}
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------
|
||||||
|
|
||||||
void VoxelGame::debugUI(){
|
void VoxelGame::debugUI(){
|
||||||
ImGui::Begin("Debug");
|
ImGui::Begin("Debug");
|
||||||
ImGui::Text("Chunks Loaded: %d", totalChunksLoaded);
|
|
||||||
ImGui::Text("Chunks Ever Loaded: %d", totalChunksEverLoaded);
|
ImGui::Text("FPS: %.1f", fps);
|
||||||
|
ImGui::Text("Delta Time: %.3f ms", lastDeltaTime*1000.0f);
|
||||||
|
ImGui::Text("Avg Frame: %.3f ms", (frameTimeSum/frameCount)*1000.0);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Text("Last Chunk Gen: %.3f ms", lastChunkGenTime*1000.0);
|
||||||
|
ImGui::Text("Chunks this gen: %d", chunkGenCount);
|
||||||
|
ImGui::Text("Avg Chunk Gen: %.3f ms",
|
||||||
|
(chunkGenCount?(totalChunkGenTime/chunkGenCount)*1000.0:0.0));
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Text("Chunks Loaded: %d", totalChunksLoaded);
|
||||||
|
ImGui::Text("Chunks Ever: %d", totalChunksEverLoaded);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::Text("Faces Drawn: %d", lastFaceCount);
|
||||||
|
ImGui::Text("GL Draw Calls: %d", lastGLCalls);
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
ImGui::SliderInt("Render Distance",&renderDistance,1,64);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Shadow Maps:");
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
ImGui::Text(" Cascade %d", i);
|
||||||
|
ImGui::Image((intptr_t)depthTex[i],
|
||||||
|
ImVec2(128,128),
|
||||||
|
ImVec2(0,1),ImVec2(1,0));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
static bool wf=false;
|
static bool wf=false;
|
||||||
if(ImGui::Checkbox("Wireframe",&wf))
|
if(ImGui::Checkbox("Wireframe",&wf))
|
||||||
glPolygonMode(GL_FRONT_AND_BACK, wf?GL_LINE:GL_FILL);
|
glPolygonMode(GL_FRONT_AND_BACK,wf?GL_LINE:GL_FILL);
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VoxelGame::destroyMesh(ChunkMesh* m){
|
||||||
|
glDeleteBuffers(1,&m->vbo);
|
||||||
|
glDeleteBuffers(1,&m->ibo);
|
||||||
|
glDeleteVertexArrays(1,&m->vao);
|
||||||
|
}
|
||||||
|
@ -1,33 +1,53 @@
|
|||||||
#ifndef VOXELGAME_H
|
#pragma once
|
||||||
#define VOXELGAME_H
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "GreedyMesher.h"
|
#include <memory>
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <thread>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
// Structure representing a chunk (16×16×16 voxels)
|
#include <GL/glew.h>
|
||||||
struct Chunk {
|
#include <glm/glm.hpp>
|
||||||
int x, z; // Chunk grid coordinates
|
|
||||||
// 3D voxel data: dimensions [16][16][16]
|
// world dimensions
|
||||||
std::vector<std::vector<std::vector<int>>> voxels;
|
static constexpr int CHUNK_SIZE = 16;
|
||||||
// **Merged mesh faces** produced by the greedy mesher
|
static constexpr int CHUNK_HEIGHT = 64;
|
||||||
std::vector<Quad> mesh;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A key for identifying a chunk by its grid coordinates.
|
|
||||||
struct ChunkKey {
|
struct ChunkKey {
|
||||||
int x, z;
|
int x,z;
|
||||||
bool operator==(const ChunkKey &other) const {
|
bool operator==(ChunkKey const& o) const { return x==o.x && z==o.z; }
|
||||||
return x == other.x && z == other.z;
|
};
|
||||||
|
struct ChunkKeyHash {
|
||||||
|
std::size_t operator()(ChunkKey const& k) const noexcept {
|
||||||
|
return std::hash<long long>()(((long long)k.x<<32)|(unsigned)k.z);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ChunkKeyHash {
|
struct Chunk {
|
||||||
std::size_t operator()(const ChunkKey &key) const {
|
int x,z;
|
||||||
return std::hash<int>()(key.x) ^ (std::hash<int>()(key.z) << 1);
|
// voxels[x][y][z], y ∈ [0,CHUNK_HEIGHT)
|
||||||
}
|
std::vector<std::vector<std::vector<int>>> voxels;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ChunkMesh {
|
||||||
|
GLuint vao,vbo,ibo;
|
||||||
|
GLsizei indexCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec3 pos;
|
||||||
|
glm::vec3 normal;
|
||||||
|
glm::vec2 uv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RawMesh {
|
||||||
|
ChunkKey key;
|
||||||
|
std::vector<Vertex> verts;
|
||||||
|
std::vector<uint32_t> indices;
|
||||||
};
|
};
|
||||||
|
|
||||||
class VoxelGame {
|
class VoxelGame {
|
||||||
@ -35,24 +55,63 @@ public:
|
|||||||
VoxelGame();
|
VoxelGame();
|
||||||
~VoxelGame();
|
~VoxelGame();
|
||||||
|
|
||||||
bool init();
|
// initialize with screen size (for cascades)
|
||||||
void update(float deltaTime, const float cameraPos[3]);
|
bool init(int screenW,int screenH);
|
||||||
void render();
|
void update(float dt, glm::vec3 const& camPos);
|
||||||
|
void render(glm::mat4 const& view, glm::mat4 const& proj);
|
||||||
void debugUI();
|
void debugUI();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// chunk generation
|
||||||
|
Chunk generateChunk(int cx,int cz);
|
||||||
|
void updateChunks(glm::vec3 const& camPos);
|
||||||
|
|
||||||
|
// CPU meshing
|
||||||
|
void workerLoop();
|
||||||
|
void greedyMesh(Chunk const&, std::vector<Vertex>&, std::vector<uint32_t>&);
|
||||||
|
void destroyMesh(ChunkMesh*);
|
||||||
|
|
||||||
|
std::unordered_map<ChunkKey,Chunk,ChunkKeyHash> chunks;
|
||||||
|
std::unordered_map<ChunkKey,std::unique_ptr<ChunkMesh>,ChunkKeyHash> meshes;
|
||||||
|
|
||||||
|
std::mutex queueMutex;
|
||||||
|
std::condition_variable queueCV;
|
||||||
|
std::queue<ChunkKey> toBuild;
|
||||||
|
std::queue<RawMesh> readyToUpload;
|
||||||
|
bool stopWorkers=false;
|
||||||
|
std::vector<std::thread> workers;
|
||||||
|
|
||||||
|
// stats
|
||||||
|
int totalChunksLoaded=0, totalChunksEverLoaded=0;
|
||||||
|
int chunkGenCount=0;
|
||||||
|
double totalChunkGenTime=0, lastChunkGenTime=0;
|
||||||
|
float fps=0, lastDeltaTime=0;
|
||||||
|
double frameTimeSum=0; int frameCount=0;
|
||||||
|
int lastFaceCount=0, lastGLCalls=0;
|
||||||
|
|
||||||
|
// textures
|
||||||
|
GLuint textureDirt, textureGrass, textureWood;
|
||||||
|
|
||||||
|
// cascaded shadow maps
|
||||||
|
static constexpr int NUM_CASCADES = 3;
|
||||||
|
static constexpr int SHADOW_MAP_SIZE = 1024;
|
||||||
|
GLuint depthFBO[NUM_CASCADES];
|
||||||
|
GLuint depthTex[NUM_CASCADES];
|
||||||
|
float cascadeSplits[NUM_CASCADES];
|
||||||
|
glm::mat4 lightSpaceMat[NUM_CASCADES];
|
||||||
|
|
||||||
|
// shaders
|
||||||
|
GLuint depthProg, mainProg;
|
||||||
|
|
||||||
|
// light & camera
|
||||||
|
glm::vec3 lightDir = glm::normalize(glm::vec3(1.0f, -1.0f, 0.5f));
|
||||||
|
glm::vec3 cameraPos;
|
||||||
|
|
||||||
|
int screenW, screenH;
|
||||||
|
int renderDistance = 2;
|
||||||
|
|
||||||
bool loadTextures();
|
bool loadTextures();
|
||||||
Chunk generateChunk(int cx, int cz);
|
bool loadShaders();
|
||||||
void updateChunks(const float cameraPos[3]);
|
void initShadowMaps();
|
||||||
void drawCube(float x, float y, float z, int voxelType);
|
void renderScene(GLuint prog);
|
||||||
|
|
||||||
std::unordered_map<ChunkKey, Chunk, ChunkKeyHash> chunks;
|
|
||||||
int totalChunksLoaded;
|
|
||||||
int totalChunksEverLoaded;
|
|
||||||
|
|
||||||
unsigned int textureGrass;
|
|
||||||
unsigned int textureDirt;
|
|
||||||
unsigned int textureWood;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VOXELGAME_H
|
|
||||||
|
Binary file not shown.
74
cpp-voxel-engine/a
Normal file
74
cpp-voxel-engine/a
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
static void computeLightSpace(glm::mat4 view, glm::mat4 proj,
|
||||||
|
glm::vec3 lightDir,
|
||||||
|
glm::mat4 out[3]){
|
||||||
|
glm::mat4 inv = glm::inverse(proj*view);
|
||||||
|
glm::vec4 corners[8] = {
|
||||||
|
{-1,-1,-1,1},{1,-1,-1,1},{1,1,-1,1},{-1,1,-1,1},
|
||||||
|
{-1,-1, 1,1},{1,-1, 1,1},{1,1, 1,1},{-1,1, 1,1}
|
||||||
|
};
|
||||||
|
for(int i=0;i<3;i++){
|
||||||
|
glm::mat4 lightView = glm::lookAt(-lightDir*100.0f,glm::vec3(0),glm::vec3(0,1,0));
|
||||||
|
glm::vec3 mn(FLT_MAX), mx(-FLT_MAX);
|
||||||
|
for(auto &c: corners){
|
||||||
|
glm::vec4 wc = inv * c; wc/=wc.w;
|
||||||
|
glm::vec4 lc = lightView * wc;
|
||||||
|
mn = glm::min(mn,glm::vec3(lc));
|
||||||
|
mx = glm::max(mx,glm::vec3(lc));
|
||||||
|
}
|
||||||
|
out[i] = glm::ortho(mn.x,mx.x,mn.y,mx.y,-mx.z-50.0f,-mn.z+50.0f) * lightView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void VoxelGame::render(glm::mat4 const& view, glm::mat4 const& proj){
|
||||||
|
computeLightSpace(view,proj,lightDir,lightSpaceMat);
|
||||||
|
|
||||||
|
// 1) depth passes
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
glViewport(0,0,SHADOW_MAP_SIZE,SHADOW_MAP_SIZE);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,depthFBO[i]);
|
||||||
|
glClear(GL_DEPTH_BUFFER_BIT);
|
||||||
|
glUseProgram(depthProg);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(depthProg,"uLightSpace"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
|
||||||
|
renderScene(depthProg);
|
||||||
|
}
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER,0);
|
||||||
|
|
||||||
|
// 2) main pass
|
||||||
|
glViewport(0,0,screenW,screenH);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
|
||||||
|
glUseProgram(mainProg);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,"uView"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(view));
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,"uProj"),
|
||||||
|
1,GL_FALSE,glm::value_ptr(proj));
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
char buf[32]; sprintf(buf,"uLightSpace[%d]",i);
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(mainProg,buf),
|
||||||
|
1,GL_FALSE,glm::value_ptr(lightSpaceMat[i]));
|
||||||
|
}
|
||||||
|
glUniform1fv(glGetUniformLocation(mainProg,"cascadeSplit"),
|
||||||
|
NUM_CASCADES,cascadeSplits);
|
||||||
|
glUniform3fv(glGetUniformLocation(mainProg,"lightDir"),
|
||||||
|
1,glm::value_ptr(lightDir));
|
||||||
|
glUniform3fv(glGetUniformLocation(mainProg,"viewPos"),
|
||||||
|
1,glm::value_ptr(cameraPos));
|
||||||
|
|
||||||
|
// bind textures
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,textureDirt);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,"uTexDirt"),0);
|
||||||
|
glActiveTexture(GL_TEXTURE1);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,textureGrass);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,"uTexGrass"),1);
|
||||||
|
for(int i=0;i<NUM_CASCADES;i++){
|
||||||
|
glActiveTexture(GL_TEXTURE2+i);
|
||||||
|
glBindTexture(GL_TEXTURE_2D,depthTex[i]);
|
||||||
|
char buf[32]; sprintf(buf,"uShadowMap[%d]",i);
|
||||||
|
glUniform1i(glGetUniformLocation(mainProg,buf),2+i);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderScene(mainProg);
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ Size=400,400
|
|||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
[Window][Debug]
|
[Window][Debug]
|
||||||
Pos=492,48
|
Pos=12,41
|
||||||
Size=214,119
|
Size=336,392
|
||||||
Collapsed=0
|
Collapsed=0
|
||||||
|
|
||||||
|
@ -1,189 +1,160 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
#include <GL/glu.h>
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "imgui_internal.h" // For Im_PI
|
|
||||||
#include "imgui_impl_glfw.h"
|
#include "imgui_impl_glfw.h"
|
||||||
#include "imgui_impl_opengl3.h"
|
#include "imgui_impl_opengl3.h"
|
||||||
|
|
||||||
#include "VoxelGame.h"
|
#include "VoxelGame.h"
|
||||||
|
|
||||||
// Global camera variables.
|
// camera globals
|
||||||
float yaw = -90.0f;
|
static glm::vec3 cameraPos = {32.0f, 32.0f, 80.0f};
|
||||||
float pitch = 0.0f;
|
static glm::vec3 cameraFront = {0.0f, 0.0f, -1.0f};
|
||||||
float lastX = 400.0f, lastY = 300.0f;
|
static glm::vec3 cameraUp = {0.0f, 1.0f, 0.0f};
|
||||||
bool firstMouse = true;
|
static float yaw = -90.0f, pitch = 0.0f;
|
||||||
float cameraPos[3] = {32.0f, 32.0f, 80.0f};
|
static bool firstMouse = true;
|
||||||
float cameraFront[3] = {0.0f, 0.0f, -1.0f};
|
static float lastX, lastY;
|
||||||
float cameraUp[3] = {0.0f, 1.0f, 0.0f};
|
|
||||||
|
|
||||||
void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
|
static void mouse_callback(GLFWwindow* window, double xpos, double ypos) {
|
||||||
if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL)
|
if (glfwGetInputMode(window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL)
|
||||||
return;
|
return; // do not rotate if cursor is free
|
||||||
|
|
||||||
if (firstMouse) {
|
if (firstMouse) {
|
||||||
lastX = static_cast<float>(xpos);
|
lastX = float(xpos);
|
||||||
lastY = static_cast<float>(ypos);
|
lastY = float(ypos);
|
||||||
firstMouse = false;
|
firstMouse = false;
|
||||||
}
|
}
|
||||||
float xoffset = static_cast<float>(xpos) - lastX;
|
float dx = float(xpos) - lastX;
|
||||||
float yoffset = lastY - static_cast<float>(ypos);
|
float dy = lastY - float(ypos);
|
||||||
lastX = static_cast<float>(xpos);
|
lastX = float(xpos);
|
||||||
lastY = static_cast<float>(ypos);
|
lastY = float(ypos);
|
||||||
|
|
||||||
float sensitivity = 0.1f;
|
const float sensitivity = 0.1f;
|
||||||
xoffset *= sensitivity;
|
dx *= sensitivity;
|
||||||
yoffset *= sensitivity;
|
dy *= sensitivity;
|
||||||
|
|
||||||
yaw += xoffset;
|
yaw += dx;
|
||||||
pitch += yoffset;
|
pitch += dy;
|
||||||
if (pitch > 89.0f) pitch = 89.0f;
|
pitch = std::clamp(pitch, -89.0f, 89.0f);
|
||||||
if (pitch < -89.0f) pitch = -89.0f;
|
|
||||||
|
|
||||||
float radYaw = yaw * static_cast<float>(IM_PI) / 180.0f;
|
float ry = glm::radians(yaw);
|
||||||
float radPitch = pitch * static_cast<float>(IM_PI) / 180.0f;
|
float rp = glm::radians(pitch);
|
||||||
|
cameraFront = glm::normalize(glm::vec3(
|
||||||
cameraFront[0] = cos(radYaw) * cos(radPitch);
|
cos(ry)*cos(rp),
|
||||||
cameraFront[1] = sin(radPitch);
|
sin(rp),
|
||||||
cameraFront[2] = sin(radYaw) * cos(radPitch);
|
sin(ry)*cos(rp)
|
||||||
float len = std::sqrt(cameraFront[0]*cameraFront[0] +
|
));
|
||||||
cameraFront[1]*cameraFront[1] +
|
|
||||||
cameraFront[2]*cameraFront[2]);
|
|
||||||
cameraFront[0] /= len;
|
|
||||||
cameraFront[1] /= len;
|
|
||||||
cameraFront[2] /= len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWwindow* initWindow(int width, int height, const char* title) {
|
static GLFWwindow* initWindow(int w, int h, const char* title) {
|
||||||
if (!glfwInit()) {
|
if (!glfwInit()) return nullptr;
|
||||||
std::cerr << "Failed to initialize GLFW\n";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
GLFWwindow* window = glfwCreateWindow(width, height, title, nullptr, nullptr);
|
#ifdef __APPLE__
|
||||||
if (!window) {
|
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||||
std::cerr << "Failed to create GLFW window\n";
|
#endif
|
||||||
|
GLFWwindow* win = glfwCreateWindow(w, h, title, nullptr, nullptr);
|
||||||
|
if (!win) { glfwTerminate(); return nullptr; }
|
||||||
|
glfwMakeContextCurrent(win);
|
||||||
|
glewExperimental = GL_TRUE;
|
||||||
|
if (glewInit() != GLEW_OK) {
|
||||||
|
glfwDestroyWindow(win);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
glfwMakeContextCurrent(window);
|
glViewport(0, 0, w, h);
|
||||||
glfwSetCursorPosCallback(window, mouse_callback);
|
glfwSetCursorPosCallback(win, mouse_callback);
|
||||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
// start with cursor disabled
|
||||||
|
glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
glewExperimental = GL_TRUE;
|
lastX = w / 2.0f;
|
||||||
if (glewInit() != GLEW_OK) {
|
lastY = h / 2.0f;
|
||||||
std::cerr << "GLEW Error\n";
|
return win;
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
GLFWwindow* window = initWindow(800, 600, "Voxel Game");
|
const int SCR_W = 1280, SCR_H = 720;
|
||||||
if (!window)
|
GLFWwindow* window = initWindow(SCR_W, SCR_H, "Voxel CSM");
|
||||||
return -1;
|
if (!window) return -1;
|
||||||
|
|
||||||
|
// ImGui
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
ImGuiIO &io = ImGui::GetIO(); (void)io;
|
|
||||||
ImGui::StyleColorsDark();
|
|
||||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||||
ImGui_ImplOpenGL3_Init("#version 330");
|
ImGui_ImplOpenGL3_Init("#version 330");
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
|
||||||
|
// Our game
|
||||||
VoxelGame game;
|
VoxelGame game;
|
||||||
if (!game.init()) {
|
if (!game.init(SCR_W, SCR_H)) {
|
||||||
std::cerr << "Failed to initialize game\n";
|
std::cerr << "Failed to init VoxelGame\n";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
float deltaTime = 0.0f, lastFrame = 0.0f;
|
float lastFrame = 0.0f;
|
||||||
static bool cursorEnabled = false;
|
bool cursorEnabled = false;
|
||||||
static bool f1Pressed = false;
|
bool f1Pressed = false;
|
||||||
|
|
||||||
while (!glfwWindowShouldClose(window)) {
|
while (!glfwWindowShouldClose(window)) {
|
||||||
float currentFrame = static_cast<float>(glfwGetTime());
|
float now = float(glfwGetTime());
|
||||||
deltaTime = currentFrame - lastFrame;
|
float dt = now - lastFrame;
|
||||||
lastFrame = currentFrame;
|
lastFrame = now;
|
||||||
|
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
// Toggle cursor lock with F1 (if ImGui isn’t capturing the mouse).
|
// Toggle cursor with F1 (when ImGui not capturing)
|
||||||
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !ImGui::GetIO().WantCaptureMouse) {
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !io.WantCaptureMouse) {
|
||||||
if (!f1Pressed) {
|
if (!f1Pressed) {
|
||||||
cursorEnabled = !cursorEnabled;
|
cursorEnabled = !cursorEnabled;
|
||||||
glfwSetInputMode(window, GLFW_CURSOR, cursorEnabled ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
|
glfwSetInputMode(window, GLFW_CURSOR,
|
||||||
|
cursorEnabled ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
|
||||||
f1Pressed = true;
|
f1Pressed = true;
|
||||||
firstMouse = true;
|
firstMouse = true; // reset for mouse callback
|
||||||
}
|
}
|
||||||
} else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) {
|
} else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) {
|
||||||
f1Pressed = false;
|
f1Pressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float cameraSpeed = 5.0f * deltaTime;
|
// WASD movement
|
||||||
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
|
float speed = 5.0f * dt;
|
||||||
cameraPos[0] += cameraFront[0] * cameraSpeed;
|
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
|
||||||
cameraPos[1] += cameraFront[1] * cameraSpeed;
|
cameraPos += cameraFront * speed;
|
||||||
cameraPos[2] += cameraFront[2] * cameraSpeed;
|
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
|
||||||
}
|
cameraPos -= cameraFront * speed;
|
||||||
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
|
glm::vec3 right = glm::normalize(glm::cross(cameraFront, cameraUp));
|
||||||
cameraPos[0] -= cameraFront[0] * cameraSpeed;
|
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
|
||||||
cameraPos[1] -= cameraFront[1] * cameraSpeed;
|
cameraPos -= right * speed;
|
||||||
cameraPos[2] -= cameraFront[2] * cameraSpeed;
|
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
|
||||||
}
|
cameraPos += right * speed;
|
||||||
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
|
|
||||||
float right[3] = {
|
|
||||||
cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1],
|
|
||||||
cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2],
|
|
||||||
cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0]
|
|
||||||
};
|
|
||||||
float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]);
|
|
||||||
right[0] /= rLen; right[1] /= rLen; right[2] /= rLen;
|
|
||||||
cameraPos[0] -= right[0] * cameraSpeed;
|
|
||||||
cameraPos[1] -= right[1] * cameraSpeed;
|
|
||||||
cameraPos[2] -= right[2] * cameraSpeed;
|
|
||||||
}
|
|
||||||
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
|
|
||||||
float right[3] = {
|
|
||||||
cameraFront[1]*cameraUp[2] - cameraFront[2]*cameraUp[1],
|
|
||||||
cameraFront[2]*cameraUp[0] - cameraFront[0]*cameraUp[2],
|
|
||||||
cameraFront[0]*cameraUp[1] - cameraFront[1]*cameraUp[0]
|
|
||||||
};
|
|
||||||
float rLen = std::sqrt(right[0]*right[0] + right[1]*right[1] + right[2]*right[2]);
|
|
||||||
right[0] /= rLen; right[1] /= rLen; right[2] /= rLen;
|
|
||||||
cameraPos[0] += right[0] * cameraSpeed;
|
|
||||||
cameraPos[1] += right[1] * cameraSpeed;
|
|
||||||
cameraPos[2] += right[2] * cameraSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Build view/proj
|
||||||
|
glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
|
||||||
|
glm::mat4 proj = glm::perspective(glm::radians(45.0f),
|
||||||
|
float(SCR_W)/SCR_H, 0.1f, 200.0f);
|
||||||
|
|
||||||
|
// Update & render
|
||||||
|
game.update(dt, cameraPos);
|
||||||
|
game.render(view, proj);
|
||||||
|
|
||||||
|
// Debug UI
|
||||||
ImGui_ImplOpenGL3_NewFrame();
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
ImGui_ImplGlfw_NewFrame();
|
ImGui_ImplGlfw_NewFrame();
|
||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
glMatrixMode(GL_PROJECTION);
|
|
||||||
glLoadIdentity();
|
|
||||||
gluPerspective(45.0, 800.0/600.0, 0.1, 200.0);
|
|
||||||
|
|
||||||
glMatrixMode(GL_MODELVIEW);
|
|
||||||
glLoadIdentity();
|
|
||||||
float centerX = cameraPos[0] + cameraFront[0];
|
|
||||||
float centerY = cameraPos[1] + cameraFront[1];
|
|
||||||
float centerZ = cameraPos[2] + cameraFront[2];
|
|
||||||
gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2],
|
|
||||||
centerX, centerY, centerZ,
|
|
||||||
cameraUp[0], cameraUp[1], cameraUp[2]);
|
|
||||||
|
|
||||||
game.update(deltaTime, cameraPos);
|
|
||||||
game.render();
|
|
||||||
game.debugUI();
|
game.debugUI();
|
||||||
|
|
||||||
ImGui::Render();
|
ImGui::Render();
|
||||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
|
||||||
glfwSwapBuffers(window);
|
glfwSwapBuffers(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
ImGui_ImplGlfw_Shutdown();
|
ImGui_ImplGlfw_Shutdown();
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user