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",
|
||||
"*.c": "c",
|
||||
"*.scene": "yaml",
|
||||
"vector": "cpp"
|
||||
"vector": "cpp",
|
||||
"cmath": "cpp"
|
||||
}
|
||||
}
|
@ -1,208 +1,569 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "VoxelGame.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <GL/glew.h>
|
||||
#include <GL/glu.h>
|
||||
#include "stb_image.h"
|
||||
#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 "GreedyMesher.h"
|
||||
|
||||
namespace {
|
||||
// --- Perlin Noise Helpers ---
|
||||
float fade_(float t) {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
float lerp_(float a, float b, float t) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
float grad_(int hash, float x, float y) {
|
||||
int h = hash & 7;
|
||||
float u = h < 4 ? x : y;
|
||||
float v = h < 4 ? y : x;
|
||||
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
|
||||
}
|
||||
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) )
|
||||
);
|
||||
}
|
||||
#include <GLFW/glfw3.h>
|
||||
//----------------------------------------------------------------------
|
||||
// Perlin noise
|
||||
|
||||
static std::vector<int> perm,p;
|
||||
static std::once_flag initFlag;
|
||||
static void initPerlin(){
|
||||
perm.resize(256);
|
||||
std::iota(perm.begin(),perm.end(),0);
|
||||
std::mt19937 gen(1337);
|
||||
std::shuffle(perm.begin(),perm.end(),gen);
|
||||
p.resize(512);
|
||||
for(int i=0;i<512;++i) p[i]=perm[i&255];
|
||||
}
|
||||
|
||||
// Helper to safely query voxel (returns 0 if out of bounds)
|
||||
inline int voxelAt(const Chunk& c, int x, int y, int z) {
|
||||
if (x<0||y<0||z<0||x>=16||y>=16||z>=16) return 0;
|
||||
static inline float fade(float t){ return t*t*t*(t*(t*6-15)+10); }
|
||||
static inline float lerp_(float a,float b,float t){ return a+t*(b-a); }
|
||||
static inline float grad(int h,float x,float y,float z){
|
||||
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];
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Greedy mesh with normals
|
||||
|
||||
VoxelGame::VoxelGame()
|
||||
: textureGrass(0), textureDirt(0), textureWood(0),
|
||||
totalChunksLoaded(0), totalChunksEverLoaded(0)
|
||||
void VoxelGame::greedyMesh(const Chunk &chunk,
|
||||
std::vector<Vertex> &vertices,
|
||||
std::vector<uint32_t> &indices)
|
||||
{
|
||||
}
|
||||
|
||||
VoxelGame::~VoxelGame() {
|
||||
glDeleteTextures(1, &textureGrass);
|
||||
glDeleteTextures(1, &textureDirt);
|
||||
glDeleteTextures(1, &textureWood);
|
||||
}
|
||||
|
||||
|
||||
bool VoxelGame::init() {
|
||||
if (!loadTextures()) return false;
|
||||
// Initial camera position
|
||||
float cam[3] = {32,32,80};
|
||||
updateChunks(cam);
|
||||
// Build mesh for each chunk
|
||||
for (auto &p : chunks) {
|
||||
p.second.mesh = GreedyMesher::mesh(p.second.voxels);
|
||||
const int dims[3]={CHUNK_SIZE,CHUNK_HEIGHT,CHUNK_SIZE};
|
||||
for(int d=0;d<3;d++){
|
||||
int u=(d+1)%3, v=(d+2)%3;
|
||||
int x[3]={0,0,0}, q[3]={0,0,0}; q[d]=1;
|
||||
for(x[d]=-1;x[d]<dims[d];){
|
||||
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;
|
||||
int p0[3]={x[0],x[1],x[2]};
|
||||
int p1[3]={x[0]+q[0],x[1]+q[1],x[2]+q[2]};
|
||||
if(x[d]>=0) a=voxelAt(chunk,p0[0],p0[1],p0[2]);
|
||||
if(x[d]<dims[d]-1) b=voxelAt(chunk,p1[0],p1[1],p1[2]);
|
||||
mask[x[u]+dims[u]*x[v]] = ((a!=0)!=(b!=0)) ? ((a!=0)?a:-b) : 0;
|
||||
}
|
||||
}
|
||||
++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_TEXTURE_2D);
|
||||
glEnable(GL_CULL_FACE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VoxelGame::loadTextures() {
|
||||
int w,h,n;
|
||||
unsigned char* data;
|
||||
data = stbi_load("grass.jpg",&w,&h,&n,4);
|
||||
if (!data) { std::cerr<<"grass.jpg load fail\n"; return false; }
|
||||
glGenTextures(1,&textureGrass); glBindTexture(GL_TEXTURE_2D,textureGrass);
|
||||
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);
|
||||
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("dirt.jpg",&w,&h,&n,4);
|
||||
if (!data) { std::cerr<<"dirt.jpg load fail\n"; return false; }
|
||||
glGenTextures(1,&textureDirt); glBindTexture(GL_TEXTURE_2D,textureDirt);
|
||||
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);
|
||||
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);
|
||||
if (!data) { std::cerr<<"wood.png load fail\n"; return false; }
|
||||
glGenTextures(1,&textureWood); glBindTexture(GL_TEXTURE_2D,textureWood);
|
||||
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);
|
||||
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,data);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
stbi_image_free(data);
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
bool VoxelGame::loadTextures(){
|
||||
auto load=[&](const char*f,GLuint &t){
|
||||
int W,H,N; unsigned char*d=stbi_load(f,&W,&H,&N,4);
|
||||
if(!d) return false;
|
||||
glGenTextures(1,&t); glBindTexture(GL_TEXTURE_2D,t);
|
||||
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);
|
||||
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,W,H,0,GL_RGBA,GL_UNSIGNED_BYTE,d);
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
stbi_image_free(d);
|
||||
return true;
|
||||
};
|
||||
return load("dirt.jpg",textureDirt)
|
||||
&& load("grass.jpg",textureGrass)
|
||||
&& load("wood.png",textureWood);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
bool VoxelGame::loadShaders(){
|
||||
auto compile=[&](const char*s,GLenum t){
|
||||
GLuint sh=glCreateShader(t);
|
||||
glShaderSource(sh,1,&s,nullptr);
|
||||
glCompileShader(sh);
|
||||
return sh;
|
||||
};
|
||||
// depth shader
|
||||
const char* dvsrc = R"(
|
||||
#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;
|
||||
}
|
||||
|
||||
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)));
|
||||
float scale=0.1f;
|
||||
for(int x=0;x<16;x++)for(int z=0;z<16;z++){
|
||||
float wx = cx*16 + x, wz = cz*16 + z;
|
||||
float n = perlin(wx*scale, wz*scale);
|
||||
int h = int(((n+1)*0.5f)*16);
|
||||
h = std::clamp(h,0,16);
|
||||
for(int y=0;y<h;y++){
|
||||
c.voxels[x][y][z] = (y==h-1?1:2);
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void VoxelGame::initShadowMaps(){
|
||||
glGenFramebuffers(NUM_CASCADES,depthFBO);
|
||||
glGenTextures(NUM_CASCADES,depthTex);
|
||||
for(int i=0;i<NUM_CASCADES;i++){
|
||||
glBindTexture(GL_TEXTURE_2D,depthTex[i]);
|
||||
glTexImage2D(GL_TEXTURE_2D,0,GL_DEPTH_COMPONENT,
|
||||
SHADOW_MAP_SIZE,SHADOW_MAP_SIZE,
|
||||
0,GL_DEPTH_COMPONENT,GL_FLOAT,nullptr);
|
||||
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;
|
||||
}
|
||||
|
||||
void VoxelGame::updateChunks(const float cam[3]) {
|
||||
int cx = int(floor(cam[0]/16)), cz=int(floor(cam[2]/16));
|
||||
int r=2;
|
||||
std::unordered_set<ChunkKey,ChunkKeyHash> keep;
|
||||
for(int dx=-r;dx<=r;dx++)for(int dz=-r;dz<=r;dz++){
|
||||
ChunkKey key{cx+dx,cz+dz};
|
||||
keep.insert(key);
|
||||
if(chunks.find(key)==chunks.end()){
|
||||
Chunk nc = generateChunk(key.x,key.z);
|
||||
nc.mesh = GreedyMesher::mesh(nc.voxels);
|
||||
chunks[key]=std::move(nc);
|
||||
totalChunksEverLoaded++;
|
||||
|
||||
void VoxelGame::update(float dt, glm::vec3 const& camPos){
|
||||
lastDeltaTime=dt;
|
||||
fps=1.0f/dt;
|
||||
frameTimeSum+=dt; frameCount++;
|
||||
|
||||
int old=totalChunksEverLoaded;
|
||||
double t0=glfwGetTime();
|
||||
updateChunks(camPos);
|
||||
double t1=glfwGetTime();
|
||||
lastChunkGenTime=t1-t0;
|
||||
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);
|
||||
else ++it;
|
||||
|
||||
// Unload chunks that are out of range
|
||||
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(){
|
||||
for(auto &p:chunks){
|
||||
float ox = p.first.x*16, oz = p.first.z*16;
|
||||
for(auto &q:p.second.mesh){
|
||||
// shift by chunk offset
|
||||
float v0[3]={q.x+ox,q.y,q.z+oz},
|
||||
v1[3]={q.x+q.du[0]+ox,q.y+q.du[1],q.z+q.du[2]+oz},
|
||||
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));
|
||||
glBindTexture(GL_TEXTURE_2D,tex);
|
||||
glBegin(GL_QUADS);
|
||||
glTexCoord2f(0,0); glVertex3fv(v0);
|
||||
glTexCoord2f(1,0); glVertex3fv(v1);
|
||||
glTexCoord2f(1,1); glVertex3fv(v2);
|
||||
glTexCoord2f(0,1); glVertex3fv(v3);
|
||||
glEnd();
|
||||
// Copy voxel data under lock
|
||||
Chunk chunkCopy;
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(queueMutex);
|
||||
auto it = chunks.find(key);
|
||||
if (it == chunks.end()) continue;
|
||||
chunkCopy = it->second;
|
||||
}
|
||||
|
||||
// Phase 1: CPU‐side greedy meshing
|
||||
RawMesh rm;
|
||||
rm.key = key;
|
||||
greedyMesh(chunkCopy, rm.verts, rm.indices);
|
||||
|
||||
// Enqueue for main‐thread GPU upload
|
||||
{
|
||||
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(){
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
void VoxelGame::destroyMesh(ChunkMesh* m){
|
||||
glDeleteBuffers(1,&m->vbo);
|
||||
glDeleteBuffers(1,&m->ibo);
|
||||
glDeleteVertexArrays(1,&m->vao);
|
||||
}
|
||||
|
@ -1,58 +1,117 @@
|
||||
#ifndef VOXELGAME_H
|
||||
#define VOXELGAME_H
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <algorithm>
|
||||
#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)
|
||||
struct Chunk {
|
||||
int x, z; // Chunk grid coordinates
|
||||
// 3D voxel data: dimensions [16][16][16]
|
||||
std::vector<std::vector<std::vector<int>>> voxels;
|
||||
// **Merged mesh faces** produced by the greedy mesher
|
||||
std::vector<Quad> mesh;
|
||||
};
|
||||
#include <GL/glew.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// world dimensions
|
||||
static constexpr int CHUNK_SIZE = 16;
|
||||
static constexpr int CHUNK_HEIGHT = 64;
|
||||
|
||||
// A key for identifying a chunk by its grid coordinates.
|
||||
struct ChunkKey {
|
||||
int x, z;
|
||||
bool operator==(const ChunkKey &other) const {
|
||||
return x == other.x && z == other.z;
|
||||
int x,z;
|
||||
bool operator==(ChunkKey const& o) const { return x==o.x && z==o.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 {
|
||||
std::size_t operator()(const ChunkKey &key) const {
|
||||
return std::hash<int>()(key.x) ^ (std::hash<int>()(key.z) << 1);
|
||||
}
|
||||
struct Chunk {
|
||||
int x,z;
|
||||
// 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 {
|
||||
public:
|
||||
VoxelGame();
|
||||
~VoxelGame();
|
||||
|
||||
bool init();
|
||||
void update(float deltaTime, const float cameraPos[3]);
|
||||
void render();
|
||||
void debugUI();
|
||||
|
||||
private:
|
||||
bool loadTextures();
|
||||
Chunk generateChunk(int cx, int cz);
|
||||
void updateChunks(const float cameraPos[3]);
|
||||
void drawCube(float x, float y, float z, int voxelType);
|
||||
|
||||
std::unordered_map<ChunkKey, Chunk, ChunkKeyHash> chunks;
|
||||
int totalChunksLoaded;
|
||||
int totalChunksEverLoaded;
|
||||
|
||||
unsigned int textureGrass;
|
||||
unsigned int textureDirt;
|
||||
unsigned int textureWood;
|
||||
};
|
||||
|
||||
#endif // VOXELGAME_H
|
||||
// initialize with screen size (for cascades)
|
||||
bool init(int screenW,int screenH);
|
||||
void update(float dt, glm::vec3 const& camPos);
|
||||
void render(glm::mat4 const& view, glm::mat4 const& proj);
|
||||
void debugUI();
|
||||
|
||||
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 loadShaders();
|
||||
void initShadowMaps();
|
||||
void renderScene(GLuint prog);
|
||||
};
|
||||
|
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
|
||||
|
||||
[Window][Debug]
|
||||
Pos=492,48
|
||||
Size=214,119
|
||||
Pos=12,41
|
||||
Size=336,392
|
||||
Collapsed=0
|
||||
|
||||
|
@ -1,189 +1,160 @@
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
|
||||
#include <GL/glew.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <GL/glu.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h" // For Im_PI
|
||||
#include "imgui_impl_glfw.h"
|
||||
#include "imgui_impl_opengl3.h"
|
||||
|
||||
#include "VoxelGame.h"
|
||||
|
||||
// Global camera variables.
|
||||
float yaw = -90.0f;
|
||||
float pitch = 0.0f;
|
||||
float lastX = 400.0f, lastY = 300.0f;
|
||||
bool firstMouse = true;
|
||||
float cameraPos[3] = {32.0f, 32.0f, 80.0f};
|
||||
float cameraFront[3] = {0.0f, 0.0f, -1.0f};
|
||||
float cameraUp[3] = {0.0f, 1.0f, 0.0f};
|
||||
// camera globals
|
||||
static glm::vec3 cameraPos = {32.0f, 32.0f, 80.0f};
|
||||
static glm::vec3 cameraFront = {0.0f, 0.0f, -1.0f};
|
||||
static glm::vec3 cameraUp = {0.0f, 1.0f, 0.0f};
|
||||
static float yaw = -90.0f, pitch = 0.0f;
|
||||
static bool firstMouse = true;
|
||||
static float lastX, lastY;
|
||||
|
||||
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)
|
||||
return;
|
||||
return; // do not rotate if cursor is free
|
||||
|
||||
if (firstMouse) {
|
||||
lastX = static_cast<float>(xpos);
|
||||
lastY = static_cast<float>(ypos);
|
||||
lastX = float(xpos);
|
||||
lastY = float(ypos);
|
||||
firstMouse = false;
|
||||
}
|
||||
float xoffset = static_cast<float>(xpos) - lastX;
|
||||
float yoffset = lastY - static_cast<float>(ypos);
|
||||
lastX = static_cast<float>(xpos);
|
||||
lastY = static_cast<float>(ypos);
|
||||
|
||||
float sensitivity = 0.1f;
|
||||
xoffset *= sensitivity;
|
||||
yoffset *= sensitivity;
|
||||
|
||||
yaw += xoffset;
|
||||
pitch += yoffset;
|
||||
if (pitch > 89.0f) pitch = 89.0f;
|
||||
if (pitch < -89.0f) pitch = -89.0f;
|
||||
|
||||
float radYaw = yaw * static_cast<float>(IM_PI) / 180.0f;
|
||||
float radPitch = pitch * static_cast<float>(IM_PI) / 180.0f;
|
||||
|
||||
cameraFront[0] = cos(radYaw) * cos(radPitch);
|
||||
cameraFront[1] = sin(radPitch);
|
||||
cameraFront[2] = sin(radYaw) * cos(radPitch);
|
||||
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;
|
||||
float dx = float(xpos) - lastX;
|
||||
float dy = lastY - float(ypos);
|
||||
lastX = float(xpos);
|
||||
lastY = float(ypos);
|
||||
|
||||
const float sensitivity = 0.1f;
|
||||
dx *= sensitivity;
|
||||
dy *= sensitivity;
|
||||
|
||||
yaw += dx;
|
||||
pitch += dy;
|
||||
pitch = std::clamp(pitch, -89.0f, 89.0f);
|
||||
|
||||
float ry = glm::radians(yaw);
|
||||
float rp = glm::radians(pitch);
|
||||
cameraFront = glm::normalize(glm::vec3(
|
||||
cos(ry)*cos(rp),
|
||||
sin(rp),
|
||||
sin(ry)*cos(rp)
|
||||
));
|
||||
}
|
||||
|
||||
GLFWwindow* initWindow(int width, int height, const char* title) {
|
||||
if (!glfwInit()) {
|
||||
std::cerr << "Failed to initialize GLFW\n";
|
||||
return nullptr;
|
||||
}
|
||||
static GLFWwindow* initWindow(int w, int h, const char* title) {
|
||||
if (!glfwInit()) return nullptr;
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
|
||||
GLFWwindow* window = glfwCreateWindow(width, height, title, nullptr, nullptr);
|
||||
if (!window) {
|
||||
std::cerr << "Failed to create GLFW window\n";
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
#ifdef __APPLE__
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
|
||||
#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();
|
||||
return nullptr;
|
||||
}
|
||||
glfwMakeContextCurrent(window);
|
||||
glfwSetCursorPosCallback(window, mouse_callback);
|
||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||
|
||||
glewExperimental = GL_TRUE;
|
||||
if (glewInit() != GLEW_OK) {
|
||||
std::cerr << "GLEW Error\n";
|
||||
return nullptr;
|
||||
}
|
||||
return window;
|
||||
glViewport(0, 0, w, h);
|
||||
glfwSetCursorPosCallback(win, mouse_callback);
|
||||
// start with cursor disabled
|
||||
glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||
lastX = w / 2.0f;
|
||||
lastY = h / 2.0f;
|
||||
return win;
|
||||
}
|
||||
|
||||
int main() {
|
||||
GLFWwindow* window = initWindow(800, 600, "Voxel Game");
|
||||
if (!window)
|
||||
return -1;
|
||||
|
||||
const int SCR_W = 1280, SCR_H = 720;
|
||||
GLFWwindow* window = initWindow(SCR_W, SCR_H, "Voxel CSM");
|
||||
if (!window) return -1;
|
||||
|
||||
// ImGui
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO &io = ImGui::GetIO(); (void)io;
|
||||
ImGui::StyleColorsDark();
|
||||
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
||||
ImGui_ImplOpenGL3_Init("#version 330");
|
||||
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// Our game
|
||||
VoxelGame game;
|
||||
if (!game.init()) {
|
||||
std::cerr << "Failed to initialize game\n";
|
||||
if (!game.init(SCR_W, SCR_H)) {
|
||||
std::cerr << "Failed to init VoxelGame\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
float deltaTime = 0.0f, lastFrame = 0.0f;
|
||||
static bool cursorEnabled = false;
|
||||
static bool f1Pressed = false;
|
||||
|
||||
|
||||
float lastFrame = 0.0f;
|
||||
bool cursorEnabled = false;
|
||||
bool f1Pressed = false;
|
||||
|
||||
while (!glfwWindowShouldClose(window)) {
|
||||
float currentFrame = static_cast<float>(glfwGetTime());
|
||||
deltaTime = currentFrame - lastFrame;
|
||||
lastFrame = currentFrame;
|
||||
float now = float(glfwGetTime());
|
||||
float dt = now - lastFrame;
|
||||
lastFrame = now;
|
||||
|
||||
glfwPollEvents();
|
||||
|
||||
// Toggle cursor lock with F1 (if ImGui isn’t capturing the mouse).
|
||||
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !ImGui::GetIO().WantCaptureMouse) {
|
||||
|
||||
// Toggle cursor with F1 (when ImGui not capturing)
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_PRESS && !io.WantCaptureMouse) {
|
||||
if (!f1Pressed) {
|
||||
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;
|
||||
firstMouse = true;
|
||||
firstMouse = true; // reset for mouse callback
|
||||
}
|
||||
} else if (glfwGetKey(window, GLFW_KEY_F1) == GLFW_RELEASE) {
|
||||
f1Pressed = false;
|
||||
}
|
||||
|
||||
float cameraSpeed = 5.0f * deltaTime;
|
||||
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
|
||||
cameraPos[0] += cameraFront[0] * cameraSpeed;
|
||||
cameraPos[1] += cameraFront[1] * cameraSpeed;
|
||||
cameraPos[2] += cameraFront[2] * cameraSpeed;
|
||||
}
|
||||
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
|
||||
cameraPos[0] -= cameraFront[0] * cameraSpeed;
|
||||
cameraPos[1] -= cameraFront[1] * cameraSpeed;
|
||||
cameraPos[2] -= cameraFront[2] * cameraSpeed;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
// WASD movement
|
||||
float speed = 5.0f * dt;
|
||||
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
|
||||
cameraPos += cameraFront * speed;
|
||||
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
|
||||
cameraPos -= cameraFront * speed;
|
||||
glm::vec3 right = glm::normalize(glm::cross(cameraFront, cameraUp));
|
||||
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
|
||||
cameraPos -= right * speed;
|
||||
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
|
||||
cameraPos += right * speed;
|
||||
|
||||
// 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_ImplGlfw_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();
|
||||
|
||||
ImGui::Render();
|
||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
|
||||
// Cleanup
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user