Skip to content

Commit

Permalink
Merge pull request #578 from DanielGibson/soft-particles
Browse files Browse the repository at this point in the history
Implement Soft Particles (and capturing depth buffer), based on old The Dark Mod code
  • Loading branch information
DanielGibson authored Jul 25, 2024
2 parents 10eee1e + 7e772f0 commit d3b2794
Show file tree
Hide file tree
Showing 22 changed files with 789 additions and 89 deletions.
15 changes: 14 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
It can also be opened while in the game, which then is paused (if Single Player) but still visible,
so the effect of most graphics settings can be seen immediately.
Needs SDL2 and C++11.
* "Soft" Particles (that don't "cut" into geometry but fade smoothly), based on code from The Dark Mod
2.04. Can be enabled/disabled with `r_useSoftParticles`, is applied automatically for all appropriate
particles (view-aligned, using additive or alpha blending and not too small)
* `r_enableDepthCapture`: Enable capturing depth buffer to texture, needed for the soft particles.
Can be used in custom materials by using the `"_currentDepth"` texture
* Replaced dependency on (external) zlib with integrated [miniz](https://github.com/richgel999/miniz)
* HighDPI/Retina support
* Allow inverted mouse look (horizontally, vertically or both) with `m_invertLook`
Expand All @@ -28,7 +33,15 @@ Note: Numbers starting with a "#" like #330 refer to the bugreport with that num
downscaling by OpenAL's output limiter
* If `r_windowResizable` is set, the dhewm3 window (when in windowed mode..) can be freely resized.
Needs SDL2; with 2.0.5 and newer it's applied immediately, otherwise when creating the window.

* If switching between fullscreen and windowed mode or similar changes causes issues (like
[here](https://github.com/dhewm/dhewm3/issues/587#issuecomment-2205807989)), you can set
`r_vidRestartAlwaysFull 1`, so (again) a full `vid_restart` is done, instead of the partial one
which *usually* suffices for just changing the resolution or fullscreen state. If you run into that
issue (probably a driver bug), you'll probably also want to set `r_windowResizable 0`, because
resizing the window that way also triggered the bug, and in that case no `vid_restart` is done at all
* Fixed screenshots when using native Wayland (`SDL_VIDEODRIVER=wayland`)
* If you enter the `map` command in the console, without any arguments, the current map name is printed
* Support OpenGL debug contexts and messages (`GL_ARB_debug_output`). Can be enabled with `r_glDebugContext 1`. Changing that CVar requires a `vid_restart` (or set it as startup argument)

1.5.3 (2024-03-29)
------------------------------------------------------------------------
Expand Down
5 changes: 5 additions & 0 deletions Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ This can be configured with the following CVars:
at the end of each frame. Needed at least when using Wayland.
`1`: do this, `0`: don't do it, `-1`: let dhewm3 decide (default)

- `r_useSoftParticles` Soften particle transitions when player walks through them or they cross solid geometry.
Needs r_enableDepthCapture. Can slow down rendering! `1`: enable (default), `0`: disable
- `r_enableDepthCapture` Enable capturing depth buffer to texture. `0`: disable, `1`: enable,
`-1`: enable automatically (if soft particles are enabled; the default).
This can be used in custom materials with the "_currentDepth" texture.
- `r_useCarmacksReverse` Use Z-Fail ("Carmack's Reverse") when rendering shadows (default `1`)
- `r_useStencilOpSeparate` Use glStencilOpSeparate() (if available) when rendering shadow (default `1`)
- `r_scaleMenusTo43` Render full-screen menus in 4:3 by adding black bars on the left/right if necessary (default `1`)
Expand Down
6 changes: 3 additions & 3 deletions neo/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ if(D3_COMPILER_IS_GCC_OR_CLANG)
set(CMAKE_C_FLAGS_DEBUG "-g -ggdb -D_DEBUG -O0")
set(CMAKE_C_FLAGS_DEBUGALL "-g -ggdb -D_DEBUG")
set(CMAKE_C_FLAGS_PROFILE "-g -ggdb -D_DEBUG -O1 -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_RELEASE "-O2 -fno-math-errno -fno-trapping-math -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -ggdb -O2 -fno-math-errno -fno-trapping-math -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELEASE "-O2 -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer")
set(CMAKE_C_FLAGS_RELWITHDEBINFO "-g -ggdb -O2 -fno-math-errno -fno-trapping-math -ffinite-math-only -fno-omit-frame-pointer")
set(CMAKE_C_FLAGS_MINSIZEREL "-Os -fno-math-errno -fno-trapping-math -ffinite-math-only -fomit-frame-pointer")

set(CMAKE_CXX_FLAGS_DEBUGALL ${CMAKE_C_FLAGS_DEBUGALL})
set(CMAKE_CXX_FLAGS_PROFILE ${CMAKE_C_FLAGS_PROFILE})
Expand Down
12 changes: 12 additions & 0 deletions neo/framework/DeclParticle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,13 @@ idParticleStage *idDeclParticle::ParseParticleStage( idLexer &src ) {
stage->gravity = src.ParseFloat();
continue;
}
if ( !token.Icmp( "softeningRadius" ) ) { // #3878 soft particles
common->Warning( "Particle %s from %s has stage with \"softeningRadius\" attribute, which is currently ignored (we soften all suitable particles)\n",
this->GetName(), src.GetFileName() );
// DG: disable this for now to avoid breaking the game ABI
//stage->softeningRadius = src.ParseFloat();
continue;
}

src.Error( "unknown token %s\n", token.c_str() );
}
Expand Down Expand Up @@ -733,6 +740,9 @@ idParticleStage::idParticleStage( void ) {
hidden = false;
boundsExpansion = 0.0f;
bounds.Clear();
// DG: disable softeningRadius for now to avoid breaking the game ABI
// (will always behave like if softeningRadius = -2.0f)
//softeningRadius = -2.0f; // -2 means "auto" - #3878 soft particles
}

/*
Expand Down Expand Up @@ -806,6 +816,8 @@ void idParticleStage::Default() {
randomDistribution = true;
entityColor = false;
cycleMsec = ( particleLife + deadTime ) * 1000;
// DG: disable softeningRadius for now to avoid breaking game ABI
//softeningRadius = -2.0f; // -2 means "auto" - #3878 soft particles
}

/*
Expand Down
12 changes: 12 additions & 0 deletions neo/framework/DeclParticle.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,18 @@ class idParticleStage {
float boundsExpansion; // user tweak to fix poorly calculated bounds

idBounds bounds; // derived

/* Soft particles -- SteveL #3878
-2.0 is the value at initialization, meaning no user specification: "auto".
-1.0 means no change to old system: suppress soft particles, but allow modelDepthhack if specified.
0 means disable all softening for this stage, including modelDepthHack.
+ve value means apply soft particle effect, allowing overdraw up to the specified depth.
This is more flexible even when not using soft particles, as modelDepthHack
can be turned off for specific stages to stop them poking through walls.
*/
// DG: disable this for now because it breaks the game DLL's ABI (re-enable in dhewm3 1.6.0 or 2.0.0)
// (this header is part of the SDK)
//float softeningRadius;
};


Expand Down
42 changes: 32 additions & 10 deletions neo/framework/Dhewm3SettingsMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,14 @@ static void InitBindingEntries()
bindingEntries.Append( BindingEntry( bet ) );
}

// player.def defines, in player_base, used by player_doommarine and player_doommarine_mp (and player_doommarine_ctf),
// "def_weapon0" "weapon_fists", "def_weapon1" "weapon_pistol" etc
// => get all those definitions (up to MAX_WEAPONS=16) from Player, and then
// get the entities for the corresponding keys ("weapon_fists" etc),
// which should have an entry like "inv_name" "Pistol" (could also be #str_00100207 though!)

// hardcorps uses: idCVar pm_character("pm_character", "0", CVAR_GAME | CVAR_BOOL, "Change Player character. 1 = Scarlet. 0 = Doom Marine");
// but I guess (hope) they use the same weapons..
const idDict* playerDict = GetEntityDefDict( "player_doommarine" );
const idDict* playerDictMP = GetEntityDefDict( "player_doommarine_mp" );
bool impulse27used = false;
Expand Down Expand Up @@ -1209,15 +1217,6 @@ static void InitBindingEntries()
idStr impName = idStr::Format( "_impulse%d", i );
bindingEntries.Append( BindingEntry( impName, impName ) );
}

