Torque3D/Engine/source/T3D/spotLight.cpp
Azaezel 2753f562e8 shadow caching
SPECIAL NOTE: highly suggest https://github.com/GarageGames/Torque3D/pull/1441 or a variation thereof to prevent debug spew and false-postives for occlusion results.

With significant research, development and prototyping assistance from both @andr3wmac (shaders and partial hook work), and @LuisAntonRebollo (additional culling)

System operates as follows:
1) materials are given an additional castDynamicShadows boolean entry. (Default at time of writing is true by request. Personal usage at time of writing defaults to false. value is default-initialized in materialDefinition.cpp. script/gui exposed)
2) lights are given a staticRefreshFreq and dynamicRefreshFreq (in milliseconds). script/gui exposed
3) materials are (effectively) sorted into dynamic and static shadowmap render lists based on flag. (see shadowMapPass.cpp)
4) initial shadowmaps are generated for each light and 'list'.
5) as each refreshFreq times out, the relevant shadowmap for a given light is refreshed.

Special notes:
dynamicRefreshFreq for all lights is set to a (script exposed) 8MS refresh timer.
StaticRefreshFreq for the lions share of lights defaults to 250 MS (1/4 of a second)
scattersky's embedded light, which is intended to operate in a mobile manner, defaults to 8
to reiterate, these are all customizable per-light via script/inspector gui in the case of alternate project needs.
2015-10-13 18:12:19 -05:00

