//========= Copyright Valve Corporation, All rights reserved. ============// // // Functionality to render a glowing outline around client renderable objects. // //=============================================================================== #include "cbase.h" #include "glow_outline_effect.h" #include "model_types.h" #include "shaderapi/ishaderapi.h" #include "materialsystem/imaterialvar.h" #include "materialsystem/itexture.h" #include "view_shared.h" #include "viewpostprocess.h" #define FULL_FRAME_TEXTURE "_rt_FullFrameFB" #ifdef GLOWS_ENABLE ConVar glow_outline_effect_enable( "glow_outline_effect_enable", "1", FCVAR_ARCHIVE, "Enable entity outline glow effects." ); ConVar glow_outline_effect_width( "glow_outline_width", "10.0f", FCVAR_CHEAT, "Width of glow outline effect in screen space." ); extern bool g_bDumpRenderTargets; // in viewpostprocess.cpp CGlowObjectManager g_GlowObjectManager; struct ShaderStencilState_t { bool m_bEnable; StencilOperation_t m_FailOp; StencilOperation_t m_ZFailOp; StencilOperation_t m_PassOp; StencilComparisonFunction_t m_CompareFunc; int m_nReferenceValue; uint32 m_nTestMask; uint32 m_nWriteMask; ShaderStencilState_t() { m_bEnable = false; m_PassOp = m_FailOp = m_ZFailOp = STENCILOPERATION_KEEP; m_CompareFunc = STENCILCOMPARISONFUNCTION_ALWAYS; m_nReferenceValue = 0; m_nTestMask = m_nWriteMask = 0xFFFFFFFF; } void SetStencilState( CMatRenderContextPtr &pRenderContext ) { pRenderContext->SetStencilEnable( m_bEnable ); pRenderContext->SetStencilFailOperation( m_FailOp ); pRenderContext->SetStencilZFailOperation( m_ZFailOp ); pRenderContext->SetStencilPassOperation( m_PassOp ); pRenderContext->SetStencilCompareFunction( m_CompareFunc ); pRenderContext->SetStencilReferenceValue( m_nReferenceValue ); pRenderContext->SetStencilTestMask( m_nTestMask ); pRenderContext->SetStencilWriteMask( m_nWriteMask ); } }; void CGlowObjectManager::RenderGlowEffects( const CViewSetup *pSetup, int nSplitScreenSlot ) { if ( g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_0() ) { if ( glow_outline_effect_enable.GetBool() ) { CMatRenderContextPtr pRenderContext( materials ); int nX, nY, nWidth, nHeight; pRenderContext->GetViewport( nX, nY, nWidth, nHeight ); PIXEvent _pixEvent( pRenderContext, "EntityGlowEffects" ); ApplyEntityGlowEffects( pSetup, nSplitScreenSlot, pRenderContext, glow_outline_effect_width.GetFloat(), nX, nY, nWidth, nHeight ); } } } static void SetRenderTargetAndViewPort( ITexture *rt, int w, int h ) { CMatRenderContextPtr pRenderContext( materials ); pRenderContext->SetRenderTarget(rt); pRenderContext->Viewport(0,0,w,h); } void CGlowObjectManager::RenderGlowModels( const CViewSetup *pSetup, int nSplitScreenSlot, CMatRenderContextPtr &pRenderContext ) { //==========================================================================================// // This renders solid pixels with the correct coloring for each object that needs the glow. // // After this function returns, this image will then be blurred and added into the frame // // buffer with the objects stenciled out. // //==========================================================================================// pRenderContext->PushRenderTargetAndViewport(); // Save modulation color and blend Vector vOrigColor; render->GetColorModulation( vOrigColor.Base() ); float flOrigBlend = render->GetBlend(); // Get pointer to FullFrameFB ITexture *pRtFullFrame = NULL; pRtFullFrame = materials->FindTexture( FULL_FRAME_TEXTURE, TEXTURE_GROUP_RENDER_TARGET ); SetRenderTargetAndViewPort( pRtFullFrame, pSetup->width, pSetup->height ); pRenderContext->ClearColor3ub( 0, 0, 0 ); pRenderContext->ClearBuffers( true, false, false ); // Set override material for glow color IMaterial *pMatGlowColor = NULL; pMatGlowColor = materials->FindMaterial( "dev/glow_color", TEXTURE_GROUP_OTHER, true ); g_pStudioRender->ForcedMaterialOverride( pMatGlowColor ); ShaderStencilState_t stencilState; stencilState.m_bEnable = false; stencilState.m_nReferenceValue = 0; stencilState.m_nTestMask = 0xFF; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_ALWAYS; stencilState.m_PassOp = STENCILOPERATION_KEEP; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_KEEP; stencilState.SetStencilState( pRenderContext ); //==================// // Draw the objects // //==================// for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; render->SetBlend( m_GlowObjectDefinitions[i].m_flGlowAlpha ); Vector vGlowColor = m_GlowObjectDefinitions[i].m_vGlowColor * m_GlowObjectDefinitions[i].m_flGlowAlpha; render->SetColorModulation( &vGlowColor[0] ); // This only sets rgb, not alpha m_GlowObjectDefinitions[i].DrawModel(); } if ( g_bDumpRenderTargets ) { DumpTGAofRenderTarget( pSetup->width, pSetup->height, "GlowModels" ); } g_pStudioRender->ForcedMaterialOverride( NULL ); render->SetColorModulation( vOrigColor.Base() ); render->SetBlend( flOrigBlend ); ShaderStencilState_t stencilStateDisable; stencilStateDisable.m_bEnable = false; stencilStateDisable.SetStencilState( pRenderContext ); pRenderContext->PopRenderTargetAndViewport(); } void CGlowObjectManager::ApplyEntityGlowEffects( const CViewSetup *pSetup, int nSplitScreenSlot, CMatRenderContextPtr &pRenderContext, float flBloomScale, int x, int y, int w, int h ) { //=======================================================// // Render objects into stencil buffer // //=======================================================// // Set override shader to the same simple shader we use to render the glow models IMaterial *pMatGlowColor = materials->FindMaterial( "dev/glow_color", TEXTURE_GROUP_OTHER, true ); g_pStudioRender->ForcedMaterialOverride( pMatGlowColor ); ShaderStencilState_t stencilStateDisable; stencilStateDisable.m_bEnable = false; float flSavedBlend = render->GetBlend(); // Set alpha to 0 so we don't touch any color pixels render->SetBlend( 0.0f ); pRenderContext->OverrideDepthEnable( true, false ); int iNumGlowObjects = 0; for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded || m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded && m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { ShaderStencilState_t stencilState; stencilState.m_bEnable = true; stencilState.m_nReferenceValue = 1; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_ALWAYS; stencilState.m_PassOp = STENCILOPERATION_REPLACE; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_REPLACE; stencilState.SetStencilState( pRenderContext ); m_GlowObjectDefinitions[i].DrawModel(); } else if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded ) { ShaderStencilState_t stencilState; stencilState.m_bEnable = true; stencilState.m_nReferenceValue = 1; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_ALWAYS; stencilState.m_PassOp = STENCILOPERATION_KEEP; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_REPLACE; stencilState.SetStencilState( pRenderContext ); m_GlowObjectDefinitions[i].DrawModel(); } else if ( m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { ShaderStencilState_t stencilState; stencilState.m_bEnable = true; stencilState.m_nReferenceValue = 2; stencilState.m_nTestMask = 0x1; stencilState.m_nWriteMask = 0x3; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_EQUAL; stencilState.m_PassOp = STENCILOPERATION_INCRSAT; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_REPLACE; stencilState.SetStencilState( pRenderContext ); m_GlowObjectDefinitions[i].DrawModel(); } } iNumGlowObjects++; } // Need to do a 2nd pass to warm stencil for objects which are rendered only when occluded for ( int i = 0; i < m_GlowObjectDefinitions.Count(); ++ i ) { if ( m_GlowObjectDefinitions[i].IsUnused() || !m_GlowObjectDefinitions[i].ShouldDraw( nSplitScreenSlot ) ) continue; if ( m_GlowObjectDefinitions[i].m_bRenderWhenOccluded && !m_GlowObjectDefinitions[i].m_bRenderWhenUnoccluded ) { ShaderStencilState_t stencilState; stencilState.m_bEnable = true; stencilState.m_nReferenceValue = 2; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_ALWAYS; stencilState.m_PassOp = STENCILOPERATION_REPLACE; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_KEEP; stencilState.SetStencilState( pRenderContext ); m_GlowObjectDefinitions[i].DrawModel(); } } pRenderContext->OverrideDepthEnable( false, false ); render->SetBlend( flSavedBlend ); stencilStateDisable.SetStencilState( pRenderContext ); g_pStudioRender->ForcedMaterialOverride( NULL ); // If there aren't any objects to glow, don't do all this other stuff // this fixes a bug where if there are glow objects in the list, but none of them are glowing, // the whole screen blooms. if ( iNumGlowObjects <= 0 ) return; //============================================= // Render the glow colors to _rt_FullFrameFB //============================================= { PIXEvent pixEvent( pRenderContext, "RenderGlowModels" ); RenderGlowModels( pSetup, nSplitScreenSlot, pRenderContext ); } // Get viewport int nSrcWidth = pSetup->width; int nSrcHeight = pSetup->height; int nViewportX, nViewportY, nViewportWidth, nViewportHeight; pRenderContext->GetViewport( nViewportX, nViewportY, nViewportWidth, nViewportHeight ); // Get material and texture pointers ITexture *pRtQuarterSize1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET ); { //=======================================================================================================// // At this point, pRtQuarterSize0 is filled with the fully colored glow around everything as solid glowy // // blobs. Now we need to stencil out the original objects by only writing pixels that have no // // stencil bits set in the range we care about. // //=======================================================================================================// IMaterial *pMatHaloAddToScreen = materials->FindMaterial( "dev/halo_add_to_screen", TEXTURE_GROUP_OTHER, true ); // Do not fade the glows out at all (weight = 1.0) IMaterialVar *pDimVar = pMatHaloAddToScreen->FindVar( "$C0_X", NULL ); pDimVar->SetFloatValue( 1.0f ); // Set stencil state ShaderStencilState_t stencilState; stencilState.m_bEnable = true; stencilState.m_nWriteMask = 0x0; // We're not changing stencil stencilState.m_nTestMask = 0xFF; stencilState.m_nReferenceValue = 0x0; stencilState.m_CompareFunc = STENCILCOMPARISONFUNCTION_EQUAL; stencilState.m_PassOp = STENCILOPERATION_KEEP; stencilState.m_FailOp = STENCILOPERATION_KEEP; stencilState.m_ZFailOp = STENCILOPERATION_KEEP; stencilState.SetStencilState( pRenderContext ); // Draw quad pRenderContext->DrawScreenSpaceRectangle( pMatHaloAddToScreen, 0, 0, nViewportWidth, nViewportHeight, 0.0f, -0.5f, nSrcWidth / 4 - 1, nSrcHeight / 4 - 1, pRtQuarterSize1->GetActualWidth(), pRtQuarterSize1->GetActualHeight() ); stencilStateDisable.SetStencilState( pRenderContext ); } } void CGlowObjectManager::GlowObjectDefinition_t::DrawModel() { if ( m_hEntity.Get() ) { m_hEntity->DrawModel( STUDIO_RENDER ); C_BaseEntity *pAttachment = m_hEntity->FirstMoveChild(); while ( pAttachment != NULL ) { if ( !g_GlowObjectManager.HasGlowEffect( pAttachment ) && pAttachment->ShouldDraw() ) { pAttachment->DrawModel( STUDIO_RENDER ); } pAttachment = pAttachment->NextMovePeer(); } } } #endif // GLOWS_ENABLE