// player.def defines, in player_base, used by player_doommarine and player_doommarine_mp (and player_doommarine_ctf),
// "def_weapon0" "weapon_fists", "def_weapon1" "weapon_pistol" etc
// => get all those definitions (up to MAX_WEAPONS=16) from Player, and then
// get the entities for the corresponding keys ("weapon_fists" etc),
// which should have an entry like "inv_name" "Pistol" (could also be #str_00100207 though!)

// hardcorps uses: idCVar pm_character("pm_character", "0", CVAR_GAME | CVAR_BOOL, "Change Player character. 1 = Scarlet. 0 = Doom Marine");
// but I guess (hope) they use the same weapons..
}

// this initialization should be done every time the bindings tab is opened,
Expand Down Expand Up @@ -1666,8 +1665,31 @@ static CVarOption videoOptionsImmediately[] = {
} ),
CVarOption( "r_screenshotPngCompression", "Compression level for PNG screenshots", OT_INT, 0, 9 ),
CVarOption( "r_screenshotJpgQuality", "Quality level for JPG screenshots", OT_INT, 1, 100 ),
CVarOption( "r_useSoftParticles", []( idCVar& cvar ) {
bool enable = cvar.GetBool();
if ( ImGui::Checkbox( "Use Soft Particles", &enable ) ) {
cvar.SetBool( enable );
if ( enable && r_enableDepthCapture.GetInteger() == 0 ) {
r_enableDepthCapture.SetInteger(-1);
D3::ImGuiHooks::ShowWarningOverlay( "Capturing the Depth Buffer was disabled.\nEnabled it because soft particles need it!" );
}
}
AddCVarOptionTooltips( cvar );
} ),

