summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 0cf82d2)
raw | patch | inline | side by side (parent: 0cf82d2)
author | Ketmar Dark <ketmar@ketmar.no-ip.org> | |
Sun, 13 Aug 2017 11:50:58 +0000 (14:50 +0300) | ||
committer | Ketmar Dark <ketmar@ketmar.no-ip.org> | |
Wed, 16 Aug 2017 09:27:46 +0000 (12:27 +0300) |
real-time lighting based on shadow volumes and stencil buffer
basically, the same thing that is used in 3d, but simplified
(we only need to draw shadow volumes, and unmarked area is lit)
basically, the same thing that is used in 3d, but simplified
(we only need to draw shadow volumes, and unmarked area is lit)
diff --git a/src/game/g_game.pas b/src/game/g_game.pas
index 66c1edc5688a048a6506442636133df48414fdf6..c56bc488529fa761b9efd506474c5e4ffc26ad73 100644 (file)
--- a/src/game/g_game.pas
+++ b/src/game/g_game.pas
P1MoveButton: Byte = 0;
P2MoveButton: Byte = 0;
+procedure g_ResetDynlights ();
+procedure g_AddDynLight (x, y, radius: Integer; r, g, b, a: Single);
+procedure g_DynLightExplosion (x, y, radius: Integer; r, g, b: Single);
+
implementation
uses
ENet, e_fixedbuffer, g_netmsg, g_netmaster, GL, GLExt,
utils, sfs;
+type
+ TDynLight = record
+ x, y, radius: Integer;
+ r, g, b, a: Single;
+ exploCount: Integer;
+ exploRadius: Integer;
+ end;
+
+var
+ g_dynLights: array of TDynLight = nil;
+ g_dynLightCount: Integer = 0;
+ g_playerLight: Boolean = false;
+
+procedure g_ResetDynlights ();
+var
+ lnum, idx: Integer;
+begin
+ lnum := 0;
+ for idx := 0 to g_dynLightCount-1 do
+ begin
+ if g_dynLights[idx].exploCount = -666 then
+ begin
+ // skip it
+ end
+ else
+ begin
+ // explosion
+ Inc(g_dynLights[idx].exploCount);
+ if (g_dynLights[idx].exploCount < 10) then
+ begin
+ g_dynLights[idx].radius := g_dynLights[idx].exploRadius+g_dynLights[idx].exploCount*8;
+ g_dynLights[idx].a := 0.4+g_dynLights[idx].exploCount/10;
+ if (g_dynLights[idx].a > 0.8) then g_dynLights[idx].a := 0.8;
+ if lnum <> idx then g_dynLights[lnum] := g_dynLights[idx];
+ Inc(lnum);
+ end;
+ end;
+ end;
+ g_dynLightCount := lnum;
+end;
+
+procedure g_AddDynLight (x, y, radius: Integer; r, g, b, a: Single);
+begin
+ if g_dynLightCount = length(g_dynLights) then SetLength(g_dynLights, g_dynLightCount+1024);
+ g_dynLights[g_dynLightCount].x := x;
+ g_dynLights[g_dynLightCount].y := y;
+ g_dynLights[g_dynLightCount].radius := radius;
+ g_dynLights[g_dynLightCount].r := r;
+ g_dynLights[g_dynLightCount].g := g;
+ g_dynLights[g_dynLightCount].b := b;
+ g_dynLights[g_dynLightCount].a := a;
+ g_dynLights[g_dynLightCount].exploCount := -666;
+ Inc(g_dynLightCount);
+end;
+
+procedure g_DynLightExplosion (x, y, radius: Integer; r, g, b: Single);
+begin
+ if g_dynLightCount = length(g_dynLights) then SetLength(g_dynLights, g_dynLightCount+1024);
+ g_dynLights[g_dynLightCount].x := x;
+ g_dynLights[g_dynLightCount].y := y;
+ g_dynLights[g_dynLightCount].radius := 0;
+ g_dynLights[g_dynLightCount].exploRadius := radius;
+ g_dynLights[g_dynLightCount].r := r;
+ g_dynLights[g_dynLightCount].g := g;
+ g_dynLights[g_dynLightCount].b := b;
+ g_dynLights[g_dynLightCount].a := 0;
+ g_dynLights[g_dynLightCount].exploCount := 0;
+ Inc(g_dynLightCount);
+end;
+
type
TEndCustomGameStat = record
PlayerStat: TPlayerStatArray;
if isKeyPressed(KeyWeapon[i], KeyWeapon2[i]) then
plr.QueueWeaponSwitch(i); // all choices are passed there, and god will take the best
end;
+
+ // HACK: add dynlight here
+ if e_KeyPressed(IK_F8) and gGameOn and (not gConsoleShow) and (g_ActiveWindow = nil) then
+ begin
+ g_playerLight := true;
+ end;
+ if e_KeyPressed(IK_F9) and gGameOn and (not gConsoleShow) and (g_ActiveWindow = nil) then
+ begin
+ g_playerLight := false;
+ end;
+
+ if (g_playerLight) then g_AddDynLight(plr.GameX+32, plr.GameY+40, 128, 1, 1, 0, 0.6);
end;
procedure g_Game_Update();
w: Word;
i, b: Integer;
begin
+ g_ResetDynlights();
// Ïîðà âûêëþ÷àòü èãðó:
if gExit = EXIT_QUIT then
Exit;
UPSCounter := 0;
UPSTime := Time;
end;
+
+ if gGameOn then g_Weapon_AddDynLights();
end;
procedure g_Game_LoadData();
var
px, py, a, b, c, d: Integer;
//R: TRect;
+ lln: Integer;
+ lx, ly, lrad: Integer;
begin
if (p = nil) or (p.FDummy) then
begin
g_Player_DrawHealth();
end;
+ if gwin_has_stencil and (g_dynLightCount > 0) then
+ begin
+ // setup OpenGL parameters
+ glStencilMask($FFFFFFFF);
+ glStencilFunc(GL_ALWAYS, 0, $FFFFFFFF);
+ glEnable(GL_STENCIL_TEST);
+ //!glEnable(GL_SCISSOR_TEST);
+ //glClear(GL_STENCIL_BUFFER_BIT);
+ glStencilFunc(GL_EQUAL, 0, $ff);
+
+ for lln := 0 to g_dynLightCount-1 do
+ begin
+ lx := g_dynLights[lln].x;
+ ly := g_dynLights[lln].y;
+ lrad := g_dynLights[lln].radius;
+ if lrad < 2 then continue;
+ // set scissor to optimize drawing
+ //!glScissor((lx-cameraOfsX)-lrad+2, v_height-(ly-cameraOfsY)-lrad-1+2, lrad*2-4, lrad*2-4);
+ // clear stencil buffer
+ glClear(GL_STENCIL_BUFFER_BIT); //!!!
+ glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
+ // draw extruded panels
+ glDisable(GL_TEXTURE_2D);
+ glDisable(GL_BLEND);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no need to modify color buffer
+ if (lrad > 4) then g_Map_DrawPanelShadowVolumes(lx, ly, lrad);
+ // render light texture
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // modify color buffer
+ //glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_ZERO); // draw light, and clear stencil buffer
+ // blend it
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_TEXTURE_2D);
+ // color and opacity
+ glColor4f(g_dynLights[lln].r, g_dynLights[lln].g, g_dynLights[lln].b, g_dynLights[lln].a);
+ glBindTexture(GL_TEXTURE_2D, g_Texture_Light());
+ glBegin(GL_QUADS);
+ glTexCoord2f(0.0, 0.0); glVertex2i(lx-lrad, ly-lrad); // top-left
+ glTexCoord2f(1.0, 0.0); glVertex2i(lx+lrad, ly-lrad); // top-right
+ glTexCoord2f(1.0, 1.0); glVertex2i(lx+lrad, ly+lrad); // bottom-right
+ glTexCoord2f(0.0, 1.0); glVertex2i(lx-lrad, ly+lrad); // bottom-left
+ glEnd();
+ end;
+ // done
+ glDisable(GL_STENCIL_TEST);
+ glDisable(GL_BLEND);
+ glDisable(GL_SCISSOR_TEST);
+ //!glScissor((lx-cameraOfsX)-radius+2, v_height-(ly-cameraOfsY)-radius-1+2, radius*2-4, radius*2-4);
+ end;
+
if p.FSpectator then
e_TextureFontPrintEx(p.GameX + PLAYER_RECT_CX - 4,
p.GameY + PLAYER_RECT_CY - 4,
diff --git a/src/game/g_map.pas b/src/game/g_map.pas
index e67e5359be1d51ac372100db16ff444515b6619a..e080bca7cc0e9ba2423956395a48235d460d3581 100644 (file)
--- a/src/game/g_map.pas
+++ b/src/game/g_map.pas
procedure g_Map_SaveState(Var Mem: TBinMemoryWriter);
procedure g_Map_LoadState(Var Mem: TBinMemoryReader);
+procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
+
const
RESPAWNPOINT_PLAYER1 = 1;
RESPAWNPOINT_PLAYER2 = 2;
end;
end;
+procedure g_Map_DrawPanelShadowVolumes(lightX: Integer; lightY: Integer; radius: Integer);
+
+ procedure drawPanels (var panels: TPanelArray);
+ var
+ a: Integer;
+ begin
+ if panels <> nil then
+ begin
+ for a := 0 to High(panels) do
+ begin
+ panels[a].DrawShadowVolume(lightX, lightY, radius);
+ end;
+ end;
+ end;
+
+begin
+ drawPanels(gWalls);
+ drawPanels(gRenderForegrounds);
+end;
+
procedure g_Map_DrawBack(dx, dy: Integer);
begin
if gDrawBackGround and (BackID <> DWORD(-1)) then
diff --git a/src/game/g_panel.pas b/src/game/g_panel.pas
index 5034697f5c1f3ce3b9a5358c846f27118d59c439..af9d35f89cb78dd58e2f60395e06aeb7fd00de01 100644 (file)
--- a/src/game/g_panel.pas
+++ b/src/game/g_panel.pas
destructor Destroy(); override;
procedure Draw();
+ procedure DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
procedure Update();
procedure SetFrame(Frame: Integer; Count: Byte);
procedure NextTexture(AnimLoop: Byte = 0);
uses
SysUtils, g_basic, g_map, MAPDEF, g_game, e_graphics,
- g_console, g_language, e_log;
+ g_console, g_language, e_log, GL;
const
PANEL_SIGNATURE = $4C4E4150; // 'PANL'
end;
end;
+procedure TPanel.DrawShadowVolume(lightX: Integer; lightY: Integer; radius: Integer);
+ procedure extrude (x: Integer; y: Integer);
+ begin
+ glVertex2i(x+(x-lightX)*500, y+(y-lightY)*500);
+ //e_WriteLog(Format(' : (%d,%d)', [x+(x-lightX)*300, y+(y-lightY)*300]), MSG_WARNING);
+ end;
+
+ procedure drawLine (x0: Integer; y0: Integer; x1: Integer; y1: Integer);
+ begin
+ // does this side facing the light?
+ if ((x1-x0)*(lightY-y0)-(lightX-x0)*(y1-y0) >= 0) then exit;
+ //e_WriteLog(Format('lightpan: (%d,%d)-(%d,%d)', [x0, y0, x1, y1]), MSG_WARNING);
+ // this edge is facing the light, extrude and draw it
+ glVertex2i(x0, y0);
+ glVertex2i(x1, y1);
+ extrude(x1, y1);
+ extrude(x0, y0);
+ end;
+
+begin
+ if radius < 4 then exit;
+ if Enabled and (FCurTexture >= 0) and (Width > 0) and (Height > 0) and (FAlpha < 255) and g_Collide(X, Y, Width, Height, sX, sY, sWidth, sHeight) then
+ begin
+ if not FTextureIDs[FCurTexture].Anim then
+ begin
+ case FTextureIDs[FCurTexture].Tex of
+ TEXTURE_SPECIAL_WATER: exit;
+ TEXTURE_SPECIAL_ACID1: exit;
+ TEXTURE_SPECIAL_ACID2: exit;
+ TEXTURE_NONE: exit;
+ end;
+ end;
+ if (X+Width < lightX-radius) then exit;
+ if (Y+Height < lightY-radius) then exit;
+ if (X > lightX+radius) then exit;
+ if (Y > lightY+radius) then exit;
+ //e_DrawFill(FTextureIDs[FCurTexture].Tex, X, Y, Width div FTextureWidth, Height div FTextureHeight, FAlpha, True, FBlending);
+
+ glBegin(GL_QUADS);
+ drawLine(x, y, x+width, y); // top
+ drawLine(x+width, y, x+width, y+height); // right
+ drawLine(x+width, y+height, x, y+height); // bottom
+ drawLine(x, y+height, x, y); // left
+ glEnd();
+ end;
+end;
+
procedure TPanel.Update();
begin
if Enabled and (FCurTexture >= 0) and
index 93d8c63d056e93ad0fc5fef015b1b637173b6ad6..9d9720c9b753e436dc0891dbf9a7e6ff920d676b 100644 (file)
--- a/src/game/g_textures.pas
+++ b/src/game/g_textures.pas
procedure DumpTextureNames();
+function g_Texture_Light(): Integer;
+
implementation
uses
g_game, e_log, g_basic, SysUtils, g_console, wadreader,
- g_language;
+ g_language, GL;
type
_TTexture = record
Mem.ReadBoolean(FRevert);
end;
+
+var
+ ltexid: Integer = 0;
+
+function g_Texture_Light(): Integer;
+const
+ Radius: Integer = 128;
+var
+ tex, tpp: PByte;
+ x, y, a: Integer;
+ dist: Double;
+begin
+ if ltexid = 0 then
+ begin
+ GetMem(tex, (Radius*2)*(Radius*2)*4);
+ tpp := tex;
+ for y := 0 to Radius*2-1 do
+ begin
+ for x := 0 to Radius*2-1 do
+ begin
+ dist := 1.0-sqrt((x-Radius)*(x-Radius)+(y-Radius)*(y-Radius))/Radius;
+ if (dist < 0) then
+ begin
+ tpp^ := 0; Inc(tpp);
+ tpp^ := 0; Inc(tpp);
+ tpp^ := 0; Inc(tpp);
+ tpp^ := 0; Inc(tpp);
+ end
+ else
+ begin
+ //tc.setPixel(x, y, Color(cast(int)(dist*255), cast(int)(dist*255), cast(int)(dist*255)));
+ if (dist > 0.5) then dist := 0.5;
+ a := round(dist*255);
+ if (a < 0) then a := 0 else if (a > 255) then a := 255;
+ tpp^ := 255; Inc(tpp);
+ tpp^ := 255; Inc(tpp);
+ tpp^ := 255; Inc(tpp);
+ tpp^ := Byte(a); Inc(tpp);
+ end;
+ end;
+ end;
+
+ glGenTextures(1, @ltexid);
+ //if (tid == 0) assert(0, "VGL: can't create screen texture");
+
+ glBindTexture(GL_TEXTURE_2D, ltexid);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ //GLfloat[4] bclr = 0.0;
+ //glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, bclr.ptr);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Radius*2, Radius*2, 0, GL_RGBA{gltt}, GL_UNSIGNED_BYTE, tex);
+ end;
+
+ result := ltexid;
+end;
+
end.
diff --git a/src/game/g_weapons.pas b/src/game/g_weapons.pas
index 4df78ad7533548aa4f98f97f29404278a35ba7df..baf71083a364a4808f2ea94424ac3cacdbcbe0e9 100644 (file)
--- a/src/game/g_weapons.pas
+++ b/src/game/g_weapons.pas
procedure g_Weapon_SaveState(var Mem: TBinMemoryWriter);
procedure g_Weapon_LoadState(var Mem: TBinMemoryReader);
+procedure g_Weapon_AddDynLights();
+
const
WEAPON_KASTET = 0;
WEAPON_SAW = 1;
Anim := TAnimation.Create(TextureID, False, 8);
Anim.Blending := False;
g_GFX_OnceAnim((Obj.X+32)-58, (Obj.Y+8)-36, Anim);
+ g_DynLightExplosion((Obj.X+32), (Obj.Y+8), 64, 1, 0, 0);
Anim.Free();
end;
end
Anim := TAnimation.Create(TextureID, False, 6);
Anim.Blending := False;
g_GFX_OnceAnim(cx-64, cy-64, Anim);
+ g_DynLightExplosion(cx, cy, 64, 1, 0, 0);
Anim.Free();
end;
end;
Anim.Blending := False;
g_GFX_OnceAnim(cx-16, cy-16, Anim);
Anim.Free();
+ g_DynLightExplosion(cx, cy, 32, 0, 0.5, 0.5);
end;
g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEPLASMA', Obj.X, Obj.Y);
end;
g_GFX_OnceAnim(tcx-(Anim.Width div 2), tcy-(Anim.Height div 2), Anim, ONCEANIM_SMOKE);
Anim.Free();
+ //g_DynLightExplosion(tcx, tcy, 1, 1, 0.8, 0.3);
end;
end;
Anim.Blending := False;
g_GFX_OnceAnim(cx-64, cy-64, Anim);
Anim.Free();
+ g_DynLightExplosion(cx, cy, 96, 0, 1, 0);
end;
g_Sound_PlayExAt('SOUND_WEAPON_EXPLODEBFG', Obj.X, Obj.Y);
end;
end;
+
+procedure g_Weapon_AddDynLights();
+var
+ i: Integer;
+begin
+ if Shots = nil then Exit;
+ for i := 0 to High(Shots) do
+ begin
+ if Shots[i].ShotType = 0 then continue;
+ if (Shots[i].ShotType = WEAPON_ROCKETLAUNCHER) or
+ (Shots[i].ShotType = WEAPON_BARON_FIRE) or
+ (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
+ (Shots[i].ShotType = WEAPON_SKEL_FIRE) or
+ (Shots[i].ShotType = WEAPON_IMP_FIRE) or
+ (Shots[i].ShotType = WEAPON_CACO_FIRE) or
+ (Shots[i].ShotType = WEAPON_MANCUB_FIRE) or
+ (Shots[i].ShotType = WEAPON_BSP_FIRE) or
+ (Shots[i].ShotType = WEAPON_PLASMA) or
+ (Shots[i].ShotType = WEAPON_BFG) or
+ (Shots[i].ShotType = WEAPON_FLAMETHROWER) or
+ false then
+ begin
+ if (Shots[i].ShotType = WEAPON_PLASMA) then
+ g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 0, 0.3, 1, 0.4)
+ else if (Shots[i].ShotType = WEAPON_BFG) then
+ g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 0, 1, 0, 0.5)
+ else if (Shots[i].ShotType = WEAPON_FLAMETHROWER) then
+ g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 42, 1, 0.8, 0, 0.4)
+ else
+ g_AddDynLight(Shots[i].Obj.X+(Shots[i].Obj.Rect.Width div 2), Shots[i].Obj.Y+(Shots[i].Obj.Rect.Height div 2), 128, 1, 0, 0, 0.4);
+ end;
+ end;
+end;
+
end.
diff --git a/src/game/g_window.pas b/src/game/g_window.pas
index 9d27d57e974d056eb6910c4e71abea088d66d9d5..6dfa778096ff33f616b553c00539c81b542227fd 100644 (file)
--- a/src/game/g_window.pas
+++ b/src/game/g_window.pas
var
gwin_dump_extensions: Boolean = false;
+ gwin_has_stencil: Boolean = false;
implementation
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1); // lights; it is enough to have 1-bit stencil buffer for lighting
SDL_GL_SetSwapInterval(v);
end;
function SDLMain(): Integer;
var
idx: Integer;
+ ltmp: Integer;
begin
{$IFDEF HEADLESS}
e_NoGraphics := True;
if ParamStr(idx) = '--opengl-dump-exts' then gwin_dump_extensions := true;
end;
+ SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, @ltmp);
+ e_WriteLog(Format('stencil buffer size: %d', [ltmp]), MSG_WARNING);
+
+ gwin_has_stencil := (ltmp > 0);
+
if not glHasExtension('GL_ARB_texture_non_power_of_two') then
begin
e_WriteLog('Driver DID''T advertised NPOT textures support', MSG_WARNING);