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.
392 lines
12 KiB
392 lines
12 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// r_studio.cpp: routines for setting up to draw 3DStudio models |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
|
|
#include "studio.h" |
|
#include "studiorender.h" |
|
#include "studiorendercontext.h" |
|
#include "materialsystem/imaterial.h" |
|
#include "materialsystem/imaterialvar.h" |
|
#include "tier0/vprof.h" |
|
#include "tier3/tier3.h" |
|
#include "datacache/imdlcache.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//----------------------------------------------------------------------------- |
|
// Figures out what kind of lighting we're gonna want |
|
//----------------------------------------------------------------------------- |
|
FORCEINLINE StudioModelLighting_t CStudioRender::R_StudioComputeLighting( IMaterial *pMaterial, int materialFlags, ColorMeshInfo_t *pColorMeshes ) |
|
{ |
|
// Here, we only do software lighting when the following conditions are met. |
|
// 1) The material is vertex lit and we don't have hardware lighting |
|
// 2) We're drawing an eyeball |
|
// 3) We're drawing mouth-lit stuff |
|
|
|
// FIXME: When we move software lighting into the material system, only need to |
|
// test if it's vertex lit |
|
|
|
Assert( pMaterial ); |
|
bool doMouthLighting = materialFlags && (m_pStudioHdr->nummouths >= 1); |
|
|
|
if ( IsX360() ) |
|
{ |
|
// 360 does not do software lighting |
|
return doMouthLighting ? LIGHTING_MOUTH : LIGHTING_HARDWARE; |
|
} |
|
|
|
bool doSoftwareLighting = doMouthLighting || |
|
(pMaterial->IsVertexLit() && pMaterial->NeedsSoftwareLighting() ); |
|
|
|
if ( !m_pRC->m_Config.m_bSupportsVertexAndPixelShaders ) |
|
{ |
|
if ( !doSoftwareLighting && pColorMeshes ) |
|
{ |
|
pMaterial->SetUseFixedFunctionBakedLighting( true ); |
|
} |
|
else |
|
{ |
|
doSoftwareLighting = true; |
|
pMaterial->SetUseFixedFunctionBakedLighting( false ); |
|
} |
|
} |
|
|
|
StudioModelLighting_t lighting = LIGHTING_HARDWARE; |
|
if ( doMouthLighting ) |
|
lighting = LIGHTING_MOUTH; |
|
else if ( doSoftwareLighting ) |
|
lighting = LIGHTING_SOFTWARE; |
|
|
|
return lighting; |
|
} |
|
|
|
|
|
IMaterial* CStudioRender::R_StudioSetupSkinAndLighting( IMatRenderContext *pRenderContext, int index, IMaterial **ppMaterials, int materialFlags, |
|
void /*IClientRenderable*/ *pClientRenderable, ColorMeshInfo_t *pColorMeshes, StudioModelLighting_t &lighting ) |
|
{ |
|
VPROF( "R_StudioSetupSkin" ); |
|
IMaterial *pMaterial = NULL; |
|
bool bCheckForConVarDrawTranslucentSubModels = false; |
|
if( m_pRC->m_Config.bWireframe && !m_pRC->m_pForcedMaterial ) |
|
{ |
|
if ( m_pRC->m_Config.bDrawZBufferedWireframe ) |
|
pMaterial = m_pMaterialMRMWireframeZBuffer; |
|
else |
|
pMaterial = m_pMaterialMRMWireframe; |
|
} |
|
else if( m_pRC->m_Config.bShowEnvCubemapOnly ) |
|
{ |
|
pMaterial = m_pMaterialModelEnvCubemap; |
|
} |
|
else |
|
{ |
|
if ( !m_pRC->m_pForcedMaterial && ( m_pRC->m_nForcedMaterialType != OVERRIDE_DEPTH_WRITE && m_pRC->m_nForcedMaterialType != OVERRIDE_SSAO_DEPTH_WRITE ) ) |
|
{ |
|
pMaterial = ppMaterials[index]; |
|
if ( !pMaterial ) |
|
{ |
|
Assert( 0 ); |
|
return 0; |
|
} |
|
} |
|
else |
|
{ |
|
materialFlags = 0; |
|
pMaterial = m_pRC->m_pForcedMaterial; |
|
if (m_pRC->m_nForcedMaterialType == OVERRIDE_BUILD_SHADOWS) |
|
{ |
|
// Connect the original material up to the shadow building material |
|
// Also bind the original material so its proxies are in the correct state |
|
static unsigned int translucentCache = 0; |
|
IMaterialVar* pOriginalMaterialVar = pMaterial->FindVarFast( "$translucent_material", &translucentCache ); |
|
Assert( pOriginalMaterialVar ); |
|
IMaterial *pOriginalMaterial = ppMaterials[index]; |
|
if ( pOriginalMaterial ) |
|
{ |
|
// Disable any alpha modulation on the original material that was left over from when it was last rendered |
|
pOriginalMaterial->AlphaModulate( 1.0f ); |
|
pRenderContext->Bind( pOriginalMaterial, pClientRenderable ); |
|
if ( pOriginalMaterial->IsTranslucent() || pOriginalMaterial->IsAlphaTested() ) |
|
{ |
|
if ( pOriginalMaterialVar ) |
|
pOriginalMaterialVar->SetMaterialValue( pOriginalMaterial ); |
|
} |
|
else |
|
{ |
|
if ( pOriginalMaterialVar ) |
|
pOriginalMaterialVar->SetMaterialValue( NULL ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( pOriginalMaterialVar ) |
|
pOriginalMaterialVar->SetMaterialValue( NULL ); |
|
} |
|
} |
|
else if ( m_pRC->m_nForcedMaterialType == OVERRIDE_DEPTH_WRITE || m_pRC->m_nForcedMaterialType == OVERRIDE_SSAO_DEPTH_WRITE ) |
|
{ |
|
// Disable any alpha modulation on the original material that was left over from when it was last rendered |
|
ppMaterials[index]->AlphaModulate( 1.0f ); |
|
|
|
// Bail if the material is still considered translucent after setting the AlphaModulate to 1.0 |
|
if ( ppMaterials[index]->IsTranslucent() ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
static unsigned int originalTextureVarCache = 0; |
|
IMaterialVar *pOriginalTextureVar = ppMaterials[index]->FindVarFast( "$basetexture", &originalTextureVarCache ); |
|
|
|
// Select proper override material |
|
int nAlphaTest = (int) ( ppMaterials[index]->IsAlphaTested() && pOriginalTextureVar->IsTexture() ); // alpha tested base texture |
|
int nNoCull = (int) ppMaterials[index]->IsTwoSided(); |
|
if ( m_pRC->m_nForcedMaterialType == OVERRIDE_SSAO_DEPTH_WRITE ) |
|
{ |
|
pMaterial = m_pSSAODepthWrite[nAlphaTest][nNoCull]; |
|
} |
|
else |
|
{ |
|
pMaterial = m_pDepthWrite[nAlphaTest][nNoCull]; |
|
} |
|
|
|
// If we're alpha tested, we should set up the texture variables from the original material |
|
if ( nAlphaTest != 0 ) |
|
{ |
|
static unsigned int originalTextureFrameVarCache = 0; |
|
IMaterialVar *pOriginalTextureFrameVar = ppMaterials[index]->FindVarFast( "$frame", &originalTextureFrameVarCache ); |
|
static unsigned int originalAlphaRefCache = 0; |
|
IMaterialVar *pOriginalAlphaRefVar = ppMaterials[index]->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache ); |
|
|
|
static unsigned int textureVarCache = 0; |
|
IMaterialVar *pTextureVar = pMaterial->FindVarFast( "$basetexture", &textureVarCache ); |
|
static unsigned int textureFrameVarCache = 0; |
|
IMaterialVar *pTextureFrameVar = pMaterial->FindVarFast( "$frame", &textureFrameVarCache ); |
|
static unsigned int alphaRefCache = 0; |
|
IMaterialVar *pAlphaRefVar = pMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache ); |
|
|
|
if ( pOriginalTextureVar->IsTexture() ) // If $basetexture is defined |
|
{ |
|
if( pTextureVar && pOriginalTextureVar ) |
|
{ |
|
pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() ); |
|
} |
|
|
|
if( pTextureFrameVar && pOriginalTextureFrameVar ) |
|
{ |
|
pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() ); |
|
} |
|
|
|
if( pAlphaRefVar && pOriginalAlphaRefVar ) |
|
{ |
|
pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Set this bool to check after the bind below |
|
bCheckForConVarDrawTranslucentSubModels = true; |
|
|
|
if ( m_pRC->m_nForcedMaterialType != OVERRIDE_DEPTH_WRITE && m_pRC->m_nForcedMaterialType != OVERRIDE_SSAO_DEPTH_WRITE) |
|
{ |
|
// Try to set the alpha based on the blend |
|
pMaterial->AlphaModulate( m_pRC->m_AlphaMod ); |
|
|
|
// Try to set the color based on the colormod |
|
pMaterial->ColorModulate( m_pRC->m_ColorMod[0], m_pRC->m_ColorMod[1], m_pRC->m_ColorMod[2] ); |
|
} |
|
} |
|
|
|
lighting = R_StudioComputeLighting( pMaterial, materialFlags, pColorMeshes ); |
|
if ( lighting == LIGHTING_MOUTH ) |
|
{ |
|
if ( !m_pRC->m_Config.bTeeth || !R_TeethAreVisible() ) |
|
return NULL; |
|
// skin it and light it, but only if we need to. |
|
if ( m_pRC->m_Config.m_bSupportsVertexAndPixelShaders ) |
|
{ |
|
R_MouthSetupVertexShader( pMaterial ); |
|
} |
|
} |
|
|
|
// TODO: It's possible we don't want to use the color texels--for example because of a convar. |
|
// We should check that here in addition to whether or not we have the data available. |
|
static unsigned int lightmapVarCache = 0; |
|
IMaterialVar *pLightmapVar = pMaterial->FindVarFast( "$lightmap", &lightmapVarCache ); |
|
if ( pLightmapVar ) |
|
{ |
|
ITexture* newTex = pColorMeshes ? pColorMeshes->m_pLightmap : NULL; |
|
|
|
if (newTex) |
|
pLightmapVar->SetTextureValue(newTex); |
|
else |
|
pLightmapVar->SetUndefined(); |
|
} |
|
|
|
pRenderContext->Bind( pMaterial, pClientRenderable ); |
|
|
|
if ( bCheckForConVarDrawTranslucentSubModels ) |
|
{ |
|
bool translucent = pMaterial->IsTranslucent(); |
|
|
|
if (( m_bDrawTranslucentSubModels && !translucent ) || |
|
( !m_bDrawTranslucentSubModels && translucent )) |
|
{ |
|
m_bSkippedMeshes = true; |
|
return NULL; |
|
} |
|
} |
|
|
|
return pMaterial; |
|
} |
|
|
|
|
|
|
|
//============================================================================= |
|
|
|
|
|
/* |
|
================= |
|
R_StudioSetupModel |
|
based on the body part, figure out which mesh it should be using. |
|
inputs: |
|
outputs: |
|
pstudiomesh |
|
pmdl |
|
================= |
|
*/ |
|
int R_StudioSetupModel( int bodypart, int entity_body, mstudiomodel_t **ppSubModel, |
|
const studiohdr_t *pStudioHdr ) |
|
{ |
|
int index; |
|
mstudiobodyparts_t *pbodypart; |
|
|
|
if (bodypart > pStudioHdr->numbodyparts) |
|
{ |
|
ConDMsg ("R_StudioSetupModel: no such bodypart %d\n", bodypart); |
|
bodypart = 0; |
|
} |
|
|
|
pbodypart = pStudioHdr->pBodypart( bodypart ); |
|
|
|
if ( pbodypart->base == 0 ) |
|
{ |
|
Warning( "Model has missing body part: %s\n", pStudioHdr->pszName() ); |
|
Assert( 0 ); |
|
} |
|
index = entity_body / pbodypart->base; |
|
index = index % pbodypart->nummodels; |
|
|
|
Assert( ppSubModel ); |
|
*ppSubModel = pbodypart->pModel( index ); |
|
return index; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates the PoseToBone Matrix nessecary to align the given bone with the |
|
// world. |
|
//----------------------------------------------------------------------------- |
|
static void ScreenAlignBone( matrix3x4_t *pPoseToWorld, mstudiobone_t *pCurBone, |
|
const Vector& vecViewOrigin, const matrix3x4_t &boneToWorld ) |
|
{ |
|
// Grab the world translation: |
|
Vector vT( boneToWorld[0][3], boneToWorld[1][3], boneToWorld[2][3] ); |
|
|
|
// Construct the coordinate frame: |
|
// Initialized to get rid of compiler |
|
Vector vX, vY, vZ; |
|
|
|
if( pCurBone->flags & BONE_SCREEN_ALIGN_SPHERE ) |
|
{ |
|
vX = vecViewOrigin - vT; |
|
VectorNormalize(vX); |
|
vZ = Vector(0,0,1); |
|
vY = vZ.Cross(vX); |
|
VectorNormalize(vY); |
|
vZ = vX.Cross(vY); |
|
VectorNormalize(vZ); |
|
} |
|
else |
|
{ |
|
Assert( pCurBone->flags & BONE_SCREEN_ALIGN_CYLINDER ); |
|
vX.Init( boneToWorld[0][0], boneToWorld[1][0], boneToWorld[2][0] ); |
|
vZ = vecViewOrigin - vT; |
|
VectorNormalize(vZ); |
|
vY = vZ.Cross(vX); |
|
VectorNormalize(vY); |
|
vZ = vX.Cross(vY); |
|
VectorNormalize(vZ); |
|
} |
|
|
|
matrix3x4_t matBoneBillboard( |
|
vX.x, vY.x, vZ.x, vT.x, |
|
vX.y, vY.y, vZ.y, vT.y, |
|
vX.z, vY.z, vZ.z, vT.z ); |
|
ConcatTransforms( matBoneBillboard, pCurBone->poseToBone, *pPoseToWorld ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes PoseToWorld from BoneToWorld |
|
//----------------------------------------------------------------------------- |
|
void ComputePoseToWorld( matrix3x4_t *pPoseToWorld, studiohdr_t *pStudioHdr, int boneMask, const Vector& vecViewOrigin, const matrix3x4_t *pBoneToWorld ) |
|
{ |
|
if ( pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP ) |
|
{ |
|
// by definition, these always have an identity poseToBone transform |
|
MatrixCopy( pBoneToWorld[ 0 ], pPoseToWorld[ 0 ] ); |
|
return; |
|
} |
|
|
|
if ( !pStudioHdr->pLinearBones() ) |
|
{ |
|
// convert bone to world transformations into pose to world transformations |
|
for (int i = 0; i < pStudioHdr->numbones; i++) |
|
{ |
|
mstudiobone_t *pCurBone = pStudioHdr->pBone( i ); |
|
if ( !(pCurBone->flags & boneMask) ) |
|
continue; |
|
|
|
ConcatTransforms( pBoneToWorld[ i ], pCurBone->poseToBone, pPoseToWorld[ i ] ); |
|
} |
|
} |
|
else |
|
{ |
|
mstudiolinearbone_t *pLinearBones = pStudioHdr->pLinearBones(); |
|
|
|
// convert bone to world transformations into pose to world transformations |
|
for (int i = 0; i < pStudioHdr->numbones; i++) |
|
{ |
|
if ( !(pLinearBones->flags(i) & boneMask) ) |
|
continue; |
|
|
|
ConcatTransforms( pBoneToWorld[ i ], pLinearBones->poseToBone(i), pPoseToWorld[ i ] ); |
|
} |
|
} |
|
|
|
#if 0 |
|
// These don't seem to be used in any existing QC file, re-enable in a future project? |
|
// Pretransform |
|
if( !( pCurBone->flags & ( BONE_SCREEN_ALIGN_SPHERE | BONE_SCREEN_ALIGN_CYLINDER ))) |
|
{ |
|
ConcatTransforms( pBoneToWorld[ i ], pCurBone->poseToBone, pPoseToWorld[ i ] ); |
|
} |
|
else |
|
{ |
|
// If this bone is screen aligned, then generate a PoseToWorld matrix that billboards the bone |
|
ScreenAlignBone( &pPoseToWorld[i], pCurBone, vecViewOrigin, pBoneToWorld[i] ); |
|
} |
|
#endif |
|
} |
|
|
|
|
|
|