From b8bcbe819be221b9fed81615158fdcc0dda2e861 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Tue, 15 Jul 2014 20:49:21 +0200 Subject: [PATCH] - removed texture based dynamic lighting. For GL 3.x the shader approach is always better. - fixed: sky fog was not unset. --- src/gl/dynlights/a_dynlight.cpp | 19 +-- src/gl/dynlights/gl_dynlight.h | 2 - src/gl/dynlights/gl_dynlight1.cpp | 108 ------------- src/gl/renderer/gl_renderer.cpp | 4 +- src/gl/renderer/gl_renderer.h | 3 +- src/gl/scene/gl_drawinfo.h | 22 +-- src/gl/scene/gl_flats.cpp | 163 ++------------------ src/gl/scene/gl_portal.cpp | 2 +- src/gl/scene/gl_scene.cpp | 97 +----------- src/gl/scene/gl_skydome.cpp | 1 + src/gl/scene/gl_sprite.cpp | 2 +- src/gl/scene/gl_spritelight.cpp | 55 ++++--- src/gl/scene/gl_wall.h | 2 +- src/gl/scene/gl_walls.cpp | 52 +------ src/gl/scene/gl_walls_draw.cpp | 248 ++++++++---------------------- src/r_defs.h | 6 +- wadsrc/static/glstuff/gllight.png | Bin 13073 -> 0 bytes 17 files changed, 129 insertions(+), 657 deletions(-) delete mode 100644 wadsrc/static/glstuff/gllight.png diff --git a/src/gl/dynlights/a_dynlight.cpp b/src/gl/dynlights/a_dynlight.cpp index b462b62ac1..bcc126ecf0 100644 --- a/src/gl/dynlights/a_dynlight.cpp +++ b/src/gl/dynlights/a_dynlight.cpp @@ -521,14 +521,12 @@ void ADynamicLight::CollectWithinRadius(subsector_t *subSec, float radius) { if (!subSec) return; - bool additive = (flags4&MF4_ADDITIVE) || gl_lights_additive; - subSec->validcount = ::validcount; - touching_subsectors = AddLightNode(&subSec->lighthead[additive], subSec, this, touching_subsectors); + touching_subsectors = AddLightNode(&subSec->lighthead, subSec, this, touching_subsectors); if (subSec->sector->validcount != ::validcount) { - touching_sector = AddLightNode(&subSec->render_sector->lighthead[additive], subSec->sector, this, touching_sector); + touching_sector = AddLightNode(&subSec->render_sector->lighthead, subSec->sector, this, touching_sector); subSec->sector->validcount = ::validcount; } @@ -542,7 +540,7 @@ void ADynamicLight::CollectWithinRadius(subsector_t *subSec, float radius) if (DMulScale32 (y-seg->v1->y, seg->v2->x-seg->v1->x, seg->v1->x-x, seg->v2->y-seg->v1->y) <=0) { seg->linedef->validcount=validcount; - touching_sides = AddLightNode(&seg->sidedef->lighthead[additive], seg->sidedef, this, touching_sides); + touching_sides = AddLightNode(&seg->sidedef->lighthead, seg->sidedef, this, touching_sides); } } @@ -755,22 +753,15 @@ CCMD(listsublights) { subsector_t *sub = &subsectors[i]; int lights = 0; - int addlights = 0; - FLightNode * node = sub->lighthead[0]; + FLightNode * node = sub->lighthead; while (node != NULL) { lights++; node = node->nextLight; } - node = sub->lighthead[1]; - while (node != NULL) - { - addlights++; - node = node->nextLight; - } - Printf(PRINT_LOG, "Subsector %d - %d lights, %d additive lights\n", i, lights, addlights); + Printf(PRINT_LOG, "Subsector %d - %d lights\n", i, lights); } } diff --git a/src/gl/dynlights/gl_dynlight.h b/src/gl/dynlights/gl_dynlight.h index 37cbc4f932..568630898d 100644 --- a/src/gl/dynlights/gl_dynlight.h +++ b/src/gl/dynlights/gl_dynlight.h @@ -184,8 +184,6 @@ struct FDynLightData bool gl_GetLight(Plane & p, ADynamicLight * light, bool checkside, bool forceadditive, FDynLightData &data); -bool gl_SetupLight(Plane & p, ADynamicLight * light, Vector & nearPt, Vector & up, Vector & right, float & scale, int desaturation, bool checkside=true, bool forceadditive=true); -bool gl_SetupLightTexture(); void gl_UploadLights(FDynLightData &data); diff --git a/src/gl/dynlights/gl_dynlight1.cpp b/src/gl/dynlights/gl_dynlight1.cpp index 42053e8a53..cee31ee428 100644 --- a/src/gl/dynlights/gl_dynlight1.cpp +++ b/src/gl/dynlights/gl_dynlight1.cpp @@ -68,17 +68,6 @@ CUSTOM_CVAR (Bool, gl_lights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOIN else gl_DeleteAllAttachedLights(); } -CUSTOM_CVAR (Bool, gl_dynlight_shader, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) -{ - if (self) - { - if (gl.maxuniforms < 1024 && !(gl.flags & RFL_SHADER_STORAGE_BUFFER)) - { - self = false; - } - } -} - CVAR (Bool, gl_attachedlights, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Bool, gl_lights_checkside, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); CVAR (Float, gl_lights_intensity, 1.0f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); @@ -155,75 +144,6 @@ bool gl_GetLight(Plane & p, ADynamicLight * light, bool checkside, bool forceadd } -//========================================================================== -// -// Sets up the parameters to render one dynamic light onto one plane -// -//========================================================================== -bool gl_SetupLight(Plane & p, ADynamicLight * light, Vector & nearPt, Vector & up, Vector & right, - float & scale, int desaturation, bool checkside, bool forceadditive) -{ - Vector fn, pos; - - float x = FIXED2FLOAT(light->x); - float y = FIXED2FLOAT(light->y); - float z = FIXED2FLOAT(light->z); - - float dist = fabsf(p.DistToPoint(x, z, y)); - float radius = (light->GetRadius() * gl_lights_size); - - if (radius <= 0.f) return false; - if (dist > radius) return false; - if (checkside && gl_lights_checkside && p.PointOnSide(x, z, y)) - { - return false; - } - if (light->owned && light->target != NULL && !light->target->IsVisibleToPlayer()) - { - return false; - } - - scale = 1.0f / ((2.f * radius) - dist); - - // project light position onto plane (find closest point on plane) - - - pos.Set(x,z,y); - fn=p.Normal(); - fn.GetRightUp(right, up); - -#ifdef _MSC_VER - nearPt = pos + fn * dist; -#else - Vector tmpVec = fn * dist; - nearPt = pos + tmpVec; -#endif - - float cs = 1.0f - (dist / radius); - if (gl_lights_additive || light->flags4&MF4_ADDITIVE || forceadditive) cs*=0.2f; // otherwise the light gets too strong. - float r = light->GetRed() / 255.0f * cs * gl_lights_intensity; - float g = light->GetGreen() / 255.0f * cs * gl_lights_intensity; - float b = light->GetBlue() / 255.0f * cs * gl_lights_intensity; - - if (light->IsSubtractive()) - { - Vector v; - - gl_RenderState.BlendEquation(GL_FUNC_REVERSE_SUBTRACT); - v.Set(r, g, b); - r = v.Length() - r; - g = v.Length() - g; - b = v.Length() - b; - } - else - { - gl_RenderState.BlendEquation(GL_FUNC_ADD); - } - gl_RenderState.SetColor(r, g, b, 1.f, desaturation); - return true; -} - - //========================================================================== // // @@ -257,31 +177,3 @@ void gl_UploadLights(FDynLightData &data) } } #endif - -//========================================================================== -// -// -// -//========================================================================== - -bool gl_SetupLightTexture() -{ - - if (GLRenderer->gllight == NULL) return false; - FMaterial * pat = FMaterial::ValidateTexture(GLRenderer->gllight); - pat->BindPatch(0); - return true; -} - - -//========================================================================== -// -// -// -//========================================================================== - -inline fixed_t P_AproxDistance3(fixed_t dx, fixed_t dy, fixed_t dz) -{ - return P_AproxDistance(P_AproxDistance(dx,dy),dz); -} - diff --git a/src/gl/renderer/gl_renderer.cpp b/src/gl/renderer/gl_renderer.cpp index ae2ba21250..42773496e9 100644 --- a/src/gl/renderer/gl_renderer.cpp +++ b/src/gl/renderer/gl_renderer.cpp @@ -96,7 +96,7 @@ FGLRenderer::FGLRenderer(OpenGLFrameBuffer *fb) mSkyVBO = NULL; gl_spriteindex = 0; mShaderManager = NULL; - glpart2 = glpart = gllight = mirrortexture = NULL; + glpart2 = glpart = mirrortexture = NULL; } void FGLRenderer::Initialize() @@ -104,7 +104,6 @@ void FGLRenderer::Initialize() glpart2 = FTexture::CreateTexture(Wads.GetNumForFullName("glstuff/glpart2.png"), FTexture::TEX_MiscPatch); glpart = FTexture::CreateTexture(Wads.GetNumForFullName("glstuff/glpart.png"), FTexture::TEX_MiscPatch); mirrortexture = FTexture::CreateTexture(Wads.GetNumForFullName("glstuff/mirror.png"), FTexture::TEX_MiscPatch); - gllight = FTexture::CreateTexture(Wads.GetNumForFullName("glstuff/gllight.png"), FTexture::TEX_MiscPatch); mVBO = new FFlatVertexBuffer; mSkyVBO = new FSkyVertexBuffer; @@ -128,7 +127,6 @@ FGLRenderer::~FGLRenderer() if (glpart2) delete glpart2; if (glpart) delete glpart; if (mirrortexture) delete mirrortexture; - if (gllight) delete gllight; if (mFBID != 0) glDeleteFramebuffers(1, &mFBID); } diff --git a/src/gl/renderer/gl_renderer.h b/src/gl/renderer/gl_renderer.h index 7331235994..a2679f665a 100644 --- a/src/gl/renderer/gl_renderer.h +++ b/src/gl/renderer/gl_renderer.h @@ -62,8 +62,7 @@ public: FTexture *glpart2; FTexture *glpart; FTexture *mirrortexture; - FTexture *gllight; - + float mSky1Pos, mSky2Pos; FRotator mAngles; diff --git a/src/gl/scene/gl_drawinfo.h b/src/gl/scene/gl_drawinfo.h index 01a72701b5..f4b1043923 100644 --- a/src/gl/scene/gl_drawinfo.h +++ b/src/gl/scene/gl_drawinfo.h @@ -13,14 +13,6 @@ enum GLDrawItemType enum DrawListType { - // These are organized so that the various multipass rendering modes - // have to be set as few times as possible - GLDL_LIGHT, - GLDL_LIGHTBRIGHT, - GLDL_LIGHTMASKED, - GLDL_LIGHTFOG, - GLDL_LIGHTFOGMASKED, - GLDL_PLAIN, GLDL_MASKED, GLDL_FOG, @@ -30,25 +22,15 @@ enum DrawListType GLDL_TRANSLUCENTBORDER, GLDL_TYPES, - - GLDL_FIRSTLIGHT = GLDL_LIGHT, - GLDL_LASTLIGHT = GLDL_LIGHTFOGMASKED, - GLDL_FIRSTNOLIGHT = GLDL_PLAIN, - GLDL_LASTNOLIGHT = GLDL_FOGMASKED, }; enum Drawpasses { - GLPASS_BASE, // Draws the untextured surface only - GLPASS_BASE_MASKED, // Draws an untextured surface that is masked by the texture - GLPASS_PLAIN, // Draws a texture that isn't affected by dynamic lights with sector light settings - GLPASS_LIGHT, // Draws dynamic lights - GLPASS_LIGHT_ADDITIVE, // Draws additive dynamic lights - GLPASS_TEXTURE, // Draws the texture to be modulated with the light information on the base surface + GLPASS_PLAIN, // Main pass without dynamic lights + GLPASS_ALL, // Main pass with dynamic lights GLPASS_DECALS, // Draws a decal GLPASS_DECALS_NOFOG,// Draws a decal without setting the fog (used for passes that need a fog layer) GLPASS_TRANSLUCENT, // Draws translucent objects - GLPASS_ALL // Everything at once, using shaders for dynamic lights }; //========================================================================== diff --git a/src/gl/scene/gl_flats.cpp b/src/gl/scene/gl_flats.cpp index 19eb3b9e1b..2767a5e805 100644 --- a/src/gl/scene/gl_flats.cpp +++ b/src/gl/scene/gl_flats.cpp @@ -103,26 +103,24 @@ void gl_SetPlaneTextureRotation(const GLSectorPlane * secplane, FMaterial * glte } + //========================================================================== // // Flats // //========================================================================== +extern FDynLightData lightdata; -void GLFlat::DrawSubsectorLights(subsector_t * sub, int pass) +bool GLFlat::SetupSubsectorLights(bool lightsapplied, subsector_t * sub) { Plane p; - Vector nearPt, up, right, t1; - float scale; - unsigned int k; - seg_t *v; - FLightNode * node = sub->lighthead[pass==GLPASS_LIGHT_ADDITIVE]; - gl_RenderState.Apply(); + lightdata.Clear(); + FLightNode * node = sub->lighthead; while (node) { ADynamicLight * light = node->lightsource; - + if (light->flags2&MF2_DORMANT) { node=node->nextLight; @@ -140,72 +138,9 @@ void GLFlat::DrawSubsectorLights(subsector_t * sub, int pass) } p.Set(plane.plane); - if (!gl_SetupLight(p, light, nearPt, up, right, scale, Colormap.desaturation, false, foggy)) - { - node=node->nextLight; - continue; - } - draw_dlightf++; - gl_RenderState.Apply(); - - // Render the light - FFlatVertex *ptr = GLRenderer->mVBO->GetBuffer(); - for (k = 0, v = sub->firstline; k < sub->numlines; k++, v++) - { - vertex_t *vt = v->v1; - float zc = plane.plane.ZatPoint(vt->fx, vt->fy) + dz; - - t1.Set(vt->fx, zc, vt->fy); - Vector nearToVert = t1 - nearPt; - ptr->Set(vt->fx, zc, vt->fy, (nearToVert.Dot(right) * scale) + 0.5f, (nearToVert.Dot(up) * scale) + 0.5f); - ptr++; - } - GLRenderer->mVBO->RenderCurrent(ptr, GL_TRIANGLE_FAN); + gl_GetLight(p, light, false, false, lightdata); node = node->nextLight; } -} - - -//========================================================================== -// -// Flats -// -//========================================================================== -extern FDynLightData lightdata; - -bool GLFlat::SetupSubsectorLights(bool lightsapplied, subsector_t * sub) -{ - Plane p; - - lightdata.Clear(); - for(int i=0;i<2;i++) - { - FLightNode * node = sub->lighthead[i]; - while (node) - { - ADynamicLight * light = node->lightsource; - - if (light->flags2&MF2_DORMANT) - { - node=node->nextLight; - continue; - } - iter_dlightf++; - - // we must do the side check here because gl_SetupLight needs the correct plane orientation - // which we don't have for Legacy-style 3D-floors - fixed_t planeh = plane.plane.ZatPoint(light->x, light->y); - if (gl_lights_checkside && ((planehz && ceiling) || (planeh>light->z && !ceiling))) - { - node=node->nextLight; - continue; - } - - p.Set(plane.plane); - gl_GetLight(p, light, false, false, lightdata); - node = node->nextLight; - } - } int numlights[3]; @@ -347,7 +282,6 @@ void GLFlat::DrawSubsectors(int pass, bool istrans) //========================================================================== void GLFlat::Draw(int pass) { - int i; int rel = getExtraLight(); #ifdef _DEBUG @@ -360,65 +294,15 @@ void GLFlat::Draw(int pass) switch (pass) { - case GLPASS_BASE: - gl_SetColor(lightlevel, rel, Colormap,1.0f); - if (!foggy) gl_SetFog(lightlevel, rel, &Colormap, false); - DrawSubsectors(pass, false); - break; - case GLPASS_PLAIN: // Single-pass rendering case GLPASS_ALL: - case GLPASS_BASE_MASKED: gl_SetColor(lightlevel, rel, Colormap,1.0f); - if (!foggy || pass != GLPASS_BASE_MASKED) gl_SetFog(lightlevel, rel, &Colormap, false); - // fall through - case GLPASS_TEXTURE: - { + gl_SetFog(lightlevel, rel, &Colormap, false); gltexture->Bind(); gl_SetPlaneTextureRotation(&plane, gltexture); DrawSubsectors(pass, false); gl_RenderState.EnableTextureMatrix(false); break; - } - - case GLPASS_LIGHT: - case GLPASS_LIGHT_ADDITIVE: - - if (!foggy) gl_SetFog((255+lightlevel)>>1, 0, &Colormap, false); - else gl_SetFog(lightlevel, 0, &Colormap, true); - - if (sub) - { - DrawSubsectorLights(sub, pass); - } - else - { - // Draw the subsectors belonging to this sector - for (i=0; isubsectorcount; i++) - { - subsector_t * sub = sector->subsectors[i]; - - if (gl_drawinfo->ss_renderflags[sub-subsectors]&renderflags) - { - DrawSubsectorLights(sub, pass); - } - } - - // Draw the subsectors assigned to it due to missing textures - if (!(renderflags&SSRF_RENDER3DPLANES)) - { - gl_subsectorrendernode * node = (renderflags&SSRF_RENDERFLOOR)? - gl_drawinfo->GetOtherFloorPlanes(sector->sectornum) : - gl_drawinfo->GetOtherCeilingPlanes(sector->sectornum); - - while (node) - { - DrawSubsectorLights(node->sub, pass); - node = node->next; - } - } - } - break; case GLPASS_TRANSLUCENT: if (renderstyle==STYLE_Add) gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE); @@ -462,38 +346,15 @@ inline void GLFlat::PutFlat(bool fog) } if (renderstyle!=STYLE_Translucent || alpha < 1.f - FLT_EPSILON || fog) { - int list = (renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER; - gl_drawinfo->drawlists[list].AddFlat (this); + // translucent 3D floors go into the regular translucent list, translucent portals go into the translucent border list. + list = (renderflags&SSRF_RENDER3DPLANES) ? GLDL_TRANSLUCENT : GLDL_TRANSLUCENTBORDER; } else if (gltexture != NULL) { - static DrawListType list_indices[2][2][2]={ - { { GLDL_PLAIN, GLDL_FOG }, { GLDL_MASKED, GLDL_FOGMASKED } }, - { { GLDL_LIGHT, GLDL_LIGHTFOG }, { GLDL_LIGHTMASKED, GLDL_LIGHTFOGMASKED } } - }; - - bool light = false; bool masked = gltexture->isMasked() && ((renderflags&SSRF_RENDER3DPLANES) || stack); - - if (!gl_fixedcolormap) - { - foggy = gl_CheckFog(&Colormap, lightlevel) || level.flags&LEVEL_HASFADETABLE; - - if (gl_lights && !gl_dynlight_shader && GLRenderer->mLightCount) // Are lights touching this sector? - { - for(int i=0;isubsectorcount;i++) if (sector->subsectors[i]->lighthead[0] != NULL) - { - light=true; - } - } - } - else foggy = false; - - list = list_indices[light][masked][foggy]; - if (list == GLDL_LIGHT && gltexture->tex->gl_info.Brightmap) list = GLDL_LIGHTBRIGHT; - - gl_drawinfo->drawlists[list].AddFlat (this); + list = masked ? GLDL_MASKED : GLDL_PLAIN; } + gl_drawinfo->drawlists[list].AddFlat (this); } //========================================================================== diff --git a/src/gl/scene/gl_portal.cpp b/src/gl/scene/gl_portal.cpp index 0cc602967b..afc5cf1f29 100644 --- a/src/gl/scene/gl_portal.cpp +++ b/src/gl/scene/gl_portal.cpp @@ -144,7 +144,7 @@ void GLPortal::DrawPortalStencil() for (unsigned int i = 0; i 0 && gl_fixedcolormap == CM_DEFAULT && gl_lights && gl_dynlight_shader) + if (mLightCount > 0 && gl_fixedcolormap == CM_DEFAULT && gl_lights) { pass = GLPASS_ALL; } - else if (gl_texture) + else { pass = GLPASS_PLAIN; } - else - { - pass = GLPASS_BASE; - } gl_RenderState.EnableTexture(gl_texture); - gl_RenderState.EnableBrightmap(gl_fixedcolormap == CM_DEFAULT); + gl_RenderState.EnableBrightmap(true); gl_drawinfo->drawlists[GLDL_PLAIN].Sort(); gl_drawinfo->drawlists[GLDL_PLAIN].Draw(pass); - gl_drawinfo->drawlists[GLDL_FOG].Sort(); - gl_drawinfo->drawlists[GLDL_FOG].Draw(pass); - gl_drawinfo->drawlists[GLDL_LIGHTFOG].Sort(); - gl_drawinfo->drawlists[GLDL_LIGHTFOG].Draw(pass); - // Part 2: masked geometry. This is set up so that only pixels with alpha>0.5 will show + // Part 2: masked geometry. This is set up so that only pixels with alpha>gl_mask_threshold will show if (!gl_texture) { gl_RenderState.EnableTexture(true); gl_RenderState.SetTextureMode(TM_MASK); } - if (pass == GLPASS_BASE) pass = GLPASS_BASE_MASKED; gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); gl_drawinfo->drawlists[GLDL_MASKED].Sort(); gl_drawinfo->drawlists[GLDL_MASKED].Draw(pass); - gl_drawinfo->drawlists[GLDL_FOGMASKED].Sort(); - gl_drawinfo->drawlists[GLDL_FOGMASKED].Draw(pass); - gl_drawinfo->drawlists[GLDL_LIGHTFOGMASKED].Sort(); - gl_drawinfo->drawlists[GLDL_LIGHTFOGMASKED].Draw(pass); - // And now the multipass stuff - if (!gl_dynlight_shader && gl_lights) - { - // First pass: empty background with sector light only - - // Part 1: solid geometry. This is set up so that there are no transparent parts - - // remove any remaining texture bindings and shaders whick may get in the way. - gl_RenderState.EnableTexture(false); - gl_RenderState.EnableBrightmap(false); - gl_RenderState.Apply(); - gl_drawinfo->drawlists[GLDL_LIGHT].Draw(GLPASS_BASE); - gl_RenderState.EnableTexture(true); - - // Part 2: masked geometry. This is set up so that only pixels with alpha>0.5 will show - // This creates a blank surface that only fills the nontransparent parts of the texture - gl_RenderState.SetTextureMode(TM_MASK); - gl_RenderState.EnableBrightmap(true); - gl_drawinfo->drawlists[GLDL_LIGHTBRIGHT].Draw(GLPASS_BASE_MASKED); - gl_drawinfo->drawlists[GLDL_LIGHTMASKED].Draw(GLPASS_BASE_MASKED); - gl_RenderState.EnableBrightmap(false); - gl_RenderState.SetTextureMode(TM_MODULATE); - - - // second pass: draw lights (on fogged surfaces they are added to the textures!) - glDepthMask(false); - if (mLightCount && !gl_fixedcolormap) - { - if (gl_SetupLightTexture()) - { - gl_RenderState.BlendFunc(GL_ONE, GL_ONE); - glDepthFunc(GL_EQUAL); - gl_RenderState.SetSoftLightLevel(255); - for(int i=GLDL_FIRSTLIGHT; i<=GLDL_LASTLIGHT; i++) - { - gl_drawinfo->drawlists[i].Draw(GLPASS_LIGHT); - } - gl_RenderState.BlendEquation(GL_FUNC_ADD); - } - else gl_lights=false; - } - - // third pass: modulated texture - gl_RenderState.ResetColor(); - gl_RenderState.BlendFunc(GL_DST_COLOR, GL_ZERO); - gl_RenderState.EnableFog(false); - glDepthFunc(GL_LEQUAL); - if (gl_texture) - { - gl_RenderState.AlphaFunc(GL_GEQUAL, 0.f); - gl_drawinfo->drawlists[GLDL_LIGHT].Sort(); - gl_drawinfo->drawlists[GLDL_LIGHT].Draw(GLPASS_TEXTURE); - gl_RenderState.AlphaFunc(GL_GEQUAL, gl_mask_threshold); - gl_drawinfo->drawlists[GLDL_LIGHTBRIGHT].Sort(); - gl_drawinfo->drawlists[GLDL_LIGHTBRIGHT].Draw(GLPASS_TEXTURE); - gl_drawinfo->drawlists[GLDL_LIGHTMASKED].Sort(); - gl_drawinfo->drawlists[GLDL_LIGHTMASKED].Draw(GLPASS_TEXTURE); - } - - // fourth pass: additive lights - gl_RenderState.EnableFog(true); - if (gl_lights && mLightCount && !gl_fixedcolormap) - { - gl_RenderState.BlendFunc(GL_ONE, GL_ONE); - glDepthFunc(GL_EQUAL); - if (gl_SetupLightTexture()) - { - for(int i=0; idrawlists[i].Draw(GLPASS_LIGHT_ADDITIVE); - } - gl_RenderState.BlendEquation(GL_FUNC_ADD); - } - else gl_lights=false; - } - } gl_RenderState.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); diff --git a/src/gl/scene/gl_skydome.cpp b/src/gl/scene/gl_skydome.cpp index a2c0cf9811..d0932a89fb 100644 --- a/src/gl/scene/gl_skydome.cpp +++ b/src/gl/scene/gl_skydome.cpp @@ -511,6 +511,7 @@ void GLSkyPortal::DrawContents() gl_RenderState.Apply(); glDrawArrays(GL_TRIANGLES, 0, 12); gl_RenderState.EnableTexture(true); + gl_RenderState.SetObjectColor(0xffffffff); } gl_RenderState.SetVertexBuffer(GLRenderer->mVBO); } diff --git a/src/gl/scene/gl_sprite.cpp b/src/gl/scene/gl_sprite.cpp index c70422b323..554b8ff1f5 100644 --- a/src/gl/scene/gl_sprite.cpp +++ b/src/gl/scene/gl_sprite.cpp @@ -112,7 +112,7 @@ CVAR(Bool, gl_nolayer, false, 0) //========================================================================== void GLSprite::Draw(int pass) { - if (pass!=GLPASS_PLAIN && pass != GLPASS_ALL && pass!=GLPASS_TRANSLUCENT) return; + if (pass == GLPASS_DECALS) return; diff --git a/src/gl/scene/gl_spritelight.cpp b/src/gl/scene/gl_spritelight.cpp index e4e2e855a5..e669412830 100644 --- a/src/gl/scene/gl_spritelight.cpp +++ b/src/gl/scene/gl_spritelight.cpp @@ -71,47 +71,44 @@ void gl_SetDynSpriteLight(AActor *self, fixed_t x, fixed_t y, fixed_t z, subsect float out[3] = { 0.0f, 0.0f, 0.0f }; // Go through both light lists - for (int i = 0; i < 2; i++) + FLightNode * node = subsec->lighthead; + while (node) { - FLightNode * node = subsec->lighthead[i]; - while (node) + light=node->lightsource; + if (!light->owned || light->target == NULL || light->target->IsVisibleToPlayer()) { - light = node->lightsource; - if (!light->owned || light->target == NULL || light->target->IsVisibleToPlayer()) + if (!(light->flags2&MF2_DORMANT) && + (!(light->flags4&MF4_DONTLIGHTSELF) || light->target != self)) { - if (!(light->flags2&MF2_DORMANT) && - (!(light->flags4&MF4_DONTLIGHTSELF) || light->target != self)) + float dist = FVector3(FIXED2FLOAT(x - light->x), FIXED2FLOAT(y - light->y), FIXED2FLOAT(z - light->z)).Length(); + radius = light->GetRadius() * gl_lights_size; + + if (dist < radius) { - float dist = FVector3(FIXED2FLOAT(x - light->x), FIXED2FLOAT(y - light->y), FIXED2FLOAT(z - light->z)).Length(); - radius = light->GetRadius() * gl_lights_size; + frac = 1.0f - (dist / radius); - if (dist < radius) + if (frac > 0) { - frac = 1.0f - (dist / radius); - - if (frac > 0) + lr = light->GetRed() / 255.0f * gl_lights_intensity; + lg = light->GetGreen() / 255.0f * gl_lights_intensity; + lb = light->GetBlue() / 255.0f * gl_lights_intensity; + if (light->IsSubtractive()) { - lr = light->GetRed() / 255.0f * gl_lights_intensity; - lg = light->GetGreen() / 255.0f * gl_lights_intensity; - lb = light->GetBlue() / 255.0f * gl_lights_intensity; - if (light->IsSubtractive()) - { - float bright = FVector3(lr, lg, lb).Length(); - FVector3 lightColor(lr, lg, lb); - lr = (bright - lr) * -1; - lg = (bright - lg) * -1; - lb = (bright - lb) * -1; - } - - out[0] += lr * frac; - out[1] += lg * frac; - out[2] += lb * frac; + float bright = FVector3(lr, lg, lb).Length(); + FVector3 lightColor(lr, lg, lb); + lr = (bright - lr) * -1; + lg = (bright - lg) * -1; + lb = (bright - lb) * -1; } + + out[0] += lr * frac; + out[1] += lg * frac; + out[2] += lb * frac; } } } - node = node->nextLight; } + node = node->nextLight; } gl_RenderState.SetDynLight(out[0], out[1], out[2]); } diff --git a/src/gl/scene/gl_wall.h b/src/gl/scene/gl_wall.h index 28fa518683..fe2ec6e8e0 100644 --- a/src/gl/scene/gl_wall.h +++ b/src/gl/scene/gl_wall.h @@ -169,7 +169,7 @@ private: void SetupLights(); bool PrepareLight(texcoord * tcs, ADynamicLight * light); - void RenderWall(int textured, ADynamicLight * light=NULL, unsigned int *store = NULL); + void RenderWall(int textured, unsigned int *store = NULL); void FloodPlane(int pass); diff --git a/src/gl/scene/gl_walls.cpp b/src/gl/scene/gl_walls.cpp index f720f1678c..60e9287bf6 100644 --- a/src/gl/scene/gl_walls.cpp +++ b/src/gl/scene/gl_walls.cpp @@ -114,9 +114,8 @@ void GLWall::PutWall(bool translucent) 4, //RENDERWALL_SECTORSTACK, // special 4, //RENDERWALL_PLANEMIRROR, // special 4, //RENDERWALL_MIRROR, // special - 1, //RENDERWALL_MIRRORSURFACE, // needs special handling - 2, //RENDERWALL_M2SNF, // depends on render and texture settings, no fog - 2, //RENDERWALL_M2SFOG, // depends on render and texture settings, no fog + 1, //RENDERWALL_MIRRORSURFACE, // only created here from RENDERWALL_MIRROR + 2, //RENDERWALL_M2SNF, // depends on render and texture settings, no fog, used on mid texture lines with a fog boundary. 3, //RENDERWALL_COLOR, // translucent 2, //RENDERWALL_FFBLOCK // depends on render and texture settings 4, //RENDERWALL_COLORLAYER // color layer needs special handling @@ -145,47 +144,11 @@ void GLWall::PutWall(bool translucent) } else if (passflag[type]!=4) // non-translucent walls { - static DrawListType list_indices[2][2][2]={ - { { GLDL_PLAIN, GLDL_FOG }, { GLDL_MASKED, GLDL_FOGMASKED } }, - { { GLDL_LIGHT, GLDL_LIGHTFOG }, { GLDL_LIGHTMASKED, GLDL_LIGHTFOGMASKED } } - }; bool masked; - bool light = false; - if (!gl_fixedcolormap) - { - if (gl_lights && !gl_dynlight_shader) - { - if (seg->sidedef == NULL) - { - light = false; - } - else if (!(seg->sidedef->Flags & WALLF_POLYOBJ)) - { - light = seg->sidedef->lighthead[0] != NULL; - } - else if (sub) - { - // for polyobjects we cannot use the side's light list. - // We must use the subsector's. - light = sub->lighthead[0] != NULL; - } - } - } - else - { - flags&=~GLWF_FOGGY; - } - - masked = passflag[type]==1? false : (light && type!=RENDERWALL_FFBLOCK) || (gltexture && gltexture->isMasked()); - - list = list_indices[light][masked][!!(flags&GLWF_FOGGY)]; - if (list == GLDL_LIGHT) - { - if (gltexture->tex->gl_info.Brightmap) list = GLDL_LIGHTBRIGHT; - if (flags & GLWF_GLOW) list = GLDL_LIGHTBRIGHT; - } + masked = passflag[type]==1? false : (gltexture && gltexture->isMasked()); + list = masked ? GLDL_MASKED : GLDL_PLAIN; gl_drawinfo->drawlists[list].AddWall(this); } @@ -1514,11 +1477,12 @@ void GLWall::Process(seg_t *seg, sector_t * frontsector, sector_t * backsector) glseg.x2= FIXED2FLOAT(v2->x); glseg.y2= FIXED2FLOAT(v2->y); Colormap=frontsector->ColorMap; - flags = (!gl_isBlack(Colormap.FadeColor) || level.flags&LEVEL_HASFADETABLE)? GLWF_FOGGY : 0; + flags = 0; int rel = 0; int orglightlevel = gl_ClampLight(frontsector->lightlevel); - lightlevel = gl_ClampLight(seg->sidedef->GetLightLevel(!!(flags&GLWF_FOGGY), orglightlevel, false, &rel)); + bool foggy = (!gl_isBlack(Colormap.FadeColor) || level.flags&LEVEL_HASFADETABLE); // fog disables fake contrast + lightlevel = gl_ClampLight(seg->sidedef->GetLightLevel(foggy, orglightlevel, false, &rel)); if (orglightlevel >= 253) // with the software renderer fake contrast won't be visible above this. { rellight = 0; @@ -1764,7 +1728,7 @@ void GLWall::ProcessLowerMiniseg(seg_t *seg, sector_t * frontsector, sector_t * glseg.fracleft = 0; glseg.fracright = 1; - flags = (!gl_isBlack(Colormap.FadeColor) || level.flags&LEVEL_HASFADETABLE)? GLWF_FOGGY : 0; + flags = 0; // can't do fake contrast without a sidedef lightlevel = gl_ClampLight(frontsector->lightlevel); diff --git a/src/gl/scene/gl_walls_draw.cpp b/src/gl/scene/gl_walls_draw.cpp index e28f6315c6..8cdae097a2 100644 --- a/src/gl/scene/gl_walls_draw.cpp +++ b/src/gl/scene/gl_walls_draw.cpp @@ -62,57 +62,6 @@ EXTERN_CVAR(Bool, gl_seamless) -//========================================================================== -// -// Sets up the texture coordinates for one light to be rendered -// -//========================================================================== -bool GLWall::PrepareLight(texcoord * tcs, ADynamicLight * light) -{ - float vtx[]={glseg.x1,zbottom[0],glseg.y1, glseg.x1,ztop[0],glseg.y1, glseg.x2,ztop[1],glseg.y2, glseg.x2,zbottom[1],glseg.y2}; - Plane p; - Vector nearPt, up, right; - float scale; - - p.Init(vtx,4); - - if (!p.ValidNormal()) - { - return false; - } - - if (!gl_SetupLight(p, light, nearPt, up, right, scale, Colormap.desaturation, true, !!(flags&GLWF_FOGGY))) - { - return false; - } - - if (tcs != NULL) - { - Vector t1; - int outcnt[4]={0,0,0,0}; - - for(int i=0;i<4;i++) - { - t1.Set(&vtx[i*3]); - Vector nearToVert = t1 - nearPt; - tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f; - tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f; - - // quick check whether the light touches this polygon - if (tcs[i].u<0) outcnt[0]++; - if (tcs[i].u>1) outcnt[1]++; - if (tcs[i].v<0) outcnt[2]++; - if (tcs[i].v>1) outcnt[3]++; - - } - // The light doesn't touch this polygon - if (outcnt[0]==4 || outcnt[1]==4 || outcnt[2]==4 || outcnt[3]==4) return false; - } - - draw_dlight++; - return true; -} - //========================================================================== // // Collect lights for shader @@ -132,77 +81,74 @@ void GLWall::SetupLights() { return; } - for(int i=0;i<2;i++) + FLightNode *node; + if (seg->sidedef == NULL) { - FLightNode *node; - if (seg->sidedef == NULL) - { - node = NULL; - } - else if (!(seg->sidedef->Flags & WALLF_POLYOBJ)) - { - node = seg->sidedef->lighthead[i]; - } - else if (sub) - { - // Polobject segs cannot be checked per sidedef so use the subsector instead. - node = sub->lighthead[i]; - } - else node = NULL; + node = NULL; + } + else if (!(seg->sidedef->Flags & WALLF_POLYOBJ)) + { + node = seg->sidedef->lighthead; + } + else if (sub) + { + // Polobject segs cannot be checked per sidedef so use the subsector instead. + node = sub->lighthead; + } + else node = NULL; - // Iterate through all dynamic lights which touch this wall and render them - while (node) + // Iterate through all dynamic lights which touch this wall and render them + while (node) + { + if (!(node->lightsource->flags2&MF2_DORMANT)) { - if (!(node->lightsource->flags2&MF2_DORMANT)) + iter_dlight++; + + Vector fn, pos; + + float x = FIXED2FLOAT(node->lightsource->x); + float y = FIXED2FLOAT(node->lightsource->y); + float z = FIXED2FLOAT(node->lightsource->z); + float dist = fabsf(p.DistToPoint(x, z, y)); + float radius = (node->lightsource->GetRadius() * gl_lights_size); + float scale = 1.0f / ((2.f * radius) - dist); + + if (radius > 0.f && dist < radius) { - iter_dlight++; + Vector nearPt, up, right; - Vector fn, pos; + pos.Set(x,z,y); + fn=p.Normal(); + fn.GetRightUp(right, up); - float x = FIXED2FLOAT(node->lightsource->x); - float y = FIXED2FLOAT(node->lightsource->y); - float z = FIXED2FLOAT(node->lightsource->z); - float dist = fabsf(p.DistToPoint(x, z, y)); - float radius = (node->lightsource->GetRadius() * gl_lights_size); - float scale = 1.0f / ((2.f * radius) - dist); + Vector tmpVec = fn * dist; + nearPt = pos + tmpVec; - if (radius > 0.f && dist < radius) + Vector t1; + int outcnt[4]={0,0,0,0}; + texcoord tcs[4]; + + // do a quick check whether the light touches this polygon + for(int i=0;i<4;i++) { - Vector nearPt, up, right; + t1.Set(&vtx[i*3]); + Vector nearToVert = t1 - nearPt; + tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f; + tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f; - pos.Set(x,z,y); - fn=p.Normal(); - fn.GetRightUp(right, up); + if (tcs[i].u<0) outcnt[0]++; + if (tcs[i].u>1) outcnt[1]++; + if (tcs[i].v<0) outcnt[2]++; + if (tcs[i].v>1) outcnt[3]++; - Vector tmpVec = fn * dist; - nearPt = pos + tmpVec; - - Vector t1; - int outcnt[4]={0,0,0,0}; - texcoord tcs[4]; - - // do a quick check whether the light touches this polygon - for(int i=0;i<4;i++) - { - t1.Set(&vtx[i*3]); - Vector nearToVert = t1 - nearPt; - tcs[i].u = (nearToVert.Dot(right) * scale) + 0.5f; - tcs[i].v = (nearToVert.Dot(up) * scale) + 0.5f; - - if (tcs[i].u<0) outcnt[0]++; - if (tcs[i].u>1) outcnt[1]++; - if (tcs[i].v<0) outcnt[2]++; - if (tcs[i].v>1) outcnt[3]++; - - } - if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4) - { - gl_GetLight(p, node->lightsource, true, false, lightdata); - } + } + if (outcnt[0]!=4 && outcnt[1]!=4 && outcnt[2]!=4 && outcnt[3]!=4) + { + gl_GetLight(p, node->lightsource, true, false, lightdata); } } - node = node->nextLight; } + node = node->nextLight; } int numlights[3]; @@ -223,29 +169,20 @@ void GLWall::SetupLights() // //========================================================================== -void GLWall::RenderWall(int textured, ADynamicLight * light, unsigned int *store) +void GLWall::RenderWall(int textured, unsigned int *store) { static texcoord tcs[4]; // making this variable static saves us a relatively costly stack integrity check. bool split = (gl_seamless && !(textured&RWF_NOSPLIT) && seg->sidedef != NULL && !(seg->sidedef->Flags & WALLF_POLYOBJ)); - if (!light) + tcs[0]=lolft; + tcs[1]=uplft; + tcs[2]=uprgt; + tcs[3]=lorgt; + if ((flags&GLWF_GLOW) && (textured & RWF_GLOW)) { - tcs[0]=lolft; - tcs[1]=uplft; - tcs[2]=uprgt; - tcs[3]=lorgt; - if ((flags&GLWF_GLOW) && (textured & RWF_GLOW)) - { - gl_RenderState.SetGlowPlanes(topplane, bottomplane); - gl_RenderState.SetGlowParams(topglowcolor, bottomglowcolor); - } - + gl_RenderState.SetGlowPlanes(topplane, bottomplane); + gl_RenderState.SetGlowParams(topglowcolor, bottomglowcolor); } - else - { - if (!PrepareLight(tcs, light)) return; - } - if (!(textured & RWF_NORENDER)) { @@ -422,7 +359,6 @@ void GLWall::RenderTranslucentWall() //========================================================================== void GLWall::Draw(int pass) { - FLightNode * node; int rel; #ifdef _DEBUG @@ -461,64 +397,6 @@ void GLWall::Draw(int pass) gl_RenderState.EnableLight(false); break; - case GLPASS_BASE: // Base pass for non-masked polygons (all opaque geometry) - case GLPASS_BASE_MASKED: // Base pass for masked polygons (2sided mid-textures and transparent 3D floors) - rel = rellight + getExtraLight(); - gl_SetColor(lightlevel, rel, Colormap,1.0f); - if (!(flags&GLWF_FOGGY)) - { - if (type!=RENDERWALL_M2SNF) gl_SetFog(lightlevel, rel, &Colormap, false); - else gl_SetFog(255, 0, NULL, false); - } - gl_RenderState.EnableGlow(!!(flags & GLWF_GLOW)); - // fall through - - if (pass != GLPASS_BASE) - { - gltexture->Bind(flags, 0); - } - RenderWall(RWF_TEXTURED|RWF_GLOW); - gl_RenderState.EnableGlow(false); - gl_RenderState.EnableLight(false); - break; - - case GLPASS_TEXTURE: // modulated texture - gltexture->Bind(flags, 0); - RenderWall(RWF_TEXTURED); - break; - - case GLPASS_LIGHT: - case GLPASS_LIGHT_ADDITIVE: - // black fog is diminishing light and should affect lights less than the rest! - if (!(flags&GLWF_FOGGY)) gl_SetFog((255+lightlevel)>>1, 0, NULL, false); - else gl_SetFog(lightlevel, 0, &Colormap, true); - - if (seg->sidedef == NULL) - { - node = NULL; - } - else if (!(seg->sidedef->Flags & WALLF_POLYOBJ)) - { - // Iterate through all dynamic lights which touch this wall and render them - node = seg->sidedef->lighthead[pass==GLPASS_LIGHT_ADDITIVE]; - } - else if (sub) - { - // To avoid constant rechecking for polyobjects use the subsector's lightlist instead - node = sub->lighthead[pass==GLPASS_LIGHT_ADDITIVE]; - } - else node = NULL; - while (node) - { - if (!(node->lightsource->flags2&MF2_DORMANT)) - { - iter_dlight++; - RenderWall(RWF_TEXTURED, node->lightsource); - } - node = node->nextLight; - } - break; - case GLPASS_DECALS: case GLPASS_DECALS_NOFOG: if (seg->sidedef && seg->sidedef->AttachedDecals) diff --git a/src/r_defs.h b/src/r_defs.h index 6b76803b4d..dea227cc50 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -772,7 +772,7 @@ struct sector_t int subsectorcount; // list of subsectors subsector_t ** subsectors; FPortal * portals[2]; // floor and ceiling portals - FLightNode * lighthead[2]; + FLightNode * lighthead; enum { @@ -949,7 +949,7 @@ struct side_t vertex_t *V2() const; //For GL - FLightNode * lighthead[2]; // all blended lights that may affect this wall + FLightNode * lighthead; // all blended lights that may affect this wall seg_t **segs; // all segs belonging to this sidedef in ascending order. Used for precise rendering int numsegs; @@ -1082,7 +1082,7 @@ struct subsector_t void BuildPolyBSP(); // subsector related GL data - FLightNode * lighthead[2]; // Light nodes (blended and additive) + FLightNode * lighthead; // Light nodes (blended and additive) int validcount; short mapsection; char hacked; // 1: is part of a render hack diff --git a/wadsrc/static/glstuff/gllight.png b/wadsrc/static/glstuff/gllight.png deleted file mode 100644 index a3a296b9f856f4654aae244e3400a4f33fd95902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13073 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