Torque3D/Engine/source/lighting/common/projectedShadow.cpp

594 lines
19 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "lighting/common/projectedShadow.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxTextureManager.h"
#include "gfx/bitmap/gBitmap.h"
#include "gfx/gfxDebugEvent.h"
#include "math/mathUtils.h"
#include "lighting/lightInfo.h"
#include "lighting/lightingInterfaces.h"
#include "T3D/shapeBase.h"
#include "scene/sceneManager.h"
#include "lighting/lightManager.h"
#include "ts/tsMesh.h"
#include "T3D/decal/decalManager.h"
#include "T3D/decal/decalInstance.h"
#include "renderInstance/renderPassManager.h"
#include "renderInstance/renderMeshMgr.h"
#include "gfx/gfxTransformSaver.h"
#include "materials/customMaterialDefinition.h"
#include "materials/materialFeatureTypes.h"
#include "console/console.h"
#include "postFx/postEffect.h"
#ifdef TORQUE_BASIC_LIGHTING
#include "lighting/basic/basicLightManager.h"
#else
#include "lighting/advanced/advancedLightManager.h"
#endif
#include "lighting/shadowMap/shadowMatHook.h"
#include "materials/materialManager.h"
#include "lighting/shadowMap/lightShadowMap.h"
SimObjectPtr<RenderPassManager> ProjectedShadow::smRenderPass = NULL;
SimObjectPtr<PostEffect> ProjectedShadow::smShadowFilter = NULL;
F32 ProjectedShadow::smDepthAdjust = 10.0f;
F32 ProjectedShadow::smFadeStartPixelSize = 200.0f;
F32 ProjectedShadow::smFadeEndPixelSize = 35.0f;
GFX_ImplementTextureProfile( BLProjectedShadowProfile,
GFXTextureProfile::DiffuseMap,
GFXTextureProfile::PreserveSize |
GFXTextureProfile::RenderTarget |
GFXTextureProfile::Pooled,
GFXTextureProfile::NONE );
GFX_ImplementTextureProfile( BLProjectedShadowZProfile,
GFXTextureProfile::DiffuseMap,
GFXTextureProfile::PreserveSize |
GFXTextureProfile::ZTarget |
GFXTextureProfile::Pooled,
GFXTextureProfile::NONE );
ProjectedShadow::ProjectedShadow( SceneObject *object )
{
mParentObject = object;
mShapeBase = dynamic_cast<ShapeBase*>( object );
mRadius = 0;
mLastRenderTime = 0;
mUpdateTexture = false;
mShadowLength = 10.0f;
mDecalData = new DecalData;
mDecalData->skipVertexNormals = true;
mDecalInstance = NULL;
mLastLightDir.set( 0, 0, 0 );
mLastObjectPosition.set( object->getRenderPosition() );
mLastObjectScale.set( object->getScale() );
CustomMaterial *customMat = NULL;
Sim::findObject( "BL_ProjectedShadowMaterial", customMat );
if ( customMat )
{
mDecalData->material = customMat;
mDecalData->matInst = customMat->createMatInstance();
}
else
mDecalData->matInst = MATMGR->createMatInstance( "WarningMaterial" );
mDecalData->matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTT>() );
mCasterPositionSC = NULL;
mShadowLengthSC = NULL;
}
ProjectedShadow::~ProjectedShadow()
{
if ( mDecalInstance )
gDecalManager->removeDecal( mDecalInstance );
delete mDecalData;
mShadowTexture = NULL;
mRenderTarget = NULL;
}
bool ProjectedShadow::shouldRender( const SceneRenderState *state )
{
// Don't render if our object has been removed from the
// scene graph.
if( !mParentObject->getSceneManager() )
return false;
// Don't render if the ShapeBase
// object's fade value is greater
// than the visibility epsilon.
bool shapeFade = mShapeBase && mShapeBase->getFadeVal() < TSMesh::VISIBILITY_EPSILON;
// Get the shapebase datablock if we have one.
ShapeBaseData *data = NULL;
if ( mShapeBase )
data = static_cast<ShapeBaseData*>( mShapeBase->getDataBlock() );
// Also don't render if
// the camera distance is greater
// than the shadow length.
if ( shapeFade || !mDecalData ||
( mDecalInstance &&
mDecalInstance->calcPixelSize( state->getViewport().extent.y, state->getCameraPosition(), state->getWorldToScreenScale().y ) < mDecalInstance->mDataBlock->fadeEndPixelSize ) )
{
// Release our shadow texture
// so that others can grab it out
// of the pool.
mShadowTexture = NULL;
return false;
}
return true;
}
bool ProjectedShadow::_updateDecal( const SceneRenderState *state )
{
PROFILE_SCOPE( ProjectedShadow_UpdateDecal );
if ( !LIGHTMGR )
return false;
// Get the position of the decal first.
const Box3F &objBox = mParentObject->getObjBox();
const Point3F boxCenter = objBox.getCenter();
Point3F decalPos = boxCenter;
const MatrixF &renderTransform = mParentObject->getRenderTransform();
{
// Set up the decal position.
// We use the object space box center
// multiplied by the render transform
// of the object to ensure we benefit
// from interpolation.
MatrixF t( renderTransform );
t.setColumn(2,Point3F::UnitZ);
t.mulP( decalPos );
}
if ( mDecalInstance )
{
mDecalInstance->mPosition = decalPos;
if ( !shouldRender( state ) )
return false;
}
// Get the sunlight for the shadow projection.
// We want the LightManager to return NULL if it can't
// get the "real" sun, so we specify false for the useDefault parameter.
LightInfo *lights[4] = {0};
LightQuery query;
query.init( mParentObject->getWorldSphere() );
query.getLights( lights, 4 );
Point3F pos = renderTransform.getPosition();
Point3F lightDir( 0, 0, 0 );
Point3F tmp( 0, 0, 0 );
F32 weight = 0;
F32 range = 0;
U32 lightCount = 0;
F32 dist = 0;
F32 fade = 0;
for ( U32 i = 0; i < 4; i++ )
{
// If we got a NULL light,
// we're at the end of the list.
if ( !lights[i] )
break;
if ( !lights[i]->getCastShadows() )
continue;
if ( lights[i]->getType() != LightInfo::Point )
tmp = lights[i]->getDirection();
else
tmp = pos - lights[i]->getPosition();
range = lights[i]->getRange().x;
dist = ( (tmp.lenSquared()) / ((range * range) * 0.5f));
weight = mClampF( 1.0f - ( tmp.lenSquared() / (range * range)), 0.00001f, 1.0f );
if ( lights[i]->getType() == LightInfo::Vector )
fade = getMax( fade, 1.0f );
else
fade = getMax( fade, mClampF( 1.0f - dist, 0.00001f, 1.0f ) );
lightDir += tmp * weight;
lightCount++;
}
if (mShapeBase)
fade *= mShapeBase->getFadeVal();
lightDir.normalize();
// No light... no shadow.
if ( !lights[0] )
return false;
// Has the light direction
// changed since last update?
bool lightDirChanged = !mLastLightDir.equal( lightDir );
// Has the parent object moved
// or scaled since the last update?
bool hasMoved = !mLastObjectPosition.equal( mParentObject->getRenderPosition() );
bool hasScaled = !mLastObjectScale.equal( mParentObject->getScale() );
// Set the last light direction
// to the current light direction.
mLastLightDir = lightDir;
mLastObjectPosition = mParentObject->getRenderPosition();
mLastObjectScale = mParentObject->getScale();
// Temps used to generate
// tangent vector for DecalInstance below.
VectorF right( 0, 0, 0 );
VectorF fwd( 0, 0, 0 );
VectorF tmpFwd( 0, 0, 0 );
U32 idx = lightDir.getLeastComponentIndex();
tmpFwd[idx] = 1.0f;
right = mCross( tmpFwd, lightDir );
fwd = mCross( lightDir, right );
right = mCross( fwd, lightDir );
right.normalize();
// Set up the world to light space
// matrix, along with proper position
// and rotation to be used as the world
// matrix for the render to texture later on.
static MatrixF sRotMat(EulerF( 0.0f, -(M_PI_F/2.0f), 0.0f));
mWorldToLight.identity();
MathUtils::getMatrixFromForwardVector( lightDir, &mWorldToLight );
mWorldToLight.setPosition( ( pos + boxCenter ) - ( ( (mRadius * smDepthAdjust) + 0.001f ) * lightDir ) );
mWorldToLight.mul( sRotMat );
mWorldToLight.inverse();
// Get the shapebase datablock if we have one.
ShapeBaseData *data = NULL;
if ( mShapeBase )
data = static_cast<ShapeBaseData*>( mShapeBase->getDataBlock() );
// We use the object box's extents multiplied
// by the object's scale divided by 2 for the radius
// because the object's worldsphere radius is not
// rotationally invariant.
mRadius = (objBox.getExtents() * mParentObject->getScale()).len() * 0.5f;
if ( data )
mRadius *= data->shadowSphereAdjust;
// Create the decal if we don't have one yet.
if ( !mDecalInstance )
mDecalInstance = gDecalManager->addDecal( decalPos,
lightDir,
right,
mDecalData,
1.0f,
0,
PermanentDecal | ClipDecal | CustomDecal );
if ( !mDecalInstance )
return false;
mDecalInstance->mVisibility = fade;
// Setup decal parameters.
mDecalInstance->mSize = mRadius * 2.0f;
mDecalInstance->mNormal = -lightDir;
mDecalInstance->mTangent = -right;
mDecalInstance->mRotAroundNormal = 0;
mDecalInstance->mPosition = decalPos;
mDecalInstance->mDataBlock = mDecalData;
// If the position of the world
// space box center is the same
// as the decal's position, and
// the light direction has not
// changed, we don't need to clip.
bool shouldClip = lightDirChanged || hasMoved || hasScaled;
// Now, check and see if the object is visible.
const Frustum &frust = state->getCullingFrustum();
if ( frust.isCulled( SphereF( mDecalInstance->mPosition, mDecalInstance->mSize * mDecalInstance->mSize ) ) && !shouldClip )
return false;
F32 shadowLen = 10.0f;
if ( data )
shadowLen = data->shadowProjectionDistance;
const Point3F &boxExtents = objBox.getExtents();
mShadowLength = shadowLen * mParentObject->getScale().z;
// Set up clip depth, and box half
// offset for decal clipping.
Point2F clipParams( mShadowLength, (boxExtents.x + boxExtents.y) * 0.25f );
bool render = false;
bool clipSucceeded = true;
// Clip!
if ( shouldClip )
{
clipSucceeded = gDecalManager->clipDecal( mDecalInstance,
NULL,
&clipParams );
}
// If the clip failed,
// we'll return false in
// order to keep from
// unnecessarily rendering
// into the texture. If
// there was no reason to clip
// on this update, we'll assume we
// should update the texture.
render = clipSucceeded;
// Tell the DecalManager we've changed this decal.
gDecalManager->notifyDecalModified( mDecalInstance );
return render;
}
void ProjectedShadow::_calcScore( const SceneRenderState *state )
{
if ( !mDecalInstance )
return;
F32 pixRadius = mDecalInstance->calcPixelSize( state->getViewport().extent.y, state->getCameraPosition(), state->getWorldToScreenScale().y );
F32 pct = pixRadius / mDecalInstance->mDataBlock->fadeStartPixelSize;
U32 msSinceLastRender = Platform::getVirtualMilliseconds() - getLastRenderTime();
ShapeBaseData *data = NULL;
if ( mShapeBase )
data = static_cast<ShapeBaseData*>( mShapeBase->getDataBlock() );
// For every 1s this shadow hasn't been
// updated we'll add 10 to the score.
F32 secs = mFloor( (F32)msSinceLastRender / 1000.0f );
mScore = pct + secs;
mClampF( mScore, 0.0f, 2000.0f );
}
void ProjectedShadow::update( const SceneRenderState *state )
{
mUpdateTexture = true;
// Set the decal lod settings.
mDecalData->fadeStartPixelSize = smFadeStartPixelSize;
mDecalData->fadeEndPixelSize = smFadeEndPixelSize;
// Update our decal before
// we render to texture.
// If it fails, something bad happened
// (no light to grab/failed clip) and we should return.
if ( !_updateDecal( state ) )
{
// Release our shadow texture
// so that others can grab it out
// of the pool.
mShadowTexture = NULL;
mUpdateTexture = false;
return;
}
_calcScore( state );
if ( !mCasterPositionSC || !mCasterPositionSC->isValid() )
mCasterPositionSC = mDecalData->matInst->getMaterialParameterHandle( "$shadowCasterPosition" );
if ( !mShadowLengthSC || !mShadowLengthSC->isValid() )
mShadowLengthSC = mDecalData->matInst->getMaterialParameterHandle( "$shadowLength" );
MaterialParameters *matParams = mDecalData->matInst->getMaterialParameters();
matParams->setSafe( mCasterPositionSC, mParentObject->getRenderPosition() );
matParams->setSafe( mShadowLengthSC, mShadowLength / 4.0f );
}
void ProjectedShadow::render( F32 camDist, const TSRenderState &rdata )
{
if ( !mUpdateTexture )
return;
// Do the render to texture,
// DecalManager handles rendering
// the shadow onto the world.
_renderToTexture( camDist, rdata );
}
BaseMatInstance* ProjectedShadow::_getShadowMaterial( BaseMatInstance *inMat )
{
// See if we have an existing material hook.
ShadowMaterialHook *hook = static_cast<ShadowMaterialHook*>( inMat->getHook( ShadowMaterialHook::Type ) );
if ( !hook )
{
// Create a hook and initialize it using the incoming material.
hook = new ShadowMaterialHook;
hook->init( inMat );
inMat->addHook( hook );
}
return hook->getShadowMat( ShadowType_Spot );
}
void ProjectedShadow::_renderToTexture( F32 camDist, const TSRenderState &rdata )
{
PROFILE_SCOPE( ProjectedShadow_RenderToTexture );
GFXDEBUGEVENT_SCOPE( ProjectedShadow_RenderToTexture, ColorI( 255, 0, 0 ) );
RenderPassManager *renderPass = _getRenderPass();
if ( !renderPass )
return;
GFXTransformSaver saver;
// NOTE: GFXTransformSaver does not save/restore the frustum
// so we must save it here before we modify it.
F32 l, r, b, t, n, f;
bool ortho;
GFX->getFrustum( &l, &r, &b, &t, &n, &f, &ortho );
// Set the orthographic projection
// matrix up, to be based on the radius
// generated based on our shape.
GFX->setOrtho( -mRadius, mRadius, -mRadius, mRadius, 0.001f, (mRadius * 2) * smDepthAdjust, true );
// Set the world to light space
// matrix set up in shouldRender().
GFX->setWorldMatrix( mWorldToLight );
// Get the shapebase datablock if we have one.
ShapeBaseData *data = NULL;
if ( mShapeBase )
data = static_cast<ShapeBaseData*>( mShapeBase->getDataBlock() );
// Init or update the shadow texture size.
if ( mShadowTexture.isNull() || ( data && data->shadowSize != mShadowTexture.getWidth() ) )
{
U32 texSize = getNextPow2( data ? data->shadowSize : 256 * LightShadowMap::smShadowTexScalar );
mShadowTexture.set( texSize, texSize, GFXFormatR8G8B8A8, &PostFxTargetProfile, "BLShadow" );
}
GFX->pushActiveRenderTarget();
if ( !mRenderTarget )
mRenderTarget = GFX->allocRenderToTextureTarget();
mRenderTarget->attachTexture( GFXTextureTarget::DepthStencil, _getDepthTarget( mShadowTexture->getWidth(), mShadowTexture->getHeight() ) );
mRenderTarget->attachTexture( GFXTextureTarget::Color0, mShadowTexture );
GFX->setActiveRenderTarget( mRenderTarget );
GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, ColorI( 0, 0, 0, 0 ), 1.0f, 0 );
const SceneRenderState *diffuseState = rdata.getSceneState();
SceneManager *sceneManager = diffuseState->getSceneManager();
SceneRenderState baseState
(
sceneManager,
SPT_Shadow,
SceneCameraState::fromGFXWithViewport( diffuseState->getViewport() ),
renderPass
);
baseState.getMaterialDelegate().bind( &ProjectedShadow::_getShadowMaterial );
baseState.setDiffuseCameraTransform( diffuseState->getCameraTransform() );
baseState.setWorldToScreenScale( diffuseState->getWorldToScreenScale() );
baseState.getCullingState().disableZoneCulling( true );
mParentObject->prepRenderImage( &baseState );
renderPass->renderPass( &baseState );
// Delete the SceneRenderState we allocated.
mRenderTarget->resolve();
GFX->popActiveRenderTarget();
// If we're close enough then filter the shadow.
#ifdef TORQUE_BASIC_LIGHTING
if (camDist < BasicLightManager::getShadowFilterDistance())
#else
if (camDist < AdvancedLightManager::getShadowFilterDistance())
#endif
{
if ( !smShadowFilter )
{
PostEffect *filter = NULL;
if ( !Sim::findObject( "BL_ShadowFilterPostFx", filter ) )
Con::errorf( "ProjectedShadow::_renderToTexture() - 'BL_ShadowFilterPostFx' not found!" );
smShadowFilter = filter;
}
if ( smShadowFilter )
smShadowFilter->process( NULL, mShadowTexture );
}
// Restore frustum
if (!ortho)
GFX->setFrustum(l, r, b, t, n, f);
else
GFX->setOrtho(l, r, b, t, n, f);
// Set the last render time.
mLastRenderTime = Platform::getVirtualMilliseconds();
// HACK: Will remove in future release!
mDecalInstance->mCustomTex = &mShadowTexture;
}
RenderPassManager* ProjectedShadow::_getRenderPass()
{
if ( smRenderPass.isNull() )
{
SimObject* renderPass = NULL;
if ( !Sim::findObject( "BL_ProjectedShadowRPM", renderPass ) )
Con::errorf( "ProjectedShadow::init() - 'BL_ProjectedShadowRPM' not initialized" );
else
smRenderPass = dynamic_cast<RenderPassManager*>(renderPass);
}
return smRenderPass;
}
GFXTextureObject* ProjectedShadow::_getDepthTarget( U32 width, U32 height )
{
// Get a depth texture target from the pooled profile
// which is returned as a temporary.
GFXTexHandle depthTex( width, height, GFXFormatD24S8, &BLProjectedShadowZProfile,
"ProjectedShadow::_getDepthTarget()" );
return depthTex;
}