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.
928 lines
29 KiB
928 lines
29 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $Workfile: $ |
|
// $Date: $ |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#include "studio.h" |
|
#include "studiorendercontext.h" |
|
#include "bitmap/imageformat.h" |
|
#include "materialsystem/imaterialsystem.h" |
|
#include "materialsystem/imaterial.h" |
|
#include "materialsystem/imaterialvar.h" |
|
#include "materialsystem/itexture.h" |
|
#include "materialsystem/imesh.h" |
|
#include "mathlib/mathlib.h" |
|
#include "studiorender.h" |
|
#include "pixelwriter.h" |
|
#include "vtf/vtf.h" |
|
#include "tier1/convar.h" |
|
#include "tier1/KeyValues.h" |
|
#include "tier0/vprof.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define sign( a ) (((a) < 0) ? -1 : (((a) > 0) ? 1 : 0 )) |
|
|
|
void CStudioRender::R_StudioEyeballPosition( const mstudioeyeball_t *peyeball, eyeballstate_t *pstate ) |
|
{ |
|
// Vector forward; |
|
// Vector org, right, up; |
|
|
|
pstate->peyeball = peyeball; |
|
|
|
Vector tmp; |
|
// move eyeball into worldspace |
|
{ |
|
// ConDMsg("%.2f %.2f %.2f\n", peyeball->org[0], peyeball->org[1], peyeball->org[2] ); |
|
|
|
VectorCopy( peyeball->org, tmp ); |
|
|
|
tmp[0] += m_pRC->m_Config.fEyeShiftX * sign( tmp[0] ); |
|
tmp[1] += m_pRC->m_Config.fEyeShiftY * sign( tmp[1] ); |
|
tmp[2] += m_pRC->m_Config.fEyeShiftZ * sign( tmp[2] ); |
|
} |
|
VectorTransform( tmp, m_pBoneToWorld[peyeball->bone], pstate->org ); |
|
VectorRotate( peyeball->up, m_pBoneToWorld[peyeball->bone], pstate->up ); |
|
|
|
// look directly at target |
|
VectorSubtract( m_pRC->m_ViewTarget, pstate->org, pstate->forward ); |
|
VectorNormalize( pstate->forward ); |
|
|
|
if ( !m_pRC->m_Config.bEyeMove ) |
|
{ |
|
VectorRotate( peyeball->forward, m_pBoneToWorld[peyeball->bone], pstate->forward ); |
|
VectorScale( pstate->forward, -1 ,pstate->forward ); // ??? |
|
} |
|
|
|
CrossProduct( pstate->forward, pstate->up, pstate->right ); |
|
VectorNormalize( pstate->right ); |
|
|
|
// shift N degrees off of the target |
|
float dz; |
|
dz = peyeball->zoffset; |
|
|
|
VectorMA( pstate->forward, peyeball->zoffset + dz, pstate->right, pstate->forward ); |
|
|
|
#if 0 |
|
// add random jitter |
|
VectorMA( forward, RandomFloat( -0.02, 0.02 ), right, forward ); |
|
VectorMA( forward, RandomFloat( -0.02, 0.02 ), up, forward ); |
|
#endif |
|
|
|
VectorNormalize( pstate->forward ); |
|
// re-aim eyes |
|
CrossProduct( pstate->forward, pstate->up, pstate->right ); |
|
VectorNormalize( pstate->right ); |
|
|
|
CrossProduct( pstate->right, pstate->forward, pstate->up ); |
|
VectorNormalize( pstate->up ); |
|
|
|
float scale = (1.0 / peyeball->iris_scale) + m_pRC->m_Config.fEyeSize; |
|
|
|
if (scale > 0) |
|
scale = 1.0 / scale; |
|
|
|
VectorScale( &pstate->right[0], -scale, pstate->mat[0] ); |
|
VectorScale( &pstate->up[0], -scale, pstate->mat[1] ); |
|
|
|
pstate->mat[0][3] = -DotProduct( &pstate->org[0], pstate->mat[0] ) + 0.5f; |
|
pstate->mat[1][3] = -DotProduct( &pstate->org[0], pstate->mat[1] ) + 0.5f; |
|
|
|
// FIXME: push out vertices for cornea |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
void CStudioRender::R_StudioEyelidFACS( const mstudioeyeball_t *peyeball, const eyeballstate_t *pstate ) |
|
{ |
|
if ( peyeball->m_bNonFACS ) |
|
return; |
|
|
|
Vector headup; |
|
Vector headforward; |
|
Vector pos; |
|
|
|
float upperlid = DEG2RAD( 9.5 ); |
|
float lowerlid = DEG2RAD( -26.4 ); |
|
|
|
// FIXME: Crash workaround |
|
Vector vecNormTarget; |
|
vecNormTarget.Init( peyeball->uppertarget[0], peyeball->uppertarget[1], peyeball->uppertarget[2] ); |
|
vecNormTarget /= peyeball->radius; |
|
vecNormTarget.x = clamp( vecNormTarget.x, -1.0f, 1.0f ); |
|
vecNormTarget.y = clamp( vecNormTarget.y, -1.0f, 1.0f ); |
|
vecNormTarget.z = clamp( vecNormTarget.z, -1.0f, 1.0f ); |
|
|
|
// get weighted position of eyeball angles based on the "raiser", "neutral", and "lowerer" controls |
|
upperlid = m_pFlexWeights[peyeball->upperflexdesc[0]] * asin( vecNormTarget.x ); |
|
upperlid += m_pFlexWeights[peyeball->upperflexdesc[1]] * asin( vecNormTarget.y ); |
|
upperlid += m_pFlexWeights[peyeball->upperflexdesc[2]] * asin( vecNormTarget.z ); |
|
|
|
vecNormTarget.Init( peyeball->lowertarget[0], peyeball->lowertarget[1], peyeball->lowertarget[2] ); |
|
vecNormTarget /= peyeball->radius; |
|
vecNormTarget.x = clamp( vecNormTarget.x, -1.0f, 1.0f ); |
|
vecNormTarget.y = clamp( vecNormTarget.y, -1.0f, 1.0f ); |
|
vecNormTarget.z = clamp( vecNormTarget.z, -1.0f, 1.0f ); |
|
|
|
lowerlid = m_pFlexWeights[peyeball->lowerflexdesc[0]] * asin( vecNormTarget.x ); |
|
lowerlid += m_pFlexWeights[peyeball->lowerflexdesc[1]] * asin( vecNormTarget.y ); |
|
lowerlid += m_pFlexWeights[peyeball->lowerflexdesc[2]] * asin( vecNormTarget.z ); |
|
|
|
// ConDMsg("%.1f %.1f\n", RAD2DEG( upperlid ), RAD2DEG( lowerlid ) ); |
|
|
|
float sinupper, cosupper, sinlower, coslower; |
|
SinCos( upperlid, &sinupper, &cosupper ); |
|
SinCos( lowerlid, &sinlower, &coslower ); |
|
|
|
// convert to head relative space |
|
VectorIRotate( pstate->up, m_pBoneToWorld[peyeball->bone], headup ); |
|
VectorIRotate( pstate->forward, m_pBoneToWorld[peyeball->bone], headforward ); |
|
|
|
// upper lid |
|
VectorScale( headup, sinupper * peyeball->radius, pos ); |
|
VectorMA( pos, cosupper * peyeball->radius, headforward, pos ); |
|
m_pFlexWeights[peyeball->upperlidflexdesc] = DotProduct( pos, peyeball->up ); |
|
|
|
// lower lid |
|
VectorScale( headup, sinlower * peyeball->radius, pos ); |
|
VectorMA( pos, coslower * peyeball->radius, headforward, pos ); |
|
m_pFlexWeights[peyeball->lowerlidflexdesc] = DotProduct( pos, peyeball->up ); |
|
// ConDMsg("%.4f %.4f\n", m_pRC->m_FlexWeights[peyeball->upperlidflex], m_pRC->m_FlexWeights[peyeball->lowerlidflex] ); |
|
} |
|
|
|
|
|
void CStudioRender::MaterialPlanerProjection( const matrix3x4_t& mat, int count, const Vector *psrcverts, Vector2D *pdesttexcoords ) |
|
{ |
|
for (int i = 0; i < count; i++) |
|
{ |
|
pdesttexcoords[i][0] = DotProduct( &psrcverts[i].x, mat[0] ) + mat[0][3]; |
|
pdesttexcoords[i][1] = DotProduct( &psrcverts[i].x, mat[1] ) + mat[1][3]; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Ramp and clamp the flex weight |
|
//----------------------------------------------------------------------------- |
|
float CStudioRender::RampFlexWeight( mstudioflex_t &flex, float w ) |
|
{ |
|
if (w <= flex.target0 || w >= flex.target3) |
|
{ |
|
// value outside of range |
|
w = 0.0; |
|
} |
|
else if (w < flex.target1) |
|
{ |
|
// 0 to 1 ramp |
|
w = (w - flex.target0) / (flex.target1 - flex.target0); |
|
} |
|
else if (w > flex.target2) |
|
{ |
|
// 1 to 0 ramp |
|
w = (flex.target3 - w) / (flex.target3 - flex.target2); |
|
} |
|
else |
|
{ |
|
// plat |
|
w = 1.0; |
|
} |
|
return w; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Setup the flex verts for this rendering |
|
//----------------------------------------------------------------------------- |
|
void CStudioRender::R_StudioFlexVerts( mstudiomesh_t *pmesh, int lod ) |
|
{ |
|
VPROF_BUDGET( "CStudioRender::R_StudioFlexVerts", VPROF_BUDGETGROUP_MODEL_RENDERING ); |
|
|
|
Assert( pmesh ); |
|
|
|
const float flVertAnimFixedPointScale = m_pStudioHdr->VertAnimFixedPointScale(); |
|
|
|
// There's a chance we can actually do the flex twice on a single mesh |
|
// since there's flexed HW + SW portions of the mesh. |
|
if (m_VertexCache.IsFlexComputationDone()) |
|
return; |
|
|
|
// get pointers to geometry |
|
if ( !pmesh->pModel()->CacheVertexData( m_pStudioHdr ) ) |
|
{ |
|
// not available yet |
|
return; |
|
} |
|
const mstudio_meshvertexdata_t *vertData = pmesh->GetVertexData( m_pStudioHdr ); |
|
Assert( vertData ); |
|
if ( !vertData ) |
|
{ |
|
static unsigned int warnCount = 0; |
|
if ( warnCount++ < 20 ) |
|
Warning( "ERROR: R_StudioFlexVerts, model verts have been compressed, cannot render! (use \"-no_compressed_vvds\")" ); |
|
return; |
|
} |
|
|
|
// The flex data should have been converted to the new (fixed-point) format on load: |
|
Assert( m_pStudioHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED ); |
|
if ( ( m_pStudioHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED ) == 0 ) |
|
{ |
|
static unsigned int flexConversionTimesWarned = 0; |
|
if ( flexConversionTimesWarned++ < 6 ) |
|
Warning( "ERROR: flex verts have not been converted (queued loader refcount bug?) - expect to see 'exploded' faces" ); |
|
} |
|
|
|
|
|
mstudiovertex_t *pVertices = vertData->Vertex( 0 ); |
|
Vector4D *pStudioTangentS; |
|
if ( vertData->HasTangentData() ) |
|
{ |
|
pStudioTangentS = vertData->TangentS( 0 ); |
|
} |
|
else |
|
{ |
|
pStudioTangentS = NULL; |
|
} |
|
|
|
mstudioflex_t *pflex = pmesh->pFlex( 0 ); |
|
|
|
m_VertexCache.SetupComputation( pmesh, true ); |
|
|
|
// apply flex weights |
|
int i, j, n; |
|
|
|
for (i = 0; i < pmesh->numflexes; i++) |
|
{ |
|
float w1 = RampFlexWeight( pflex[i], m_pFlexWeights[ pflex[i].flexdesc ] ); |
|
float w2 = RampFlexWeight( pflex[i], m_pFlexDelayedWeights[ pflex[i].flexdesc ] ); |
|
|
|
float w3, w4; |
|
if ( pflex[i].flexpair != 0) |
|
{ |
|
w3 = RampFlexWeight( pflex[i], m_pFlexWeights[ pflex[i].flexpair ] ); |
|
w4 = RampFlexWeight( pflex[i], m_pFlexDelayedWeights[ pflex[i].flexpair ] ); |
|
} |
|
else |
|
{ |
|
w3 = w1; |
|
w4 = w2; |
|
} |
|
|
|
if ( w1 > -0.001 && w1 < 0.001 && w2 > -0.001 && w2 < 0.001 ) |
|
{ |
|
if ( w3 > -0.001 && w3 < 0.001 && w4 > -0.001 && w4 < 0.001 ) |
|
{ |
|
continue; |
|
} |
|
} |
|
|
|
// We may have wrinkle information for this flex, but if we're software skinning |
|
// we're going to ignore it. |
|
byte *pvanim = pflex[i].pBaseVertanim(); |
|
int nVAnimSizeBytes = pflex[i].VertAnimSizeBytes(); |
|
|
|
for (j = 0; j < pflex[i].numverts; j++) |
|
{ |
|
mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pvanim + j * nVAnimSizeBytes ); |
|
n = pAnim->index; |
|
|
|
// Only flex the indices that are (still) part of this mesh |
|
// need lod restriction here |
|
if (n < pmesh->vertexdata.numLODVertexes[lod]) |
|
{ |
|
mstudiovertex_t &vert = pVertices[n]; |
|
|
|
CachedPosNormTan_t* pFlexedVertex; |
|
if (!m_VertexCache.IsVertexFlexed(n)) |
|
{ |
|
// Add a new flexed vert to the flexed vertex list |
|
pFlexedVertex = m_VertexCache.CreateFlexVertex(n); |
|
// skip processing if no more flexed verts can be allocated |
|
if (pFlexedVertex == NULL) |
|
continue; |
|
|
|
VectorCopy( vert.m_vecPosition, pFlexedVertex->m_Position ); |
|
VectorCopy( vert.m_vecNormal, pFlexedVertex->m_Normal ); |
|
|
|
if (pStudioTangentS) |
|
{ |
|
Vector4DCopy( pStudioTangentS[n], pFlexedVertex->m_TangentS ); |
|
Assert( pFlexedVertex->m_TangentS.w == -1.0f || pFlexedVertex->m_TangentS.w == 1.0f ); |
|
} |
|
} |
|
else |
|
{ |
|
pFlexedVertex = m_VertexCache.GetFlexVertex(n); |
|
} |
|
|
|
float s = pAnim->speed * (1.0F/255.0F); |
|
float b = pAnim->side * (1.0F/255.0F); |
|
|
|
float w = (w1 * s + (1.0f - s) * w2) * (1.0f - b) + b * (w3 * s + (1.0f - s) * w4); |
|
|
|
// Accumulate weighted deltas |
|
pFlexedVertex->m_Position += pAnim->GetDeltaFixed( flVertAnimFixedPointScale ) * w; |
|
pFlexedVertex->m_Normal += pAnim->GetNDeltaFixed( flVertAnimFixedPointScale ) * w; |
|
|
|
if ( pStudioTangentS ) |
|
{ |
|
pFlexedVertex->m_TangentS.AsVector3D() += pAnim->GetNDeltaFixed( flVertAnimFixedPointScale ) * w; |
|
Assert( pFlexedVertex->m_TangentS.w == -1.0f || pFlexedVertex->m_TangentS.w == 1.0f ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
m_VertexCache.RenormalizeFlexVertices( vertData->HasTangentData() ); |
|
} |
|
|
|
// REMOVED!! Look in version 32 if you need it. |
|
//static void R_StudioEyeballNormals( const mstudioeyeball_t *peyeball, int count, const Vector *psrcverts, Vector *pdestnorms ) |
|
|
|
#define KERNEL_DIAMETER 2 |
|
#define KERNEL_TEXELS (KERNEL_DIAMETER) |
|
#define KERNEL_TEXEL_RADIUS (KERNEL_TEXELS / 2) |
|
|
|
inline float GlintGaussSpotCoefficient( float dx, float dy /*, float *table */ ) |
|
{ |
|
const float radius = KERNEL_DIAMETER / 2; |
|
const float rsq = 1.0f / (radius * radius); |
|
float r2 = (dx * dx + dy * dy) * rsq; |
|
if (r2 <= 1.0f) |
|
{ |
|
return exp( -25.0 * r2 ); |
|
// NOTE: This optimization doesn't make much of a difference |
|
//int index = r2 * (GLINT_TABLE_ENTRIES-1); |
|
//return table[index]; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
void CStudioRender::AddGlint( CPixelWriter &pixelWriter, float x, float y, const Vector& color ) |
|
{ |
|
x = (x + 0.5f) * m_GlintWidth; |
|
y = (y + 0.5f) * m_GlintHeight; |
|
const float texelRadius = KERNEL_DIAMETER / 2; |
|
|
|
int x0 = (int)x; |
|
int y0 = (int)y; |
|
int x1 = x0 + texelRadius; |
|
int y1 = y0 + texelRadius; |
|
x0 -= texelRadius; |
|
y0 -= texelRadius; |
|
|
|
// clip light to texture |
|
if ( (x0 >= m_GlintWidth) || (x1 < 0) || (y0 >= m_GlintHeight) || (y1 < 0) ) |
|
return; |
|
|
|
// clamp coordinates |
|
if ( x0 < 0 ) |
|
{ |
|
x0 = 0; |
|
} |
|
if ( y0 < 0 ) |
|
{ |
|
y0 = 0; |
|
} |
|
if ( x1 >= m_GlintWidth ) |
|
{ |
|
x1 = m_GlintWidth-1; |
|
} |
|
if ( y1 >= m_GlintHeight ) |
|
{ |
|
y1 = m_GlintHeight-1; |
|
} |
|
|
|
for (int v = y0; v <= y1; ++v ) |
|
{ |
|
pixelWriter.Seek( x0, v ); |
|
|
|
for (int u = x0; u <= x1; ++u ) |
|
{ |
|
float fu = ((float)u) - x; |
|
float fv = ((float)v) - y; |
|
const float offset = 0.25; |
|
float intensity = GlintGaussSpotCoefficient( fu-offset, fv-offset ) + |
|
GlintGaussSpotCoefficient( fu+offset, fv-offset ) + |
|
5 * GlintGaussSpotCoefficient( fu, fv ) + |
|
GlintGaussSpotCoefficient( fu-offset, fv+offset ) + |
|
GlintGaussSpotCoefficient( fu+offset, fv+offset ); |
|
|
|
// NOTE: Old filter code multiplies the signal by 8X, so we will too |
|
intensity *= (4.0f/9.0f); |
|
|
|
// NOTE: It's much faster to do the work in the dest texture than to touch the memory more |
|
// or make more buffers |
|
Vector outColor = intensity * color; |
|
int r, g, b, a; |
|
pixelWriter.ReadPixelNoAdvance( r, g, b, a ); |
|
outColor.x += TextureToLinear(r); |
|
outColor.y += TextureToLinear(g); |
|
outColor.z += TextureToLinear(b); |
|
pixelWriter.WritePixel( LinearToTexture(outColor.x), LinearToTexture(outColor.y), LinearToTexture(outColor.z) ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// glint |
|
//----------------------------------------------------------------------------- |
|
|
|
// test/stub code |
|
#if 0 |
|
class CEmptyTextureRegen : public ITextureRegenerator |
|
{ |
|
public: |
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) |
|
{ |
|
// get the texture |
|
unsigned char *pTextureData = pVTFTexture->ImageData( 0, 0, 0 ); |
|
int nImageSize = pVTFTexture->ComputeMipSize( 0 ); |
|
memset( pTextureData, 0, nImageSize ); |
|
} |
|
|
|
// We've got a global instance, no need to delete it |
|
virtual void Release() {} |
|
}; |
|
static CEmptyTextureRegen s_GlintTextureRegen; |
|
#endif |
|
|
|
class CGlintTextureRegenerator : public ITextureRegenerator |
|
{ |
|
public: |
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) |
|
{ |
|
// We don't need to reconstitute the bits after a task switch |
|
// since we reconstitute them every frame they are used anyways |
|
if ( !m_pStudioRender ) |
|
return; |
|
|
|
if ( ( m_pStudioRender->m_GlintWidth != pVTFTexture->Width() ) || |
|
( m_pStudioRender->m_GlintHeight != pVTFTexture->Height() ) ) |
|
{ |
|
m_pStudioRender->m_GlintWidth = pVTFTexture->Width(); |
|
m_pStudioRender->m_GlintHeight = pVTFTexture->Height(); |
|
} |
|
|
|
CStudioRender::GlintRenderData_t pRenderData[16]; |
|
int nGlintCount = m_pStudioRender->BuildGlintRenderData( pRenderData, |
|
ARRAYSIZE(pRenderData), m_pState, *m_pVRight, *m_pVUp, *m_pROrigin ); |
|
|
|
// setup glint texture |
|
unsigned char *pTextureData = pVTFTexture->ImageData( 0, 0, 0 ); |
|
CPixelWriter pixelWriter; |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), pTextureData, pVTFTexture->RowSizeInBytes( 0 ) ); |
|
int nImageSize = pVTFTexture->ComputeMipSize( 0 ); |
|
memset( pTextureData, 0, nImageSize ); |
|
|
|
// Put in glints due to the lights in the scene |
|
for ( int i = 0; i < nGlintCount; ++i ) |
|
{ |
|
// NOTE: AddGlint is a more expensive solution but it looks better close-up |
|
m_pStudioRender->AddGlint( pixelWriter, pRenderData[i].m_vecPosition[0], |
|
pRenderData[i].m_vecPosition[1], pRenderData[i].m_vecIntensity ); |
|
} |
|
} |
|
|
|
// We've got a global instance, no need to delete it |
|
virtual void Release() {} |
|
|
|
const eyeballstate_t *m_pState; |
|
const Vector *m_pVRight; |
|
const Vector *m_pVUp; |
|
const Vector *m_pROrigin; |
|
CStudioRender *m_pStudioRender; |
|
}; |
|
|
|
static CGlintTextureRegenerator s_GlintTextureRegen; |
|
|
|
static ITexture *s_pProcGlint = NULL; |
|
void CStudioRender::PrecacheGlint() |
|
{ |
|
if ( !m_pGlintTexture ) |
|
{ |
|
// Begin block in which all render targets should be allocated |
|
g_pMaterialSystem->BeginRenderTargetAllocation(); |
|
|
|
// Get the texture that we are going to be updating procedurally. |
|
m_pGlintTexture = g_pMaterialSystem->CreateNamedRenderTargetTextureEx2( |
|
"_rt_eyeglint", 32, 32, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_BGRA8888, MATERIAL_RT_DEPTH_NONE ); |
|
m_pGlintTexture->IncrementReferenceCount(); |
|
|
|
// Begin block in which all render targets should be allocated |
|
g_pMaterialSystem->EndRenderTargetAllocation(); |
|
|
|
if ( !IsX360() ) |
|
{ |
|
// Get the texture that we are going to be updating procedurally. |
|
s_pProcGlint = g_pMaterialSystem->CreateProceduralTexture( |
|
"proc_eyeglint", TEXTURE_GROUP_MODEL, 32, 32, IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_NOMIP|TEXTUREFLAGS_NOLOD ); |
|
s_pProcGlint->SetTextureRegenerator( &s_GlintTextureRegen ); |
|
} |
|
|
|
// JAY: I don't see this pattern in the code often. It looks like the material system |
|
// would rather than I deal exclusively with IMaterials instead. |
|
// So maybe we should bake the LOD texture into the eyes shader. |
|
// For now, just hardcode one |
|
// UNDONE: Add a $lodtexture to the eyes shader. Maybe add a $lodsize too. |
|
// UNDONE: Make eyes texture load $lodtexture and switch to that here instead of black |
|
m_pGlintLODTexture = g_pMaterialSystem->FindTexture( IsX360() ? "black" : "vgui/black", NULL, false ); |
|
m_pGlintLODTexture->IncrementReferenceCount(); |
|
} |
|
} |
|
|
|
void CStudioRender::UncacheGlint() |
|
{ |
|
if ( m_pGlintTexture ) |
|
{ |
|
if ( s_pProcGlint ) |
|
{ |
|
s_pProcGlint->SetTextureRegenerator( NULL ); |
|
s_pProcGlint->DecrementReferenceCount(); |
|
s_pProcGlint = NULL; |
|
} |
|
m_pGlintTexture->DecrementReferenceCount(); |
|
m_pGlintTexture = NULL; |
|
m_pGlintLODTexture->DecrementReferenceCount(); |
|
m_pGlintLODTexture = NULL; |
|
} |
|
} |
|
|
|
int CStudioRender::BuildGlintRenderData( GlintRenderData_t *pData, int nMaxGlints, |
|
const eyeballstate_t *pState, const Vector& vright, const Vector& vup, const Vector& r_origin ) |
|
{ |
|
// NOTE: See version 25 for lots of #if 0ed out stuff I removed |
|
Vector viewdelta; |
|
VectorSubtract( r_origin, pState->org, viewdelta ); |
|
VectorNormalize( viewdelta ); |
|
|
|
// hack cornea position |
|
float iris_radius = pState->peyeball->radius * (6.0 / 12.0); |
|
float cornea_radius = pState->peyeball->radius * (8.0 / 12.0); |
|
|
|
Vector cornea; |
|
// position on eyeball that matches iris radius |
|
float er = ( iris_radius / pState->peyeball->radius ); |
|
er = FastSqrt( 1 - er * er ); |
|
|
|
// position on cornea sphere that matches iris radius |
|
float cr = ( iris_radius / cornea_radius ); |
|
cr = FastSqrt( 1 - cr * cr ); |
|
|
|
float r = ( er * pState->peyeball->radius - cr * cornea_radius ); |
|
VectorScale( pState->forward, r, cornea ); |
|
|
|
// get offset for center of cornea |
|
float dx, dy; |
|
dx = DotProduct( vright, cornea ); |
|
dy = DotProduct( vup, cornea ); |
|
|
|
// move cornea to world space |
|
VectorAdd( cornea, pState->org, cornea ); |
|
|
|
Vector delta, intensity; |
|
Vector reflection, coord; |
|
|
|
// Put in glints due to the lights in the scene |
|
int nGlintCount = 0; |
|
for ( int i = 0; R_LightGlintPosition( i, cornea, delta, intensity ); ++i ) |
|
{ |
|
VectorNormalize( delta ); |
|
if ( DotProduct( delta, pState->forward ) <= 0 ) |
|
continue; |
|
|
|
VectorAdd( delta, viewdelta, reflection ); |
|
VectorNormalize( reflection ); |
|
|
|
pData[nGlintCount].m_vecPosition[0] = dx + cornea_radius * DotProduct( vright, reflection ); |
|
pData[nGlintCount].m_vecPosition[1] = dy + cornea_radius * DotProduct( vup, reflection ); |
|
pData[nGlintCount].m_vecIntensity = intensity; |
|
if ( ++nGlintCount >= nMaxGlints ) |
|
return nMaxGlints; |
|
|
|
if ( !R_LightGlintPosition( i, pState->org, delta, intensity ) ) |
|
continue; |
|
|
|
VectorNormalize( delta ); |
|
if ( DotProduct( delta, pState->forward ) >= er ) |
|
continue; |
|
|
|
pData[nGlintCount].m_vecPosition[0] = pState->peyeball->radius * DotProduct( vright, reflection ); |
|
pData[nGlintCount].m_vecPosition[1] = pState->peyeball->radius * DotProduct( vup, reflection ); |
|
pData[nGlintCount].m_vecIntensity = intensity; |
|
if ( ++nGlintCount >= nMaxGlints ) |
|
return nMaxGlints; |
|
} |
|
return nGlintCount; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renders a glint texture procedurally |
|
//----------------------------------------------------------------------------- |
|
ITexture* CStudioRender::RenderGlintTexture( const eyeballstate_t *pState, |
|
const Vector& vright, const Vector& vup, const Vector& r_origin ) |
|
{ |
|
GlintRenderData_t pRenderData[16]; |
|
int nGlintCount = BuildGlintRenderData( pRenderData, ARRAYSIZE(pRenderData), |
|
pState, vright, vup, r_origin ); |
|
|
|
if ( nGlintCount == 0 ) |
|
return m_pGlintLODTexture; |
|
|
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
pRenderContext->PushRenderTargetAndViewport( m_pGlintTexture ); |
|
|
|
IMaterial *pPrevMaterial = pRenderContext->GetCurrentMaterial(); |
|
void *pPrevProxy = pRenderContext->GetCurrentProxy(); |
|
int nPrevBoneCount = pRenderContext->GetCurrentNumBones(); |
|
MaterialHeightClipMode_t nPrevClipMode = pRenderContext->GetHeightClipMode( ); |
|
bool bPrevClippingEnabled = pRenderContext->EnableClipping( false ); |
|
bool bInFlashlightMode = pRenderContext->GetFlashlightMode(); |
|
|
|
if ( bInFlashlightMode ) |
|
{ |
|
DisableScissor(); |
|
} |
|
pRenderContext->ClearColor4ub( 0, 0, 0, 0 ); |
|
pRenderContext->ClearBuffers( true, false, false ); |
|
|
|
pRenderContext->SetFlashlightMode( false ); |
|
pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE ); |
|
pRenderContext->SetNumBoneWeights( 0 ); |
|
pRenderContext->Bind( m_pGlintBuildMaterial ); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PushMatrix(); |
|
pRenderContext->LoadIdentity(); |
|
|
|
CMeshBuilder meshBuilder; |
|
IMesh *pMesh = pRenderContext->GetDynamicMesh( ); |
|
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nGlintCount * 4, nGlintCount * 6 ); |
|
|
|
const float epsilon = 0.5f / 32.0f; |
|
int nIndex = 0; |
|
for ( int i = 0; i < nGlintCount; ++i ) |
|
{ |
|
const GlintRenderData_t &glint = pRenderData[i]; |
|
|
|
// Position of glint 0..31 range |
|
float x = (glint.m_vecPosition.x + 0.5f) * m_GlintWidth; |
|
float y = (glint.m_vecPosition.y + 0.5f) * m_GlintHeight; |
|
Vector vGlintCenter = Vector( x, y, 0.0f ); |
|
float ooWidth = 1.0f / (float)m_GlintWidth; |
|
float ooHeight = 1.0f / (float)m_GlintHeight; |
|
|
|
int x0 = floor(x); |
|
int y0 = floor(y); |
|
int x1 = x0 + 1.0f; |
|
int y1 = y0 + 1.0f; |
|
x0 -= 2.0f; // Fill rules make us pad this out more than the procedural version |
|
y0 -= 2.0f; |
|
|
|
float screenX0 = x0 * 2 * ooWidth + epsilon - 1; |
|
float screenX1 = x1 * 2 * ooWidth + epsilon - 1; |
|
float screenY0 = -(y0 * 2 * ooHeight + epsilon - 1); |
|
float screenY1 = -(y1 * 2 * ooHeight + epsilon - 1); |
|
|
|
meshBuilder.Position3f( screenX0, screenY0, 0.0f ); |
|
meshBuilder.TexCoord2f( 0, x0, y0 ); |
|
meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() ); |
|
meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3f( screenX1, screenY0, 0.0f ); |
|
meshBuilder.TexCoord2f( 0, x1, y0 ); |
|
meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() ); |
|
meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3f( screenX1, screenY1, 0.0f ); |
|
meshBuilder.TexCoord2f( 0, x1, y1 ); |
|
meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() ); |
|
meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3f( screenX0, screenY1, 0.0f ); |
|
meshBuilder.TexCoord2f( 0, x0, y1 ); |
|
meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() ); |
|
meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.FastIndex( nIndex ); |
|
meshBuilder.FastIndex( nIndex+1 ); |
|
meshBuilder.FastIndex( nIndex+2 ); |
|
meshBuilder.FastIndex( nIndex ); |
|
meshBuilder.FastIndex( nIndex+2 ); |
|
meshBuilder.FastIndex( nIndex+3 ); |
|
nIndex += 4; |
|
} |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_MODEL ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_VIEW ); |
|
pRenderContext->PopMatrix(); |
|
|
|
pRenderContext->MatrixMode( MATERIAL_PROJECTION ); |
|
pRenderContext->PopMatrix(); |
|
|
|
if ( IsX360() ) |
|
{ |
|
pRenderContext->CopyRenderTargetToTextureEx( m_pGlintTexture, 0, NULL, NULL ); |
|
} |
|
|
|
pRenderContext->PopRenderTargetAndViewport( ); |
|
|
|
pRenderContext->Bind( pPrevMaterial, pPrevProxy ); |
|
pRenderContext->SetNumBoneWeights( nPrevBoneCount ); |
|
pRenderContext->SetHeightClipMode( nPrevClipMode ); |
|
pRenderContext->EnableClipping( bPrevClippingEnabled ); |
|
pRenderContext->SetFlashlightMode( bInFlashlightMode ); |
|
|
|
return m_pGlintTexture; |
|
} |
|
|
|
static ConVar r_glint_procedural( "r_glint_procedural", "0" ); |
|
static ConVar r_glint_alwaysdraw( "r_glint_alwaysdraw", "0" ); |
|
|
|
void CStudioRender::R_StudioEyeballGlint( const eyeballstate_t *pstate, IMaterialVar *pGlintVar, |
|
const Vector& vright, const Vector& vup, const Vector& r_origin ) |
|
{ |
|
// Kick off a PIX event, since this process encompasses a bunch of locks etc... |
|
CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); |
|
PIXEVENT( pRenderContext, "GenerateEyeballGlint" ); |
|
|
|
// Don't do a procedural glint texture if there are enough pixels covered by the eyeball onscreen, |
|
// and the eye isn't backfaced. |
|
if ( m_pGlintLODTexture && r_glint_alwaysdraw.GetInt() == 0 ) |
|
{ |
|
// backfaced or too small to bother? |
|
float pixelArea = pRenderContext->ComputePixelWidthOfSphere( pstate->org, pstate->peyeball->radius ); |
|
if( |
|
// FIXME: this backface doesn't work for something that isn't a plane. |
|
// DotProduct( pstate->forward, m_ViewPlaneNormal ) > 0.0f || |
|
pixelArea < m_pRC->m_Config.fEyeGlintPixelWidthLODThreshold ) |
|
{ |
|
// use black glint texture |
|
pGlintVar->SetTextureValue( m_pGlintLODTexture ); |
|
return; |
|
} |
|
} |
|
|
|
// Legacy method for DX8 |
|
if ( !IsX360() && ( r_glint_procedural.GetInt() || g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) ) |
|
{ |
|
// Set up the texture regenerator |
|
s_GlintTextureRegen.m_pVRight = &vright; |
|
s_GlintTextureRegen.m_pVUp = &vup; |
|
s_GlintTextureRegen.m_pROrigin = &r_origin; |
|
s_GlintTextureRegen.m_pState = pstate; |
|
s_GlintTextureRegen.m_pStudioRender = this; |
|
|
|
// This will cause the glint texture to be re-generated and then downloaded |
|
s_pProcGlint->Download( ); |
|
|
|
// This is necessary to make sure we don't reconstitute the bits |
|
// after coming back from a task switch |
|
s_GlintTextureRegen.m_pStudioRender = NULL; |
|
|
|
// Use the normal glint instead of the black glint |
|
pGlintVar->SetTextureValue( s_pProcGlint ); |
|
} |
|
else // Queued hardware version |
|
{ |
|
// Make sure we know the correct size of the glint texture |
|
m_GlintWidth = m_pGlintTexture->GetActualWidth(); |
|
m_GlintHeight = m_pGlintTexture->GetActualHeight(); |
|
|
|
// Render glint render target |
|
ITexture *pUseGlintTexture = RenderGlintTexture( pstate, vright, vup, r_origin ); |
|
|
|
// Use the normal glint instead of the black glint |
|
pGlintVar->SetTextureValue( pUseGlintTexture ); |
|
} |
|
} |
|
|
|
void CStudioRender::ComputeGlintTextureProjection( eyeballstate_t const* pState, |
|
const Vector& vright, const Vector& vup, matrix3x4_t& mat ) |
|
{ |
|
// project eyeball into screenspace texture |
|
float scale = 1.0 / (pState->peyeball->radius * 2); |
|
VectorScale( &vright.x, scale, mat[0] ); |
|
VectorScale( &vup.x, scale, mat[1] ); |
|
|
|
mat[0][3] = -DotProduct( pState->org.Base(), mat[0] ) + 0.5; |
|
mat[1][3] = -DotProduct( pState->org.Base(), mat[1] ) + 0.5; |
|
} |
|
|
|
|
|
/* |
|
void R_MouthLighting( int count, const Vector *psrcverts, const Vector *psrcnorms, Vector4D *pdestlightvalues ) |
|
{ |
|
Vector forward; |
|
|
|
if (m_pStudioHdr->nummouths < 1) return; |
|
|
|
mstudiomouth_t *pMouth = r_pstudiohdr->pMouth( 0 ); // FIXME: this needs to get the mouth index from the shader |
|
|
|
float fIllum = m_FlexWeights[pMouth->flexdesc]; |
|
if (fIllum < 0) fIllum = 0; |
|
if (fIllum > 1) fIllum = 1; |
|
fIllum = LinearToTexture( fIllum ) / 255.0; |
|
|
|
|
|
VectorRotate( pMouth->forward, g_StudioInternalState.boneToWorld[ pMouth->bone ], forward ); |
|
|
|
for (int i = 0; i < count; i++) |
|
{ |
|
float dot = -DotProduct( psrcnorms[i], forward ); |
|
if (dot > 0) |
|
{ |
|
dot = LinearToTexture( dot ) / 255.0; // FIXME: this isn't robust |
|
VectorScale( pdestlightvalues[i], dot, pdestlightvalues[i] ); |
|
} |
|
else |
|
VectorFill( pdestlightvalues[i], 0 ); |
|
|
|
VectorScale( pdestlightvalues[i], fIllum, pdestlightvalues[i] ); |
|
} |
|
} |
|
*/ |
|
|
|
void CStudioRender::R_MouthComputeLightingValues( float& fIllum, Vector& forward ) |
|
{ |
|
// FIXME: this needs to get the mouth index from the shader |
|
mstudiomouth_t *pMouth = m_pStudioHdr->pMouth( 0 ); |
|
|
|
fIllum = m_pFlexWeights[pMouth->flexdesc]; |
|
if (fIllum < 0) fIllum = 0; |
|
if (fIllum > 1) fIllum = 1; |
|
fIllum = LinearToTexture( fIllum ) / 255.0; |
|
|
|
VectorRotate( pMouth->forward, m_pBoneToWorld[ pMouth->bone ], forward ); |
|
} |
|
|
|
void CStudioRender::R_MouthLighting( float fIllum, const Vector& normal, const Vector& forward, Vector &light ) |
|
{ |
|
float dot = -DotProduct( normal, forward ); |
|
if (dot > 0) |
|
{ |
|
VectorScale( light, dot * fIllum, light ); |
|
} |
|
else |
|
{ |
|
VectorFill( light, 0 ); |
|
} |
|
} |
|
|
|
static unsigned int illumVarCache = 0; |
|
static unsigned int forwardVarCache = 0; |
|
void CStudioRender::R_MouthSetupVertexShader( IMaterial* pMaterial ) |
|
{ |
|
if (!pMaterial) |
|
return; |
|
|
|
// FIXME: this needs to get the mouth index from the shader |
|
mstudiomouth_t *pMouth = m_pStudioHdr->pMouth( 0 ); |
|
|
|
// Don't deal with illum gamma, we apply it at a different point |
|
// for vertex shaders |
|
float fIllum = m_pFlexWeights[pMouth->flexdesc]; |
|
if (fIllum < 0) fIllum = 0; |
|
if (fIllum > 1) fIllum = 1; |
|
|
|
Vector forward; |
|
VectorRotate( pMouth->forward, m_pBoneToWorld[ pMouth->bone ], forward ); |
|
forward *= -1; |
|
|
|
IMaterialVar* pIllumVar = pMaterial->FindVarFast( "$illumfactor", &illumVarCache ); |
|
if (pIllumVar) |
|
{ |
|
pIllumVar->SetFloatValue( fIllum ); |
|
} |
|
|
|
IMaterialVar* pFowardVar = pMaterial->FindVarFast( "$forward", &forwardVarCache ); |
|
if (pFowardVar) |
|
{ |
|
pFowardVar->SetVecValue( forward.Base(), 3 ); |
|
} |
|
}
|
|
|