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);
}