Changeset View
Standalone View
source/renderer/ShadowMap.cpp
Context not available. | |||||
int DepthTextureBits; | int DepthTextureBits; | ||||
// the EXT_framebuffer_object framebuffer | // the EXT_framebuffer_object framebuffer | ||||
GLuint Framebuffer; | GLuint Framebuffer; | ||||
GLuint Copybuffer; | |||||
vladislavbelov: Let's call it at least `BlockerFramebuffer`. | |||||
Done Inline ActionsFair enough. aeonios: Fair enough. | |||||
// handle of shadow map | // handle of shadow map | ||||
GLuint Texture; | GLuint Texture; | ||||
GLuint blockerTexture; | |||||
// width, height of shadow map | // width, height of shadow map | ||||
int Width, Height; | int Width, Height; | ||||
// Shadow map quality (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) | // Shadow map quality (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) | ||||
Context not available. | |||||
{ | { | ||||
m = new ShadowMapInternals; | m = new ShadowMapInternals; | ||||
m->Framebuffer = 0; | m->Framebuffer = 0; | ||||
m->Copybuffer = 0; | |||||
m->Texture = 0; | m->Texture = 0; | ||||
m->blockerTexture = 0; | |||||
m->DummyTexture = 0; | m->DummyTexture = 0; | ||||
m->Width = 0; | m->Width = 0; | ||||
m->Height = 0; | m->Height = 0; | ||||
Context not available. | |||||
{ | { | ||||
if (m->Texture) | if (m->Texture) | ||||
glDeleteTextures(1, &m->Texture); | glDeleteTextures(1, &m->Texture); | ||||
if (m->blockerTexture) | |||||
glDeleteTextures(1, &m->blockerTexture); | |||||
if (m->DummyTexture) | if (m->DummyTexture) | ||||
glDeleteTextures(1, &m->DummyTexture); | glDeleteTextures(1, &m->DummyTexture); | ||||
if (m->Framebuffer) | if (m->Framebuffer) | ||||
pglDeleteFramebuffersEXT(1, &m->Framebuffer); | pglDeleteFramebuffersEXT(1, &m->Framebuffer); | ||||
if (m->Copybuffer) | |||||
pglDeleteFramebuffersEXT(1, &m->Copybuffer); | |||||
delete m; | delete m; | ||||
} | } | ||||
Context not available. | |||||
{ | { | ||||
if (m->Texture) | if (m->Texture) | ||||
glDeleteTextures(1, &m->Texture); | glDeleteTextures(1, &m->Texture); | ||||
if (m->blockerTexture) | |||||
glDeleteTextures(1, &m->blockerTexture); | |||||
if (m->DummyTexture) | if (m->DummyTexture) | ||||
glDeleteTextures(1, &m->DummyTexture); | glDeleteTextures(1, &m->DummyTexture); | ||||
if (m->Framebuffer) | if (m->Framebuffer) | ||||
pglDeleteFramebuffersEXT(1, &m->Framebuffer); | pglDeleteFramebuffersEXT(1, &m->Framebuffer); | ||||
if (m->Copybuffer) | |||||
pglDeleteFramebuffersEXT(1, &m->Copybuffer); | |||||
m->Texture = 0; | m->Texture = 0; | ||||
m->blockerTexture = 0; | |||||
m->DummyTexture = 0; | m->DummyTexture = 0; | ||||
m->Framebuffer = 0; | m->Framebuffer = 0; | ||||
m->Copybuffer = 0; | |||||
// (Texture will be constructed in next SetupFrame) | m->CreateTexture(); | ||||
} | } | ||||
////////////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////////////// | ||||
Context not available. | |||||
// SetupFrame: camera and light direction for this frame | // SetupFrame: camera and light direction for this frame | ||||
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) | void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) | ||||
{ | { | ||||
if (!m->Texture) | /*if (!m->Texture) | ||||
m->CreateTexture(); | m->CreateTexture();*/ | ||||
Done Inline ActionsWhy so? vladislavbelov: Why so? | |||||
Done Inline ActionsOh, it used to defer recreating the shadow textures/framebuffers until the following frame when options changed, but that caused it to do all kinds of screwy things and basically fail to render at all. I made it recreate the textures immediately which fixed the issue, but forgot to delete the commented out bit. aeonios: Oh, it used to defer recreating the shadow textures/framebuffers until the following frame when… | |||||
CVector3D z = lightdir; | CVector3D z = lightdir; | ||||
CVector3D y; | CVector3D y; | ||||
Not Done Inline ActionsAny reason for this ? :) Stan: Any reason for this ? :) | |||||
Not Done Inline ActionsAny reason for this ? :) Stan: Any reason for this ? :) | |||||
Context not available. | |||||
// Create the shadow map | // Create the shadow map | ||||
void ShadowMapInternals::CreateTexture() | void ShadowMapInternals::CreateTexture() | ||||
{ | { | ||||
// Cleanup | |||||
if (Texture) | |||||
{ | |||||
glDeleteTextures(1, &Texture); | |||||
Texture = 0; | |||||
} | |||||
if (DummyTexture) | |||||
{ | |||||
glDeleteTextures(1, &DummyTexture); | |||||
DummyTexture = 0; | |||||
} | |||||
if (Framebuffer) | |||||
{ | |||||
pglDeleteFramebuffersEXT(1, &Framebuffer); | |||||
Framebuffer = 0; | |||||
} | |||||
// save the caller's FBO | // save the caller's FBO | ||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO); | glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO); | ||||
Not Done Inline ActionsI would have put a static_cast<float> but I don't think it's even needed. I'll let @wraitii and @vladislavbelov argue about it :P Stan: I would have put a static_cast<float> but I don't think it's even needed. I'll let @wraitii and… | |||||
Context not available. | |||||
int shadow_map_size = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())); | int shadow_map_size = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())); | ||||
switch (QualityLevel) | switch (QualityLevel) | ||||
{ | { | ||||
// Very Low | |||||
case -2: | |||||
shadow_map_size /= 4; | |||||
break; | |||||
// Low | // Low | ||||
case -1: | case 0: | ||||
shadow_map_size /= 2; | shadow_map_size /= 2; | ||||
break; | break; | ||||
// High | // High | ||||
case 1: | case 2: | ||||
shadow_map_size *= 2; | shadow_map_size *= 2; | ||||
break; | break; | ||||
// Ultra | |||||
Done Inline ActionsI agree with removing low values, but disagree with high values, because we use it for cinematics. For me the high value isn't big enough even in the game. vladislavbelov: I agree with removing low values, but disagree with high values, because we use it for… | |||||
Done Inline ActionsWell, I'll reimplement that for now, but the real problem for low-angle shots is that the shadow map pixels all end up on faraway objects which makes shadows close to the camera look like crap. The only way to really fix it is to use lightspace perspective shadow mapping, which I haven't implemented yet and which I'm not totally confident that I can do. It's honestly not that complicated but it requires some matrix math that I'm not sure entirely how it works or how to do it with the libraries that we're using. aeonios: Well, I'll reimplement that for now, but the real problem for low-angle shots is that the… | |||||
case 2: | |||||
shadow_map_size *= 4; | |||||
break; | |||||
// Medium as is | // Medium as is | ||||
default: | default: | ||||
break; | break; | ||||
Context not available. | |||||
EffectiveHeight = Height; | EffectiveHeight = Height; | ||||
const char* formatname; | const char* formatname; | ||||
GLenum format; | |||||
switch(DepthTextureBits) | #if CONFIG2_GLES | ||||
{ | formatname = "DEPTH_COMPONENT"; | ||||
case 16: formatname = "DEPTH_COMPONENT16"; break; | format = GL_DEPTH_COMPONENT; | ||||
case 24: formatname = "DEPTH_COMPONENT24"; break; | #else | ||||
case 32: formatname = "DEPTH_COMPONENT32"; break; | switch (DepthTextureBits) | ||||
default: formatname = "DEPTH_COMPONENT"; break; | { | ||||
} | case 16: formatname = "DEPTH_COMPONENT16"; format = GL_DEPTH_COMPONENT16; break; | ||||
case 24: formatname = "DEPTH_COMPONENT24"; format = GL_DEPTH_COMPONENT24; break; | |||||
case 32: formatname = "DEPTH_COMPONENT32"; format = GL_DEPTH_COMPONENT32; break; | |||||
default: formatname = "DEPTH_COMPONENT16"; format = GL_DEPTH_COMPONENT16; break; | |||||
} | |||||
#endif | |||||
LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)", | LOGMESSAGE("Creating shadow textures (size %d x %d) (format = %s)", | ||||
Width, Height, formatname); | Width, Height, formatname); | ||||
if (g_Renderer.m_Options.m_ShadowAlphaFix) | if (g_Renderer.m_Options.m_ShadowAlphaFix) | ||||
{ | { | ||||
glGenTextures(1, &DummyTexture); | glGenTextures(1, &DummyTexture); | ||||
Not Done Inline ActionsMight want to use newlines. Stan: Might want to use newlines.
Also is there a constant somewhere for those strings ? | |||||
Context not available. | |||||
glGenTextures(1, &Texture); | glGenTextures(1, &Texture); | ||||
g_Renderer.BindTexture(0, Texture); | g_Renderer.BindTexture(0, Texture); | ||||
GLenum format; | |||||
#if CONFIG2_GLES | |||||
format = GL_DEPTH_COMPONENT; | |||||
#else | |||||
switch (DepthTextureBits) | |||||
{ | |||||
case 16: format = GL_DEPTH_COMPONENT16; break; | |||||
case 24: format = GL_DEPTH_COMPONENT24; break; | |||||
case 32: format = GL_DEPTH_COMPONENT32; break; | |||||
default: format = GL_DEPTH_COMPONENT; break; | |||||
} | |||||
#endif | |||||
glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); | glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); | ||||
// GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT | // GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT | ||||
Context not available. | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||
#if CONFIG2_GLES | #if CONFIG2_GLES | ||||
// GLES doesn't do depth comparisons, so treat it as a | // GLES doesn't do depth comparisons, so treat it as a | ||||
// basic unfiltered depth texture | // basic unfiltered depth texture | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 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_MAG_FILTER, GL_NEAREST); | ||||
#else | #else | ||||
// Enable automatic depth comparisons | // Enable automatic depth comparisons | ||||
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); | glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); | ||||
// Use GL_LINEAR to trigger automatic PCF on some devices | // Use GL_LINEAR for higher quality sampling. | ||||
Done Inline Actionswrong tabs or this is inconsistent with above - honestly not sure :p wraitii: wrong tabs or this is inconsistent with above - honestly not sure :p | |||||
Not Done Inline ActionsI think you're tripping. aeonios: I think you're tripping. | |||||
Not Done Inline ActionsIndeed the convention is to not indent macros, so keep as is. elexis: Indeed the convention is to not indent macros, so keep as is. | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||
#endif | #endif | ||||
ogl_WarnIfError(); | ogl_WarnIfError(); | ||||
Context not available. | |||||
{ | { | ||||
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0); | pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0); | ||||
} | } | ||||
else | |||||
{ | |||||
#if CONFIG2_GLES | |||||
#warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES | |||||
#else | |||||
glDrawBuffer(GL_NONE); | |||||
Done Inline ActionsWhy it was removed? vladislavbelov: Why it was removed? | |||||
Done Inline ActionsThere was a comment that asked if it was necessary, and it wasn't so I removed it. When you bind a framebuffer with GL_FRAMEBUFFER_EXT it binds to both read and draw. Note that when I copy the shadow map for PCSS I also use bind framebuffer but instead of GL_FRAMEBUFFER_EXT I use: pglBindFramebufferEXT(GL_READ_FRAMEBUFFER, m->Framebuffer); instead. You only need to bind read/draw if you're actually going to read/draw something, which at that point in the code we're only constructing the buffers but not actually using them, so we don't need to. aeonios: There was a comment that asked if it was necessary, and it wasn't so I removed it. When you… | |||||
#endif | |||||
} | |||||
#if !CONFIG2_GLES | |||||
glReadBuffer(GL_NONE); | |||||
#endif | |||||
ogl_WarnIfError(); | ogl_WarnIfError(); | ||||
GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); | GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); | ||||
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO); | |||||
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) | if (status != GL_FRAMEBUFFER_COMPLETE_EXT) | ||||
{ | { | ||||
LOGWARNING("Framebuffer object incomplete: 0x%04X", status); | LOGWARNING("Shadow Map Framebuffer object incomplete: 0x%04X", status); | ||||
// Disable shadow rendering (but let the user try again if they want) | // Disable shadow rendering (but let the user try again if they want) | ||||
g_Renderer.m_Options.m_Shadows = false; | g_Renderer.m_Options.m_Shadows = false; | ||||
g_Renderer.MakeShadersDirty(); | |||||
} | } | ||||
if (g_Renderer.m_Options.m_ShadowPCF && g_Renderer.m_Options.m_ShadowPCSS) | |||||
{ | |||||
// generate texture for PCSS blocker search | |||||
pglGenFramebuffersEXT(1, &Copybuffer); | |||||
glGenTextures(1, &blockerTexture); | |||||
g_Renderer.BindTexture(0, blockerTexture); | |||||
glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL); | |||||
// GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT | |||||
// set texture parameters | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |||||
#if CONFIG2_GLES | |||||
// GLES doesn't do depth comparisons, so treat it as a | |||||
// basic unfiltered depth texture | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |||||
#else | |||||
// Disable automatic depth comparisons | |||||
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY); | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); | |||||
// Use GL_NEAREST for accurate blocker search. | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |||||
#endif | |||||
ogl_WarnIfError(); | |||||
// bind to framebuffer object | |||||
glBindTexture(GL_TEXTURE_2D, 0); | |||||
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Copybuffer); | |||||
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, blockerTexture, 0); | |||||
ogl_WarnIfError(); | |||||
status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); | |||||
if (status != GL_FRAMEBUFFER_COMPLETE_EXT) | |||||
{ | |||||
LOGWARNING("PCSS Blocker Search Framebuffer object incomplete: 0x%04X", status); | |||||
// Disable shadow rendering (but let the user try again if they want) | |||||
Not Done Inline ActionsCaps Stan: Caps | |||||
g_Renderer.m_Options.m_ShadowPCSS = false; | |||||
g_Renderer.MakeShadersDirty(); | |||||
g_ConfigDB.SetValueBool(CFG_USER, "shadowpcss", false); | |||||
} | |||||
} | |||||
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO); | |||||
} | } | ||||
Context not available. | |||||
// Finish rendering into shadow map texture | // Finish rendering into shadow map texture | ||||
void ShadowMap::EndRender() | void ShadowMap::EndRender() | ||||
{ | { | ||||
if (g_Renderer.m_Options.m_ShadowPCF && g_Renderer.m_Options.m_ShadowPCSS && m->Copybuffer) | |||||
{ | |||||
// Copy the shadow map for PCSS blocker search | |||||
pglBindFramebufferEXT(GL_READ_FRAMEBUFFER, m->Framebuffer); | |||||
pglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, m->Copybuffer); | |||||
pglBlitFramebufferEXT(0, 0, m->EffectiveWidth, m->EffectiveHeight, 0, 0, m->EffectiveWidth, m->EffectiveHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST); | |||||
} | |||||
glDisable(GL_SCISSOR_TEST); | glDisable(GL_SCISSOR_TEST); | ||||
g_Renderer.SetViewCamera(m->SavedViewCamera); | g_Renderer.SetViewCamera(m->SavedViewCamera); | ||||
Context not available. | |||||
return m->Texture; | return m->Texture; | ||||
} | } | ||||
GLuint ShadowMap::GetBlockerTexture() const | |||||
{ | |||||
return m->blockerTexture; | |||||
} | |||||
const CMatrix3D& ShadowMap::GetTextureMatrix() const | const CMatrix3D& ShadowMap::GetTextureMatrix() const | ||||
{ | { | ||||
return m->TextureMatrix; | return m->TextureMatrix; | ||||
Context not available. | |||||
{ | { | ||||
if (bits != m->DepthTextureBits) | if (bits != m->DepthTextureBits) | ||||
{ | { | ||||
if (m->Texture) | |||||
{ | |||||
glDeleteTextures(1, &m->Texture); | |||||
m->Texture = 0; | |||||
} | |||||
m->Width = m->Height = 0; | |||||
m->DepthTextureBits = bits; | m->DepthTextureBits = bits; | ||||
RecreateTexture(); | |||||
} | } | ||||
} | } | ||||
Context not available. |
Let's call it at least BlockerFramebuffer.