OctaCore/src/engine/renderlights.cc

5354 lines
182 KiB
C++

#include "renderlights.hh"
#include <climits>
#include <algorithm>
#include <shared/command.hh>
#include <shared/glemu.hh>
#include <shared/igame.hh>
#include "aa.hh"
#include "dynlight.hh"
#include "light.hh"
#include "main.hh" // initwarning, fatal, timings
#include "material.hh"
#include "octaedit.hh" // editmode
#include "octarender.hh"
#include "pvs.hh"
#include "rendergl.hh"
#include "rendermodel.hh"
#include "renderparticles.hh"
#include "rendersky.hh"
#include "renderva.hh"
#include "stain.hh"
#include "texture.hh"
#include "world.hh"
#define CHANGE_SHADERS 0
int gw = -1, gh = -1, bloomw = -1, bloomh = -1, lasthdraccum = 0;
GLuint gfbo = 0, gdepthtex = 0, gcolortex = 0, gnormaltex = 0, gglowtex = 0;
static GLuint gdepthrb = 0, gstencilrb = 0;
bool gdepthinit = false;
int scalew = -1, scaleh = -1;
GLuint scalefbo[2] = { 0, 0 }, scaletex[2] = { 0, 0 };
GLuint hdrfbo = 0, hdrtex = 0, bloompbo = 0, bloomfbo[6] = { 0, 0, 0, 0, 0, 0 }, bloomtex[6] = { 0, 0, 0, 0, 0, 0 };
int hdrclear = 0;
GLuint refractfbo = 0, refracttex = 0;
GLenum bloomformat = 0, hdrformat = 0, stencilformat = 0;
bool hdrfloat = false;
GLuint msfbo = 0, msdepthtex = 0, mscolortex = 0, msnormaltex = 0, msglowtex = 0;
static GLuint msdepthrb = 0, msstencilrb = 0, mshdrfbo = 0, mshdrtex = 0, msrefractfbo = 0, msrefracttex = 0;
vector<vec2> msaapositions;
int aow = -1, aoh = -1;
GLuint aofbo[4] = { 0, 0, 0, 0 }, aotex[4] = { 0, 0, 0, 0 }, aonoisetex = 0;
matrix4 eyematrix, worldmatrix, linearworldmatrix, screenmatrix;
extern int amd_pf_bug;
int gethdrformat(int prec, int fallback = GL_RGB)
{
if(prec >= 3 && hasTF) return GL_RGB16F;
if(prec >= 2 && hasPF && !amd_pf_bug) return GL_R11F_G11F_B10F;
if(prec >= 1) return GL_RGB10;
return fallback;
}
extern int bloomsize, bloomprec;
void setupbloom(int w, int h)
{
int maxsize = ((1<<bloomsize)*5)/4;
while(w >= maxsize || h >= maxsize)
{
w /= 2;
h /= 2;
}
w = max(w, 1);
h = max(h, 1);
if(w == bloomw && h == bloomh) return;
bloomw = w;
bloomh = h;
loopi(5) if(!bloomtex[i]) glGenTextures(1, &bloomtex[i]);
loopi(5) if(!bloomfbo[i]) glGenFramebuffers_(1, &bloomfbo[i]);
bloomformat = gethdrformat(bloomprec);
createtexture(bloomtex[0], max(gw/2, bloomw), max(gh/2, bloomh), nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[1], max(gw/4, bloomw), max(gh/4, bloomh), nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[2], bloomw, bloomh, nullptr, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[3], bloomw, bloomh, nullptr, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
if(bloomformat != GL_RGB)
{
if(!bloomtex[5]) glGenTextures(1, &bloomtex[5]);
if(!bloomfbo[5]) glGenFramebuffers_(1, &bloomfbo[5]);
createtexture(bloomtex[5], bloomw, bloomh, nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
}
if(hwvtexunits < 4)
{
glGenBuffers_(1, &bloompbo);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, bloompbo);
glBufferData_(GL_PIXEL_PACK_BUFFER, 4*(hasTF ? sizeof(GLfloat) : sizeof(GLushort))*(hasTRG ? 1 : 3), nullptr, GL_DYNAMIC_COPY);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, 0);
}
static const uchar gray[12] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 };
static const float grayf[12] = { 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f };
createtexture(bloomtex[4], bloompbo ? 4 : 1, 1, hasTF ? (const void *)grayf : (const void *)gray, 3, 1, hasTF ? (hasTRG ? GL_R16F : GL_RGB16F) : (hasTRG ? GL_R16 : GL_RGB16));
loopi(5 + (bloomformat != GL_RGB ? 1 : 0))
{
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[i]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, i==4 ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE, bloomtex[i], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating bloom buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
}
void cleanupbloom()
{
if(bloompbo) { glDeleteBuffers_(1, &bloompbo); bloompbo = 0; }
loopi(6) if(bloomfbo[i]) { glDeleteFramebuffers_(1, &bloomfbo[i]); bloomfbo[i] = 0; }
loopi(6) if(bloomtex[i]) { glDeleteTextures(1, &bloomtex[i]); bloomtex[i] = 0; }
bloomw = bloomh = -1;
lasthdraccum = 0;
}
extern int ao, aotaps, aoreduce, aoreducedepth, aonoise, aobilateral, aobilateralupscale, aopackdepth, aodepthformat, aoprec, aoderivnormal;
static Shader *bilateralshader[2] = { nullptr, nullptr };
Shader *loadbilateralshader(int pass)
{
if(!aobilateral) return nullshader;
string opts;
int optslen = 0;
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1),
upscale = aoreduce && aobilateralupscale,
reduce = aoreduce && (upscale || (!linear && !aopackdepth));
if(reduce)
{
opts[optslen++] = 'r';
opts[optslen++] = '0' + aoreduce;
}
if(upscale) opts[optslen++] = 'u';
else if(linear) opts[optslen++] = 'l';
if(aopackdepth) opts[optslen++] = 'p';
opts[optslen] = '\0';
defformatstring(name, "bilateral%c%s%d", 'x' + pass, opts, aobilateral);
return generateshader(name, "bilateralshader \"%s\" %d %d", opts, aobilateral, reduce ? aoreduce : 0);
}
void loadbilateralshaders()
{
loopk(2) bilateralshader[k] = loadbilateralshader(k);
}
void clearbilateralshaders()
{
loopk(2) bilateralshader[k] = nullptr;
}
void setbilateralparams(int radius, float depth)
{
float sigma = blursigma*2*radius;
LOCALPARAMF(bilateralparams, 1.0f/(M_LN2*2*sigma*sigma), 1.0f/(M_LN2*depth*depth));
}
void setbilateralshader(int radius, int pass, float depth)
{
bilateralshader[pass]->set();
setbilateralparams(radius, depth);
}
static Shader *ambientobscuranceshader = nullptr;
Shader *loadambientobscuranceshader()
{
string opts;
int optslen = 0;
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1);
if(linear) opts[optslen++] = 'l';
if(aoderivnormal) opts[optslen++] = 'd';
if(aobilateral && aopackdepth) opts[optslen++] = 'p';
opts[optslen] = '\0';
defformatstring(name, "ambientobscurance%s%d", opts, aotaps);
return generateshader(name, "ambientobscuranceshader \"%s\" %d", opts, aotaps);
}
void loadaoshaders()
{
ambientobscuranceshader = loadambientobscuranceshader();
}
void clearaoshaders()
{
ambientobscuranceshader = nullptr;
}
void setupao(int w, int h)
{
int sw = w>>aoreduce, sh = h>>aoreduce;
if(sw == aow && sh == aoh) return;
aow = sw;
aoh = sh;
if(!aonoisetex) glGenTextures(1, &aonoisetex);
bvec *noise = new bvec[(1<<aonoise)*(1<<aonoise)];
loopk((1<<aonoise)*(1<<aonoise)) noise[k] = bvec(vec(rndscale(2)-1, rndscale(2)-1, 0).normalize());
createtexture(aonoisetex, 1<<aonoise, 1<<aonoise, noise, 0, 0, GL_RGB, GL_TEXTURE_2D);
delete[] noise;
bool upscale = aoreduce && aobilateral && aobilateralupscale;
GLenum format = aoprec && hasTRG ? GL_R8 : GL_RGBA8,
packformat = aobilateral && aopackdepth ? (aodepthformat ? GL_RG16F : GL_RGBA8) : format;
int packfilter = upscale && aopackdepth && !aodepthformat ? 0 : 1;
loopi(upscale ? 3 : 2)
{
if(!aotex[i]) glGenTextures(1, &aotex[i]);
if(!aofbo[i]) glGenFramebuffers_(1, &aofbo[i]);
createtexture(aotex[i], upscale && i ? w : aow, upscale && i >= 2 ? h : aoh, nullptr, 3, i < 2 ? packfilter : 1, i < 2 ? packformat : format, GL_TEXTURE_RECTANGLE);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[i]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, aotex[i], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating AO buffer!");
if(!upscale && packformat == GL_RG16F)
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
}
if(aoreducedepth && (aoreduce || aoreducedepth > 1))
{
if(!aotex[3]) glGenTextures(1, &aotex[3]);
if(!aofbo[3]) glGenFramebuffers_(1, &aofbo[3]);
createtexture(aotex[3], aow, aoh, nullptr, 3, 0, aodepthformat > 1 ? GL_R32F : (aodepthformat ? GL_R16F : GL_RGBA8), GL_TEXTURE_RECTANGLE);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[3]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, aotex[3], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating AO buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadaoshaders();
loadbilateralshaders();
}
void cleanupao()
{
loopi(4) if(aofbo[i]) { glDeleteFramebuffers_(1, &aofbo[i]); aofbo[i] = 0; }
loopi(4) if(aotex[i]) { glDeleteTextures(1, &aotex[i]); aotex[i] = 0; }
if(aonoisetex) { glDeleteTextures(1, &aonoisetex); aonoisetex = 0; }
aow = aoh = -1;
clearaoshaders();
clearbilateralshaders();
}
VARFP(ao, 0, 1, 1, { cleanupao(); cleardeferredlightshaders(); });
FVARR(aoradius, 0, 5, 256);
FVAR(aocutoff, 0, 2.0f, 1e3f);
FVARR(aodark, 1e-3f, 11.0f, 1e3f);
FVARR(aosharp, 1e-3f, 1, 1e3f);
FVAR(aoprefilterdepth, 0, 1, 1e3f);
FVARR(aomin, 0, 0.25f, 1);
VARFR(aosun, 0, 1, 1, cleardeferredlightshaders());
FVARR(aosunmin, 0, 0.5f, 1);
VARP(aoblur, 0, 4, 7);
VARP(aoiter, 0, 0, 4);
VARFP(aoreduce, 0, 1, 2, cleanupao());
VARF(aoreducedepth, 0, 1, 2, cleanupao());
VARFP(aofloatdepth, 0, 1, 2, initwarning("AO setup", INIT_LOAD, CHANGE_SHADERS));
VARFP(aoprec, 0, 1, 1, cleanupao());
VAR(aodepthformat, 1, 0, 0);
VARF(aonoise, 0, 5, 8, cleanupao());
VARFP(aobilateral, 0, 3, 10, cleanupao());
FVARP(aobilateraldepth, 0, 4, 1e3f);
VARFP(aobilateralupscale, 0, 0, 1, cleanupao());
VARF(aopackdepth, 0, 1, 1, cleanupao());
VARFP(aotaps, 1, 5, 12, cleanupao());
VARF(aoderivnormal, 0, 0, 1, cleanupao());
VAR(aoderiv, -1, 1, 1);
VAR(debugao, 0, 0, 1);
void initao()
{
aodepthformat = aofloatdepth && hasTRG && hasTF ? aofloatdepth : 0;
}
void viewao()
{
if(!ao) return;
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[2] ? aotex[2] : aotex[0]);
int tw = aotex[2] ? gw : aow, th = aotex[2] ? gh : aoh;
debugquad(0, 0, w, h, 0, 0, tw, th);
}
void renderao()
{
if(!ao) return;
timer *aotimer = begintimer("ambient obscurance");
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1);
float xscale = eyematrix.a.x, yscale = eyematrix.b.y;
if(linear)
{
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[3]);
glViewport(0, 0, aow, aoh);
SETSHADER(linearizedepth);
screenquad(vieww, viewh);
xscale *= float(vieww)/aow;
yscale *= float(viewh)/aoh;
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[3]);
}
ambientobscuranceshader->set();
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[0]);
glViewport(0, 0, aow, aoh);
glActiveTexture_(GL_TEXTURE1);
if(aoderivnormal)
{
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
}
else
{
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gnormaltex);
LOCALPARAM(normalmatrix, matrix3(cammatrix));
}
glActiveTexture_(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, aonoisetex);
glActiveTexture_(GL_TEXTURE0);
LOCALPARAMF(tapparams, aoradius*eyematrix.d.z/xscale, aoradius*eyematrix.d.z/yscale, aoradius*aoradius*aocutoff*aocutoff);
LOCALPARAMF(contrastparams, (2.0f*aodark)/aotaps, aosharp);
LOCALPARAMF(offsetscale, xscale/eyematrix.d.z, yscale/eyematrix.d.z, eyematrix.d.x/eyematrix.d.z, eyematrix.d.y/eyematrix.d.z);
LOCALPARAMF(prefilterdepth, aoprefilterdepth);
if(aoderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, aoderiv ? GL_NICEST : GL_FASTEST);
screenquad(vieww, viewh, aow/float(1<<aonoise), aoh/float(1<<aonoise));
if(aoderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_DONT_CARE);
if(aobilateral)
{
if(aoreduce && aobilateralupscale) loopi(2)
{
setbilateralshader(aobilateral, i, aobilateraldepth);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[i+1]);
glViewport(0, 0, vieww, i ? viewh : aoh);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i]);
glActiveTexture_(GL_TEXTURE1);
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
screenquad(vieww, viewh, i ? vieww : aow, aoh);
}
else loopi(2 + 2*aoiter)
{
setbilateralshader(aobilateral, i%2, aobilateraldepth);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[(i+1)%2]);
glViewport(0, 0, aow, aoh);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i%2]);
glActiveTexture_(GL_TEXTURE1);
if(linear) glBindTexture(GL_TEXTURE_RECTANGLE, aotex[3]);
else if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
screenquad(vieww, viewh);
}
}
else if(aoblur)
{
float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1];
setupblurkernel(aoblur, blurweights, bluroffsets);
loopi(2 + 2*aoiter)
{
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[(i+1)%2]);
glViewport(0, 0, aow, aoh);
setblurshader(i%2, 1, aoblur, blurweights, bluroffsets, GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i%2]);
screenquad(aow, aoh);
}
}
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
endtimer(aotimer);
}
void cleanupscale()
{
loopi(2) if(scalefbo[i]) { glDeleteFramebuffers_(1, &scalefbo[i]); scalefbo[i] = 0; }
loopi(2) if(scaletex[i]) { glDeleteTextures(1, &scaletex[i]); scaletex[i] = 0; }
scalew = scaleh = -1;
}
extern int gscalecubic, gscalenearest;
void setupscale(int sw, int sh, int w, int h)
{
scalew = w;
scaleh = h;
loopi(gscalecubic ? 2 : 1)
{
if(!scaletex[i]) glGenTextures(1, &scaletex[i]);
if(!scalefbo[i]) glGenFramebuffers_(1, &scalefbo[i]);
glBindFramebuffer_(GL_FRAMEBUFFER, scalefbo[i]);
createtexture(scaletex[i], sw, i ? h : sh, nullptr, 3, gscalecubic || !gscalenearest ? 1 : 0, GL_RGB, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, scaletex[i], 0);
if(!i) bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating scale buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(gscalecubic)
{
useshaderbyname("scalecubicx");
useshaderbyname("scalecubicy");
}
}
GLuint shouldscale()
{
return scalefbo[0];
}
void doscale(GLuint outfbo)
{
if(!scaletex[0]) return;
timer *scaletimer = begintimer("scaling");
if(gscalecubic)
{
glBindFramebuffer_(GL_FRAMEBUFFER, scalefbo[1]);
glViewport(0, 0, gw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[0]);
SETSHADER(scalecubicy);
screenquad(gw, gh);
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, hudw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[1]);
SETSHADER(scalecubicx);
screenquad(gw, hudh);
}
else
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, hudw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[0]);
SETSHADER(scalelinear);
screenquad(gw, gh);
}
endtimer(scaletimer);
}
VARFP(glineardepth, 0, 0, 3, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VAR(gdepthformat, 1, 0, 0);
VARF(gstencil, 0, 0, 1, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VARF(gdepthstencil, 0, 2, 2, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VAR(ghasstencil, 1, 0, 0);
VARFP(msaa, 0, 0, 16, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaadepthstencil, 0, 2, 2, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaastencil, 0, 0, 1, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaaedgedetect, 0, 1, 1, cleanupgbuffer());
VARFP(msaalineardepth, -1, -1, 3, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARFP(msaatonemap, 0, 0, 1, cleanupgbuffer());
VARF(msaatonemapblit, 0, 0, 1, cleanupgbuffer());
VAR(msaamaxsamples, 1, 0, 0);
VAR(msaamaxdepthtexsamples, 1, 0, 0);
VAR(msaamaxcolortexsamples, 1, 0, 0);
VAR(msaaminsamples, 1, 0, 0);
VAR(msaasamples, 1, 0, 0);
VAR(msaalight, 1, 0, 0);
VARF(msaapreserve, -1, 0, 1, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
void checkmsaasamples()
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
GLint samples;
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaaminsamples, GL_RGBA8, 1, 1, GL_TRUE);
glGetTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &samples);
msaasamples = samples;
glDeleteTextures(1, &tex);
}
void initgbuffer()
{
msaamaxsamples = msaamaxdepthtexsamples = msaamaxcolortexsamples = msaaminsamples = msaasamples = msaalight = 0;
msaapositions.setsize(0);
if(hasFBMS && hasFBB && hasTMS)
{
GLint val;
glGetIntegerv(GL_MAX_SAMPLES, &val);
msaamaxsamples = val;
glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &val);
msaamaxdepthtexsamples = val;
glGetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &val);
msaamaxcolortexsamples = val;
}
int maxsamples = min(msaamaxsamples, msaamaxcolortexsamples), reqsamples = min(msaa, maxsamples);
if(reqsamples >= 2)
{
msaaminsamples = 2;
while(msaaminsamples*2 <= reqsamples) msaaminsamples *= 2;
}
int lineardepth = glineardepth;
if(msaaminsamples)
{
if(msaamaxdepthtexsamples < msaaminsamples)
{
if(msaalineardepth > 0) lineardepth = msaalineardepth;
else if(!lineardepth) lineardepth = 1;
}
else if(msaalineardepth >= 0) lineardepth = msaalineardepth;
}
if(lineardepth > 1 && (!hasAFBO || !hasTF || !hasTRG)) gdepthformat = 1;
else gdepthformat = lineardepth;
if(msaaminsamples)
{
ghasstencil = (msaadepthstencil > 1 || (msaadepthstencil && gdepthformat)) && hasDS ? 2 : (msaastencil ? 1 : 0);
checkmsaasamples();
if(msaapreserve >= 0) msaalight = hasMSS ? 3 : (msaasamples==2 ? 2 : msaapreserve);
}
else ghasstencil = (gdepthstencil > 1 || (gdepthstencil && gdepthformat)) && hasDS ? 2 : (gstencil ? 1 : 0);
initao();
}
VARF(forcepacknorm, 0, 0, 1, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
bool usepacknorm() { return forcepacknorm || msaasamples || !useavatarmask(); }
ICOMMAND(usepacknorm, "", (), intret(usepacknorm() ? 1 : 0));
void maskgbuffer(const char *mask)
{
GLenum drawbufs[4];
int numbufs = 0;
while(*mask) switch(*mask++)
{
case 'c': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT0; break;
case 'n': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT1; break;
case 'd': if(gdepthformat) drawbufs[numbufs++] = GL_COLOR_ATTACHMENT3; break;
case 'g': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT2; break;
}
glDrawBuffers_(numbufs, drawbufs);
}
extern int hdrprec, gscale;
void cleanupmsbuffer()
{
if(msfbo) { glDeleteFramebuffers_(1, &msfbo); msfbo = 0; }
if(msdepthtex) { glDeleteTextures(1, &msdepthtex); msdepthtex = 0; }
if(mscolortex) { glDeleteTextures(1, &mscolortex); mscolortex = 0; }
if(msnormaltex) { glDeleteTextures(1, &msnormaltex); msnormaltex = 0; }
if(msglowtex) { glDeleteTextures(1, &msglowtex); msglowtex = 0; }
if(msstencilrb) { glDeleteRenderbuffers_(1, &msstencilrb); msstencilrb = 0; }
if(msdepthrb) { glDeleteRenderbuffers_(1, &msdepthrb); msdepthrb = 0; }
if(mshdrfbo) { glDeleteFramebuffers_(1, &mshdrfbo); mshdrfbo = 0; }
if(mshdrtex) { glDeleteTextures(1, &mshdrtex); mshdrtex = 0; }
if(msrefractfbo) { glDeleteFramebuffers_(1, &msrefractfbo); msrefractfbo = 0; }
if(msrefracttex) { glDeleteTextures(1, &msrefracttex); msrefracttex = 0; }
}
void bindmsdepth()
{
if(gdepthformat)
{
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msdepthrb);
if(ghasstencil > 1) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msdepthrb);
else if(msaalight && ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msstencilrb);
}
else
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
if(ghasstencil > 1) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
else if(msaalight && ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msstencilrb);
}
}
void setupmsbuffer(int w, int h)
{
if(!msfbo) glGenFramebuffers_(1, &msfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, msfbo);
stencilformat = ghasstencil > 1 ? GL_DEPTH24_STENCIL8 : (ghasstencil ? GL_STENCIL_INDEX8 : 0);
if(gdepthformat)
{
if(!msdepthrb) glGenRenderbuffers_(1, &msdepthrb);
glBindRenderbuffer_(GL_RENDERBUFFER, msdepthrb);
glRenderbufferStorageMultisample_(GL_RENDERBUFFER, msaasamples, ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24, w, h);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(msaalight && ghasstencil == 1)
{
if(!msstencilrb) glGenRenderbuffers_(1, &msstencilrb);
glBindRenderbuffer_(GL_RENDERBUFFER, msstencilrb);
glRenderbufferStorageMultisample_(GL_RENDERBUFFER, msaasamples, GL_STENCIL_INDEX8, w, h);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msdepthtex) glGenTextures(1, &msdepthtex);
if(!mscolortex) glGenTextures(1, &mscolortex);
if(!msnormaltex) glGenTextures(1, &msnormaltex);
maskgbuffer(msaalight ? "cndg" : "cnd");
static const GLenum depthformats[] = { GL_RGBA8, GL_R16F, GL_R32F };
GLenum depthformat = gdepthformat ? depthformats[gdepthformat-1] : (ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, depthformat, w, h, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mscolortex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
if(msaalight)
{
if(!msglowtex) glGenTextures(1, &msglowtex);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msglowtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, hasAFBO ? hdrformat : GL_RGBA8, w, h, GL_TRUE);
}
bindmsdepth();
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mscolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, msnormaltex, 0);
if(msaalight) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, msglowtex, 0);
if(gdepthformat) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
if(msaalight && hasAFBO)
{
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msglowtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, msglowtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA g-buffer!");
}
else fatal("failed allocating MSAA g-buffer!");
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (ghasstencil ? GL_STENCIL_BUFFER_BIT : 0));
msaapositions.setsize(0);
loopi(msaasamples)
{
GLfloat vals[2];
glGetMultisamplefv_(GL_SAMPLE_POSITION, i, vals);
msaapositions.add(vec2(vals[0], vals[1]));
}
if(msaalight)
{
if(!mshdrtex) glGenTextures(1, &mshdrtex);
if(!mshdrfbo) glGenFramebuffers_(1, &mshdrfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, mshdrfbo);
bindmsdepth();
hdrformat = 0;
for(int prec = hdrprec; prec >= 0; prec--)
{
GLenum format = gethdrformat(prec);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glGetError();
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, format, w, h, GL_TRUE);
if(glGetError() == GL_NO_ERROR)
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mshdrtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
hdrformat = format;
break;
}
}
}
if(!hdrformat || glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA HDR buffer!");
if(!msrefracttex) glGenTextures(1, &msrefracttex);
if(!msrefractfbo) glGenFramebuffers_(1, &msrefractfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, msrefractfbo);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msrefracttex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGB, w, h, GL_TRUE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msrefracttex, 0);
bindmsdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA refraction buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
useshaderbyname("msaaedgedetect");
useshaderbyname("msaaresolve");
useshaderbyname("msaareducew");
useshaderbyname("msaareduce");
if(!msaalight) useshaderbyname("msaaresolvedepth");
if(msaalight > 1 && msaatonemap)
{
useshaderbyname("msaatonemap");
if(msaalight > 2) useshaderbyname("msaatonemapsample");
}
}
void bindgdepth()
{
if(gdepthformat || msaalight)
{
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gdepthrb);
if(ghasstencil > 1) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gdepthrb);
else if(!msaalight || ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gstencilrb);
}
else
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
if(ghasstencil > 1) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
else if(ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gstencilrb);
}
}
void setupgbuffer()
{
int sw = renderw, sh = renderh;
if(gscale != 100)
{
sw = max((renderw*gscale + 99)/100, 1);
sh = max((renderh*gscale + 99)/100, 1);
}
if(gw == sw && gh == sh && ((sw >= hudw && sh >= hudh && !scalefbo[0]) || (scalew == hudw && scaleh == hudh))) return;
cleanupscale();
cleanupbloom();
cleanupao();
cleanupvolumetric();
cleanupaa();
cleanuppostfx();
gw = sw;
gh = sh;
hdrformat = gethdrformat(hdrprec);
stencilformat = ghasstencil > 1 ? GL_DEPTH24_STENCIL8 : (ghasstencil ? GL_STENCIL_INDEX8 : 0);
if(msaasamples) setupmsbuffer(gw, gh);
hdrfloat = floatformat(hdrformat);
hdrclear = 3;
gdepthinit = false;
if(gdepthformat || msaalight)
{
if(!gdepthrb) glGenRenderbuffers_(1, &gdepthrb);
glBindRenderbuffer_(GL_RENDERBUFFER, gdepthrb);
glRenderbufferStorage_(GL_RENDERBUFFER, ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24, gw, gh);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msaalight && ghasstencil == 1)
{
if(!gstencilrb) glGenRenderbuffers_(1, &gstencilrb);
glBindRenderbuffer_(GL_RENDERBUFFER, gstencilrb);
glRenderbufferStorage_(GL_RENDERBUFFER, GL_STENCIL_INDEX8, gw, gh);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msaalight)
{
if(!gdepthtex) glGenTextures(1, &gdepthtex);
if(!gcolortex) glGenTextures(1, &gcolortex);
if(!gnormaltex) glGenTextures(1, &gnormaltex);
if(!gglowtex) glGenTextures(1, &gglowtex);
if(!gfbo) glGenFramebuffers_(1, &gfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, gfbo);
maskgbuffer("cndg");
static const GLenum depthformats[] = { GL_RGBA8, GL_R16F, GL_R32F };
GLenum depthformat = gdepthformat ? depthformats[gdepthformat-1] : (ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24);
createtexture(gdepthtex, gw, gh, nullptr, 3, 0, depthformat, GL_TEXTURE_RECTANGLE);
createtexture(gcolortex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
createtexture(gnormaltex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
createtexture(gglowtex, gw, gh, nullptr, 3, 0, hasAFBO ? hdrformat : GL_RGBA8, GL_TEXTURE_RECTANGLE);
bindgdepth();
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, gcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE, gnormaltex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_RECTANGLE, gglowtex, 0);
if(gdepthformat) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
if(hasAFBO)
{
createtexture(gglowtex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_RECTANGLE, gglowtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating g-buffer!");
}
else fatal("failed allocating g-buffer!");
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (ghasstencil ? GL_STENCIL_BUFFER_BIT : 0));
}
if(!hdrtex) glGenTextures(1, &hdrtex);
if(!hdrfbo) glGenFramebuffers_(1, &hdrfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
createtexture(hdrtex, gw, gh, nullptr, 3, 1, hdrformat, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, hdrtex, 0);
bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating HDR buffer!");
if(!msaalight || (msaalight > 2 && msaatonemap && msaatonemapblit))
{
if(!refracttex) glGenTextures(1, &refracttex);
if(!refractfbo) glGenFramebuffers_(1, &refractfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, refractfbo);
createtexture(refracttex, gw, gh, nullptr, 3, 0, GL_RGB, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, refracttex, 0);
bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating refraction buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(gw < hudw || gh < hudh) setupscale(gw, gh, hudw, hudh);
}
void cleanupgbuffer()
{
if(gfbo) { glDeleteFramebuffers_(1, &gfbo); gfbo = 0; }
if(gdepthtex) { glDeleteTextures(1, &gdepthtex); gdepthtex = 0; }
if(gcolortex) { glDeleteTextures(1, &gcolortex); gcolortex = 0; }
if(gnormaltex) { glDeleteTextures(1, &gnormaltex); gnormaltex = 0; }
if(gglowtex) { glDeleteTextures(1, &gglowtex); gglowtex = 0; }
if(gstencilrb) { glDeleteRenderbuffers_(1, &gstencilrb); gstencilrb = 0; }
if(gdepthrb) { glDeleteRenderbuffers_(1, &gdepthrb); gdepthrb = 0; }
if(hdrfbo) { glDeleteFramebuffers_(1, &hdrfbo); hdrfbo = 0; }
if(hdrtex) { glDeleteTextures(1, &hdrtex); hdrtex = 0; }
if(refractfbo) { glDeleteFramebuffers_(1, &refractfbo); refractfbo = 0; }
if(refracttex) { glDeleteTextures(1, &refracttex); refracttex = 0; }
gw = gh = -1;
cleanupscale();
cleanupmsbuffer();
cleardeferredlightshaders();
}
VAR(msaadepthblit, 0, 0, 1);
void resolvemsaadepth(int w = vieww, int h = viewh)
{
if(!msaasamples || msaalight) return;
timer *resolvetimer = drawtex ? nullptr : begintimer("msaa depth resolve");
if(msaadepthblit)
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, msfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, gfbo);
if(ghasstencil) glClear(GL_STENCIL_BUFFER_BIT);
glBlitFramebuffer_(0, 0, w, h, 0, 0, w, h, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
}
if(!msaadepthblit || gdepthformat)
{
glBindFramebuffer_(GL_FRAMEBUFFER, gfbo);
glViewport(0, 0, w, h);
maskgbuffer("d");
if(!msaadepthblit)
{
if(ghasstencil)
{
glStencilFunc(GL_ALWAYS, 0, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_STENCIL_TEST);
}
glDepthFunc(GL_ALWAYS);
SETSHADER(msaaresolvedepth);
}
else
{
glDisable(GL_DEPTH_TEST);
SETSHADER(msaaresolve);
}
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
screenquad();
maskgbuffer("cnd");
if(!msaadepthblit)
{
if(ghasstencil) glDisable(GL_STENCIL_TEST);
glDepthFunc(GL_LESS);
}
else glEnable(GL_DEPTH_TEST);
}
endtimer(resolvetimer);
}
void resolvemsaacolor(int w = vieww, int h = viewh)
{
if(!msaalight) return;
timer *resolvetimer = drawtex ? nullptr : begintimer("msaa resolve");
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
endtimer(resolvetimer);
}
FVAR(bloomthreshold, 1e-3f, 0.85f, 1e3f);
FVARP(bloomscale, 0, 1.0f, 1e3f);
VARP(bloomblur, 0, 7, 7);
VARP(bloomiter, 0, 0, 4);
VARFP(bloomsize, 6, 9, 11, cleanupbloom());
VARFP(bloomprec, 0, 2, 3, cleanupbloom());
FVAR(hdraccumscale, 0, 0.98f, 1);
VAR(hdraccummillis, 1, 33, 1000);
VAR(hdrreduce, 0, 2, 2);
VARFP(hdrprec, 0, 2, 3, cleanupgbuffer());
FVARFP(hdrgamma, 1e-3f, 2, 1e3f, initwarning("HDR setup", INIT_LOAD, CHANGE_SHADERS));
FVARR(hdrbright, 1e-4f, 1.0f, 1e4f);
FVAR(hdrsaturate, 1e-3f, 0.85f, 1e3f);
FVAR(hdrminexposure, 0, 0.03f, 1);
FVAR(hdrmaxexposure, 0, 0.3f, 1);
VARFP(gscale, 25, 100, 100, cleanupgbuffer());
VARFP(gscalecubic, 0, 0, 1, cleanupgbuffer());
VARFP(gscalenearest, 0, 0, 1, cleanupgbuffer());
FVARFP(gscalecubicsoft, 0, 0, 1, initwarning("scaling setup", INIT_LOAD, CHANGE_SHADERS));
float ldrscale = 1.0f, ldrscaleb = 1.0f/255;
void copyhdr(int sw, int sh, GLuint fbo, int dw, int dh, bool flipx, bool flipy, bool swapxy)
{
if(!dw) dw = sw;
if(!dh) dh = sh;
if(msaalight) resolvemsaacolor(sw, sh);
GLERROR;
glBindFramebuffer_(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, dw, dh);
SETSHADER(reorient);
vec reorientx(flipx ? -0.5f : 0.5f, 0, 0.5f), reorienty(0, flipy ? -0.5f : 0.5f, 0.5f);
if(swapxy) swap(reorientx, reorienty);
reorientx.mul(sw);
reorienty.mul(sh);
LOCALPARAM(reorientx, reorientx);
LOCALPARAM(reorienty, reorienty);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
screenquad();
GLERROR;
hdrclear = 3;
}
void loadhdrshaders(int aa)
{
switch(aa)
{
case AA_LUMA:
useshaderbyname("hdrtonemapluma");
useshaderbyname("hdrnopluma");
if(msaalight > 1 && msaatonemap) useshaderbyname("msaatonemapluma");
break;
case AA_MASKED:
if(!msaasamples && ghasstencil) useshaderbyname("hdrtonemapstencil");
else
{
useshaderbyname("hdrtonemapmasked");
useshaderbyname("hdrnopmasked");
if(msaalight > 1 && msaatonemap) useshaderbyname("msaatonemapmasked");
}
break;
case AA_SPLIT:
useshaderbyname("msaatonemapsplit");
break;
case AA_SPLIT_LUMA:
useshaderbyname("msaatonemapsplitluma");
break;
case AA_SPLIT_MASKED:
useshaderbyname("msaatonemapsplitmasked");
break;
default:
break;
}
}
void processhdr(GLuint outfbo, int aa)
{
timer *hdrtimer = begintimer("hdr processing");
GLOBALPARAMF(hdrparams, hdrbright, hdrsaturate, bloomthreshold, bloomscale);
GLuint b0fbo = bloomfbo[1], b0tex = bloomtex[1], b1fbo = bloomfbo[0], b1tex = bloomtex[0], ptex = hdrtex;
int b0w = max(vieww/4, bloomw), b0h = max(viewh/4, bloomh), b1w = max(vieww/2, bloomw), b1h = max(viewh/2, bloomh),
pw = vieww, ph = viewh;
if(msaalight)
{
if(aa < AA_SPLIT && (msaalight <= 1 || !msaatonemap))
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, vieww, viewh, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
else if(hasFBMSBS && (vieww > bloomw || viewh > bloomh))
{
int cw = max(vieww/2, bloomw), ch = max(viewh/2, bloomh);
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, cw, ch, GL_COLOR_BUFFER_BIT, GL_SCALED_RESOLVE_FASTEST_EXT);
pw = cw;
ph = ch;
}
else
{
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
if(vieww/2 >= bloomw)
{
pw = vieww/2;
if(viewh/2 >= bloomh)
{
ph = viewh/2;
glViewport(0, 0, pw, ph);
SETSHADER(msaareduce);
}
else
{
glViewport(0, 0, pw, viewh);
SETSHADER(msaareducew);
}
}
else
{
glViewport(0, 0, vieww, viewh);
SETSHADER(msaaresolve);
}
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
screenquad(vieww, viewh);
}
}
if(hdrreduce) while(pw > bloomw || ph > bloomh)
{
GLuint cfbo = b1fbo, ctex = b1tex;
int cw = max(pw/2, bloomw), ch = max(ph/2, bloomh);
if(hdrreduce > 1 && cw/2 >= bloomw)
{
cw /= 2;
if(ch/2 >= bloomh)
{
ch /= 2;
SETSHADER(hdrreduce2);
}
else SETSHADER(hdrreduce2w);
}
else SETSHADER(hdrreduce);
if(cw == bloomw && ch == bloomh) { if(bloomfbo[5]) { cfbo = bloomfbo[5]; ctex = bloomtex[5]; } else { cfbo = bloomfbo[2]; ctex = bloomtex[2]; } }
glBindFramebuffer_(GL_FRAMEBUFFER, cfbo);
glViewport(0, 0, cw, ch);
glBindTexture(GL_TEXTURE_RECTANGLE, ptex);
screenquad(pw, ph);
ptex = ctex;
pw = cw;
ph = ch;
swap(b0fbo, b1fbo);
swap(b0tex, b1tex);
swap(b0w, b1w);
swap(b0h, b1h);
}
if(!lasthdraccum || lastmillis - lasthdraccum >= hdraccummillis)
{
GLuint ltex = ptex;
int lw = pw, lh = ph;
for(int i = 0; lw > 2 || lh > 2; i++)
{
int cw = max(lw/2, 2), ch = max(lh/2, 2);
if(hdrreduce > 1 && cw/2 >= 2)
{
cw /= 2;
if(ch/2 >= 2)
{
ch /= 2;
if(i) SETSHADER(hdrreduce2); else SETSHADER(hdrluminance2);
}
else if(i) SETSHADER(hdrreduce2w); else SETSHADER(hdrluminance2w);
}
else if(i) SETSHADER(hdrreduce); else SETSHADER(hdrluminance);
glBindFramebuffer_(GL_FRAMEBUFFER, b1fbo);
glViewport(0, 0, cw, ch);
glBindTexture(GL_TEXTURE_RECTANGLE, ltex);
screenquad(lw, lh);
ltex = b1tex;
lw = cw;
lh = ch;
swap(b0fbo, b1fbo);
swap(b0tex, b1tex);
swap(b0w, b1w);
swap(b0h, b1h);
}
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[4]);
glViewport(0, 0, bloompbo ? 4 : 1, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
SETSHADER(hdraccum);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
LOCALPARAMF(accumscale, lasthdraccum ? pow(hdraccumscale, float(lastmillis - lasthdraccum)/hdraccummillis) : 0);
screenquad(2, 2);
glDisable(GL_BLEND);
if(bloompbo)
{
glBindBuffer_(GL_PIXEL_PACK_BUFFER, bloompbo);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, 4, 1, hasTRG ? GL_RED : GL_RGB, hasTF ? GL_FLOAT : GL_UNSIGNED_SHORT, nullptr);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, 0);
}
lasthdraccum = lastmillis;
}
if(bloompbo)
{
gle::bindvbo(bloompbo);
gle::enablecolor();
gle::colorpointer(hasTF ? sizeof(GLfloat) : sizeof(GLushort), (const void *)0, hasTF ? GL_FLOAT : GL_UNSIGNED_SHORT, 1);
gle::clearvbo();
}
b0fbo = bloomfbo[3];
b0tex = bloomtex[3];
b1fbo = bloomfbo[2];
b1tex = bloomtex[2];
b0w = b1w = bloomw;
b0h = b1h = bloomh;
glActiveTexture_(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, bloomtex[4]);
glActiveTexture_(GL_TEXTURE0);
glBindFramebuffer_(GL_FRAMEBUFFER, b0fbo);
glViewport(0, 0, b0w, b0h);
SETSHADER(hdrbloom);
glBindTexture(GL_TEXTURE_RECTANGLE, ptex);
screenquad(pw, ph);
if(bloomblur)
{
float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1];
setupblurkernel(bloomblur, blurweights, bluroffsets);
loopi(2 + 2*bloomiter)
{
glBindFramebuffer_(GL_FRAMEBUFFER, b1fbo);
glViewport(0, 0, b1w, b1h);
setblurshader(i%2, 1, bloomblur, blurweights, bluroffsets, GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
screenquad(b0w, b0h);
swap(b0w, b1w);
swap(b0h, b1h);
swap(b0tex, b1tex);
swap(b0fbo, b1fbo);
}
}
if(aa >= AA_SPLIT)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
switch(aa)
{
case AA_SPLIT_LUMA: SETSHADER(msaatonemapsplitluma); break;
case AA_SPLIT_MASKED:
SETSHADER(msaatonemapsplitmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(msaatonemapsplit); break;
}
screenquad(vieww, viewh, b0w, b0h);
}
else if(msaalight <= 1 || !msaatonemap)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
switch(aa)
{
case AA_LUMA: SETSHADER(hdrtonemapluma); break;
case AA_MASKED:
if(!msaasamples && ghasstencil)
{
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 0, 0x80);
glEnable(GL_STENCIL_TEST);
SETSHADER(hdrtonemap);
screenquad(vieww, viewh, b0w, b0h);
glStencilFunc(GL_EQUAL, 0x80, 0x80);
SETSHADER(hdrtonemapstencil);
screenquad(vieww, viewh, b0w, b0h);
glDisable(GL_STENCIL_TEST);
goto done;
}
SETSHADER(hdrtonemapmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(hdrtonemap); break;
}
screenquad(vieww, viewh, b0w, b0h);
}
else
{
bool blit = msaalight > 2 && msaatonemapblit && (!aa || !outfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, blit ? msrefractfbo : outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
if(blit) SETSHADER(msaatonemapsample);
else switch(aa)
{
case AA_LUMA: SETSHADER(msaatonemapluma); break;
case AA_MASKED:
SETSHADER(msaatonemapmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(msaatonemap); break;
}
screenquad(vieww, viewh, b0w, b0h);
if(blit)
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, msrefractfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, aa || !outfbo ? refractfbo : outfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, vieww, viewh, GL_COLOR_BUFFER_BIT, GL_NEAREST);
if(!outfbo)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
if(!blit) SETSHADER(hdrnop);
else switch(aa)
{
case AA_LUMA: SETSHADER(hdrnopluma); break;
case AA_MASKED:
SETSHADER(hdrnopmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(hdrnop); break;
}
glBindTexture(GL_TEXTURE_RECTANGLE, refracttex);
screenquad(vieww, viewh);
}
}
}
done:
if(bloompbo) gle::disablecolor();
endtimer(hdrtimer);
}
static void getavglum(int *numargs, ident *id)
{
if(!bloomfbo[4]) return;
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[4]);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
float avglum = -1;
glReadPixels(0, 0, 1, 1, GL_RED, GL_FLOAT, &avglum);
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(avglum < 0) return;
avglum *= 4;
if(*numargs < 0) floatret(avglum);
else printfvar(id, avglum);
}
COMMANDN(avglum, getavglum, "N$");
VAR(debugbloom, 0, 0, 1);
static void viewbloom()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, bloomtex[3]);
debugquad(0, 0, w, h, 0, 0, bloomw, bloomh);
}
VAR(debugdepth, 0, 0, 1);
void viewdepth()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
VAR(debugstencil, 0, 0, 0xFF);
void viewstencil()
{
if(!ghasstencil || !hdrfbo) return;
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
glViewport(0, 0, gw, gh);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glStencilFunc(GL_NOTEQUAL, 0, debugstencil);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_STENCIL_TEST);
SETSHADER(hudnotexture);
gle::colorf(1, 1, 1);
debugquad(0, 0, hudw, hudh, 0, 0, gw, gh);
glDisable(GL_STENCIL_TEST);
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
glViewport(0, 0, hudw, hudh);
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
VAR(debugrefract, 0, 0, 1);
void viewrefract()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, refracttex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
#define RH_MAXSPLITS 4
#define RH_MAXGRID 64
GLuint rhtex[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }, rhrb[4] = { 0, 0, 0, 0 }, rhfbo = 0;
uint rhclearmasks[2][RH_MAXSPLITS][(RH_MAXGRID+2+31)/32];
GLuint rsmdepthtex = 0, rsmcolortex = 0, rsmnormaltex = 0, rsmfbo = 0;
extern int rhrect, rhgrid, rhsplits, rhborder, rhprec, rhtaps, rhcache, rhforce, rsmprec, rsmdepthprec, rsmsize;
static Shader *radiancehintsshader = nullptr;
Shader *rsmworldshader = nullptr;
Shader *loadradiancehintsshader()
{
defformatstring(name, "radiancehints%d", rhtaps);
return generateshader(name, "radiancehintsshader %d", rhtaps);
}
void loadrhshaders()
{
if(rhborder) useshaderbyname("radiancehintsborder");
if(rhcache) useshaderbyname("radiancehintscached");
useshaderbyname("radiancehintsdisable");
radiancehintsshader = loadradiancehintsshader();
rsmworldshader = useshaderbyname("rsmworld");
useshaderbyname("rsmsky");
}
void clearrhshaders()
{
radiancehintsshader = nullptr;
rsmworldshader = nullptr;
}
void setupradiancehints()
{
GLenum rhformat = hasTF && rhprec >= 1 ? GL_RGBA16F : GL_RGBA8;
loopi(!rhrect && rhcache ? 8 : 4)
{
if(!rhtex[i]) glGenTextures(1, &rhtex[i]);
create3dtexture(rhtex[i], rhgrid+2*rhborder, rhgrid+2*rhborder, (rhgrid+2*rhborder)*rhsplits, nullptr, 7, 1, rhformat);
if(rhborder)
{
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
GLfloat border[4] = { 0.5f, 0.5f, 0.5f, 0 };
glTexParameterfv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, border);
}
}
if(!rhfbo) glGenFramebuffers_(1, &rhfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, rhfbo);
if(rhrect) loopi(4)
{
if(!rhrb[i]) glGenRenderbuffers_(1, &rhrb[i]);
glBindRenderbuffer_(GL_RENDERBUFFER, rhrb[i]);
glRenderbufferStorage_(GL_RENDERBUFFER, rhformat, (rhgrid + 2*rhborder)*(rhgrid + 2*rhborder), (rhgrid + 2*rhborder)*rhsplits);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, rhrb[i]);
}
else loopi(4) glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_3D, rhtex[i], 0, 0);
static const GLenum drawbufs[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers_(4, drawbufs);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating radiance hints buffer!");
if(!rsmdepthtex) glGenTextures(1, &rsmdepthtex);
if(!rsmcolortex) glGenTextures(1, &rsmcolortex);
if(!rsmnormaltex) glGenTextures(1, &rsmnormaltex);
if(!rsmfbo) glGenFramebuffers_(1, &rsmfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, rsmfbo);
GLenum rsmformat = gethdrformat(rsmprec, GL_RGBA8);
createtexture(rsmdepthtex, rsmsize, rsmsize, nullptr, 3, 0, rsmdepthprec > 1 ? GL_DEPTH_COMPONENT32 : (rsmdepthprec ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16), GL_TEXTURE_RECTANGLE);
createtexture(rsmcolortex, rsmsize, rsmsize, nullptr, 3, 0, rsmformat, GL_TEXTURE_RECTANGLE);
createtexture(rsmnormaltex, rsmsize, rsmsize, nullptr, 3, 0, rsmformat, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_RECTANGLE, rsmdepthtex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, rsmcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE, rsmnormaltex, 0);
glDrawBuffers_(2, drawbufs);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating RSM buffer!");
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadrhshaders();
clearradiancehintscache();
}
void cleanupradiancehints()
{
clearradiancehintscache();
loopi(8) if(rhtex[i]) { glDeleteTextures(1, &rhtex[i]); rhtex[i] = 0; }
loopi(4) if(rhrb[i]) { glDeleteRenderbuffers_(1, &rhrb[i]); rhrb[i] = 0; }
if(rhfbo) { glDeleteFramebuffers_(1, &rhfbo); rhfbo = 0; }
if(rsmdepthtex) { glDeleteTextures(1, &rsmdepthtex); rsmdepthtex = 0; }
if(rsmcolortex) { glDeleteTextures(1, &rsmcolortex); rsmcolortex = 0; }
if(rsmnormaltex) { glDeleteTextures(1, &rsmnormaltex); rsmnormaltex = 0; }
if(rsmfbo) { glDeleteFramebuffers_(1, &rsmfbo); rsmfbo = 0; }
clearrhshaders();
}
VARF(rhrect, 0, 0, 1, cleanupradiancehints());
VARF(rhsplits, 1, 2, RH_MAXSPLITS, { cleardeferredlightshaders(); cleanupradiancehints(); });
VARF(rhborder, 0, 1, 1, cleanupradiancehints());
VARF(rsmsize, 64, 384, 2048, cleanupradiancehints());
VARF(rhnearplane, 1, 1, 16, clearradiancehintscache());
VARF(rhfarplane, 64, 1024, 16384, clearradiancehintscache());
FVARF(rsmpradiustweak, 1e-3f, 1, 1e3f, clearradiancehintscache());
FVARF(rhpradiustweak, 1e-3f, 1, 1e3f, clearradiancehintscache());
FVARF(rsmdepthrange, 0, 1024, 1e6f, clearradiancehintscache());
FVARF(rsmdepthmargin, 0, 0.1f, 1e3f, clearradiancehintscache());
VARFP(rhprec, 0, 0, 1, cleanupradiancehints());
VARFP(rsmprec, 0, 0, 3, cleanupradiancehints());
VARFP(rsmdepthprec, 0, 0, 2, cleanupradiancehints());
FVAR(rhnudge, 0, 0.5f, 4);
FVARF(rhworldbias, 0, 0.5f, 10, clearradiancehintscache());
FVARF(rhsplitweight, 0.20f, 0.6f, 0.95f, clearradiancehintscache());
VARF(rhgrid, 3, 27, RH_MAXGRID, cleanupradiancehints());
FVARF(rsmspread, 0, 0.15f, 1, clearradiancehintscache());
VAR(rhclipgrid, 0, 1, 1);
VARF(rhcache, 0, 1, 1, cleanupradiancehints());
VARF(rhforce, 0, 0, 1, cleanupradiancehints());
VAR(rsmcull, 0, 1, 1);
VARFP(rhtaps, 0, 20, 32, cleanupradiancehints());
VAR(rhdyntex, 0, 0, 1);
VAR(rhdynmm, 0, 0, 1);
VARFR(gidist, 0, 384, 1024, { clearradiancehintscache(); cleardeferredlightshaders(); if(!gidist) cleanupradiancehints(); });
FVARFR(giscale, 0, 1.5f, 1e3f, { cleardeferredlightshaders(); if(!giscale) cleanupradiancehints(); });
FVARR(giaoscale, 0, 3, 1e3f);
VARFP(gi, 0, 1, 1, { cleardeferredlightshaders(); cleanupradiancehints(); });
VAR(debugrsm, 0, 0, 2);
void viewrsm()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, debugrsm == 2 ? rsmnormaltex : rsmcolortex);
debugquad(x, y, w, h, 0, 0, rsmsize, rsmsize);
}
VAR(debugrh, -1, 0, RH_MAXSPLITS*(RH_MAXGRID + 2));
void viewrh()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
gle::colorf(1, 1, 1);
if(debugrh < 0 && rhrect)
{
SETSHADER(hudrect);
glBindTexture(GL_TEXTURE_RECTANGLE, rhtex[5]);
float tw = (rhgrid+2*rhborder)*(rhgrid+2*rhborder), th = (rhgrid+2*rhborder)*rhsplits;
gle::defvertex(2);
gle::deftexcoord0(2);
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x, y); gle::attribf(0, 0);
gle::attribf(x+w, y); gle::attribf(tw, 0);
gle::attribf(x, y+h); gle::attribf(0, th);
gle::attribf(x+w, y+h); gle::attribf(tw, th);
gle::end();
}
else
{
SETSHADER(hud3d);
glBindTexture(GL_TEXTURE_3D, rhtex[1]);
float z = (max(debugrh, 1)-1+0.5f)/float((rhgrid+2*rhborder)*rhsplits);
gle::defvertex(2);
gle::deftexcoord0(3);
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x, y); gle::attribf(0, 0, z);
gle::attribf(x+w, y); gle::attribf(1, 0, z);
gle::attribf(x, y+h); gle::attribf(0, 1, z);
gle::attribf(x+w, y+h); gle::attribf(1, 1, z);
gle::end();
}
}
#define SHADOWATLAS_SIZE 4096
PackNode shadowatlaspacker(0, 0, SHADOWATLAS_SIZE, SHADOWATLAS_SIZE);
extern int smminradius;
struct lightinfo
{
int ent, shadowmap;
ushort flags, batched;
vec o, color;
float radius, dist;
vec dir, spotx, spoty;
int spot;
float sx1, sy1, sx2, sy2, sz1, sz2;
occludequery *query;
lightinfo() {}
lightinfo(const vec &o, const vec &color, float radius, ushort flags = 0, const vec &dir = vec(0, 0, 0), int spot = 0)
: ent(-1), shadowmap(-1), flags(flags), batched(~0),
o(o), color(color), radius(radius), dist(camera1->o.dist(o)),
dir(dir), spot(spot), query(nullptr)
{
if(spot > 0) calcspot();
calcscissor();
}
lightinfo(int i, const extentity &e)
: ent(i), shadowmap(-1), flags(e.attr5), batched(~0),
o(e.o), color(vec(e.attr2, e.attr3, e.attr4).max(0)), radius(e.attr1), dist(camera1->o.dist(e.o)),
dir(0, 0, 0), spot(0), query(nullptr)
{
if(e.attached && e.attached->type == ET_SPOTLIGHT)
{
dir = vec(e.attached->o).sub(e.o).normalize();
spot = std::clamp(int(e.attached->attr1), 1, 89);
calcspot();
}
calcscissor();
}
void calcspot()
{
quat orient(dir, vec(0, 0, dir.z < 0 ? -1 : 1));
spotx = orient.invertedrotate(vec(1, 0, 0));
spoty = orient.invertedrotate(vec(0, 1, 0));
}
bool noshadow() const { return flags&L_NOSHADOW || radius <= smminradius; }
bool nospec() const { return (flags&L_NOSPEC) != 0; }
bool volumetric() const { return (flags&L_VOLUMETRIC) != 0; }
bool colorshadow() const { return (flags&L_SMALPHA) != 0; }
void addscissor(float &dx1, float &dy1, float &dx2, float &dy2) const
{
dx1 = min(dx1, sx1);
dy1 = min(dy1, sy1);
dx2 = max(dx2, sx2);
dy2 = max(dy2, sy2);
}
void addscissor(float &dx1, float &dy1, float &dx2, float &dy2, float &dz1, float &dz2) const
{
addscissor(dx1, dy1, dx2, dy2);
dz1 = min(dz1, sz1);
dz2 = max(dz2, sz2);
}
bool validscissor() const { return sx1 < sx2 && sy1 < sy2 && sz1 < sz2; }
void calcscissor()
{
sx1 = sy1 = sz1 = -1;
sx2 = sy2 = sz2 = 1;
if(spot > 0) calcspotscissor(o, radius, dir, spot, spotx, spoty, sx1, sy1, sx2, sy2, sz1, sz2);
else calcspherescissor(o, radius, sx1, sy1, sx2, sy2, sz1, sz2);
}
bool checkquery() const { return query && query->owner == this && ::checkquery(query); }
void calcbb(vec &bbmin, vec &bbmax)
{
if(spot > 0)
{
float spotscale = radius * tan360(spot);
vec up = vec(spotx).mul(spotscale).abs(), right = vec(spoty).mul(spotscale).abs(), center = vec(dir).mul(radius).add(o);
bbmin = bbmax = center;
bbmin.sub(up).sub(right);
bbmax.add(up).add(right);
bbmin.min(o);
bbmax.max(o);
}
else
{
bbmin = vec(o).sub(radius);
bbmax = vec(o).add(radius);
}
}
};
struct shadowcachekey
{
vec o;
float radius;
vec dir;
int spot;
shadowcachekey() {}
shadowcachekey(const lightinfo &l) : o(l.o), radius(l.radius), dir(l.dir), spot(l.spot) {}
};
static inline uint hthash(const shadowcachekey &k)
{
return hthash(k.o);
}
static inline bool htcmp(const shadowcachekey &x, const shadowcachekey &y)
{
return x.o == y.o && x.radius == y.radius && x.dir == y.dir && x.spot == y.spot;
}
struct shadowcacheval;
struct shadowmapinfo
{
ushort x, y, size;
uchar sidemask, transparent;
int light;
shadowcacheval *cached;
};
struct shadowcacheval
{
ushort x, y, size;
uchar sidemask, transparent;
shadowcacheval() {}
shadowcacheval(const shadowmapinfo &sm) : x(sm.x), y(sm.y), size(sm.size), sidemask(sm.sidemask), transparent(sm.transparent) {}
};
struct shadowcache : hashtable<shadowcachekey, shadowcacheval>
{
shadowcache() : hashtable<shadowcachekey, shadowcacheval>(256) {}
void reset()
{
clear();
}
};
extern int smcache, smfilter, smgather, smalpha, smalphaprec, alphashadow;
#define SHADOWCACHE_EVICT 2
GLuint shadowatlastex = 0, shadowatlasfbo = 0;
GLuint shadowcolortex = 0, shadowblanktex = 0;
GLuint shadowfiltertex = 0, shadowfilterfbo = 0;
GLenum shadowatlastarget = GL_NONE;
vector<uint> shadowcolorclears, shadowcolorblurs;
int smalign = 0;
shadowcache shadowcache;
bool shadowcachefull = false;
int evictshadowcache = 0;
Shader *smalphaworldshader = nullptr;
extern int usetexgather;
static inline bool usegatherforsm() { return smfilter > 1 && smgather && hasTG && usetexgather; }
static inline bool usesmcomparemode() { return !usegatherforsm() || (hasTG && hasGPU5 && usetexgather > 1); }
static void loadsmshaders()
{
if(smalpha && alphashadow)
{
smalphaworldshader = useshaderbyname("smalphaworld");
if(smfilter)
{
useshaderbyname("smalphaclear");
useshaderbyname(usegatherforsm() ? "smalphablur2d" : "smalphablurrect");
}
}
}
static void clearsmshaders()
{
smalphaworldshader = nullptr;
}
static inline void setsmnoncomparemode() // use texture gather
{
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
static inline void setsmcomparemode() // use embedded shadow cmp
{
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
VAR(debugshadowatlas, 0, 0, 3);
void viewshadowatlas()
{
bool color = debugshadowatlas > 1 && shadowcolortex && (debugshadowatlas <= 2 || shadowfiltertex);
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
float tw = 1, th = 1;
if((color && debugshadowatlas > 2) || shadowatlastarget == GL_TEXTURE_RECTANGLE)
{
tw = shadowatlaspacker.w;
th = shadowatlaspacker.h;
if(debugshadowatlas > 2) { tw /= 2; th /= 2; }
SETSHADER(hudrect);
}
else hudshader->set();
gle::colorf(1, 1, 1);
if(color)
{
if(debugshadowatlas > 2) glBindTexture(GL_TEXTURE_RECTANGLE, shadowcolortex);
else glBindTexture(shadowatlastarget, shadowcolortex);
}
else
{
glBindTexture(shadowatlastarget, shadowatlastex);
if(usesmcomparemode()) setsmnoncomparemode();
}
debugquad(x, y, w, h, 0, 0, tw, th);
if(!color && usesmcomparemode()) setsmcomparemode();
}
extern int smdepthprec, smsize;
void setupshadowatlas()
{
int size = min((1<<smsize), hwtexsize);
shadowatlaspacker.resize(size, size);
if(!shadowatlastex) glGenTextures(1, &shadowatlastex);
shadowatlastarget = usegatherforsm() ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE;
createtexture(shadowatlastex, shadowatlaspacker.w, shadowatlaspacker.h, nullptr, 3, 1, smdepthprec > 1 ? GL_DEPTH_COMPONENT32 : (smdepthprec ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16), shadowatlastarget);
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
smalign = 0;
if(smalpha && alphashadow)
{
if(!shadowcolortex) glGenTextures(1, &shadowcolortex);
GLenum colcomp = smalphaprec > 1 ? GL_RGB10 : (smalphaprec ? (hasES2 ? GL_RGB565 : GL_RGB5) : GL_R3_G3_B2);
createtexture(shadowcolortex, shadowatlaspacker.w, shadowatlaspacker.h, nullptr, 3, 1, colcomp, shadowatlastarget);
if(!shadowblanktex) glGenTextures(1, &shadowblanktex);
static const uchar blank[4] = {255, 255, 255, 255};
createtexture(shadowblanktex, 1, 1, blank, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
if(smfilter)
{
smalign = 1;
if(!shadowfiltertex) glGenTextures(1, &shadowfiltertex);
createtexture(shadowfiltertex, shadowatlaspacker.w/2, shadowatlaspacker.h/2, nullptr, 3, 1, colcomp, GL_TEXTURE_RECTANGLE);
if(!shadowfilterfbo) glGenFramebuffers_(1, &shadowfilterfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, shadowfilterfbo);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, shadowfiltertex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("Failed allocating shadow filter buffer!");
}
}
if(!shadowatlasfbo) glGenFramebuffers_(1, &shadowatlasfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, shadowatlasfbo);
if(shadowcolortex) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, shadowatlastarget, shadowcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowatlastarget, shadowatlastex, 0);
if(!shadowcolortex) glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating shadow atlas!");
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadsmshaders();
}
void cleanupshadowatlas()
{
if(shadowatlastex) { glDeleteTextures(1, &shadowatlastex); shadowatlastex = 0; }
if(shadowcolortex) { glDeleteTextures(1, &shadowcolortex); shadowcolortex = 0; }
if(shadowblanktex) { glDeleteTextures(1, &shadowblanktex); shadowblanktex = 0; }
if(shadowatlasfbo) { glDeleteFramebuffers_(1, &shadowatlasfbo); shadowatlasfbo = 0; }
if(shadowfiltertex) { glDeleteTextures(1, &shadowfiltertex); shadowfiltertex = 0; }
if(shadowfilterfbo) { glDeleteFramebuffers_(1, &shadowfilterfbo); shadowfilterfbo = 0; }
clearshadowcache();
clearsmshaders();
}
const matrix4 cubeshadowviewmatrix[6] =
{
// sign-preserving cubemap projections
matrix4(vec(0, 0, 1), vec(0, 1, 0), vec(-1, 0, 0)), // +X
matrix4(vec(0, 0, 1), vec(0, 1, 0), vec( 1, 0, 0)), // -X
matrix4(vec(1, 0, 0), vec(0, 0, 1), vec(0, -1, 0)), // +Y
matrix4(vec(1, 0, 0), vec(0, 0, 1), vec(0, 1, 0)), // -Y
matrix4(vec(1, 0, 0), vec(0, 1, 0), vec(0, 0, -1)), // +Z
matrix4(vec(1, 0, 0), vec(0, 1, 0), vec(0, 0, 1)) // -Z
};
FVAR(smpolyfactor, -1e3f, 1, 1e3f);
FVAR(smpolyoffset, -1e3f, 0, 1e3f);
FVAR(smbias, -1e6f, 0.01f, 1e6f);
FVAR(smpolyfactor2, -1e3f, 1.5f, 1e3f);
FVAR(smpolyoffset2, -1e3f, 0, 1e3f);
FVAR(smbias2, -1e6f, 0.02f, 1e6f);
FVAR(smprec, 1e-3f, 1, 1e3f);
FVAR(smcubeprec, 1e-3f, 1, 1e3f);
FVAR(smspotprec, 1e-3f, 1, 1e3f);
VARFP(smsize, 10, 12, 14, cleanupshadowatlas());
VARFP(smdepthprec, 0, 0, 2, cleanupshadowatlas());
VAR(smsidecull, 0, 1, 1);
VAR(smviscull, 0, 1, 1);
VAR(smborder, 0, 3, 16);
VAR(smborder2, 0, 4, 16);
VAR(smminradius, 0, 16, 10000);
VAR(smminsize, 1, 96, 1024);
VAR(smmaxsize, 1, 384, 1024);
//VAR(smmaxsize, 1, 4096, 4096);
VAR(smused, 1, 0, 0);
VAR(smquery, 0, 1, 1);
VARF(smcullside, 0, 1, 1, cleanupshadowatlas());
VARF(smcache, 0, 1, 2, cleanupshadowatlas());
VARFP(smfilter, 0, 2, 3, { cleardeferredlightshaders(); cleanupshadowatlas(); cleanupvolumetric(); });
VARFP(smgather, 0, 0, 1, { cleardeferredlightshaders(); cleanupshadowatlas(); cleanupvolumetric(); });
VAR(smnoshadow, 0, 0, 1);
VAR(smdynshadow, 0, 1, 1);
VARF(smalpha, 0, 2, 2, { cleardeferredlightshaders(); cleanupshadowatlas(); });
VARFP(smalphaprec, 0, 0, 2, cleanupshadowatlas());
VAR(lightpassesused, 1, 0, 0);
VAR(lightsvisible, 1, 0, 0);
VAR(lightsoccluded, 1, 0, 0);
VARN(lightbatches, lightbatchesused, 1, 0, 0);
VARN(lightbatchrects, lightbatchrectsused, 1, 0, 0);
VARN(lightbatchstacks, lightbatchstacksused, 1, 0, 0);
VARFR(alphashadow, 0, 0, 2, { cleardeferredlightshaders(); cleanupshadowatlas(); });
FVARFR(alphashadowscale, 0, 1, 2, clearshadowcache());
enum
{
MAXLIGHTTILEBATCH = 8
};
VARF(lighttilebatch, 0, MAXLIGHTTILEBATCH, MAXLIGHTTILEBATCH, cleardeferredlightshaders());
VARF(batchsunlight, 0, 2, 2, cleardeferredlightshaders());
int shadowmapping = 0;
struct lightrect
{
uchar x1, y1, x2, y2;
lightrect() {}
lightrect(uchar x1, uchar y1, uchar x2, uchar y2) : x1(x1), y1(y1), x2(x2), y2(y2) {}
lightrect(const lightinfo &l)
{
calctilebounds(l.sx1, l.sy1, l.sx2, l.sy2, x1, y1, x2, y2);
}
bool outside(const lightrect &o) const
{
return x1 >= o.x2 || x2 <= o.x1 || y1 >= o.y2 || y2 <= o.y1;
}
bool inside(const lightrect &o) const
{
return x1 >= o.x1 && x2 <= o.x2 && y1 >= o.y1 && y2 <= o.y2;
}
void intersect(const lightrect &o)
{
x1 = max(x1, o.x1);
y1 = max(y1, o.y1);
x2 = min(x2, o.x2);
y2 = min(y2, o.y2);
}
bool overlaps(int tx1, int ty1, int tx2, int ty2, const uint *tilemask) const
{
if(int(x2) <= tx1 || int(x1) >= tx2 || int(y2) <= ty1 || int(y1) >= ty2) return false;
if(!tilemask) return true;
uint xmask = (1<<x2) - (1<<x1);
for(int y = max(int(y1), ty1), end = min(int(y2), ty2); y < end; y++) if(tilemask[y] & xmask) return true;
return false;
}
};
enum
{
BF_SPOTLIGHT = 1<<0,
BF_NOSHADOW = 1<<1,
BF_SMALPHA = 1<<2,
BF_NOSUN = 1<<3
};
struct lightbatchkey
{
uchar flags, numlights;
ushort lights[MAXLIGHTTILEBATCH];
};
struct lightbatch : lightbatchkey
{
vector<lightrect> rects;
void reset()
{
rects.setsize(0);
}
bool overlaps(int tx1, int ty1, int tx2, int ty2, const uint *tilemask) const
{
if(!tx1 && !ty1 && tx2 >= lighttilew && ty2 >= lighttileh && !tilemask) return true;
loopv(rects) if(rects[i].overlaps(tx1, ty1, tx2, ty2, tilemask)) return true;
return false;
}
};
static inline void htrecycle(lightbatch &l)
{
l.reset();
}
static inline uint hthash(const lightbatchkey &l)
{
uint h = 0;
loopi(l.numlights) h = ((h<<8)+h)^l.lights[i];
return h;
}
static inline bool htcmp(const lightbatchkey &x, const lightbatchkey &y)
{
return x.flags == y.flags &&
x.numlights == y.numlights &&
(!x.numlights || !memcmp(x.lights, y.lights, x.numlights*sizeof(x.lights[0])));
}
vector<lightinfo> lights;
vector<int> lightorder;
hashset<lightbatch> lightbatcher(128);
vector<lightbatch *> lightbatches;
vector<shadowmapinfo> shadowmaps;
void clearshadowcache()
{
shadowmaps.setsize(0);
clearradiancehintscache();
clearshadowmeshes();
}
static shadowmapinfo *addshadowmap(ushort x, ushort y, int size, int &idx, int light = -1, shadowcacheval *cached = nullptr)
{
idx = shadowmaps.length();
shadowmapinfo *sm = &shadowmaps.add();
sm->x = x;
sm->y = y;
sm->size = size;
sm->light = light;
sm->sidemask = 0;
sm->transparent = 0;
sm->cached = cached;
return sm;
}
#define CSM_MAXSPLITS 8
VARF(csmmaxsize, 256, 768, 2048, clearshadowcache());
VARF(csmsplits, 1, 3, CSM_MAXSPLITS, { cleardeferredlightshaders(); clearshadowcache(); });
FVAR(csmsplitweight, 0.20f, 0.75f, 0.95f);
VARF(csmshadowmap, 0, 1, 1, { cleardeferredlightshaders(); clearshadowcache(); });
// cascaded shadow maps
struct cascadedshadowmap
{
struct splitinfo
{
float nearplane; // split distance to near plane
float farplane; // split distance to farplane
matrix4 proj; // one projection per split
vec scale, offset; // scale and offset of the projection
int idx; // shadowmapinfo indices
vec center, bounds; // max extents of shadowmap in sunlight model space
plane cull[4]; // world space culling planes of the split's projected sides
};
matrix4 model; // model view is shared by all splits
splitinfo splits[CSM_MAXSPLITS]; // per-split parameters
vec lightview; // view vector for light
int rendered;
void setup(); // insert shadowmaps for each split frustum if there is sunlight
void updatesplitdist(); // compute split frustum distances
void getmodelmatrix(); // compute the shared model matrix
void getprojmatrix(); // compute each cropped projection matrix
void gencullplanes(); // generate culling planes for the mvp matrix
void bindparams(); // bind any shader params necessary for lighting
};
void cascadedshadowmap::setup()
{
int size = ((csmmaxsize * shadowatlaspacker.w) / SHADOWATLAS_SIZE + smalign)&~smalign;
loopi(csmsplits)
{
ushort smx = USHRT_MAX, smy = USHRT_MAX;
splits[i].idx = -1;
if(shadowatlaspacker.insert(smx, smy, size, size))
addshadowmap(smx, smy, size, splits[i].idx);
}
getmodelmatrix();
getprojmatrix();
gencullplanes();
}
VAR(csmnearplane, 1, 1, 16);
VAR(csmfarplane, 64, 1024, 16384);
FVAR(csmpradiustweak, 1e-3f, 1, 1e3f);
FVAR(csmdepthrange, 0, 1024, 1e6f);
FVAR(csmdepthmargin, 0, 0.1f, 1e3f);
FVAR(csmpolyfactor, -1e3f, 2, 1e3f);
FVAR(csmpolyoffset, -1e4f, 0, 1e4f);
FVAR(csmbias, -1e6f, 1e-4f, 1e6f);
FVAR(csmpolyfactor2, -1e3f, 3, 1e3f);
FVAR(csmpolyoffset2, -1e4f, 0, 1e4f);
FVAR(csmbias2, -1e16f, 2e-4f, 1e6f);
VAR(csmcull, 0, 1, 1);
void cascadedshadowmap::updatesplitdist()
{
float lambda = csmsplitweight, nd = csmnearplane, fd = csmfarplane, ratio = fd/nd;
splits[0].nearplane = nd;
for(int i = 1; i < csmsplits; ++i)
{
float si = i / float(csmsplits);
splits[i].nearplane = lambda*(nd*pow(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
splits[i-1].farplane = splits[i].nearplane * 1.005f;
}
splits[csmsplits-1].farplane = fd;
}
void cascadedshadowmap::getmodelmatrix()
{
model = viewmatrix;
model.rotate_around_x(sunlightpitch*RAD);
model.rotate_around_z((180-sunlightyaw)*RAD);
}
void cascadedshadowmap::getprojmatrix()
{
lightview = vec(sunlightdir).neg();
// compute the split frustums
updatesplitdist();
// find z extent
float minz = lightview.project_bb(worldmin, worldmax), maxz = lightview.project_bb(worldmax, worldmin),
zmargin = max((maxz - minz)*csmdepthmargin, 0.5f*(csmdepthrange - (maxz - minz)));
minz -= zmargin;
maxz += zmargin;
// compute each split projection matrix
loopi(csmsplits)
{
splitinfo &split = splits[i];
if(split.idx < 0) continue;
const shadowmapinfo &sm = shadowmaps[split.idx];
vec c;
float radius = calcfrustumboundsphere(split.nearplane, split.farplane, camera1->o, camdir, c);
// compute the projected bounding box of the sphere
vec tc;
model.transform(c, tc);
int border = smfilter > 2 ? smborder2 : smborder;
const float pradius = ceil(radius * csmpradiustweak) + smalign, step = (2*pradius) / (sm.size - 2*border);
vec2 offset = vec2(tc).sub(pradius).div(step*(1+smalign));
offset.x = floor(offset.x)*(1+smalign);
offset.y = floor(offset.y)*(1+smalign);
split.center = vec(vec2(offset).mul(step).add(pradius), -0.5f*(minz + maxz));
split.bounds = vec(pradius, pradius, 0.5f*(maxz - minz));
// modify mvp with a scale and offset
// now compute the update model view matrix for this split
split.scale = vec(1/step, 1/step, -1/(maxz - minz));
split.offset = vec(border - offset.x, border - offset.y, -minz/(maxz - minz));
split.proj.identity();
split.proj.settranslation(2*split.offset.x/sm.size - 1, 2*split.offset.y/sm.size - 1, 2*split.offset.z - 1);
split.proj.setscale(2*split.scale.x/sm.size, 2*split.scale.y/sm.size, 2*split.scale.z);
}
}
void cascadedshadowmap::gencullplanes()
{
loopi(csmsplits)
{
splitinfo &split = splits[i];
matrix4 mvp;
mvp.mul(split.proj, model);
vec4 px = mvp.rowx(), py = mvp.rowy(), pw = mvp.roww();
split.cull[0] = plane(vec4(pw).add(px)).normalize(); // left plane
split.cull[1] = plane(vec4(pw).sub(px)).normalize(); // right plane
split.cull[2] = plane(vec4(pw).add(py)).normalize(); // bottom plane
split.cull[3] = plane(vec4(pw).sub(py)).normalize(); // top plane
}
}
void cascadedshadowmap::bindparams()
{
GLOBALPARAM(csmmatrix, matrix3(model));
static GlobalShaderParam csmtc("csmtc"), csmoffset("csmoffset");
vec4 *csmtcv = csmtc.reserve<vec4>(csmsplits);
vec *csmoffsetv = csmoffset.reserve<vec>(csmsplits);
loopi(csmsplits)
{
cascadedshadowmap::splitinfo &split = splits[i];
if(split.idx < 0) continue;
const shadowmapinfo &sm = shadowmaps[split.idx];
csmtcv[i] = vec4(vec2(split.center).mul(-split.scale.x), split.scale.x, split.bounds.x*split.scale.x);
const float bias = (smfilter > 2 ? csmbias2 : csmbias) * (-512.0f / sm.size) * (split.farplane - split.nearplane) / (splits[0].farplane - splits[0].nearplane);
csmoffsetv[i] = vec(sm.x, sm.y, 0.5f + bias).add2(0.5f*sm.size);
}
GLOBALPARAMF(csmz, splits[0].center.z*-splits[0].scale.z, splits[0].scale.z);
}
cascadedshadowmap csm;
int calcbbcsmsplits(const ivec &bbmin, const ivec &bbmax)
{
int mask = (1<<csmsplits)-1;
if(!csmcull) return mask;
loopi(csmsplits)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
int k;
for(k = 0; k < 4; k++)
{
const plane &p = split.cull[k];
ivec omin, omax;
if(p.x > 0) { omin.x = bbmin.x; omax.x = bbmax.x; } else { omin.x = bbmax.x; omax.x = bbmin.x; }
if(p.y > 0) { omin.y = bbmin.y; omax.y = bbmax.y; } else { omin.y = bbmax.y; omax.y = bbmin.y; }
if(p.z > 0) { omin.z = bbmin.z; omax.z = bbmax.z; } else { omin.z = bbmax.z; omax.z = bbmin.z; }
if(omax.dist(p) < 0) { mask &= ~(1<<i); goto nextsplit; }
if(omin.dist(p) < 0) goto notinside;
}
mask &= (2<<i)-1;
break;
notinside:
while(++k < 4)
{
const plane &p = split.cull[k];
ivec omax(p.x > 0 ? bbmax.x : bbmin.x, p.y > 0 ? bbmax.y : bbmin.y, p.z > 0 ? bbmax.z : bbmin.z);
if(omax.dist(p) < 0) { mask &= ~(1<<i); break; }
}
nextsplit:;
}
return mask;
}
int calcspherecsmsplits(const vec &center, float radius)
{
int mask = (1<<csmsplits)-1;
if(!csmcull) return mask;
loopi(csmsplits)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
int k;
for(k = 0; k < 4; k++)
{
const plane &p = split.cull[k];
float dist = p.dist(center);
if(dist < -radius) { mask &= ~(1<<i); goto nextsplit; }
if(dist < radius) goto notinside;
}
mask &= (2<<i)-1;
break;
notinside:
while(++k <