570 lines
20 KiB
C++
570 lines
20 KiB
C++
#define STB_IMAGE_IMPLEMENTATION
|
||
#include "VoxelGame.h"
|
||
|
||
#include <iostream>
|
||
#include <cmath>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtc/type_ptr.hpp>
|
||
#include "stb_image.h"
|
||
#include "imgui.h"
|
||
|
||
#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];
|
||
}
|
||
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
|
||
|
||
void VoxelGame::greedyMesh(const Chunk &chunk,
|
||
std::vector<Vertex> &vertices,
|
||
std::vector<uint32_t> &indices)
|
||
{
|
||
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_CULL_FACE);
|
||
return true;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
|
||
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;
|
||
}
|
||
|
||
//----------------------------------------------------------------------
|
||
|
||
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::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();
|
||
}
|
||
}
|
||
}
|
||
|
||
// 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 = int(chunks.size());
|
||
}
|
||
|
||
//----------------------------------------------------------------------------
|
||
// 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();
|
||
}
|
||
|
||
// 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("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);
|
||
|
||
ImGui::End();
|
||
}
|
||
|
||
|
||
void VoxelGame::destroyMesh(ChunkMesh* m){
|
||
glDeleteBuffers(1,&m->vbo);
|
||
glDeleteBuffers(1,&m->ibo);
|
||
glDeleteVertexArrays(1,&m->vao);
|
||
}
|