You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1305 lines
36 KiB
1305 lines
36 KiB
4 years ago
|
/*==LICENSE==*
|
||
|
|
||
|
CyanWorlds.com Engine - MMOG client, server and tools
|
||
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
||
|
|
||
|
This program is free software: you can redistribute it and/or modify
|
||
|
it under the terms of the GNU 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 General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
Additional permissions under GNU GPL version 3 section 7
|
||
|
|
||
|
If you modify this Program, or any covered work, by linking or
|
||
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
||
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
||
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
||
|
(or a modified version of those libraries),
|
||
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
||
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
||
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
||
|
licensors of this Program grant you additional
|
||
|
permission to convey the resulting work. Corresponding Source for a
|
||
|
non-source form of such a combination shall include the source code for
|
||
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
||
|
work.
|
||
|
|
||
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
||
|
or by snail mail at:
|
||
|
Cyan Worlds, Inc.
|
||
|
14617 N Newport Hwy
|
||
|
Mead, WA 99021
|
||
|
|
||
|
*==LICENSE==*/
|
||
|
|
||
|
#include "hsTypes.h"
|
||
|
#include "plShadowMaster.h"
|
||
|
#include "plShadowSlave.h"
|
||
|
|
||
|
#include "plLightInfo.h"
|
||
|
#include "plShadowCaster.h"
|
||
|
|
||
|
#include "../plIntersect/plVolumeIsect.h"
|
||
|
#include "../plMessage/plShadowCastMsg.h"
|
||
|
#include "../plMessage/plRenderMsg.h"
|
||
|
|
||
|
#include "../plDrawable/plDrawableSpans.h"
|
||
|
|
||
|
#include "../plScene/plVisMgr.h"
|
||
|
|
||
|
#include "hsBounds.h"
|
||
|
#include "plgDispatch.h"
|
||
|
#include "plPipeline.h"
|
||
|
#include "hsFastMath.h"
|
||
|
|
||
|
#include "plTweak.h"
|
||
|
|
||
|
UInt32 plShadowMaster::fGlobalMaxSize = 512;
|
||
|
hsScalar plShadowMaster::fGlobalMaxDist = 160.f; // PERSPTEST
|
||
|
// hsScalar plShadowMaster::fGlobalMaxDist = 100000.f; // PERSPTEST
|
||
|
hsScalar plShadowMaster::fGlobalVisParm = 1.f;
|
||
|
|
||
|
void plShadowMaster::SetGlobalShadowQuality(hsScalar s)
|
||
|
{
|
||
|
if( s < 0 )
|
||
|
s = 0;
|
||
|
else if( s > 1.f )
|
||
|
s = 1.f;
|
||
|
fGlobalVisParm = s;
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::SetGlobalMaxSize(UInt32 s)
|
||
|
{
|
||
|
const UInt32 kMaxMaxGlobalSize = 512;
|
||
|
const UInt32 kMinMaxGlobalSize = 32;
|
||
|
|
||
|
// Make sure it's a power of two.
|
||
|
if( ((s-1) & ~s) != (s-1) )
|
||
|
{
|
||
|
int i;
|
||
|
for( i = 31; i >= 0; i-- )
|
||
|
{
|
||
|
if( (1 << i) & s )
|
||
|
break;
|
||
|
}
|
||
|
s = 1 << i;
|
||
|
}
|
||
|
|
||
|
if( s > kMaxMaxGlobalSize )
|
||
|
s = kMaxMaxGlobalSize;
|
||
|
if( s < kMinMaxGlobalSize )
|
||
|
s = kMinMaxGlobalSize;
|
||
|
|
||
|
fGlobalMaxSize = s;
|
||
|
}
|
||
|
|
||
|
plShadowMaster::plShadowMaster()
|
||
|
: fAttenDist(0),
|
||
|
fMaxDist(0),
|
||
|
fMinDist(0),
|
||
|
fMaxSize(256),
|
||
|
fMinSize(256),
|
||
|
fPower(1.f),
|
||
|
fLightInfo(nil)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
plShadowMaster::~plShadowMaster()
|
||
|
{
|
||
|
Deactivate();
|
||
|
|
||
|
fSlavePool.SetCount(fSlavePool.GetNumAlloc());
|
||
|
int i;
|
||
|
for( i = 0; i < fSlavePool.GetCount(); i++ )
|
||
|
delete fSlavePool[i];
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::Read(hsStream* stream, hsResMgr* mgr)
|
||
|
{
|
||
|
plObjInterface::Read(stream, mgr);
|
||
|
|
||
|
fAttenDist = stream->ReadSwapScalar();
|
||
|
|
||
|
fMaxDist = stream->ReadSwapScalar();
|
||
|
fMinDist = stream->ReadSwapScalar();
|
||
|
|
||
|
fMaxSize = stream->ReadSwap32();
|
||
|
fMinSize = stream->ReadSwap32();
|
||
|
|
||
|
fPower = stream->ReadSwapScalar();
|
||
|
|
||
|
Activate();
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::Write(hsStream* stream, hsResMgr* mgr)
|
||
|
{
|
||
|
plObjInterface::Write(stream, mgr);
|
||
|
|
||
|
stream->WriteSwapScalar(fAttenDist);
|
||
|
|
||
|
stream->WriteSwapScalar(fMaxDist);
|
||
|
stream->WriteSwapScalar(fMinDist);
|
||
|
|
||
|
stream->WriteSwap32(fMaxSize);
|
||
|
stream->WriteSwap32(fMinSize);
|
||
|
|
||
|
stream->WriteSwapScalar(fPower);
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::Activate() const
|
||
|
{
|
||
|
plgDispatch::Dispatch()->RegisterForExactType(plShadowCastMsg::Index(), GetKey());
|
||
|
plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::Deactivate() const
|
||
|
{
|
||
|
plgDispatch::Dispatch()->UnRegisterForExactType(plShadowCastMsg::Index(), GetKey());
|
||
|
plgDispatch::Dispatch()->UnRegisterForExactType(plRenderMsg::Index(), GetKey());
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::SetMaxDist(hsScalar f)
|
||
|
{
|
||
|
fMaxDist = f;
|
||
|
fMinDist = f * 0.75f;
|
||
|
}
|
||
|
|
||
|
|
||
|
#include "plProfile.h"
|
||
|
plProfile_CreateTimer("ShadowMaster", "RenderSetup", ShadowMaster);
|
||
|
hsBool plShadowMaster::MsgReceive(plMessage* msg)
|
||
|
{
|
||
|
plRenderMsg* rendMsg = plRenderMsg::ConvertNoRef(msg);
|
||
|
if( rendMsg )
|
||
|
{
|
||
|
plProfile_BeginLap(ShadowMaster, this->GetKey()->GetUoid().GetObjectName());
|
||
|
IBeginRender();
|
||
|
plProfile_EndLap(ShadowMaster, this->GetKey()->GetUoid().GetObjectName());
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
plShadowCastMsg* castMsg = plShadowCastMsg::ConvertNoRef(msg);
|
||
|
if( castMsg )
|
||
|
{
|
||
|
IOnCastMsg(castMsg);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return plObjInterface::MsgReceive(msg);
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::IBeginRender()
|
||
|
{
|
||
|
fSlavePool.SetCount(0);
|
||
|
if( ISetLightInfo() )
|
||
|
fLightInfo->ClearSlaveBits();
|
||
|
}
|
||
|
|
||
|
hsBool plShadowMaster::IOnCastMsg(plShadowCastMsg* castMsg)
|
||
|
{
|
||
|
// // HACKTEST
|
||
|
// return false;
|
||
|
|
||
|
if( !fLightInfo )
|
||
|
return false;
|
||
|
|
||
|
if( !fLightInfo->InVisSet(plGlobalVisMgr::Instance()->GetVisSet())
|
||
|
|| fLightInfo->InVisNot(plGlobalVisMgr::Instance()->GetVisNot()) )
|
||
|
return false;
|
||
|
|
||
|
const UInt8 shadowQuality = UInt8(plShadowMaster::GetGlobalShadowQuality() * 3.9f);
|
||
|
if( !GetKey()->GetUoid().GetLoadMask().MatchesQuality(shadowQuality) )
|
||
|
return false;
|
||
|
|
||
|
plShadowCaster* caster = castMsg->Caster();
|
||
|
|
||
|
if( !caster->Spans().GetCount() )
|
||
|
return false;
|
||
|
|
||
|
hsBounds3Ext casterBnd;
|
||
|
IComputeCasterBounds(caster, casterBnd);
|
||
|
|
||
|
hsScalar power = IComputePower(caster, casterBnd);
|
||
|
|
||
|
static hsScalar kVisShadowPower = 1.e-1f;
|
||
|
static hsScalar kMinShadowPower = 2.e-1f;
|
||
|
static hsScalar kKneeShadowPower = 3.e-1f;
|
||
|
if( power < kMinShadowPower )
|
||
|
return false;
|
||
|
if( power < kKneeShadowPower )
|
||
|
{
|
||
|
power -= kMinShadowPower;
|
||
|
power /= kKneeShadowPower - kMinShadowPower;
|
||
|
power *= kKneeShadowPower - kVisShadowPower;
|
||
|
power += kVisShadowPower;
|
||
|
}
|
||
|
|
||
|
// Create ShadowSlave focused on ShadowCaster
|
||
|
// ShadowSlave extent just enough to cover ShadowCaster (including nearplane)
|
||
|
plShadowSlave* slave = ICreateShadowSlave(castMsg, casterBnd, power);
|
||
|
if( !slave )
|
||
|
return false;
|
||
|
|
||
|
// !!!IMPORTANT
|
||
|
// ShadowMaster contains 2 values for yon.
|
||
|
// First value applies to ShadowMaster. Any ShadowCaster beyond this distance
|
||
|
// won't cast a shadow
|
||
|
// Second value applies to ShadowSlaves. This is the distance beyond the ShadowCaster
|
||
|
// (NOT FROM SHADOW SOURCE) over which the shadow attenuates to zero
|
||
|
// The effective yon for the ShadowSlave is ShadowSlaveYon + DistanceToFarthestPointOnShadowCasterBound
|
||
|
// That's the distance used for culling ShadowReceivers
|
||
|
// The ShadowSlaveYon is used directly in the
|
||
|
|
||
|
slave->fIndex = UInt32(-1);
|
||
|
castMsg->Pipeline()->SubmitShadowSlave(slave);
|
||
|
|
||
|
if( slave->fIndex == UInt32(-1) )
|
||
|
{
|
||
|
IRecycleSlave(slave);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
fLightInfo->SetSlaveBit(slave->fIndex);
|
||
|
slave->SetFlag(plShadowSlave::kObeysLightGroups, fLightInfo->GetProperty(plLightInfo::kLPShadowLightGroup) && fLightInfo->GetProperty(plLightInfo::kLPHasIncludes));
|
||
|
slave->SetFlag(plShadowSlave::kIncludesChars, fLightInfo->GetProperty(plLightInfo::kLPIncludesChars));
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
plLightInfo* plShadowMaster::ISetLightInfo()
|
||
|
{
|
||
|
fLightInfo = nil;
|
||
|
plSceneObject* owner = IGetOwner();
|
||
|
if( owner )
|
||
|
{
|
||
|
fLightInfo = plLightInfo::ConvertNoRef(owner->GetGenericInterface(plLightInfo::Index()));
|
||
|
}
|
||
|
return fLightInfo;
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::IComputeCasterBounds(const plShadowCaster* caster, hsBounds3Ext& casterBnd)
|
||
|
{
|
||
|
casterBnd.MakeEmpty();
|
||
|
const hsTArray<plShadowCastSpan>& castSpans = caster->Spans();
|
||
|
int i;
|
||
|
for( i = 0; i < castSpans.GetCount(); i++ )
|
||
|
{
|
||
|
plDrawableSpans* dr = castSpans[i].fDraw;
|
||
|
UInt32 index = castSpans[i].fIndex;
|
||
|
|
||
|
// Right now, the generic world bounds seems close enough, even when skinned.
|
||
|
// It gets a little off on the lower LODs, but, hey, they're the lower LODs.
|
||
|
// Getting something more precise will probably involve finding a cagey place
|
||
|
// to stash the accurate world bounds, because we only compute them when the
|
||
|
// skinned objects are visible, and we need them here before the object is
|
||
|
// visibility tested.
|
||
|
casterBnd.Union(&dr->GetSpan(index)->fWorldBounds);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
plShadowSlave* plShadowMaster::INextSlave(const plShadowCaster* caster)
|
||
|
{
|
||
|
int iSlave = fSlavePool.GetCount();
|
||
|
fSlavePool.ExpandAndZero(iSlave+1);
|
||
|
plShadowSlave* slave = fSlavePool[iSlave];
|
||
|
if( !slave )
|
||
|
{
|
||
|
fSlavePool[iSlave] = slave = INewSlave(caster);
|
||
|
}
|
||
|
return slave;
|
||
|
}
|
||
|
|
||
|
plShadowSlave* plShadowMaster::ICreateShadowSlave(plShadowCastMsg* castMsg, const hsBounds3Ext& casterBnd, hsScalar power)
|
||
|
{
|
||
|
const plShadowCaster* caster = castMsg->Caster();
|
||
|
|
||
|
plShadowSlave* slave = INextSlave(caster);
|
||
|
|
||
|
slave->Init();
|
||
|
//HACKTEST
|
||
|
// slave->SetFlag(plShadowSlave::kReverseCull, true);
|
||
|
|
||
|
slave->fPower = power;
|
||
|
slave->fCaster = caster;
|
||
|
slave->fCasterWorldBounds = casterBnd;
|
||
|
slave->fAttenDist = fAttenDist * caster->GetAttenScale();
|
||
|
slave->fBlurScale = caster->GetBlurScale();
|
||
|
|
||
|
slave->SetFlag(plShadowSlave::kSelfShadow, GetProperty(kSelfShadow) || caster->GetSelfShadow());
|
||
|
|
||
|
// Order of these matters, since values calculated in one are
|
||
|
// used by later functions. Rearrange at your own risk.
|
||
|
IComputeWorldToLight(casterBnd, slave);
|
||
|
|
||
|
IComputeBounds(casterBnd, slave);
|
||
|
|
||
|
IComputeWidthAndHeight(castMsg, slave);
|
||
|
|
||
|
IComputeProjections(castMsg, slave);
|
||
|
|
||
|
IComputeLUT(castMsg, slave);
|
||
|
|
||
|
IComputeISect(casterBnd, slave);
|
||
|
|
||
|
// Make sure we really need this shadow. If we decide we
|
||
|
// don't, the returned slave will be nil;
|
||
|
slave = ILastChanceToBail(castMsg, slave);
|
||
|
|
||
|
return slave;
|
||
|
}
|
||
|
|
||
|
plShadowSlave* plShadowMaster::IRecycleSlave(plShadowSlave* slave)
|
||
|
{
|
||
|
fSlavePool.SetCount(fSlavePool.GetCount()-1);
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
plShadowSlave* plShadowMaster::ILastChanceToBail(plShadowCastMsg* castMsg, plShadowSlave* slave)
|
||
|
{
|
||
|
const hsBounds3Ext& wBnd = slave->fWorldBounds;
|
||
|
|
||
|
// If the bounds of the cast shadow aren't visible, forget it.
|
||
|
if( !castMsg->Pipeline()->TestVisibleWorld(wBnd) )
|
||
|
return IRecycleSlave(slave);
|
||
|
|
||
|
hsScalar maxDist = fMaxDist > 0
|
||
|
? (fGlobalMaxDist > 0
|
||
|
? hsMinimum(fMaxDist, fGlobalMaxDist)
|
||
|
: fMaxDist)
|
||
|
: fGlobalMaxDist;
|
||
|
|
||
|
plConst(hsScalar) kMinFrac(0.6f);
|
||
|
maxDist *= kMinFrac + GetGlobalShadowQuality() * (1.f - kMinFrac);
|
||
|
|
||
|
// If we haven't got a max distance at which the shadow stays visible
|
||
|
// then we just need to go with it.
|
||
|
if( maxDist <= 0 )
|
||
|
return slave;
|
||
|
|
||
|
plConst(hsScalar) kMinFadeFrac(0.90f);
|
||
|
plConst(hsScalar) kMaxFadeFrac(0.75f);
|
||
|
const hsScalar fadeFrac = kMinFadeFrac + GetGlobalShadowQuality() * (kMaxFadeFrac - kMinFadeFrac);
|
||
|
hsScalar minDist = maxDist * fadeFrac;
|
||
|
|
||
|
// So we want to fade out the shadow as it gets farther away, hopefully
|
||
|
// pitching it in the distance when we couldn't see it anyway.
|
||
|
hsPoint2 depth;
|
||
|
// We've been testing based on the view direction, which shows unfortunate
|
||
|
// camera facing dependency (because it is camera facing dependent) with
|
||
|
// large shadow casters and low shadow quality. Let's try using the vector
|
||
|
// connecting the caster center with the view position. It's just as wrong,
|
||
|
// but at least nothing will change when you swing the camera around.
|
||
|
#if 0
|
||
|
wBnd.TestPlane(castMsg->Pipeline()->GetViewDirWorld(), depth);
|
||
|
hsScalar eyeDist = castMsg->Pipeline()->GetViewDirWorld().InnerProduct(castMsg->Pipeline()->GetViewPositionWorld());
|
||
|
#else
|
||
|
hsVector3 dir(&wBnd.GetCenter(), &castMsg->Pipeline()->GetViewPositionWorld());
|
||
|
hsFastMath::NormalizeAppr(dir);
|
||
|
wBnd.TestPlane(dir, depth);
|
||
|
hsScalar eyeDist = dir.InnerProduct(castMsg->Pipeline()->GetViewPositionWorld());
|
||
|
#endif
|
||
|
hsScalar dist = depth.fX - eyeDist;
|
||
|
|
||
|
// If it's not far enough to be fading, just go with it as is.
|
||
|
dist -= minDist;
|
||
|
if( dist < 0 )
|
||
|
return slave;
|
||
|
|
||
|
dist /= maxDist - minDist;
|
||
|
dist = 1.f - dist;
|
||
|
|
||
|
// If it's totally faded out, recycle the slave and return nil;
|
||
|
if( dist <= 0 )
|
||
|
return IRecycleSlave(slave);
|
||
|
|
||
|
slave->fPower *= dist;
|
||
|
|
||
|
return slave;
|
||
|
}
|
||
|
|
||
|
// compute ShadowSlave power influenced by SoftRegion, current light intensity, and ShadowCaster.fMaxOpacity;
|
||
|
hsScalar plShadowMaster::IComputePower(const plShadowCaster* caster, const hsBounds3Ext& casterBnd) const
|
||
|
{
|
||
|
hsScalar power = 0;
|
||
|
if( fLightInfo && !fLightInfo->IsIdle() )
|
||
|
{
|
||
|
power = caster->fMaxOpacity;
|
||
|
hsScalar strength, scale;
|
||
|
fLightInfo->GetStrengthAndScale(casterBnd, strength, scale);
|
||
|
power *= strength;
|
||
|
}
|
||
|
power *= fPower;
|
||
|
power *= caster->GetBoost();
|
||
|
|
||
|
return power;
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::IComputeWidthAndHeight(plShadowCastMsg* castMsg, plShadowSlave* slave) const
|
||
|
{
|
||
|
slave->fWidth = fMaxSize;
|
||
|
slave->fHeight = fMaxSize;
|
||
|
|
||
|
if( GetGlobalShadowQuality() <= 0.5f )
|
||
|
{
|
||
|
slave->fWidth >>= 1;
|
||
|
slave->fHeight >>= 1;
|
||
|
}
|
||
|
if( castMsg->Caster()->GetLimitRes() )
|
||
|
{
|
||
|
slave->fWidth >>= 1;
|
||
|
slave->fHeight >>= 1;
|
||
|
}
|
||
|
|
||
|
const hsBounds3Ext& wBnd = slave->fWorldBounds;
|
||
|
|
||
|
hsPoint2 depth;
|
||
|
wBnd.TestPlane(castMsg->Pipeline()->GetViewDirWorld(), depth);
|
||
|
hsScalar eyeDist = castMsg->Pipeline()->GetViewDirWorld().InnerProduct(castMsg->Pipeline()->GetViewPositionWorld());
|
||
|
hsScalar dist = depth.fX - eyeDist;
|
||
|
if( dist < 0 )
|
||
|
dist = 0;
|
||
|
|
||
|
slave->fPriority = dist; // Might want to boost the local players priority.
|
||
|
|
||
|
plConst(hsScalar) kShiftDist = 50.f; // PERSPTEST
|
||
|
// plConst(hsScalar) kShiftDist = 5000.f; // PERSPTEST
|
||
|
int iShift = int(dist / kShiftDist);
|
||
|
slave->fWidth >>= iShift;
|
||
|
slave->fHeight >>= iShift;
|
||
|
|
||
|
if( slave->fWidth > fGlobalMaxSize )
|
||
|
slave->fWidth = fGlobalMaxSize;
|
||
|
if( slave->fHeight > fGlobalMaxSize )
|
||
|
slave->fHeight = fGlobalMaxSize;
|
||
|
|
||
|
const int kMinSize = 32;
|
||
|
if( slave->fWidth < kMinSize )
|
||
|
slave->fWidth = kMinSize;
|
||
|
if( slave->fHeight < kMinSize )
|
||
|
slave->fHeight = kMinSize;
|
||
|
}
|
||
|
|
||
|
void plShadowMaster::IComputeLUT(plShadowCastMsg* castMsg, plShadowSlave* slave) const
|
||
|
{
|
||
|
// This needs to go from camera space z to lightspace z, and then translate that
|
||
|
// into a lookup on U from our LUT.
|
||
|
// First to get into lightspace, we transform our point by
|
||
|
// worldToLight * cameraToWorld.
|
||
|
// Then we want to map like
|
||
|
// Map 0 => (closest = CasterBnd.Closest), 1 => (CasterBnd.Closest + FalloffDist = farthest)
|
||
|
// which means a matrix looking like:
|
||
|
// 0.0, 0.0, 1/(farthest - closest), -closest / (farthest - closest),
|
||
|
// 0.0, 0.0, 0.0, 0.0,
|
||
|
// 0.0, 0.0, 0.0, 0.0,
|
||
|
// 0.0, 0.0, 0.0, 0.0,
|
||
|
|
||
|
|
||
|
hsBounds3Ext bnd = slave->fCasterWorldBounds;
|
||
|
bnd.Transform(&slave->fWorldToLight);
|
||
|
hsScalar farthest = bnd.GetCenter().fZ + slave->fAttenDist;
|
||
|
hsScalar closest = bnd.GetMins().fZ;
|
||
|
|
||
|
// Shouldn't this always be negated?
|
||
|
static hsMatrix44 lightToLut; // Static ensures initialized to all zeros.
|
||
|
lightToLut.fMap[0][2] = 1/(farthest - closest);
|
||
|
lightToLut.fMap[0][3] = -closest / (farthest - closest);
|
||
|
|
||
|
// This full matrix multiply is a little overkill. Could simplify it quite a bit...
|
||
|
slave->fRcvLUT = lightToLut * slave->fWorldToLight;
|
||
|
|
||
|
// For caster, we'll be rendering in light space, so we just need to lut off
|
||
|
// cameraspace z
|
||
|
// Can put bias here if needed (probably) by adding small
|
||
|
// bias to ShadowSlave.LUTXfm.fMap[0][3]. Bias magnitude would probably be at
|
||
|
// least 0.5f/256.f to compensate for quantization.
|
||
|
|
||
|
plConst(hsScalar) kSelfBias = 2.f / 256.f;
|
||
|
plConst(hsScalar) kOtherBias = -0.5 / 256.f;
|
||
|
#if 0 // MF_NOSELF
|
||
|
lightToLut.fMap[0][3] += slave->HasFlag(plShadowSlave::kSelfShadow) ? kSelfBias : kOtherBias;
|
||
|
#else // MF_NOSELF
|
||
|
lightToLut.fMap[0][3] += kSelfBias;
|
||
|
#endif // MF_NOSELF
|
||
|
|
||
|
if( slave->CastInCameraSpace() )
|
||
|
{
|
||
|
slave->fCastLUT = lightToLut * slave->fWorldToLight;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
slave->fCastLUT = lightToLut;
|
||
|
}
|
||
|
|
||
|
#ifdef MF_HACK_SKIPLUT
|
||
|
hsMatrix44 hack;
|
||
|
hack.Reset();
|
||
|
hack.NotIdentity();
|
||
|
hack.fMap[0][0] = 0;
|
||
|
hack.fMap[0][3] = 1.f;
|
||
|
slave->fCastLUT = hack;
|
||
|
#endif // MF_HACK_SKIPLUT
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
// SOURCE CODE ENDS HERE!!!!
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////
|
||
|
// Some notes from when I was working this out, in case I ever need to figure
|
||
|
// out what I was thinking when I set this up.
|
||
|
#if 0 // Notes
|
||
|
|
||
|
|
||
|
|
||
|
MasterShadow
|
||
|
|
||
|
On RenderMsg
|
||
|
|
||
|
Harvest all shadow casters within influence from CullTree
|
||
|
|
||
|
Assign Shadow (shadow == plLightInfo) to each shadow caster
|
||
|
|
||
|
Ideally, we want a shadow caster to be a conceptual object
|
||
|
(like one Avatar), rather than individual spans. Shadow Group ID?
|
||
|
|
||
|
Each shadow renders it's caster into rendertarget, with:
|
||
|
|
||
|
ClearAlpha to 255 (alphatest will neutralize any texels not written to with shadowcaster)
|
||
|
ClearColor to 0 - then a blur will bleed black in, darkening edges, making for softer effect
|
||
|
around edge of image, which means a softer shadow.
|
||
|
|
||
|
|
||
|
Camera Matrix is from=lightPos, at=casterCenter, up=lightUp
|
||
|
|
||
|
ViewTransform = framed around casterBnd
|
||
|
|
||
|
color = (camZ - nearPointOfCasterBound) / (lightYon - nearPointOfCasterBnd)
|
||
|
alpha = color
|
||
|
|
||
|
Add all active shadows to pipeline lights (or just enable them)
|
||
|
|
||
|
During render, if a shadow is affecting the current object, as a final pass:
|
||
|
|
||
|
T0 = texture from shadow renders caster (UV = projection of pos by shadow Xform
|
||
|
|
||
|
T1 = LUT on vtxPos (same LUT as for color and alpha above).
|
||
|
|
||
|
Color = T1 - T0
|
||
|
Alpha = T1 - T0
|
||
|
|
||
|
Texture blend is Subtract (color and alpha)
|
||
|
|
||
|
FB AlphaTest = Greater
|
||
|
FB Blend = Mult
|
||
|
|
||
|
Gives a linear falloff of shadow intensity from nearPointOfCasterBnd to lightYon
|
||
|
|
||
|
Can be softened (just blur T0)
|
||
|
|
||
|
|
||
|
Big problem? On a two TMU system, we're screwed on alpha textures.
|
||
|
|
||
|
On a 3 (or greater) TMU system, we could:
|
||
|
|
||
|
// Select first texture
|
||
|
Stage0
|
||
|
Color/Alpha
|
||
|
Arg1 = T0
|
||
|
Op = SelectArg1
|
||
|
|
||
|
// Subtract first texture from second (T1 - T0)
|
||
|
Stage1
|
||
|
Color/Alpha
|
||
|
Arg1 = T1
|
||
|
Arg2 = Current
|
||
|
Op = Subtract
|
||
|
|
||
|
// Add the complement of the orig texture's alpha to the color, so where the texture
|
||
|
// is transparent, we add 255 and neutralize the shadow, where texture is opaque we
|
||
|
// add 0 and process normally, and stuff in between.
|
||
|
Stage2
|
||
|
Color
|
||
|
Arg1 = Current
|
||
|
Arg2 = origTex | D3DTA_COMPLEMENT | D3DTA_ALPHAREPLICATE;
|
||
|
Op = Add
|
||
|
|
||
|
|
||
|
|
||
|
Stage0 = T0
|
||
|
Stage1 = T1
|
||
|
Stage2 = Original texture
|
||
|
|
||
|
ColorOp0_1 = Subtract (T0 - T1, inverse of above)
|
||
|
ColorOp1_2 = Select Current
|
||
|
|
||
|
AlphaOp0_1 = Subtract (T0 - T1, inverse of above)
|
||
|
AlphaOp1_2 = Multiply (OrigTex.a * (T0 - T1))
|
||
|
|
||
|
|
||
|
Okay, time for the bonus round:
|
||
|
|
||
|
We have 4 cases;
|
||
|
|
||
|
a) 4 TMU system, base layer opaque
|
||
|
b) 4 TMU system, base layer has alpha (god help us on base layer has add)
|
||
|
c) 2 TMU system, base layer opaque
|
||
|
d) 2 TMU system, base layer has alpha
|
||
|
|
||
|
If the base layer is opaque, we can do Stage0 and Stage1 as above, whether
|
||
|
we have 2 or 4 TMU's at our disposal
|
||
|
If the base layer has alpha, and we have 4 TMU's, we can do the above
|
||
|
If the base layer has alpha and we have 2 TMU's, we skip it (early out in Apply)
|
||
|
|
||
|
So, we have the following set up (from above):
|
||
|
|
||
|
Stage0 = T0
|
||
|
Stage1 = T1, subtract
|
||
|
[Stage2 = origTex, add] - only if 4 TMU and origTex has alpha
|
||
|
|
||
|
In any case, we can add one more stage as long as it's just diffuse (we are
|
||
|
out of textures on 2 TMU system). So we use the diffuse to modulate the
|
||
|
effect as follows
|
||
|
|
||
|
Stage3
|
||
|
ColorArg1 = Diffuse
|
||
|
ColorArg2 = current | D3DTA_COMPLEMENT
|
||
|
ColorOp = Modulate
|
||
|
|
||
|
AlphaOp = Disable
|
||
|
|
||
|
The Diffuse contains the value by which to scale the effect, e.g. by SoftRegion
|
||
|
or artist input.
|
||
|
|
||
|
Now the alpha coming out is still fine (make sure you set up the alphatest),
|
||
|
but the color the inverse of what we want. That's okay, our framebuffer
|
||
|
blend now becomes
|
||
|
|
||
|
SrcBlend = ZERO
|
||
|
DstBlend = INVSRCCOLOR
|
||
|
|
||
|
That means we need to be sure to set the fog color to black
|
||
|
|
||
|
And that's that. Uh-huh.
|
||
|
|
||
|
|
||
|
Shadow Plan 9
|
||
|
|
||
|
classes:
|
||
|
|
||
|
class plShadowCaster : public plMultiModifier
|
||
|
{
|
||
|
protected:
|
||
|
class plDrawSpan
|
||
|
{
|
||
|
public:
|
||
|
plDrawableSpans* fDraw;
|
||
|
plSpan* fSpan;
|
||
|
UInt32 fIndex;
|
||
|
};
|
||
|
|
||
|
hsTArray<plDrawSpan> fSpans;
|
||
|
|
||
|
hsBounds3Ext fTotalWorldBounds;
|
||
|
hsScalar fMaxOpacity;
|
||
|
|
||
|
|
||
|
On RenderMsg
|
||
|
{
|
||
|
// Don't really like having to gather these guys up every frame,
|
||
|
// but with the avatar customization, it's all pretty volatile,
|
||
|
// subject to infrequent change, but change without warning.
|
||
|
// The number of actual targets (and hence shadow casting spans)
|
||
|
// for any ShadowCasterModifier should always be on the order of
|
||
|
// 10, so chances are we can get away with this. If not, we can
|
||
|
// figure some way of caching, like a broadcast message warning us
|
||
|
// that an avatar customization event has occurred.
|
||
|
ICollectSpans();
|
||
|
|
||
|
// Max opacity used to fade out shadows during link
|
||
|
|
||
|
//find max opacity of all spans
|
||
|
//clear shadowBits of all spans
|
||
|
hsScalar fMaxOpacity = 0.f;
|
||
|
int i;
|
||
|
for( i = 0; i < fSpans.GetCount(); i++ )
|
||
|
{
|
||
|
plLayer* baseLay = fSpans[i].fDraw->GetSubMaterial(fSpans[i].fSpan->fMaterialIdx)->GetLayer(0);
|
||
|
if( baseLay->GetOpacity() > maxOpacity )
|
||
|
fMaxOpacity = baseLay->GetOpacity();
|
||
|
|
||
|
fSpans[i].fSpan->ClearShadowBits();
|
||
|
}
|
||
|
|
||
|
|
||
|
if( fMaxOpacity > 0 )
|
||
|
{
|
||
|
Broadcast ShadowCastMsg containing
|
||
|
this (ShadowCaster)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ICollectAllSpans()
|
||
|
{
|
||
|
fSpans.SetCount(0);
|
||
|
int i;
|
||
|
for( i = 0; i < GetNumTargets(); i++ )
|
||
|
{
|
||
|
plSceneObject* so = GetTarget(i);
|
||
|
// Nil target? Shouldn't happen.
|
||
|
if( so )
|
||
|
{
|
||
|
plDrawInterface* di = so->GetDrawInterface();
|
||
|
// Nil di- either it hasn't loaded yet, or we've been applied to something that isn't visible (oops).
|
||
|
if( di )
|
||
|
{
|
||
|
int j;
|
||
|
for( j = 0; j < di->GetNumDrawables(); j++ )
|
||
|
{
|
||
|
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(j));
|
||
|
// Nil dr - it hasn't loaded yet.
|
||
|
if( dr )
|
||
|
{
|
||
|
plDISpanIndex& diIndex = dr->GetDISpans(di->GetDrawableMeshIndex(j));
|
||
|
if( !diIndex.IsMatrixOnly() )
|
||
|
{
|
||
|
int k;
|
||
|
for( k = 0; k < diIndex.GetCount(); k++ )
|
||
|
{
|
||
|
fSpans.Append(dr, dr->GetSpan(diIndex[k]), diIndex[k]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
public:
|
||
|
plShadowCaster();
|
||
|
virtual ~plShadowCaster();
|
||
|
|
||
|
CLASSNAME_REGISTER( plShadowCaster );
|
||
|
GETINTERFACE_ANY( plShadowCaster, plMultiModifier );
|
||
|
|
||
|
virtual hsBool IEval(double secs, hsScalar del, UInt32 dirty) {}
|
||
|
|
||
|
virtual hsBool MsgReceive(plMessage* msg);
|
||
|
|
||
|
virtual void Read(hsStream* stream, hsResMgr* mgr);
|
||
|
virtual void Write(hsStream* stream, hsResMgr* mgr);
|
||
|
}
|
||
|
|
||
|
class plShadowMaster
|
||
|
{
|
||
|
protected:
|
||
|
hsTArray<plShadowSlave*> fSlavePool;
|
||
|
|
||
|
plVolumeIsect* fIsect;
|
||
|
|
||
|
hsScalar fAttenDist;
|
||
|
|
||
|
plSoftVolume* fSoftVolume;
|
||
|
|
||
|
virtual void IComputeWidthAndHeight(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
virtual void IComputeWorldToLight(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
virtual void IComputeProjections(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
virtual void IComputeLUT(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
virtual void IComputeISect(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
virtual void IComputeBounds(const hsBounds3Ext& bnd, plShadowSlave* slave) const = 0;
|
||
|
|
||
|
// Override if you want to attenuate (e.g. dist for omni, cone angle for spot).
|
||
|
// But make sure you factor in base class power.
|
||
|
virtual hsScalar IComputePower(const plShadowCaster* caster);
|
||
|
|
||
|
public:
|
||
|
plVolumeIsect* GetIsect() const { return fIsect; }
|
||
|
|
||
|
hsBool CanSee(const hsBounds3Ext& bnd)
|
||
|
{
|
||
|
switch( fType )
|
||
|
{
|
||
|
case kSpot:
|
||
|
return GetIsect().Test(bnd) != kVolumeCulled;
|
||
|
case kDirectional:
|
||
|
return true;
|
||
|
case kLtdDirection:
|
||
|
return GetIsect().Test(bnd) != kVolumeCulled;
|
||
|
case kOmni:
|
||
|
return GetIsect().Test(bnd) != kVolumeCulled;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
virtual void CreateShadowSlave(const hsBounds3Ext& bnd, hsScalar power)
|
||
|
{
|
||
|
int iSlave = fSlavePool.GetCount();
|
||
|
fSlavePool.ExpandAndZero(iSlave+1);
|
||
|
plShadowSlave* slave = fSlavePool[iSlave];
|
||
|
if( !slave )
|
||
|
{
|
||
|
fSlavePool[iSlave] = slave = TRACKED_NEW plShadowSlave;
|
||
|
fISectPool[iSlave] = INewISect();
|
||
|
}
|
||
|
|
||
|
slave.SetIndex(iSlave); // To be used in not shadowing our own casters
|
||
|
|
||
|
slave->fPower = power;
|
||
|
|
||
|
IComputeWidthAndHeight(bnd, slave);
|
||
|
|
||
|
IComputeWorldToLight(bnd, slave);
|
||
|
|
||
|
IComputeProjections(bnd, slave);
|
||
|
|
||
|
IComputeLUT(bnd, slave);
|
||
|
|
||
|
IComputeISect(bnd, slave, iSlave);
|
||
|
|
||
|
IComputeBounds(bnd, slave);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// compute ShadowSlave power influenced by SoftRegion and ShadowCaster.fMaxOpacity;
|
||
|
hsScalar plShadowMaster::ComputePower(const plShadowCaster* caster)
|
||
|
{
|
||
|
hsScalar power = caster->fMaxOpacity;
|
||
|
if( fSoftVolume )
|
||
|
{
|
||
|
power *= fSoftVolume->GetStrength(caster->fTotalWorldBounds.GetCenter());
|
||
|
}
|
||
|
return power;
|
||
|
}
|
||
|
|
||
|
class OmniShadowMaster : public plShadowMaster
|
||
|
{
|
||
|
protected:
|
||
|
hsTArray<plVolumeIsect*> fIsectPool;
|
||
|
|
||
|
virtual void IComputeWidthAndHeight(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
virtual void IComputeWorldToLight(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
virtual void IComputeProjections(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
virtual void IComputeLUT(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
virtual void IComputeISect(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
virtual void IComputeBounds(const hsBounds3Ext& bnd, plShadowSlave* slave) const;
|
||
|
};
|
||
|
|
||
|
void plOmniShadowMaster::IComputeWorldToLight(const hsBounds3Ext& bnd, plShadowSlave* slave) const
|
||
|
{
|
||
|
hsPoint3 from = fPosition;
|
||
|
hsPoint3 at = bnd.GetCenter();
|
||
|
hsVector3 up = fLastUp;
|
||
|
if( (up % (at - from)).MagnitudeSqaured() < kMinMag )
|
||
|
{
|
||
|
up.Set(0, 1.f, 0);
|
||
|
if( (up % (at - from)).MagnitudeSqaured() < kMinMag )
|
||
|
{
|
||
|
up.Set(0, 0, 1.f);
|
||
|
}
|
||
|
fLastUp = up;
|
||
|
}
|
||
|
slave->fWorldToLight.MakeCamera(&from, &at, &up);
|
||
|
}
|
||
|
|
||
|
void plOmniShadowMaster::IComputeProjections(const hsBounds3Ext& wBnd, plShadowSlave* slave) const
|
||
|
{
|
||
|
hsBounds3Ext bnd = wBnd;
|
||
|
bnd.Transform(slave->fWorldToLight);
|
||
|
|
||
|
hsScalar minZ = bnd.GetMins().fZ;
|
||
|
hsScalar maxZ = bnd.GetCenter().fZ + fAttenDist;
|
||
|
|
||
|
if( minZ < kMinMinZ )
|
||
|
minZ = kMinMinZ;
|
||
|
|
||
|
hsScalar cotX, cotY;
|
||
|
if( -bnd.GetMins().fX > bnd.GetMaxs().fX )
|
||
|
{
|
||
|
hsAssert(bnd.GetMins().fX < 0, "Empty shadow caster bounds?");
|
||
|
cotX = -minZ / bnd.GetMins().fX;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsAssert(bnd.GetMaxs().fX > 0, "Empty shadow caster bounds?");
|
||
|
cotX = minZ / bnd.GetMaxs().fX;
|
||
|
}
|
||
|
|
||
|
if( -bnd.GetMins().fY > bnd.GetMaxs().fY )
|
||
|
{
|
||
|
hsAssert(bnd.GetMins().fY < 0, "Empty shadow caster bounds?");
|
||
|
cotY = -minZ / bnd.GetMins().fY;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hsAssert(bnd.GetMaxs().fY > 0, "Empty shadow caster bounds?");
|
||
|
cotY = minZ / bnd.GetMaxs().fY;
|
||
|
}
|
||
|
|
||
|
|
||
|
hsMatrix44 proj;
|
||
|
proj.Reset();
|
||
|
proj.NotIdentity();
|
||
|
|
||
|
// First the LightToTexture, which uses the above pretty much as is.
|
||
|
// Note the remapping to range [0.5..width-0.5] etc. Also, the perspective
|
||
|
// divide is by the 3rd output (not the fourth), so we make the 3rd
|
||
|
// output be W (instead of Z).
|
||
|
proj.fMap[0][0] = cotX * 0.5f;
|
||
|
proj.fMap[0][3] = 0.5f * (1.f + 1.f/slave->fWidth);
|
||
|
proj.fMap[1][1] = -cotY * 0.5f;
|
||
|
proj.fMap[1][3] = 0.5f * (1.f + 1.f/slave->fHeight);
|
||
|
#if 0 // This computes correct Z, but we really just want W in 3rd component.
|
||
|
proj.fMap[2][2] = maxZ / (maxZ - minZ);
|
||
|
proj.fMap[2][3] = minZ * maxZ / (maxZ - minZ);
|
||
|
#else
|
||
|
proj.fMap[2][2] = 1.f;
|
||
|
proj.fMap[2][3] = 0;
|
||
|
#endif
|
||
|
proj.fMap[3][3] = 0;
|
||
|
proj.fMap[3][2] = 1.f;
|
||
|
|
||
|
slave->fLightToTexture = proj;
|
||
|
slave->fCameraToTexture = slave->fLightToTexture * slave->fWorldToLight * pipe->GetCameraToWorld();
|
||
|
|
||
|
// Now the LightToNDC. This one's a little trickier, because we want to compensate for
|
||
|
// having brought in the viewport to keep our border constant, so we can clamp the
|
||
|
// projected texture and not have the edges smear off to infinity.
|
||
|
cotX -= cotX / (slave->fWidth * 0.5f);
|
||
|
cotY -= cotY / (slave->fHeight * 0.5f);
|
||
|
|
||
|
proj.fMap[0][0] = cotX;
|
||
|
proj.fMap[0][3] = 0.f;
|
||
|
proj.fMap[1][1] = cotY;
|
||
|
proj.fMap[1][3] = 0.f;
|
||
|
proj.fMap[2][2] = maxZ / (maxZ - minZ);
|
||
|
proj.fMap[2][3] = minZ * maxZ / (maxZ - minZ);
|
||
|
proj.fMap[3][3] = 0;
|
||
|
proj.fMap[3][2] = 1.f;
|
||
|
|
||
|
slave->fLightToNDC = proj;
|
||
|
}
|
||
|
|
||
|
class plShadowSlave
|
||
|
{
|
||
|
public:
|
||
|
|
||
|
hsMatrix44 fWorldToLight;
|
||
|
hsMatrix44 fLightToNDC;
|
||
|
hsMatrix44 fLightToTexture;
|
||
|
hsMatrix44 fCastLUT;
|
||
|
hsMatrix44 fRcvLUT;
|
||
|
|
||
|
hsScalar fPower;
|
||
|
|
||
|
plVolumeIsect* fISect;
|
||
|
|
||
|
UInt32 fWidth;
|
||
|
UInt32 fHeight;
|
||
|
};
|
||
|
|
||
|
BeginScene (on EvalMsg?)
|
||
|
{
|
||
|
ShadowMasters ClearShadowSlaves(); // fSlavePool.SetCount(0); fISectPool.SetCount(0);
|
||
|
}
|
||
|
|
||
|
EndScene
|
||
|
{
|
||
|
pipeline->ClearShadowSlaves();
|
||
|
}
|
||
|
|
||
|
Harvest
|
||
|
{
|
||
|
ShadowMasters wait for ShadowCastMsg broadcast
|
||
|
|
||
|
On ShadowCastMsg
|
||
|
|
||
|
if( !ShadowMaster.CanSee(ShadowCaster.fTotalWorldBounds) )
|
||
|
forget it;
|
||
|
|
||
|
hsScalar power = ComputePower(ShadowCaster);
|
||
|
|
||
|
if( power == 0 )
|
||
|
forget it;
|
||
|
|
||
|
// Create ShadowSlave focused on ShadowCaster
|
||
|
// ShadowSlave extent just enough to cover ShadowCaster (including nearplane)
|
||
|
CreateShadowSlave(ShadowCaster.fTotalWorldBounds, power);
|
||
|
|
||
|
// !!!IMPORTANT
|
||
|
// ShadowMaster contains 2 values for yon.
|
||
|
// First value applies to ShadowMaster. Any ShadowCaster beyond this distance
|
||
|
// won't cast a shadow
|
||
|
// Second value applies to ShadowSlaves. This is the distance beyond the ShadowCaster
|
||
|
// (NOT FROM SHADOW SOURCE) over which the shadow attenuates to zero
|
||
|
// The effective yon for the ShadowSlave is ShadowSlaveYon + DistanceToFarthestPointOnShadowCasterBound
|
||
|
// That's the distance used for culling ShadowReceivers
|
||
|
// The ShadowSlaveYon is used directly in the
|
||
|
|
||
|
if ShadowSlave extent not visible to current camera
|
||
|
forget it;
|
||
|
|
||
|
ShadowSlave.Generate
|
||
|
|
||
|
Submit to pipeline
|
||
|
|
||
|
endOnMsg
|
||
|
|
||
|
endfor
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
Pipeline functions
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Puts the slave in a list valid for this frame only. The list will
|
||
|
// be preprocessed at BeginRender. See IPreprocessShadows.
|
||
|
void SubmitShadowSlave(plShadowSlave* slave);
|
||
|
|
||
|
// rendering all the associated spans into
|
||
|
// a rendertarget of the correct size
|
||
|
|
||
|
// We have a (possibly empty) list of shadows submitted for this frame.
|
||
|
// At BeginRender, we need to accomplish:
|
||
|
// Find render targets for each shadow request of the requested size.
|
||
|
// Render the associated spans into the render targets. Something like the following:
|
||
|
void IPreprocessShadows()
|
||
|
{
|
||
|
|
||
|
SetupShadowCastTextureStages - see below
|
||
|
|
||
|
for each shadowCaster.fSpans
|
||
|
{
|
||
|
render shadowcaster.fSpans[i] to rendertarget
|
||
|
|
||
|
shadowCaster.fSpans[i]->SetShadowBit(shadowCaster.fIndex); //index set in CreateShadowSlave
|
||
|
}
|
||
|
|
||
|
Blur rendertarget (optional);
|
||
|
|
||
|
// Must ensure we have an alpha border of 255 (for clamping the effect)
|
||
|
//SetBorderTo255(); we don't have to do this if we can set the viewport
|
||
|
// to leave a border and compensate the fov so LightToNDC and LightToTexture match up.
|
||
|
}
|
||
|
|
||
|
// After doing the usual render for a span (all passes), we call the following.
|
||
|
// If the span accepts shadows, this will loop over all the shadows active this
|
||
|
// frame, and apply the ones that intersect this spans bounds. See below for details.
|
||
|
void IRenderSpanShadows();
|
||
|
|
||
|
// At EndRender(), we need to clear our list of shadow slaves. They are only valid for one frame.
|
||
|
void IClearShadowSlaves();
|
||
|
|
||
|
// We don't have the depth resolution to even think about self shadowing, so we just don't
|
||
|
// let a slave shadow any of the spans that were rendered into it.
|
||
|
hsBool AcceptsShadow(plSpan* span, plShadowSlave* slave)
|
||
|
{
|
||
|
return !span->IsShadowBitSet(slave->fIndex);
|
||
|
}
|
||
|
|
||
|
// Want artists to be able to just disable shadows for spans where they'll either
|
||
|
// look goofy, or won't contribute.
|
||
|
// Also, if we have less than 3 simultaneous textures, we want to skip anything with
|
||
|
// an alpha'd base layer, unless it's been overriden.
|
||
|
hsBool ReceivesShadows(plSpan* span, hsGMaterial* mat)
|
||
|
{
|
||
|
if( span.fProps & plSpan::kPropNoShadow )
|
||
|
return false;
|
||
|
|
||
|
if( span.Props & plSpan::kPropForceShadow )
|
||
|
return true;
|
||
|
|
||
|
if( (fMaxLayersAtOnce < 3) && (mat->GetLayer(0)->GetBlendFlags() & hsGMatState::kBlendAlpha) )
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
SetBorderTo255()
|
||
|
{
|
||
|
Set FB blend to Add;
|
||
|
|
||
|
render a texture the same size as the render target to the render target
|
||
|
as a single quad. Texture has black as color, 0 as alpha except the border
|
||
|
which is black with alpha=255.
|
||
|
}
|
||
|
|
||
|
Apply
|
||
|
{
|
||
|
render all passes of span
|
||
|
|
||
|
if( ShadowSlaveListNotEmpty() )
|
||
|
RenderSpanShadows
|
||
|
}
|
||
|
|
||
|
RenderSpanShadows
|
||
|
{
|
||
|
hsBool first = true;
|
||
|
if receivesShadows(span)
|
||
|
{
|
||
|
for each ShadowSlave
|
||
|
{
|
||
|
|
||
|
if AcceptsShadow(span, ShadowSlave) && (ShadowSlave->fIsect->Test(span.fBounds) != kVolumeCulled)
|
||
|
{
|
||
|
if( first )
|
||
|
{
|
||
|
SetupShadowRcvTextureStages();
|
||
|
first = false;
|
||
|
}
|
||
|
|
||
|
SetupShadowSlaveTextures()
|
||
|
|
||
|
render span
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetupShadowSlaveTextures()
|
||
|
{
|
||
|
// See below
|
||
|
}
|
||
|
|
||
|
TRANSFORMS
|
||
|
==========
|
||
|
|
||
|
Summary -
|
||
|
ShadowSlave.W2Light - world space to space of shadow slave
|
||
|
ShadowSlave.Light2W - unused
|
||
|
|
||
|
ShadowSlave.LightToNDC - normal projection matrix, maps to
|
||
|
[-1,1], [-1,1], [0,1] (after divide)
|
||
|
AND
|
||
|
has fov decreased slightly to compensate for the viewport being
|
||
|
brought down to preserve the border
|
||
|
|
||
|
ShadowSlave.LightToTexture - like LightToNDC, but maps to
|
||
|
[0.5 / width, 1 - 0.5/width], [0.5/height, 1 - 0.5/height, [0,1]
|
||
|
fov NOT brought down for border
|
||
|
|
||
|
ShadowSlave.CameraToTexture = ShadowSlave.LightToTexture * ShadowSlave.W2Light * pipe->GetCameraToWorld();
|
||
|
|
||
|
ShadowSlave.CasterLUTXfm - see below
|
||
|
|
||
|
ShadowSlave.ViewPort = {1, 1, width-2, height-2, 0, 1}
|
||
|
|
||
|
To Compensate FOV for border
|
||
|
{
|
||
|
if( perspective ) // spots and omnis
|
||
|
{
|
||
|
delX = fovX / (txtWidth/2);
|
||
|
delY = fovY / (txtHeight/2);
|
||
|
fovX -= delX;
|
||
|
fovY -= delY;
|
||
|
}
|
||
|
else // directional
|
||
|
{
|
||
|
delX = width / (txtWidth/2);
|
||
|
delY = height / (txtHeight/2);
|
||
|
|
||
|
minX += delX;
|
||
|
minY += delY;
|
||
|
maxX -= delX;
|
||
|
maxY -= delY;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
To Render ShadowCaster
|
||
|
{
|
||
|
render transform to ShadowSlave.W2Light * ShadowCaster.L2W
|
||
|
|
||
|
projection transform to ShadowSlave.LightToNDC
|
||
|
|
||
|
viewPort to ShadowSlave.ViewPort
|
||
|
|
||
|
Stage0 -
|
||
|
UVWSrc = CameraSpacePos
|
||
|
|
||
|
UVWXfm = ShadowSlave.CasterLUTXfm * ShadowSlave.W2L * CameraToWorld
|
||
|
|
||
|
Texture = U_LUT
|
||
|
|
||
|
Stage1 -
|
||
|
Disable
|
||
|
}
|
||
|
|
||
|
ShadowSlave.LUTXfm
|
||
|
{
|
||
|
// Map 0 => (closest = CasterBnd.Closest), 1 => (CasterBnd.Closest + FalloffDist = farthest)
|
||
|
0.0, 0.0, 1/(farthest - closest), -closest / (farthest - closest),
|
||
|
0.0, 0.0, 0.0, 0.0,
|
||
|
0.0, 0.0, 0.0, 0.0,
|
||
|
0.0, 0.0, 0.0, 0.0,
|
||
|
|
||
|
// FOR CASTER ONLY
|
||
|
// Can put bias here if needed (probably) by adding small NEGATIVE
|
||
|
// bias to ShadowSlave.LUTXfm.fMap[0][3]. Bias magnitude would probably be at
|
||
|
// least 0.5f/256.f to compensate for quantization.
|
||
|
}
|
||
|
|
||
|
To Project onto Shadow Receiver // SetupShadowSlaveTexture
|
||
|
{
|
||
|
render transform = current;
|
||
|
project transform = current;
|
||
|
viewport = current;
|
||
|
|
||
|
Stage0 -
|
||
|
UVWSrc = CameraSpacePos
|
||
|
|
||
|
UVWXfm = ShadowSlave.LightToTexture * ShadowSlave.W2L * CameraToWorld
|
||
|
|
||
|
Texture = ShadowMap
|
||
|
|
||
|
Stage1 -
|
||
|
UVWSrc = CameraSpacePos
|
||
|
|
||
|
UVWXfm = ShadowSlave.RcvLUTXfm * ShadowSlave.W2L * CameraToWorld
|
||
|
|
||
|
Texture = U_LUT
|
||
|
|
||
|
[ // Optional for when have > 2 TMUs AND base texture is alpha
|
||
|
Stage2 -
|
||
|
Process base texture normally normally
|
||
|
]
|
||
|
Stage2/3
|
||
|
No texture - setup as in ShadowNotes.h
|
||
|
}
|
||
|
|
||
|
ShadowSlave.Offset
|
||
|
{
|
||
|
Offset =
|
||
|
{
|
||
|
0.5, 0.0, 0.0, 0.5 + 0.5 * ShadowSlave.Width,
|
||
|
0.0, 0.5, 0.0, 0.5 + 0.5 * ShadowSlave.Height,
|
||
|
0.0, 0.0, 1.0, 0.0,
|
||
|
0.0, 0.0, 0.0, 1.0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif // Notes
|