215 lines
6.9 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 "T3D/spotLight.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "gfx/gfxDrawUtil.h"
IMPLEMENT_CO_NETOBJECT_V1( SpotLight );
ConsoleDocClass( SpotLight,
"@brief Lighting object which emits conical light in a direction.\n\n"
"SpotLight is one of the two types of lighting objects that can be added "
"to a Torque 3D level, the other being PointLight. Unlike directional or "
"point lights, the SpotLights emits lighting in a specific direction "
"within a cone. The distance of the cone is controlled by the SpotLight::range "
"variable.\n\n"
"@tsexample\n"
"// Declaration of a point light in script, or created by World Editor\n"
"new SpotLight(SampleSpotLight)\n"
"{\n"
" range = \"10\";\n"
" innerAngle = \"40\";\n"
" outerAngle = \"45\";\n"
" isEnabled = \"1\";\n"
" color = \"1 1 1 1\";\n"
" brightness = \"1\";\n"
" castShadows = \"0\";\n"
" priority = \"1\";\n"
" animate = \"1\";\n"
" animationPeriod = \"1\";\n"
" animationPhase = \"1\";\n"
" flareType = \"LightFlareExample0\";\n"
" flareScale = \"1\";\n"
" attenuationRatio = \"0 1 1\";\n"
" shadowType = \"Spot\";\n"
" texSize = \"512\";\n"
" overDarkFactor = \"2000 1000 500 100\";\n"
" shadowDistance = \"400\";\n"
" shadowSoftness = \"0.15\";\n"
" numSplits = \"1\";\n"
" logWeight = \"0.91\";\n"
" fadeStartDistance = \"0\";\n"
" lastSplitTerrainOnly = \"0\";\n"
" representedInLightmap = \"0\";\n"
" shadowDarkenColor = \"0 0 0 -1\";\n"
" includeLightmappedGeometryInShadow = \"0\";\n"
" position = \"-29.4362 -5.86289 5.58602\";\n"
" rotation = \"1 0 0 0\";\n"
"};\n"
"@endtsexample\n\n"
"@see LightBase\n\n"
"@see PointLight\n\n"
"@ingroup Lighting\n"
);
SpotLight::SpotLight()
: mRange( 10.0f ),
mInnerConeAngle( 40.0f ),
mOuterConeAngle( 45.0f )
{
// We set the type here to ensure the extended
// parameter validation works when setting fields.
mLight->setType( LightInfo::Spot );
}
SpotLight::~SpotLight()
{
}
void SpotLight::initPersistFields()
{
addGroup( "Light" );
addField( "range", TypeF32, Offset( mRange, SpotLight ) );
addField( "innerAngle", TypeF32, Offset( mInnerConeAngle, SpotLight ) );
addField( "outerAngle", TypeF32, Offset( mOuterConeAngle, SpotLight ) );
endGroup( "Light" );
// We do the parent fields at the end so that
// they show up that way in the inspector.
Parent::initPersistFields();
// Remove the scale field... it's already
// defined by the range and angle.
removeField( "scale" );
}
void SpotLight::_conformLights()
{
mLight->setTransform( getTransform() );
mRange = getMax( mRange, 0.05f );
mLight->setRange( mRange );
mLight->setColor( mColor );
mLight->setBrightness( mBrightness );
mLight->setCastShadows( mCastShadows );
mLight->setStaticRefreshFreq(mStaticRefreshFreq);
mLight->setDynamicRefreshFreq(mDynamicRefreshFreq);
mLight->setPriority( mPriority );
mOuterConeAngle = getMax( 0.01f, mOuterConeAngle );
mInnerConeAngle = getMin( mInnerConeAngle, mOuterConeAngle );
mLight->setInnerConeAngle( mInnerConeAngle );
mLight->setOuterConeAngle( mOuterConeAngle );
// Update the bounds and scale to fit our spotlight.
F32 radius = mRange * mSin( mDegToRad( mOuterConeAngle ) * 0.5f );
mObjBox.minExtents.set( -1, 0, -1 );
mObjBox.maxExtents.set( 1, 1, 1 );
mObjScale.set( radius, mRange, radius );
// Skip our transform... it just dirties mask bits.
Parent::setTransform( mObjToWorld );
}
U32 SpotLight::packUpdate(NetConnection *conn, U32 mask, BitStream *stream )
{
if ( stream->writeFlag( mask & UpdateMask ) )
{
stream->write( mRange );
stream->write( mInnerConeAngle );
stream->write( mOuterConeAngle );
}
return Parent::packUpdate( conn, mask, stream );
}
void SpotLight::unpackUpdate( NetConnection *conn, BitStream *stream )
{
if ( stream->readFlag() ) // UpdateMask
{
stream->read( &mRange );
stream->read( &mInnerConeAngle );
stream->read( &mOuterConeAngle );
}
Parent::unpackUpdate( conn, stream );
}
void SpotLight::setScale( const VectorF &scale )
{
// The y coord is the spotlight range.
mRange = getMax( scale.y, 0.05f );
// Use the average of the x and z to get a radius. This
// is the best method i've found to make the manipulation
// from the WorldEditor gizmo to feel right.
F32 radius = mClampF( ( scale.x + scale.z ) * 0.5f, 0.05f, mRange );
mOuterConeAngle = mRadToDeg( mAsin( radius / mRange ) ) * 2.0f;
// Make sure the inner angle is less than the outer.
//
// TODO: Maybe we should make the inner angle a scale
// and not an absolute angle?
//
mInnerConeAngle = getMin( mInnerConeAngle, mOuterConeAngle );
// We changed a bunch of our settings
// so notify the client.
setMaskBits( UpdateMask );
// Let the parent do the final scale.
Parent::setScale( VectorF( radius, mRange, radius ) );
}
void SpotLight::_renderViz( SceneRenderState *state )
{
GFXDrawUtil *draw = GFX->getDrawUtil();
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
desc.setCullMode( GFXCullNone );
desc.setBlend( true );
// Base the color on the light color.
ColorI color( mColor );
color.alpha = 16;
F32 radius = mRange * mSin( mDegToRad( mOuterConeAngle * 0.5f ) );
draw->drawCone( desc,
getPosition() + ( getTransform().getForwardVector() * mRange ),
getPosition(),
radius,
color );
}