CVarOption( "Advanced Options" ),
CVarOption( "r_enableDepthCapture", []( idCVar& cvar ) {
int sel = idMath::ClampInt( -1, 1, cvar.GetInteger() ) + 1; // +1 for -1..1 to 0..2
if ( ImGui::Combo( "Capture Depth Buffer to Texture", &sel, "Auto (enable if needed for Soft Particles)\0Disabled\0Always Enabled\0" ) ) {
--sel; // back to -1..1 from 0..2
cvar.SetInteger( sel );
if ( sel == 0 && r_useSoftParticles.GetBool() ) {
r_useSoftParticles.SetBool( false );
D3::ImGuiHooks::ShowWarningOverlay( "You disabled capturing the Depth Buffer.\nDisabling Soft Particles because they need the depth buffer texture." );
}
}
AddCVarOptionTooltips( cvar );
}),
CVarOption( "r_skipNewAmbient", "Disable High Quality Special Effects", OT_BOOL ),
CVarOption( "r_shadows", "Enable Shadows", OT_BOOL ),
CVarOption( "r_skipSpecular", "Disable Specular", OT_BOOL ),
Expand Down Expand Up @@ -2262,7 +2284,7 @@ void DrawGameOptionsMenu()
playerNameIso[40] = '\0'; // limit to 40 chars, like the original menu
ui_nameVar->SetString( playerNameIso );
// update the playerNameBuf to reflect the name as it is now: limited to 40 chars
// and possibly containing '?' from non-translatable unicode chars
// and possibly containing '!' from non-translatable unicode chars
D3_ISO8859_1toUTF8( ui_nameVar->GetString(), playerNameBuf, sizeof(playerNameBuf) );
} else {
D3::ImGuiHooks::ShowWarningOverlay( "Player Name way too long (max 40 chars) or contains invalid UTF-8 encoding!" );
Expand Down
2 changes: 1 addition & 1 deletion neo/idlib/Str.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1911,7 +1911,7 @@ char * D3_UTF8toISO8859_1( const char *utf8str, char *isobuf, int isobufLen, cha
else if ( invalidChar != 0 )
buffer[i++] = invalidChar;
} else if ((*str & 0xf0) == 0xe0) {
// Unicode character between 0x0800 and 0xFFF => way out of range for ISO8859-1
// Unicode character between 0x0800 and 0xFFFF => way out of range for ISO8859-1
// so just validate and skip the input bytes
if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return NULL;
if (*str == 0xed && str[1] > 0x9f) return NULL; // str[1] < 0x80 is checked below
Expand Down
6 changes: 5 additions & 1 deletion neo/renderer/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ class idImage {

void CopyFramebuffer( int x, int y, int width, int height, bool useOversizedBuffer );

void CopyDepthbuffer( int x, int y, int width, int height );
void CopyDepthbuffer( int x, int y, int width, int height, bool useOversizedBuffer );


void UploadScratch( const byte *pic, int width, int height );

Expand Down Expand Up @@ -417,6 +418,9 @@ class idImageManager {
idImage * specular2DTableImage; // 2D intensity texture with our specular function with variable specularity
idImage * borderClampImage; // white inside, black outside


idImage * currentDepthImage; // #3877. Allow shaders to access scene depth

//--------------------------------------------------------

idImage * AllocImage( const char *name );
Expand Down
14 changes: 14 additions & 0 deletions neo/renderer/Image_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,19 @@ static void R_RGBA8Image( idImage *image ) {
TF_DEFAULT, false, TR_REPEAT, TD_HIGH_QUALITY );
}

static void R_DepthImage( idImage *image ) {
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];

memset( data, 0, sizeof( data ) );
data[0][0][0] = 16;
data[0][0][1] = 32;
data[0][0][2] = 48;
data[0][0][3] = 96;

image->GenerateImage( (byte *)data, DEFAULT_SIZE, DEFAULT_SIZE,
TF_NEAREST, false, TR_CLAMP, TD_HIGH_QUALITY );
}

#if 0
static void R_RGB8Image( idImage *image ) {
byte data[DEFAULT_SIZE][DEFAULT_SIZE][4];
Expand Down Expand Up @@ -1993,6 +2006,7 @@ void idImageManager::Init() {
accumImage = ImageFromFunction("_accum", R_RGBA8Image );
scratchCubeMapImage = ImageFromFunction("_scratchCubeMap", makeNormalizeVectorCubeMap );
currentRenderImage = ImageFromFunction("_currentRender", R_RGBA8Image );
currentDepthImage = ImageFromFunction( "_currentDepth", R_DepthImage ); // #3877. Allow shaders to access scene depth

cmdSystem->AddCommand( "reloadImages", R_ReloadImages_f, CMD_FL_RENDERER, "reloads images" );
cmdSystem->AddCommand( "listImages", R_ListImages_f, CMD_FL_RENDERER, "lists images" );
Expand Down
34 changes: 19 additions & 15 deletions neo/renderer/Image_load.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1898,34 +1898,38 @@ CopyDepthbuffer
This should just be part of copyFramebuffer once we have a proper image type field
====================
*/
void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight ) {
Bind();

void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight, bool useOversizedBuffer )
{
this->Bind();
// if the size isn't a power of 2, the image must be increased in size
int potWidth, potHeight;

potWidth = MakePowerOfTwo( imageWidth );
potHeight = MakePowerOfTwo( imageHeight );

if ( uploadWidth != potWidth || uploadHeight != potHeight ) {
GetDownsize( imageWidth, imageHeight );
GetDownsize( potWidth, potHeight );
// Ensure we are reading from the back buffer:
qglReadBuffer( GL_BACK );
// only resize if the current dimensions can't hold it at all,
// otherwise subview renderings could thrash this
if ( ( useOversizedBuffer && ( uploadWidth < potWidth || uploadHeight < potHeight ) ) || ( !useOversizedBuffer && ( uploadWidth != potWidth || uploadHeight != potHeight ) ) )
{
uploadWidth = potWidth;
uploadHeight = potHeight;
if ( potWidth == imageWidth && potHeight == imageHeight ) {
qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, x, y, imageWidth, imageHeight, 0 );
} else {
// we need to create a dummy image with power of two dimensions,
// then do a qglCopyTexSubImage2D of the data we want
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
}
// This bit runs once only at map start, because it tests whether the image is too small to hold the screen.
// It resizes the texture to a power of two that can hold the screen,
// and then subsequent captures to the texture put the depth component into the RGB channels
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24_ARB, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );

} else {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
// it and don't try and do a texture compression or some other silliness
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
}

// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
Expand Down
4 changes: 4 additions & 0 deletions neo/renderer/Material.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1434,6 +1434,10 @@ void idMaterial::ParseStage( idLexer &src, const textureRepeat_t trpDefault ) {
ss->drawStateBits |= GLS_DEPTHMASK;
continue;
}
if ( !token.Icmp( "ignoreDepth" ) ) { // Added in #3877.
ss->drawStateBits |= GLS_DEPTHFUNC_ALWAYS;
continue;
}
if ( !token.Icmp( "alphaTest" ) ) {
ss->hasAlphaTest = true;
ss->alphaTestRegister = ParseExpression( src );
Expand Down
5 changes: 5 additions & 0 deletions neo/renderer/Model_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,8 +302,13 @@ class idRenderModelPrt : public idRenderModelStatic {
virtual float DepthHack() const;
virtual int Memory() const;

float SofteningRadius( const int stage ) const; // #3878 soft particles

private:
void SetSofteningRadii(); // #3878 soft particles

const idDeclParticle * particleSystem;
idList<float> softeningRadii; // #3878 soft particles
};

/*
Expand Down
65 changes: 65 additions & 0 deletions neo/renderer/Model_prt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ idRenderModelPrt::InitFromFile
void idRenderModelPrt::InitFromFile( const char *fileName ) {
name = fileName;
particleSystem = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, fileName ) );
SetSofteningRadii(); // # 3878
}

/*
Expand Down Expand Up @@ -286,3 +287,67 @@ int idRenderModelPrt::Memory() const {

return total;
}

/*
====================
idRenderModelPrt::SetSofteningRadii
Calculate "depth" of each particle stage that represents a 3d volume, so the particle can
be allowed to overdraw solid geometry by the right amount, and the particle "thickness" (visibility)
can be adjusted by how much of it is visible in front of the background surface.
"depth" is by default 0.8 of the particle radius, and particles less than 2 units in size won't be softened.
The particles that represent 3d volumes are the view-aligned ones. Others have depth set to 0.
Cache these values rather than calculate them for each stage every frame.
Added for soft particles -- SteveL #3878.
====================
*/
void idRenderModelPrt::SetSofteningRadii()
{
softeningRadii.AssureSize( particleSystem->stages.Num() );

for ( int i = 0; i < particleSystem->stages.Num(); ++i )
{
const idParticleStage* ps = particleSystem->stages[i];
// DG: for now softeningRadius isn't configurable to avoid breaking the game DLL's ABI
// => always behave like if ps->softeningRadius == -2, which means "auto"
// (doesn't make a difference, so far only TDM particles set the softeningRadius)
/* if ( ps->softeningRadius > -2.0f ) // User has specified a setting
{
softeningRadii[i] = ps->softeningRadius;
}
else */ if ( ps->orientation == POR_VIEW ) // Only view-aligned particle stages qualify for softening
{
float diameter = Max( ps->size.from, ps->size.to );
float scale = Max( ps->aspect.from, ps->aspect.to );
diameter *= Max( scale, 1.0f ); // aspect applies to 1 axis only. If it's < 1, the other axis will still be at scale 1
if ( diameter > 2.0f ) // Particle is big enough to soften
{
softeningRadii[i] = diameter * 0.8f / 2.0f;
}
else // Particle is small. Disable both softening and modelDepthHack
{
softeningRadii[i] = 0.0f;
}
}
else // Particle isn't view-aligned, and no user setting. Don't change anything.
{
softeningRadii[i] = -1;
}
}
}

/*
====================
idRenderModelPrt::SofteningRadius
Return the max radius of the individual quads that make up this stage.
Added for soft particles #3878.
====================
*/
float idRenderModelPrt::SofteningRadius( const int stage ) const {
assert( particleSystem );
assert( stage > -1 && stage < softeningRadii.Num() );
return softeningRadii[stage];
}
Loading

0 comments on commit d3b2794

Please sign in to comment.