// //--------------------------------------------------------------------------- // // Copyright(C) 2003-2018 Christoph Oelckers // All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with this program. If not, see http://www.gnu.org/licenses/ // //-------------------------------------------------------------------------- // /* ** gl_decal.cpp ** OpenGL decal processing code ** */ #include "doomdata.h" #include "a_sharedglobal.h" #include "r_utility.h" #include "g_levellocals.h" #include "hw_material.h" #include "hw_cvars.h" #include "hwrenderer/scene/hw_drawstructs.h" #include "hwrenderer/scene/hw_drawinfo.h" #include "hw_lighting.h" #include "hw_clock.h" #include "flatvertices.h" #include "hw_renderstate.h" #include "texturemanager.h" //========================================================================== // // // //========================================================================== void HWDecal::DrawDecal(HWDrawInfo *di, FRenderState &state) { PalEntry DecalColor; // alpha color only has an effect when using an alpha texture. if (decal->RenderStyle.Flags & (STYLEF_RedIsAlpha | STYLEF_ColorIsFixed)) { DecalColor = decal->AlphaColor | 0xff000000; } else DecalColor = 0xffffffff; if (!di->isFullbrightScene()) DecalColor = DecalColor.Modulate(frontsector->SpecialColors[sector_t::sprites]); state.SetObjectColor(DecalColor); state.SetLightIndex(dynlightindex); state.SetTextureMode(decal->RenderStyle); state.SetRenderStyle(decal->RenderStyle); state.SetMaterial(texture, UF_Sprite, 0, CLAMP_XY, decal->Translation, -1); // If srcalpha is one it looks better with a higher alpha threshold if (decal->RenderStyle.SrcAlpha == STYLEALPHA_One) state.AlphaFunc(Alpha_GEqual, gl_mask_sprite_threshold); else state.AlphaFunc(Alpha_Greater, 0.f); di->SetColor(state, lightlevel, rellight, di->isFullbrightScene(), Colormap, alpha); // for additively drawn decals we must temporarily set the fog color to black. PalEntry fc = state.GetFogColor(); if (decal->RenderStyle.BlendOp == STYLEOP_Add && decal->RenderStyle.DestAlpha == STYLEALPHA_One) { state.SetFog(0, -1); } state.SetNormal(Normal); if (lightlist == nullptr) { state.Draw(DT_TriangleStrip, vertindex, 4); } else { auto &lightlist = *this->lightlist; for (unsigned k = 0; k < lightlist.Size(); k++) { secplane_t &lowplane = k == lightlist.Size() - 1 ? frontsector->floorplane : lightlist[k + 1].plane; float low1 = lowplane.ZatPoint(dv[1].x, dv[1].y); float low2 = lowplane.ZatPoint(dv[2].x, dv[2].y); if (low1 < dv[1].z || low2 < dv[2].z) { int thisll = lightlist[k].caster != nullptr ? hw_ClampLight(*lightlist[k].p_lightlevel) : lightlevel; FColormap thiscm; thiscm.FadeColor = Colormap.FadeColor; CopyFrom3DLight(thiscm, &lightlist[k]); di->SetColor(state, thisll, rellight, di->isFullbrightScene(), thiscm, alpha); if (di->Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) thiscm.Decolorize(); di->SetFog(state, thisll, rellight, di->isFullbrightScene(), &thiscm, false); SetSplitPlanes(state, lightlist[k].plane, lowplane); state.Draw(DT_TriangleStrip, vertindex, 4); } if (low1 <= dv[0].z && low2 <= dv[3].z) break; } } rendered_decals++; state.SetTextureMode(TM_NORMAL); state.SetObjectColor(0xffffffff); state.SetFog(fc, -1); state.SetDynLight(0, 0, 0); } //========================================================================== // // // //========================================================================== void HWDrawInfo::DrawDecals(FRenderState &state, TArray &decals) { side_t *wall = nullptr; state.SetDepthMask(false); state.SetTextureClamp(true); state.SetDepthBias(-1, -128); for (auto gldecal : decals) { if (gldecal->decal->Side != wall) { wall = gldecal->decal->Side; if (gldecal->lightlist != nullptr) { state.EnableSplit(true); } else { state.EnableSplit(false); SetFog(state, gldecal->lightlevel, gldecal->rellight, isFullbrightScene(), &gldecal->Colormap, false); } } gldecal->DrawDecal(this, state); } state.EnableSplit(false); state.ClearDepthBias(); state.SetTextureMode(TM_NORMAL); state.SetDepthMask(true); state.SetTextureClamp(false); } //========================================================================== // // This list will never get long, so this code should be ok. // //========================================================================== void HWWall::DrawDecalsForMirror(HWDrawInfo *di, FRenderState &state, TArray &decals) { state.SetDepthMask(false); state.SetDepthBias(-1, -128); di->SetFog(state, lightlevel, rellight + getExtraLight(), di->isFullbrightScene(), &Colormap, false); for (auto gldecal : decals) { if (gldecal->decal->Side == seg->sidedef) { gldecal->DrawDecal(di, state); } } state.ClearDepthBias(); state.SetTextureMode(TM_NORMAL); state.SetDepthMask(true); } //========================================================================== // // // //========================================================================== static float mix(float a, float b, float t) { return a * (1.0f - t) + b * t; } void HWWall::ProcessDecal(HWDrawInfo *di, DBaseDecal *decal, const FVector3 &normal) { line_t * line = seg->linedef; side_t * side = seg->sidedef; int i; float zpos; bool flipx, flipy; FTextureID decalTile; if (decal->RenderFlags & RF_INVISIBLE) return; if (type == RENDERWALL_FFBLOCK && texture->isMasked()) return; // No decals on 3D floors with transparent textures. if (seg == nullptr) return; decalTile = decal->PicNum; flipx = !!(decal->RenderFlags & RF_XFLIP); flipy = !!(decal->RenderFlags & RF_YFLIP); auto texture = TexMan.GetGameTexture(decalTile); if (texture == NULL) return; // the sectors are only used for their texture origin coordinates // so we don't need the fake sectors for deep water etc. // As this is a completely split wall fragment no further splits are // necessary for the decal. sector_t *frontsector; // for 3d-floor segments use the model sector as reference if ((decal->RenderFlags&RF_CLIPMASK) == RF_CLIPMID) frontsector = decal->Sector; else frontsector = seg->frontsector; switch (decal->RenderFlags & RF_RELMASK) { default: // No valid decal can have this type. If one is encountered anyway // it is in some way invalid so skip it. return; //zpos = decal->z; //break; case RF_RELUPPER: if (type != RENDERWALL_TOP) return; if (line->flags & ML_DONTPEGTOP) { zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling); } else { zpos = decal->Z + seg->backsector->GetPlaneTexZ(sector_t::ceiling); } break; case RF_RELLOWER: if (type != RENDERWALL_BOTTOM) return; if (line->flags & ML_DONTPEGBOTTOM) { zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling); } else { zpos = decal->Z + seg->backsector->GetPlaneTexZ(sector_t::floor); } break; case RF_RELMID: if (type == RENDERWALL_TOP || type == RENDERWALL_BOTTOM) return; if (line->flags & ML_DONTPEGBOTTOM) { zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::floor); } else { zpos = decal->Z + frontsector->GetPlaneTexZ(sector_t::ceiling); } } // now clip the decal to the actual polygon float decalwidth = texture->GetDisplayWidth() * decal->ScaleX; float decalheight = texture->GetDisplayHeight() * decal->ScaleY; float decallefto = texture->GetDisplayLeftOffset() * decal->ScaleX; float decaltopo = texture->GetDisplayTopOffset() * decal->ScaleY; float leftedge = glseg.fracleft * side->TexelLength; float linelength = glseg.fracright * side->TexelLength - leftedge; // texel index of the decal's left edge float decalpixpos = (float)side->TexelLength * decal->LeftDistance - (flipx ? decalwidth - decallefto : decallefto) - leftedge; float left, right; float lefttex, righttex; // decal is off the left edge if (decalpixpos < 0) { left = 0; lefttex = -decalpixpos; } else { left = decalpixpos; lefttex = 0; } // decal is off the right edge if (decalpixpos + decalwidth > linelength) { right = linelength; righttex = right - decalpixpos; } else { right = decalpixpos + decalwidth; righttex = decalwidth; } if (right <= left) return; // nothing to draw // one texture unit on the wall as vector float vx = (glseg.x2 - glseg.x1) / linelength; float vy = (glseg.y2 - glseg.y1) / linelength; DecalVertex dv[4]; enum { LL, UL, LR, UR }; dv[UL].x = dv[LL].x = glseg.x1 + vx * left; dv[UL].y = dv[LL].y = glseg.y1 + vy * left; dv[LR].x = dv[UR].x = glseg.x1 + vx * right; dv[LR].y = dv[UR].y = glseg.y1 + vy * right; zpos += (flipy ? decalheight - decaltopo : decaltopo); dv[UL].z = dv[UR].z = zpos; dv[LL].z = dv[LR].z = dv[UL].z - decalheight; dv[UL].v = dv[UR].v = 0.f; float decalscale = float(decal->ScaleX * texture->GetDisplayWidth()); dv[UL].u = dv[LL].u = lefttex / decalscale; dv[LR].u = dv[UR].u = righttex / decalscale; dv[LL].v = dv[LR].v = 1.f; // lightmap texture index for (i = 0; i < 4; i++) { dv[i].lindex = lindex; } // lightmap texture coordinates float tleft = left / linelength; float tright = right / linelength; float tuplft = ztop[0] != zbottom[0] ? (dv[UL].z - zbottom[0]) / (ztop[0] - zbottom[0]) : 0.0f; float tuprgt = ztop[1] != zbottom[1] ? (dv[UR].z - zbottom[1]) / (ztop[1] - zbottom[1]) : 0.0f; float tlolft = ztop[0] != zbottom[0] ? (dv[LL].z - zbottom[0]) / (ztop[0] - zbottom[0]) : 0.0f; float tlorgt = ztop[1] != zbottom[1] ? (dv[LR].z - zbottom[1]) / (ztop[1] - zbottom[1]) : 0.0f; dv[LL].lu = mix(lightuv[LOLFT].u, lightuv[LORGT].u, tleft); dv[LR].lu = mix(lightuv[LOLFT].u, lightuv[LORGT].u, tright); dv[UL].lu = mix(lightuv[UPLFT].u, lightuv[UPRGT].u, tleft); dv[UR].lu = mix(lightuv[UPLFT].u, lightuv[UPRGT].u, tright); dv[LL].lv = mix(lightuv[LOLFT].v, lightuv[UPLFT].v, tlolft); dv[LR].lv = mix(lightuv[LORGT].v, lightuv[UPRGT].v, tlorgt); dv[UL].lv = mix(lightuv[LOLFT].v, lightuv[UPLFT].v, tuplft); dv[UR].lv = mix(lightuv[LORGT].v, lightuv[UPRGT].v, tuprgt); // now clip to the top plane float vzt = (ztop[UL] - ztop[LL]) / linelength; float topleft = ztop[LL] + vzt * left; float topright = ztop[LL] + vzt * right; // completely below the wall if (topleft < dv[LL].z && topright < dv[LR].z) return; if (topleft < dv[UL].z || topright < dv[UR].z) { // decal has to be clipped at the top // let texture clamping handle all extreme cases float t0 = (dv[UL].z - topleft) / (dv[UL].z - dv[LL].z); float t1 = (dv[UR].z - topright) / (dv[UR].z - dv[LR].z); dv[UL].v = t0 * dv[LL].v; dv[UR].v = t1 * dv[LR].v; dv[UL].lv = mix(dv[UL].lv, dv[LL].lv, t0); dv[UR].lv = mix(dv[UR].lv, dv[LR].lv, t1); dv[UL].z = topleft; dv[UR].z = topright; } // now clip to the bottom plane float vzb = (zbottom[UL] - zbottom[LL]) / linelength; float bottomleft = zbottom[LL] + vzb * left; float bottomright = zbottom[LL] + vzb * right; // completely above the wall if (bottomleft > dv[UL].z && bottomright > dv[UR].z) return; if (bottomleft > dv[LL].z || bottomright > dv[LR].z) { // decal has to be clipped at the bottom // let texture clamping handle all extreme cases float t0 = (dv[UL].z - bottomleft) / (dv[UL].z - dv[LL].z); float t1 = (dv[UR].z - bottomright) / (dv[UR].z - dv[LR].z); dv[LL].v = t0 * (dv[LL].v - dv[UL].v) + dv[UL].v; dv[LR].v = t1 * (dv[LR].v - dv[UR].v) + dv[UR].v; dv[LL].lv = mix(dv[UL].lv, dv[LL].lv, t0); dv[LR].lv = mix(dv[UR].lv, dv[LR].lv, t1); dv[LL].z = bottomleft; dv[LR].z = bottomright; } if (flipx) { for (i = 0; i < 4; i++) dv[i].u = 1.f - dv[i].u; } if (flipy) { for (i = 0; i < 4; i++) dv[i].v = 1.f - dv[i].v; } HWDecal *gldecal = di->AddDecal(type == RENDERWALL_MIRRORSURFACE); gldecal->texture = texture; gldecal->decal = decal; if (decal->RenderFlags & RF_FULLBRIGHT) { gldecal->lightlevel = 255; gldecal->rellight = 0; gldecal->dynlightindex = -1; } else { gldecal->lightlevel = lightlevel; gldecal->rellight = rellight + getExtraLight(); gldecal->dynlightindex = dynlightindex; } gldecal->Colormap = Colormap; if (di->Level->flags3 & LEVEL3_NOCOLOREDSPRITELIGHTING) { gldecal->Colormap.Decolorize(); } gldecal->alpha = decal->Alpha; gldecal->zcenter = zpos - decalheight * 0.5f; gldecal->frontsector = frontsector; gldecal->Normal = normal; gldecal->lightlist = lightlist; memcpy(gldecal->dv, dv, sizeof(dv)); auto verts = screen->mVertexData->AllocVertices(4); gldecal->vertindex = verts.second; for (i = 0; i < 4; i++) { verts.first[i].Set(dv[i].x, dv[i].z, dv[i].y, dv[i].u, dv[i].v, dv[i].lu, dv[i].lv, dv[i].lindex); } } //========================================================================== // // // //========================================================================== void HWWall::ProcessDecals(HWDrawInfo *di) { if (seg->sidedef != nullptr) { DBaseDecal *decal = seg->sidedef->AttachedDecals; if (decal) { auto normal = glseg.Normal(); // calculate the normal only once per wall because it requires a square root. while (decal) { ProcessDecal(di, decal, normal); decal = decal->WallNext; } } } }