Light equation changes and weapon and ai spawn proper muzzle flashes.

This commit is contained in:
Justin Marshall
2026-05-12 21:44:50 -07:00
parent a797428ee5
commit cdb45e4155
5 changed files with 3796 additions and 3165 deletions
+371 -43
View File
@@ -110,6 +110,202 @@ CLASS_DECLARATION( idAnimatedEntity, idWeapon )
EVENT(EV_Weapon_NetEndReload, idWeapon::Event_NetEndReload) EVENT(EV_Weapon_NetEndReload, idWeapon::Event_NetEndReload)
END_CLASS END_CLASS
/*
================
Weapon fire muzzle light
This is intentionally separate from idWeapon::MuzzleFlashLight(). The old
MuzzleFlashLight path is used by weapon scripts as a flashlight / persistent
weapon light, so projectile firing gets its own short-lived light here.
================
*/
struct weaponFireMuzzleLightLocal_t {
int viewHandle;
int worldHandle;
int startTime;
int endTime;
int duration;
idVec3 baseColor;
float baseRadius;
renderLight_t viewLight;
renderLight_t worldLight;
};
static weaponFireMuzzleLightLocal_t weaponFireMuzzleLights[MAX_GENTITIES];
static bool weaponFireMuzzleLightsInitialized = false;
static void InitWeaponFireMuzzleLights(void) {
if (weaponFireMuzzleLightsInitialized) {
return;
}
for (int i = 0; i < MAX_GENTITIES; i++) {
memset(&weaponFireMuzzleLights[i], 0, sizeof(weaponFireMuzzleLights[i]));
weaponFireMuzzleLights[i].viewHandle = -1;
weaponFireMuzzleLights[i].worldHandle = -1;
}
weaponFireMuzzleLightsInitialized = true;
}
static void FreeWeaponFireMuzzleLight(int entityNum) {
InitWeaponFireMuzzleLights();
if (entityNum < 0 || entityNum >= MAX_GENTITIES) {
return;
}
weaponFireMuzzleLightLocal_t& fireLight = weaponFireMuzzleLights[entityNum];
if (fireLight.viewHandle != -1) {
gameRenderWorld->FreeLightDef(fireLight.viewHandle);
fireLight.viewHandle = -1;
}
if (fireLight.worldHandle != -1) {
gameRenderWorld->FreeLightDef(fireLight.worldHandle);
fireLight.worldHandle = -1;
}
fireLight.startTime = 0;
fireLight.endTime = 0;
fireLight.duration = 0;
fireLight.baseColor.Zero();
fireLight.baseRadius = 0.0f;
memset(&fireLight.viewLight, 0, sizeof(fireLight.viewLight));
memset(&fireLight.worldLight, 0, sizeof(fireLight.worldLight));
}
static void StartWeaponFireMuzzleLight(int entityNum, int ownerEntityNum, const idVec3& viewOrigin, const idMat3& viewAxis, const idVec3& worldOrigin, const idMat3& worldAxis, const idMaterial* shader, const idVec3& color, float radius, int duration, float diversity) {
InitWeaponFireMuzzleLights();
if (entityNum < 0 || entityNum >= MAX_GENTITIES) {
return;
}
if (radius <= 0.0f || duration <= 0) {
return;
}
weaponFireMuzzleLightLocal_t& fireLight = weaponFireMuzzleLights[entityNum];
memset(&fireLight.viewLight, 0, sizeof(fireLight.viewLight));
memset(&fireLight.worldLight, 0, sizeof(fireLight.worldLight));
fireLight.startTime = gameLocal.time;
fireLight.endTime = gameLocal.time + duration;
fireLight.duration = duration;
fireLight.baseColor = color;
fireLight.baseRadius = radius * 5;
fireLight.viewLight.pointLight = true;
fireLight.viewLight.noShadows = true;
fireLight.viewLight.shader = shader;
fireLight.viewLight.origin = viewOrigin;
fireLight.viewLight.axis = viewAxis;
fireLight.viewLight.lightRadius[0] = radius;
fireLight.viewLight.lightRadius[1] = radius;
fireLight.viewLight.lightRadius[2] = radius;
fireLight.viewLight.shaderParms[SHADERPARM_RED] = color.x;
fireLight.viewLight.shaderParms[SHADERPARM_GREEN] = color.y;
fireLight.viewLight.shaderParms[SHADERPARM_BLUE] = color.z;
fireLight.viewLight.shaderParms[SHADERPARM_ALPHA] = 1.0f;
fireLight.viewLight.shaderParms[SHADERPARM_TIMESCALE] = 1.0f;
fireLight.viewLight.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC(gameLocal.time);
fireLight.viewLight.shaderParms[SHADERPARM_DIVERSITY] = diversity;
// Only the owning player should see the view-model light. This is what
// makes the light appear attached to the first-person muzzle flash joint.
if (ownerEntityNum >= 0) {
fireLight.viewLight.allowLightInViewID = ownerEntityNum + 1;
// Keep this separate from the old LIGHTID_VIEW_MUZZLE_FLASH path used by
// MuzzleFlashLight/flashlight so the two systems never fight each other.
fireLight.viewLight.lightId = LIGHTID_VIEW_MUZZLE_FLASH + ownerEntityNum + 256;
}
fireLight.worldLight = fireLight.viewLight;
fireLight.worldLight.origin = worldOrigin;
fireLight.worldLight.axis = worldAxis;
fireLight.worldLight.allowLightInViewID = 0;
if (ownerEntityNum >= 0) {
fireLight.worldLight.suppressLightInViewID = ownerEntityNum + 1;
fireLight.worldLight.lightId = LIGHTID_WORLD_MUZZLE_FLASH + ownerEntityNum + 256;
}
if (fireLight.viewHandle != -1) {
gameRenderWorld->UpdateLightDef(fireLight.viewHandle, &fireLight.viewLight);
}
else {
fireLight.viewHandle = gameRenderWorld->AddLightDef(&fireLight.viewLight);
}
if (fireLight.worldHandle != -1) {
gameRenderWorld->UpdateLightDef(fireLight.worldHandle, &fireLight.worldLight);
}
else {
fireLight.worldHandle = gameRenderWorld->AddLightDef(&fireLight.worldLight);
}
}
static bool UpdateWeaponFireMuzzleLight(int entityNum, const idVec3& viewOrigin, const idMat3& viewAxis, const idVec3& worldOrigin, const idMat3& worldAxis) {
InitWeaponFireMuzzleLights();
if (entityNum < 0 || entityNum >= MAX_GENTITIES) {
return false;
}
weaponFireMuzzleLightLocal_t& fireLight = weaponFireMuzzleLights[entityNum];
if (fireLight.viewHandle == -1 && fireLight.worldHandle == -1) {
return false;
}
if (gameLocal.time >= fireLight.endTime || fireLight.duration <= 0) {
FreeWeaponFireMuzzleLight(entityNum);
return false;
}
float frac = (float)(fireLight.endTime - gameLocal.time) / (float)fireLight.duration;
if (frac < 0.0f) {
frac = 0.0f;
}
else if (frac > 1.0f) {
frac = 1.0f;
}
// A squared falloff gives a hot first frame and then a quick natural fade.
float fade = frac * frac;
float radius = fireLight.baseRadius * (0.35f + 0.65f * fade);
fireLight.viewLight.origin = viewOrigin;
fireLight.viewLight.axis = viewAxis;
fireLight.viewLight.lightRadius[0] = radius;
fireLight.viewLight.lightRadius[1] = radius;
fireLight.viewLight.lightRadius[2] = radius;
fireLight.viewLight.shaderParms[SHADERPARM_RED] = fireLight.baseColor.x * fade;
fireLight.viewLight.shaderParms[SHADERPARM_GREEN] = fireLight.baseColor.y * fade;
fireLight.viewLight.shaderParms[SHADERPARM_BLUE] = fireLight.baseColor.z * fade;
fireLight.viewLight.shaderParms[SHADERPARM_ALPHA] = fade;
fireLight.worldLight.origin = worldOrigin;
fireLight.worldLight.axis = worldAxis;
fireLight.worldLight.lightRadius[0] = radius;
fireLight.worldLight.lightRadius[1] = radius;
fireLight.worldLight.lightRadius[2] = radius;
fireLight.worldLight.shaderParms[SHADERPARM_RED] = fireLight.baseColor.x * fade;
fireLight.worldLight.shaderParms[SHADERPARM_GREEN] = fireLight.baseColor.y * fade;
fireLight.worldLight.shaderParms[SHADERPARM_BLUE] = fireLight.baseColor.z * fade;
fireLight.worldLight.shaderParms[SHADERPARM_ALPHA] = fade;
if (fireLight.viewHandle != -1) {
gameRenderWorld->UpdateLightDef(fireLight.viewHandle, &fireLight.viewLight);
}
if (fireLight.worldHandle != -1) {
gameRenderWorld->UpdateLightDef(fireLight.worldHandle, &fireLight.worldLight);
}
return true;
}
/*********************************************************************** /***********************************************************************
init init
@@ -427,14 +623,16 @@ void idWeapon::Restore( idRestoreGame *savefile ) {
const idDeclEntityDef* projectileDef = gameLocal.FindEntityDef(weaponDef->dict.GetString("def_projectile"), false); const idDeclEntityDef* projectileDef = gameLocal.FindEntityDef(weaponDef->dict.GetString("def_projectile"), false);
if (projectileDef) { if (projectileDef) {
projectileDict = projectileDef->dict; projectileDict = projectileDef->dict;
} else { }
else {
projectileDict.Clear(); projectileDict.Clear();
} }
const idDeclEntityDef* brassDef = gameLocal.FindEntityDef(weaponDef->dict.GetString("def_ejectBrass"), false); const idDeclEntityDef* brassDef = gameLocal.FindEntityDef(weaponDef->dict.GetString("def_ejectBrass"), false);
if (brassDef) { if (brassDef) {
brassDict = brassDef->dict; brassDict = brassDef->dict;
} else { }
else {
brassDict.Clear(); brassDict.Clear();
} }
@@ -560,6 +758,7 @@ void idWeapon::Clear( void ) {
gameRenderWorld->FreeLightDef(worldMuzzleFlashHandle); gameRenderWorld->FreeLightDef(worldMuzzleFlashHandle);
worldMuzzleFlashHandle = -1; worldMuzzleFlashHandle = -1;
} }
FreeWeaponFireMuzzleLight(entityNumber);
if (guiLightHandle != -1) { if (guiLightHandle != -1) {
gameRenderWorld->FreeLightDef(guiLightHandle); gameRenderWorld->FreeLightDef(guiLightHandle);
guiLightHandle = -1; guiLightHandle = -1;
@@ -739,7 +938,8 @@ void idWeapon::InitWorldModel( const idDeclEntityDef *def ) {
worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber + 1; worldModelRenderEntity->suppressShadowInViewID = owner->entityNumber + 1;
worldModelRenderEntity->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; worldModelRenderEntity->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber;
} }
} else { }
else {
ent->SetModel(""); ent->SetModel("");
ent->Hide(); ent->Hide();
} }
@@ -795,7 +995,8 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) {
smokeName = weaponDef->dict.GetString("smoke_muzzle"); smokeName = weaponDef->dict.GetString("smoke_muzzle");
if (*smokeName != '\0') { if (*smokeName != '\0') {
weaponSmoke = static_cast<const idDeclParticle*>(declManager->FindType(DECL_PARTICLE, smokeName)); weaponSmoke = static_cast<const idDeclParticle*>(declManager->FindType(DECL_PARTICLE, smokeName));
} else { }
else {
weaponSmoke = NULL; weaponSmoke = NULL;
} }
continuousSmoke = weaponDef->dict.GetBool("continuousSmoke"); continuousSmoke = weaponDef->dict.GetBool("continuousSmoke");
@@ -804,7 +1005,8 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) {
smokeName = weaponDef->dict.GetString("smoke_strike"); smokeName = weaponDef->dict.GetString("smoke_strike");
if (*smokeName != '\0') { if (*smokeName != '\0') {
strikeSmoke = static_cast<const idDeclParticle*>(declManager->FindType(DECL_PARTICLE, smokeName)); strikeSmoke = static_cast<const idDeclParticle*>(declManager->FindType(DECL_PARTICLE, smokeName));
} else { }
else {
strikeSmoke = NULL; strikeSmoke = NULL;
} }
strikeSmokeStartTime = 0; strikeSmokeStartTime = 0;
@@ -850,12 +1052,14 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) {
const idDeclEntityDef* projectileDef = gameLocal.FindEntityDef(projectileName, false); const idDeclEntityDef* projectileDef = gameLocal.FindEntityDef(projectileName, false);
if (!projectileDef) { if (!projectileDef) {
gameLocal.Warning("Unknown projectile '%s' in weapon '%s'", projectileName, objectname); gameLocal.Warning("Unknown projectile '%s' in weapon '%s'", projectileName, objectname);
} else { }
else {
const char* spawnclass = projectileDef->dict.GetString("spawnclass"); const char* spawnclass = projectileDef->dict.GetString("spawnclass");
idTypeInfo* cls = idClass::GetClass(spawnclass); idTypeInfo* cls = idClass::GetClass(spawnclass);
if (!cls || !cls->IsType(idProjectile::Type)) { if (!cls || !cls->IsType(idProjectile::Type)) {
gameLocal.Warning("Invalid spawnclass '%s' on projectile '%s' (used by weapon '%s')", spawnclass, projectileName, objectname); gameLocal.Warning("Invalid spawnclass '%s' on projectile '%s' (used by weapon '%s')", spawnclass, projectileName, objectname);
} else { }
else {
projectileDict = projectileDef->dict; projectileDict = projectileDef->dict;
} }
} }
@@ -939,7 +1143,8 @@ void idWeapon::GetWeaponDef( const char *objectname, int ammoinclip ) {
const idDeclEntityDef* brassDef = gameLocal.FindEntityDef(brassDefName, false); const idDeclEntityDef* brassDef = gameLocal.FindEntityDef(brassDefName, false);
if (!brassDef) { if (!brassDef) {
gameLocal.Warning("Unknown brass '%s'", brassDefName); gameLocal.Warning("Unknown brass '%s'", brassDefName);
} else { }
else {
brassDict = brassDef->dict; brassDict = brassDef->dict;
} }
} }
@@ -1049,7 +1254,8 @@ void idWeapon::UpdateGUI( void ) {
if (!p->spectating || p->spectator != owner->entityNumber) { if (!p->spectating || p->spectator != owner->entityNumber) {
return; return;
} }
} else { }
else {
return; return;
} }
} }
@@ -1060,7 +1266,8 @@ void idWeapon::UpdateGUI( void ) {
if (ammoamount < 0) { if (ammoamount < 0) {
// show infinite ammo // show infinite ammo
renderEntity.gui[0]->SetStateString("player_ammo", ""); renderEntity.gui[0]->SetStateString("player_ammo", "");
} else { }
else {
// show remaining ammo // show remaining ammo
renderEntity.gui[0]->SetStateString("player_totalammo", va("%i", ammoamount - inclip)); renderEntity.gui[0]->SetStateString("player_totalammo", va("%i", ammoamount - inclip));
renderEntity.gui[0]->SetStateString("player_ammo", ClipSize() ? va("%i", inclip) : "--"); renderEntity.gui[0]->SetStateString("player_ammo", ClipSize() ? va("%i", inclip) : "--");
@@ -1129,7 +1336,8 @@ void idWeapon::MuzzleFlashLight( void ) {
if (muzzleFlashHandle != -1) { if (muzzleFlashHandle != -1) {
gameRenderWorld->UpdateLightDef(muzzleFlashHandle, &muzzleFlash); gameRenderWorld->UpdateLightDef(muzzleFlashHandle, &muzzleFlash);
gameRenderWorld->UpdateLightDef(worldMuzzleFlashHandle, &worldMuzzleFlash); gameRenderWorld->UpdateLightDef(worldMuzzleFlashHandle, &worldMuzzleFlash);
} else { }
else {
muzzleFlashHandle = gameRenderWorld->AddLightDef(&muzzleFlash); muzzleFlashHandle = gameRenderWorld->AddLightDef(&muzzleFlash);
worldMuzzleFlashHandle = gameRenderWorld->AddLightDef(&worldMuzzleFlash); worldMuzzleFlashHandle = gameRenderWorld->AddLightDef(&worldMuzzleFlash);
} }
@@ -1176,7 +1384,8 @@ void idWeapon::SetModel( const char *modelname ) {
if (renderEntity.hModel) { if (renderEntity.hModel) {
renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin(); renderEntity.customSkin = animator.ModelDef()->GetDefaultSkin();
animator.GetJoints(&renderEntity.numJoints, &renderEntity.joints); animator.GetJoints(&renderEntity.numJoints, &renderEntity.joints);
} else { }
else {
renderEntity.customSkin = NULL; renderEntity.customSkin = NULL;
renderEntity.callback = NULL; renderEntity.callback = NULL;
renderEntity.numJoints = 0; renderEntity.numJoints = 0;
@@ -1202,7 +1411,8 @@ bool idWeapon::GetGlobalJointTransform( bool viewModel, const jointHandle_t join
axis = axis * viewWeaponAxis; axis = axis * viewWeaponAxis;
return true; return true;
} }
} else { }
else {
// world model // world model
if (worldModel.GetEntity() && worldModel.GetEntity()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.time, offset, axis)) { if (worldModel.GetEntity() && worldModel.GetEntity()->GetAnimator()->GetJointTransform(jointHandle, gameLocal.time, offset, axis)) {
offset = worldModel.GetEntity()->GetPhysics()->GetOrigin() + offset * worldModel.GetEntity()->GetPhysics()->GetAxis(); offset = worldModel.GetEntity()->GetPhysics()->GetOrigin() + offset * worldModel.GetEntity()->GetPhysics()->GetAxis();
@@ -1286,7 +1496,8 @@ void idWeapon::LowerWeapon( void ) {
hideEnd = hideDistance; hideEnd = hideDistance;
if (gameLocal.time - hideStartTime < hideTime) { if (gameLocal.time - hideStartTime < hideTime) {
hideStartTime = gameLocal.time - (hideTime - (gameLocal.time - hideStartTime)); hideStartTime = gameLocal.time - (hideTime - (gameLocal.time - hideStartTime));
} else { }
else {
hideStartTime = gameLocal.time; hideStartTime = gameLocal.time;
} }
hide = true; hide = true;
@@ -1306,7 +1517,8 @@ void idWeapon::RaiseWeapon( void ) {
hideEnd = 0.0f; hideEnd = 0.0f;
if (gameLocal.time - hideStartTime < hideTime) { if (gameLocal.time - hideStartTime < hideTime) {
hideStartTime = gameLocal.time - (hideTime - (gameLocal.time - hideStartTime)); hideStartTime = gameLocal.time - (hideTime - (gameLocal.time - hideStartTime));
} else { }
else {
hideStartTime = gameLocal.time; hideStartTime = gameLocal.time;
} }
hide = false; hide = false;
@@ -1324,6 +1536,7 @@ void idWeapon::HideWeapon( void ) {
worldModel.GetEntity()->Hide(); worldModel.GetEntity()->Hide();
} }
muzzleFlashEnd = 0; muzzleFlashEnd = 0;
FreeWeaponFireMuzzleLight(entityNumber);
} }
/* /*
@@ -1829,7 +2042,8 @@ void idWeapon::AlertMonsters( void ) {
ent = gameLocal.GetTraceEntity(tr); ent = gameLocal.GetTraceEntity(tr);
if (ent->IsType(idAI::Type)) { if (ent->IsType(idAI::Type)) {
static_cast<idAI*>(ent)->TouchedByFlashlight(owner); static_cast<idAI*>(ent)->TouchedByFlashlight(owner);
} else if ( ent->IsType( idTrigger::Type ) ) { }
else if (ent->IsType(idTrigger::Type)) {
ent->Signal(SIG_TOUCH); ent->Signal(SIG_TOUCH);
ent->ProcessEvent(&EV_Touch, owner, &tr); ent->ProcessEvent(&EV_Touch, owner, &tr);
} }
@@ -1848,7 +2062,8 @@ void idWeapon::AlertMonsters( void ) {
ent = gameLocal.GetTraceEntity(tr); ent = gameLocal.GetTraceEntity(tr);
if (ent->IsType(idAI::Type)) { if (ent->IsType(idAI::Type)) {
static_cast<idAI*>(ent)->TouchedByFlashlight(owner); static_cast<idAI*>(ent)->TouchedByFlashlight(owner);
} else if ( ent->IsType( idTrigger::Type ) ) { }
else if (ent->IsType(idTrigger::Type)) {
ent->Signal(SIG_TOUCH); ent->Signal(SIG_TOUCH);
ent->ProcessEvent(&EV_Touch, owner, &tr); ent->ProcessEvent(&EV_Touch, owner, &tr);
} }
@@ -1874,11 +2089,13 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
if (hideStart < hideEnd) { if (hideStart < hideEnd) {
frac = 1.0f - frac; frac = 1.0f - frac;
frac = 1.0f - frac * frac; frac = 1.0f - frac * frac;
} else { }
else {
frac = frac * frac; frac = frac * frac;
} }
hideOffset = hideStart + (hideEnd - hideStart) * frac; hideOffset = hideStart + (hideEnd - hideStart) * frac;
} else { }
else {
hideOffset = hideEnd; hideOffset = hideEnd;
if (hide && disabled) { if (hide && disabled) {
Hide(); Hide();
@@ -1911,7 +2128,8 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
// present the model // present the model
if (showViewModel) { if (showViewModel) {
Present(); Present();
} else { }
else {
FreeModelDef(); FreeModelDef();
} }
@@ -1920,7 +2138,8 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
// don't show shadows of the world model in first person // don't show shadows of the world model in first person
if (gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool()) { if (gameLocal.isMultiplayer || g_showPlayerShadow.GetBool() || pm_thirdPerson.GetBool()) {
worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = 0; worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = 0;
} else { }
else {
worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber + 1; worldModel.GetEntity()->GetRenderEntity()->suppressShadowInViewID = owner->entityNumber + 1;
worldModel.GetEntity()->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber; worldModel.GetEntity()->GetRenderEntity()->suppressShadowInLightID = LIGHTID_VIEW_MUZZLE_FLASH + owner->entityNumber;
} }
@@ -1935,7 +2154,8 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
// use the barrel joint if available // use the barrel joint if available
if (barrelJointView) { if (barrelJointView) {
GetGlobalJointTransform(true, barrelJointView, muzzleOrigin, muzzleAxis); GetGlobalJointTransform(true, barrelJointView, muzzleOrigin, muzzleAxis);
} else { }
else {
// default to going straight out the view // default to going straight out the view
muzzleOrigin = playerViewOrigin; muzzleOrigin = playerViewOrigin;
muzzleAxis = playerViewAxis; muzzleAxis = playerViewAxis;
@@ -1953,7 +2173,41 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
} }
} }
// remove the muzzle flash light when it's done // Update the separate weapon-fire muzzle light. This is the actual light
// spawned by firing the gun, and it stays attached to the flash joint while
// fading out independently from the flashlight/script light.
if (IsHidden()) {
FreeWeaponFireMuzzleLight(entityNumber);
}
else {
idVec3 fireViewOrigin;
idMat3 fireViewAxis;
idVec3 fireWorldOrigin;
idMat3 fireWorldAxis;
if (flashJointView != INVALID_JOINT && GetGlobalJointTransform(true, flashJointView, fireViewOrigin, fireViewAxis)) {
idVec3 fireStart = fireViewOrigin - playerViewAxis[0] * 16.0f;
idVec3 fireEnd = fireViewOrigin + playerViewAxis[0] * 8.0f;
trace_t fireTr;
gameLocal.clip.TracePoint(fireTr, fireStart, fireEnd, MASK_SHOT_RENDERMODEL, owner);
fireViewOrigin = fireTr.endpos - playerViewAxis[0] * 8.0f;
if (flashJointWorld != INVALID_JOINT && GetGlobalJointTransform(false, flashJointWorld, fireWorldOrigin, fireWorldAxis)) {
// valid world joint
}
else {
fireWorldOrigin = fireViewOrigin;
fireWorldAxis = fireViewAxis;
}
UpdateWeaponFireMuzzleLight(entityNumber, fireViewOrigin, fireViewAxis, fireWorldOrigin, fireWorldAxis);
}
else {
FreeWeaponFireMuzzleLight(entityNumber);
}
}
// remove the old script/flashlight muzzle light when it's done
if ((!lightOn && (gameLocal.time >= muzzleFlashEnd)) || IsHidden()) { if ((!lightOn && (gameLocal.time >= muzzleFlashEnd)) || IsHidden()) {
if (muzzleFlashHandle != -1) { if (muzzleFlashHandle != -1) {
gameRenderWorld->FreeLightDef(muzzleFlashHandle); gameRenderWorld->FreeLightDef(muzzleFlashHandle);
@@ -1983,7 +2237,8 @@ void idWeapon::PresentWeapon( bool showViewModel ) {
if ((guiLightHandle != -1)) { if ((guiLightHandle != -1)) {
gameRenderWorld->UpdateLightDef(guiLightHandle, &guiLight); gameRenderWorld->UpdateLightDef(guiLightHandle, &guiLight);
} else { }
else {
guiLightHandle = gameRenderWorld->AddLightDef(&guiLight); guiLightHandle = gameRenderWorld->AddLightDef(&guiLight);
} }
} }
@@ -2186,7 +2441,8 @@ idWeapon::AmmoAvailable
int idWeapon::AmmoAvailable(void) const { int idWeapon::AmmoAvailable(void) const {
if (owner) { if (owner) {
return owner->inventory.HasAmmo(ammoType, ammoRequired); return owner->inventory.HasAmmo(ammoType, ammoRequired);
} else { }
else {
return 0; return 0;
} }
} }
@@ -2370,7 +2626,8 @@ void idWeapon::Event_WeaponState( const char *statename, int blendFrames ) {
if (!idealState.Icmp("Fire")) { if (!idealState.Icmp("Fire")) {
isFiring = true; isFiring = true;
} else { }
else {
isFiring = false; isFiring = false;
} }
@@ -2585,7 +2842,8 @@ void idWeapon::Event_PlayAnim( int channel, const char *animname ) {
gameLocal.Warning("missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName()); gameLocal.Warning("missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName());
animator.Clear(channel, gameLocal.time, FRAME2MS(animBlendFrames)); animator.Clear(channel, gameLocal.time, FRAME2MS(animBlendFrames));
animDoneTime = 0; animDoneTime = 0;
} else { }
else {
if (!(owner && owner->GetInfluenceLevel())) { if (!(owner && owner->GetInfluenceLevel())) {
Show(); Show();
} }
@@ -2615,7 +2873,8 @@ void idWeapon::Event_PlayCycle( int channel, const char *animname ) {
gameLocal.Warning("missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName()); gameLocal.Warning("missing '%s' animation on '%s' (%s)", animname, name.c_str(), GetEntityDefName());
animator.Clear(channel, gameLocal.time, FRAME2MS(animBlendFrames)); animator.Clear(channel, gameLocal.time, FRAME2MS(animBlendFrames));
animDoneTime = 0; animDoneTime = 0;
} else { }
else {
if (!(owner && owner->GetInfluenceLevel())) { if (!(owner && owner->GetInfluenceLevel())) {
Show(); Show();
} }
@@ -2638,7 +2897,8 @@ idWeapon::Event_AnimDone
void idWeapon::Event_AnimDone(int channel, int blendFrames) { void idWeapon::Event_AnimDone(int channel, int blendFrames) {
if (animDoneTime - FRAME2MS(blendFrames) <= gameLocal.time) { if (animDoneTime - FRAME2MS(blendFrames) <= gameLocal.time) {
idThread::ReturnInt(true); idThread::ReturnInt(true);
} else { }
else {
idThread::ReturnInt(false); idThread::ReturnInt(false);
} }
} }
@@ -2681,7 +2941,8 @@ void idWeapon::Event_SetSkin( const char *skinname ) {
if (!skinname || !skinname[0]) { if (!skinname || !skinname[0]) {
skinDecl = NULL; skinDecl = NULL;
} else { }
else {
skinDecl = declManager->FindSkin(skinname); skinDecl = declManager->FindSkin(skinname);
} }
@@ -2711,7 +2972,8 @@ void idWeapon::Event_Flashlight( int enable ) {
if (enable) { if (enable) {
lightOn = true; lightOn = true;
MuzzleFlashLight(); MuzzleFlashLight();
} else { }
else {
lightOn = false; lightOn = false;
muzzleFlashEnd = 0; muzzleFlashEnd = 0;
} }
@@ -2779,7 +3041,8 @@ void idWeapon::Event_CreateProjectile( void ) {
projectileEnt->Hide(); projectileEnt->Hide();
} }
idThread::ReturnEntity(projectileEnt); idThread::ReturnEntity(projectileEnt);
} else { }
else {
idThread::ReturnEntity(NULL); idThread::ReturnEntity(NULL);
} }
} }
@@ -2860,7 +3123,8 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float
if (barrelJointView != INVALID_JOINT && projectileDict.GetBool("launchFromBarrel")) { if (barrelJointView != INVALID_JOINT && projectileDict.GetBool("launchFromBarrel")) {
// there is an explicit joint for the muzzle // there is an explicit joint for the muzzle
GetGlobalJointTransform(true, barrelJointView, muzzleOrigin, muzzleAxis); GetGlobalJointTransform(true, barrelJointView, muzzleOrigin, muzzleAxis);
} else { }
else {
// go straight out of the view // go straight out of the view
muzzleOrigin = playerViewOrigin; muzzleOrigin = playerViewOrigin;
muzzleAxis = playerViewAxis; muzzleAxis = playerViewAxis;
@@ -2893,7 +3157,8 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float
} }
} }
} else { }
else {
ownerBounds = owner->GetPhysics()->GetAbsBounds(); ownerBounds = owner->GetPhysics()->GetAbsBounds();
@@ -2911,7 +3176,8 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float
ent->Show(); ent->Show();
ent->Unbind(); ent->Unbind();
projectileEnt = NULL; projectileEnt = NULL;
} else { }
else {
gameLocal.SpawnEntityDef(projectileDict, &ent, false); gameLocal.SpawnEntityDef(projectileDict, &ent, false);
} }
@@ -2935,7 +3201,8 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float
muzzle_pos = muzzleOrigin + playerViewAxis[0] * 2.0f; muzzle_pos = muzzleOrigin + playerViewAxis[0] * 2.0f;
if ((ownerBounds - projBounds).RayIntersection(muzzle_pos, playerViewAxis[0], distance)) { if ((ownerBounds - projBounds).RayIntersection(muzzle_pos, playerViewAxis[0], distance)) {
start = muzzle_pos + distance * playerViewAxis[0]; start = muzzle_pos + distance * playerViewAxis[0];
} else { }
else {
start = ownerBounds.GetCenter(); start = ownerBounds.GetCenter();
} }
gameLocal.clip.Translation(tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner); gameLocal.clip.Translation(tr, start, muzzle_pos, proj->GetPhysics()->GetClipModel(), proj->GetPhysics()->GetClipModel()->GetAxis(), MASK_SHOT_RENDERMODEL, owner);
@@ -2949,9 +3216,66 @@ void idWeapon::Event_LaunchProjectiles( int num_projectiles, float spread, float
PostEventMS(&EV_Weapon_EjectBrass, brassDelay); PostEventMS(&EV_Weapon_EjectBrass, brassDelay);
} }
// add the light for the muzzleflash // Add a separate short-lived weapon-fire light. Do not use
if ( !lightOn ) { // MuzzleFlashLight() here; that path is also used by the flashlight / script
MuzzleFlashLight(); // weapon light and it should not own normal muzzle-flash firing.
if (flashJointView != INVALID_JOINT) {
idVec3 fireViewOrigin;
idMat3 fireViewAxis;
idVec3 fireWorldOrigin;
idMat3 fireWorldAxis;
GetGlobalJointTransform(true, flashJointView, fireViewOrigin, fireViewAxis);
// Keep the view light on the muzzle flash joint, but back it away from
// walls the same way the old muzzle flash code did so it does not spawn
// inside opaque geometry.
idVec3 fireStart = fireViewOrigin - playerViewAxis[0] * 16.0f;
idVec3 fireEnd = fireViewOrigin + playerViewAxis[0] * 8.0f;
trace_t fireTr;
gameLocal.clip.TracePoint(fireTr, fireStart, fireEnd, MASK_SHOT_RENDERMODEL, owner);
fireViewOrigin = fireTr.endpos - playerViewAxis[0] * 8.0f;
if (flashJointWorld != INVALID_JOINT) {
GetGlobalJointTransform(false, flashJointWorld, fireWorldOrigin, fireWorldAxis);
}
else {
fireWorldOrigin = fireViewOrigin;
fireWorldAxis = fireViewAxis;
}
const char* fireShaderName = weaponDef->dict.GetString("mtr_weaponFireLightShader");
if (!fireShaderName || !fireShaderName[0]) {
fireShaderName = weaponDef->dict.GetString("mtr_flashShader");
}
const idMaterial* fireShader = NULL;
if (fireShaderName && fireShaderName[0]) {
fireShader = declManager->FindMaterial(fireShaderName, false);
}
idVec3 fireColor = weaponDef->dict.GetVector("weaponFireLightColor", flashColor.ToString());
if (fireColor.x <= 0.0f && fireColor.y <= 0.0f && fireColor.z <= 0.0f) {
fireColor.Set(1.0f, 0.72f, 0.38f);
}
float fireIntensity = weaponDef->dict.GetFloat("weaponFireLightIntensity", "1.0");
if (fireIntensity <= 0.0f) {
fireIntensity = 1.0f;
}
fireColor *= fireIntensity;
float fireRadius = weaponDef->dict.GetFloat("weaponFireLightRadius", va("%f", muzzleFlash.lightRadius[0]));
if (fireRadius <= 0.0f) {
fireRadius = 96.0f;
}
int fireTime = SEC2MS(weaponDef->dict.GetFloat("weaponFireLightTime", weaponDef->dict.GetString("flashTime", "0.08")));
if (fireTime <= 0) {
fireTime = 80;
}
StartWeaponFireMuzzleLight(entityNumber, owner ? owner->entityNumber : -1, fireViewOrigin, fireViewAxis, fireWorldOrigin, fireWorldAxis, fireShader, fireColor, fireRadius, fireTime, renderEntity.shaderParms[SHADERPARM_DIVERSITY]);
} }
owner->WeaponFireFeedback(&weaponDef->dict); owner->WeaponFireFeedback(&weaponDef->dict);
@@ -2979,7 +3303,8 @@ void idWeapon::Event_Melee( void ) {
gameLocal.clip.TracePoint(tr, start, end, MASK_SHOT_RENDERMODEL, owner); gameLocal.clip.TracePoint(tr, start, end, MASK_SHOT_RENDERMODEL, owner);
if (tr.fraction < 1.0f) { if (tr.fraction < 1.0f) {
ent = gameLocal.GetTraceEntity(tr); ent = gameLocal.GetTraceEntity(tr);
} else { }
else {
ent = NULL; ent = NULL;
} }
@@ -3031,7 +3356,8 @@ void idWeapon::Event_Melee( void ) {
ent->AddDamageEffect(tr, impulse, meleeDef->dict.GetString("classname")); ent->AddDamageEffect(tr, impulse, meleeDef->dict.GetString("classname"));
} else { }
else {
int type = tr.c.material->GetSurfaceType(); int type = tr.c.material->GetSurfaceType();
if (type == SURFTYPE_NONE) { if (type == SURFTYPE_NONE) {
@@ -3054,7 +3380,8 @@ void idWeapon::Event_Melee( void ) {
gameLocal.ProjectDecal(tr.c.point, -tr.c.normal, 8.0f, true, 6.0, decal); gameLocal.ProjectDecal(tr.c.point, -tr.c.normal, 8.0f, true, 6.0, decal);
} }
nextStrikeFx = gameLocal.time + 200; nextStrikeFx = gameLocal.time + 200;
} else { }
else {
hitSound = ""; hitSound = "";
} }
@@ -3096,7 +3423,8 @@ idWeapon::Event_AllowDrop
void idWeapon::Event_AllowDrop(int allow) { void idWeapon::Event_AllowDrop(int allow) {
if (allow) { if (allow) {
allowDrop = true; allowDrop = true;
} else { }
else {
allowDrop = false; allowDrop = false;
} }
} }
+352 -137
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -519,6 +519,15 @@ void idAI::MuzzleFlash(const char* jointname) {
idVec3 muzzle; idVec3 muzzle;
idMat3 axis; idMat3 axis;
// Match the idWeapon behavior: the short-lived muzzle light should attach
// to the same joint the script fired from, not blindly to a hardcoded joint.
if (jointname && jointname[0]) {
jointHandle_t joint = animator.GetJointHandle(jointname);
if (joint != INVALID_JOINT) {
flashJointWorld = joint;
}
}
GetMuzzle(jointname, muzzle, axis); GetMuzzle(jointname, muzzle, axis);
TriggerWeaponEffects(muzzle); TriggerWeaponEffects(muzzle);
} }
+105 -26
View File
@@ -2308,7 +2308,12 @@ cbuffer DrawCB : register(b0)
// Full object-space displacement amplitude for the tessellation domain shader. // Full object-space displacement amplitude for the tessellation domain shader.
// The CPU side keeps this conservative, but clamp here too so a bad material // The CPU side keeps this conservative, but clamp here too so a bad material
// strength cannot explode the mesh. // strength cannot explode the mesh.
#define gTessellationDisplacement clamp(gMaterialMapPad.w, 0.0, 0.35) // Raised from the old 0.35 cap so stronger materials can push deeper, while
// the hull shader below uses shared-edge tess factors to prevent crack artifacts.
#define gTessellationDisplacement clamp(gMaterialMapPad.w, 0.0, 0.50)
#define QD3D12_TESS_MIN_FACTOR 2.0
#define QD3D12_TESS_MAX_FACTOR 15.0
#define QD3D12_TESS_TARGET_EDGE_PIXELS 28.0
Texture2D gTex0 : register(t0); Texture2D gTex0 : register(t0);
Texture2D gTex1 : register(t1); Texture2D gTex1 : register(t1);
@@ -2565,6 +2570,38 @@ float QD3D12_GetHeightFromNormalMapLOD(float2 uv, float lod)
return QD3D12_HeightFromNormalSample(gNormalMap.SampleLevel(gSamp2, uv, lod)); return QD3D12_HeightFromNormalSample(gNormalMap.SampleLevel(gSamp2, uv, lod));
} }
float2 QD3D12_NormalMapTexelSize()
{
uint w = 1;
uint h = 1;
gNormalMap.GetDimensions(w, h);
return rcp(max(float2((float)w, (float)h), float2(1.0, 1.0)));
}
float QD3D12_GetFilteredTessHeight(float2 uv)
{
// Hardware tessellation magnifies tiny normal-map compression errors.
// A light cross filter keeps the deeper displacement stable without
// blurring the color/normal sampling path used by the pixel shader.
float2 texel = QD3D12_NormalMapTexelSize();
float center = QD3D12_GetHeightFromNormalMapLOD(uv, 0.0);
float hx0 = QD3D12_GetHeightFromNormalMapLOD(uv - float2(texel.x, 0.0), 0.0);
float hx1 = QD3D12_GetHeightFromNormalMapLOD(uv + float2(texel.x, 0.0), 0.0);
float hy0 = QD3D12_GetHeightFromNormalMapLOD(uv - float2(0.0, texel.y), 0.0);
float hy1 = QD3D12_GetHeightFromNormalMapLOD(uv + float2(0.0, texel.y), 0.0);
return saturate(center * 0.50 + (hx0 + hx1 + hy0 + hy1) * 0.125);
}
float QD3D12_CleanCenteredTessHeight(float height)
{
// Suppress barely-visible single-texel noise before applying the now deeper
// displacement. This removes acne-like spikes while preserving real relief.
float centered = (height - 0.5) * 2.0;
float s = (centered < 0.0) ? -1.0 : 1.0;
float a = saturate((abs(centered) - 0.015) * (1.0 / 0.985));
return s * a;
}
float3 QD3D12_BuildFallbackTangent(float3 n) float3 QD3D12_BuildFallbackTangent(float3 n)
{ {
float3 up = (abs(n.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0); float3 up = (abs(n.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(0.0, 1.0, 0.0);
@@ -2721,7 +2758,8 @@ float4 BuildGlowEmission(VSOut i)
{ {
return QD3D12_SampleGlow(i); return QD3D12_SampleGlow(i);
} }
)HLSL"
R"HLSL(
float4 BuildSpecularAlbedo(VSOut i) float4 BuildSpecularAlbedo(VSOut i)
{ {
if (gUseSpecularMap <= 0.0) if (gUseSpecularMap <= 0.0)
@@ -2838,29 +2876,68 @@ TessCP QD3D12_MakeTessCP(VSOut i)
return o; return o;
} }
float QD3D12_ComputeNormalMapTessFactor(InputPatch<VSOut, 3> patch) float2 QD3D12_ClipXYToNdc(float4 clipPos)
{ {
float h0 = QD3D12_GetHeightFromNormalMapLOD(patch[0].uv0, 0.0); return clipPos.xy / max(abs(clipPos.w), 1.0e-6);
float h1 = QD3D12_GetHeightFromNormalMapLOD(patch[1].uv0, 0.0); }
float h2 = QD3D12_GetHeightFromNormalMapLOD(patch[2].uv0, 0.0);
float heightRange = max(abs(h0 - h1), max(abs(h1 - h2), abs(h2 - h0))); float QD3D12_EdgeLengthPixels(float4 clipA, float4 clipB)
{
float2 ndcA = QD3D12_ClipXYToNdc(clipA);
float2 ndcB = QD3D12_ClipXYToNdc(clipB);
return length((ndcA - ndcB) * max(gRenderSize, float2(1.0, 1.0)) * 0.5);
}
float QD3D12_ComputeNormalMapTessEdgeFactor(VSOut a, VSOut b)
{
if (gUseNormalMap <= 0.5)
return 1.0;
float ha = QD3D12_GetFilteredTessHeight(a.uv0);
float hb = QD3D12_GetFilteredTessHeight(b.uv0);
float hm = QD3D12_GetFilteredTessHeight((a.uv0 + b.uv0) * 0.5);
// This edge-only factor is the crack fix: neighboring triangles that share
// this edge compute the same tessellation factor because they use only the
// two shared vertices and the same midpoint sample.
float heightRange = max(abs(ha - hb), abs(hm - (ha + hb) * 0.5) * 2.0);
float edgePixels = QD3D12_EdgeLengthPixels(a.currClip, b.currClip);
float uvSpan = length(a.uv0 - b.uv0);
// Give displaced normal-mapped surfaces enough subdivision to show the larger
// height push. fractional_odd will quantize these into stable odd partitions,
// so 3/5/7 are the practical quality tiers for default/strong materials.
float strength = clamp(max(gNormalMapStrength, 0.0), 0.0, 4.0); float strength = clamp(max(gNormalMapStrength, 0.0), 0.0, 4.0);
float factor = 2.0 + strength * 1.0 + heightRange * 8.0; float screenTerm = sqrt(max(edgePixels, 1.0) / QD3D12_TESS_TARGET_EDGE_PIXELS);
return (gUseNormalMap > 0.5) ? clamp(factor, 2.0, 7.0) : 1.0; float heightTerm = heightRange * (10.0 + strength * 4.0);
float uvTerm = saturate(uvSpan * 4.0) * 2.0;
float displacementTerm = saturate(gTessellationDisplacement * 6.0) * 2.0;
float factor = 1.0 + strength * 0.75 + screenTerm * 3.0 + heightTerm + uvTerm + displacementTerm;
return clamp(factor, QD3D12_TESS_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR);
} }
HSConstOut HSMainConstants(InputPatch<VSOut, 3> patch) HSConstOut HSMainConstants(InputPatch<VSOut, 3> patch)
{ {
HSConstOut o; HSConstOut o;
float factor = QD3D12_ComputeNormalMapTessFactor(patch);
o.edge[0] = factor; if (gUseNormalMap <= 0.5)
o.edge[1] = factor; {
o.edge[2] = factor; o.edge[0] = 1.0;
o.inside = factor; o.edge[1] = 1.0;
o.edge[2] = 1.0;
o.inside = 1.0;
return o;
}
// D3D tri patch edge order is opposite the named control point:
// edge 0 = CP1-CP2, edge 1 = CP2-CP0, edge 2 = CP0-CP1.
float e0 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[1], patch[2]);
float e1 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[2], patch[0]);
float e2 = QD3D12_ComputeNormalMapTessEdgeFactor(patch[0], patch[1]);
float maxEdge = max(e0, max(e1, e2));
o.edge[0] = e0;
o.edge[1] = e1;
o.edge[2] = e2;
o.inside = clamp((e0 + e1 + e2 + maxEdge) * 0.25, QD3D12_TESS_MIN_FACTOR, QD3D12_TESS_MAX_FACTOR);
return o; return o;
} }
@@ -2869,6 +2946,7 @@ HSConstOut HSMainConstants(InputPatch<VSOut, 3> patch)
[outputtopology("triangle_ccw")] [outputtopology("triangle_ccw")]
[outputcontrolpoints(3)] [outputcontrolpoints(3)]
[patchconstantfunc("HSMainConstants")] [patchconstantfunc("HSMainConstants")]
[maxtessfactor(15.0)]
TessCP HSMain(InputPatch<VSOut, 3> patch, uint cpId : SV_OutputControlPointID) TessCP HSMain(InputPatch<VSOut, 3> patch, uint cpId : SV_OutputControlPointID)
{ {
return QD3D12_MakeTessCP(patch[cpId]); return QD3D12_MakeTessCP(patch[cpId]);
@@ -2911,8 +2989,8 @@ VSOut DSMain(HSConstOut tessFactors, float3 bary : SV_DomainLocation, const Outp
VSOut o; VSOut o;
float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0)); float3 objNormal = QD3D12_SafeNormalize(i.objNormal, float3(0.0, 0.0, 1.0));
float height = QD3D12_GetHeightFromNormalMapLOD(i.uv0, 0.0); float height = QD3D12_GetFilteredTessHeight(i.uv0);
float centeredHeight = (height - 0.5) * 2.0; float centeredHeight = QD3D12_CleanCenteredTessHeight(height);
float displacement = centeredHeight * gTessellationDisplacement; float displacement = centeredHeight * gTessellationDisplacement;
float3 displacedObjPos = i.objPos + objNormal * displacement; float3 displacedObjPos = i.objPos + objNormal * displacement;
@@ -7629,7 +7707,7 @@ static void QD3D12_FilterMipPixelGammaAware(
center[1] /= centerA; center[1] /= centerA;
center[2] /= centerA; center[2] /= centerA;
const float sharpen = 0.22f; const float sharpen = 1.0f;
filtered[0] = ClampValue<float>(filtered[0] + (center[0] - filtered[0]) * sharpen, 0.0f, 1.0f); filtered[0] = ClampValue<float>(filtered[0] + (center[0] - filtered[0]) * sharpen, 0.0f, 1.0f);
filtered[1] = ClampValue<float>(filtered[1] + (center[1] - filtered[1]) * sharpen, 0.0f, 1.0f); filtered[1] = ClampValue<float>(filtered[1] + (center[1] - filtered[1]) * sharpen, 0.0f, 1.0f);
filtered[2] = ClampValue<float>(filtered[2] + (center[2] - filtered[2]) * sharpen, 0.0f, 1.0f); filtered[2] = ClampValue<float>(filtered[2] + (center[2] - filtered[2]) * sharpen, 0.0f, 1.0f);
@@ -8935,14 +9013,15 @@ static void QD3D12_FlushQueuedBatches()
dc->materialMapPad[1] = batch.key.specularMapStrength; dc->materialMapPad[1] = batch.key.specularMapStrength;
dc->materialMapPad[2] = QD3D12_PipelineUsesAlphaBlend(batch.key.pipeline) ? 1.0f : 0.0f; dc->materialMapPad[2] = QD3D12_PipelineUsesAlphaBlend(batch.key.pipeline) ? 1.0f : 0.0f;
// Normal-map strength now drives a visibly stronger object-space displacement. // Normal-map strength now drives a visibly stronger object-space displacement.
// Keep a hard upper bound because old RGB-only normal maps are often noisy and // The hull shader can now subdivide much deeper, so the CPU side permits a
// are not authored as true height maps. Default strength 1.0 gives about // deeper push too while the shader filters/suppresses tiny bump-map noise.
// +/-0.08 object units for authored alpha-height maps and outward lift for // Default strength 1.0 gives about +/-0.12 object units for authored alpha
// RGB-only bump detail; glNormalMapStrengthf() can push it higher. // height maps and outward lift for RGB-only bump detail; glNormalMapStrengthf()
// can push it higher up to the hard 0.50 object-unit safety cap.
const float tessStrength = ClampValue<float>(batch.key.normalMapStrength, 0.0f, 4.0f); const float tessStrength = ClampValue<float>(batch.key.normalMapStrength, 0.0f, 4.0f);
const float tessDisplacementScale = 0.08f; const float tessDisplacementScale = 0.12f;
dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ? dc->materialMapPad[3] = QD3D12_UseNormalMapTessellationPSO(batch.key, nativeColorOnly) ?
ClampValue<float>(tessStrength * tessDisplacementScale, 0.0f, 0.35f) : 0.0f; ClampValue<float>(tessStrength * tessDisplacementScale, 0.0f, 0.50f) : 0.0f;
if (batch.key.useARBPrograms) if (batch.key.useARBPrograms)
{ {
+3 -3
View File
@@ -176,7 +176,7 @@ void RB_DXDrawInteractions(void)
const float r = srcLight.shaderParms[SHADERPARM_RED]; const float r = srcLight.shaderParms[SHADERPARM_RED];
const float g = srcLight.shaderParms[SHADERPARM_GREEN]; const float g = srcLight.shaderParms[SHADERPARM_GREEN];
const float b = srcLight.shaderParms[SHADERPARM_BLUE]; const float b = srcLight.shaderParms[SHADERPARM_BLUE];
const float intensity = 6.0f; const float intensity = 4.0f;
glRaytracingLight_t light = {}; glRaytracingLight_t light = {};
bool supported = true; bool supported = true;
@@ -187,8 +187,8 @@ void RB_DXDrawInteractions(void)
vLight->globalLightOrigin.x, vLight->globalLightOrigin.x,
vLight->globalLightOrigin.y, vLight->globalLightOrigin.y,
vLight->globalLightOrigin.z, vLight->globalLightOrigin.z,
srcLight.lightRadius[0] * 1.4f, srcLight.lightRadius[0] * 1.25f,
srcLight.lightRadius[1] * 1.4f, srcLight.lightRadius[1] * 1.25f,
srcLight.lightRadius[2] * 1.4f, srcLight.lightRadius[2] * 1.4f,
r, g, b, r, g, b,
intensity); intensity);