/////////////////////////////////////////////////////////////////////////////// // Copyright (C) Microsoft Corporation, 2000. // // ctexfilt.cpp // // Direct3D Reference Device - Cube Texture Map Filtering // /////////////////////////////////////////////////////////////////////////////// #include "pch.cpp" #pragma hdrstop //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void RefRast::ComputeCubeTextureFilter( int iStage, FLOAT fCrd[] ) { #define POS_NX 1 #define POS_NY 2 #define POS_NZ 3 #define NEG_NORM 4 #define NEG_NX (NEG_NORM | POS_NX) #define NEG_NY (NEG_NORM | POS_NY) #define NEG_NZ (NEG_NORM | POS_NZ) // determine which map face the texture coordinate normal is facing UINT uMap; if ( fabs(fCrd[0]) > fabs(fCrd[1]) ) { if ( fabs(fCrd[0]) > fabs(fCrd[2]) ) uMap = POS_NX | ((fCrd[0] < 0.0) ? (NEG_NORM) : 0); else uMap = POS_NZ | ((fCrd[2] < 0.0) ? (NEG_NORM) : 0); } else { if ( fabs(fCrd[1]) > fabs(fCrd[2]) ) uMap = POS_NY | ((fCrd[1] < 0.0) ? (NEG_NORM) : 0); else uMap = POS_NZ | ((fCrd[2] < 0.0) ? (NEG_NORM) : 0); } // munged texture coordinate and gradient info for cubemaps D3DCUBEMAP_FACES Face; // face index (0..5) to which normal is (mostly) pointing FLOAT fMajor; // coord in major direction FLOAT fMapCrd[2]; // coords into 2D map FLOAT fMajorGrad[2]; // dMajor/d(X,Y) FLOAT fMapGrad[2][2]; // d(U/Major,V/Major)/d(X,Y) #define _MapFaceParams( _Face, _IM, _bFlipM, _IU, _bFlipU, _IV, _bFlipV ) \ { \ Face = D3DCUBEMAP_FACE_##_Face; \ fMajor = (_bFlipM) ? (-fCrd[_IM]) : ( fCrd[_IM]); \ fMapCrd[0] = (_bFlipU) ? (-fCrd[_IU]) : ( fCrd[_IU]); \ fMapCrd[1] = (_bFlipV) ? (-fCrd[_IV]) : ( fCrd[_IV]); \ fMajorGrad[0] = m_TexCvg[iStage].fGradients[_IM][0]; if (_bFlipM) fMajorGrad[0] = -fMajorGrad[0]; \ fMajorGrad[1] = m_TexCvg[iStage].fGradients[_IM][1]; if (_bFlipM) fMajorGrad[1] = -fMajorGrad[1]; \ fMapGrad[0][0] = m_TexCvg[iStage].fGradients[_IU][0]; if (_bFlipU) fMapGrad[0][0] = -fMapGrad[0][0]; \ fMapGrad[0][1] = m_TexCvg[iStage].fGradients[_IU][1]; if (_bFlipU) fMapGrad[0][1] = -fMapGrad[0][1]; \ fMapGrad[1][0] = m_TexCvg[iStage].fGradients[_IV][0]; if (_bFlipV) fMapGrad[1][0] = -fMapGrad[1][0]; \ fMapGrad[1][1] = m_TexCvg[iStage].fGradients[_IV][1]; if (_bFlipV) fMapGrad[1][1] = -fMapGrad[1][1]; \ } switch (uMap) { case POS_NX: _MapFaceParams( POSITIVE_X, 0,0, 2,1, 1,1 ); break; case POS_NY: _MapFaceParams( POSITIVE_Y, 1,0, 0,0, 2,0 ); break; case POS_NZ: _MapFaceParams( POSITIVE_Z, 2,0, 0,0, 1,1 ); break; case NEG_NX: _MapFaceParams( NEGATIVE_X, 0,1, 2,0, 1,1 ); break; case NEG_NY: _MapFaceParams( NEGATIVE_Y, 1,1, 0,0, 2,1 ); break; case NEG_NZ: _MapFaceParams( NEGATIVE_Z, 2,1, 0,1, 1,1 ); break; } // compute gradients prior to normalizing map coords FLOAT fInvMajor = 1.F/fMajor; if ( m_TexFlt[iStage].CvgFilter != D3DTEXF_NONE ) { // Compute d(U/Major)/dx, d(U/Major)/dy, d(V/Major)/dx, d(V/Major)/dy. // // i.e., for d(U/Major))/dx // Given: U' = unprojected U0 coord (fMapCrd[0]) // U0 = U'/Major (fMapCrd[0]/fMajor) // U1 = (U' + dU'/dX)/(Major + dMajor/dX) // // d(U/Major)/dx = U1 - U0 // = (Major*(dU'/dX) - U'*(dMajor/dX)) / (Major * (Major + dMajor/dX)) // (Use FLT_MAX if denominator is zero) float fDenom; fDenom = fMajor * (fMajor + fMajorGrad[0]); if( 0 == fDenom ) { fMapGrad[0][0] = fMapGrad[1][0] = FLT_MAX; } else { fDenom = 1.F/fDenom; fMapGrad[0][0] = (fMajor*fMapGrad[0][0] - fMapCrd[0]*fMajorGrad[0])*fDenom; fMapGrad[1][0] = (fMajor*fMapGrad[1][0] - fMapCrd[1]*fMajorGrad[0])*fDenom; } fDenom = fMajor * (fMajor + fMajorGrad[1]); if( 0 == fDenom ) { fMapGrad[0][1] = fMapGrad[1][1] = FLT_MAX; } else { fDenom = 1.F/fDenom; fMapGrad[0][1] = (fMajor*fMapGrad[0][1] - fMapCrd[0]*fMajorGrad[1])*fDenom; fMapGrad[1][1] = (fMajor*fMapGrad[1][1] - fMapCrd[1]*fMajorGrad[1])*fDenom; } // scale gradients to texture LOD 0 size; scale by .5F to match coord scale below fMapGrad[0][0] *= m_pRD->m_pTexture[iStage]->m_fTexels[0][0]*.5F; fMapGrad[0][1] *= m_pRD->m_pTexture[iStage]->m_fTexels[0][0]*.5F; fMapGrad[1][0] *= m_pRD->m_pTexture[iStage]->m_fTexels[0][1]*.5F; fMapGrad[1][1] *= m_pRD->m_pTexture[iStage]->m_fTexels[0][1]*.5F; ComputeCubeCoverage( fMapGrad, m_TexCvg[iStage].fLOD ); ComputePerLODControls( iStage ); } // normalize map coords (-1. to 1. range), then map to 0. to 1. fMapCrd[0] = (fMapCrd[0]*fInvMajor)*.5F + .5F; fMapCrd[1] = (fMapCrd[1]*fInvMajor)*.5F + .5F; int iL; D3DTEXTUREFILTERTYPE Filter = m_TexCvg[iStage].bMagnify ? m_TexFlt[iStage].MagFilter : m_TexFlt[iStage].MinFilter; switch ( Filter ) { default: case D3DTEXF_POINT: for ( iL = 0; iL < m_TexCvg[iStage].cLOD; iL++ ) { m_TexFlt[iStage].pSamples[iL].iLOD = Face + 6*m_TexCvg[iStage].iLODMap[iL]; m_TexFlt[iStage].pSamples[iL].fWgt = m_TexCvg[iStage].fLODFrc[iL]; ComputePointSampleCoords( iStage, m_TexFlt[iStage].pSamples[iL].iLOD, fMapCrd, m_TexFlt[iStage].pSamples[iL].iCrd ); m_TexFlt[iStage].cSamples++; } break; case D3DTEXF_LINEAR: for ( iL = 0; iL < m_TexCvg[iStage].cLOD; iL++ ) { if ( 0 == m_TexCvg[iStage].iLODMap[iL] ) { // TODO: correct sampling position near edges on map 0 } INT32 iCrdMap[2][2]; FLOAT fCrdFrc[2][2]; ComputeLinearSampleCoords( iStage, 6*m_TexCvg[iStage].iLODMap[iL]+Face, fMapCrd, iCrdMap[0], iCrdMap[1], fCrdFrc[0], fCrdFrc[1] ); SetUpCubeMapLinearSample( iStage, Face, 6*m_TexCvg[iStage].iLODMap[iL]+Face, m_TexCvg[iStage].fLODFrc[iL], iCrdMap, fCrdFrc ); } break; } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- void RefRast::SetUpCubeMapLinearSample( int iStage, D3DCUBEMAP_FACES Face, INT32 iLODMap, FLOAT fLODScale, INT32 (*iCrd)[2], FLOAT (*fFrc)[2] ) { int iC,iS; INT32 iCrdMax[2]; iCrdMax[0] = m_pRD->m_pTexture[iStage]->m_cTexels[iLODMap][0] - 1; iCrdMax[1] = m_pRD->m_pTexture[iStage]->m_cTexels[iLODMap][1] - 1; // form flags indicating if sample coordinate is out in either direction UINT uOut[2][2] = { 0, 0, 0, 0, }; for ( iC = 0; iC < 2; iC++ ) { if ( iCrd[iC][0] < 0 ) uOut[iC][0] = 1; if ( iCrd[iC][0] > iCrdMax[0] ) uOut[iC][0] = 2; if ( iCrd[iC][1] < 0 ) uOut[iC][1] = 1; if ( iCrd[iC][1] > iCrdMax[1] ) uOut[iC][1] = 2; } // compute sample weights and per-sample out flags FLOAT fWgtS[4]; BOOL bOutS[4]; for ( iS = 0; iS < 4; iS ++ ) { fWgtS[iS] = fLODScale*fFrc[iS&1][0]*fFrc[iS>>1][1]; bOutS[iS] = uOut[iS&1][0] || uOut[iS>>1][1]; } // compute per-sample coords; discard samples which are off in corner; // conditionally remap to adjacent face INT32 iCrdS[4][2]; D3DCUBEMAP_FACES FaceS[4]; for ( iS = 0; iS < 4; iS ++ ) { iCrdS[iS][0] = iCrd[iS&1][0]; iCrdS[iS][1] = iCrd[iS>>1][1]; FaceS[iS] = Face; if ( uOut[iS&1][0] && uOut[iS>>1][1] ) { // sample is out on both sides, so don't take this sample (set weight to // zero) and divide it's weight evenly between the two singly-out samples FLOAT fWgtDist = fWgtS[iS]*.5f; fWgtS[iS] = 0.f; for ( int iSp = 0; iSp < 4; iSp ++ ) { if (iSp == iS) continue; if (bOutS[iSp]) fWgtS[iSp] += fWgtDist; // will hit 2 of 4 } continue; } if ( bOutS[iS] ) { // sample is out on one side - remap coordinate only adjacent face DoCubeRemap( iCrdS[iS], iCrdMax, FaceS[iS], uOut[iS&1][0], uOut[iS>>1][1] ); } } // form the samples TextureSample* pS = &m_TexFlt[iStage].pSamples[m_TexFlt[iStage].cSamples]; for ( iS = 0; iS < 4; iS ++ ) { pS->iLOD = iLODMap - Face + FaceS[iS]; pS->fWgt = fWgtS[iS]; pS->iCrd[0] = iCrdS[iS][0]; pS->iCrd[1] = iCrdS[iS][1]; pS++; m_TexFlt[iStage].cSamples++; } } // // uCubeEdgeTable // // This table looks up how to map a given [0] and [1] that are out of range // on their primary face. The first (leftmost) index to the table is the current // face. The second index is 0 if [1] is in range, 1 if [1] is negative // and 2 if [1] is larger than the texture. Likewise, the last index is 0 // if [0] is in range, 1 if [0] is negative, and 2 if [0] is larger than // than the texture. // // defines for the actions returned by the uCubeEdgeTable // #define CET_FACEMASK 0x0F // new face #define CET_0MASK 0x30 // coord [0] mask #define CET_00 0x00 // new face [0] is old face [0] #define CET_0c0 0x10 // new face [0] is old face ~[0] #define CET_01 0x20 // new face [0] is old face [1] #define CET_0c1 0x30 // new face [0] is old face ~[1] #define CET_1MASK 0xC0 // coord [1] mask #define CET_10 0x00 // new face [1] is old face [0] #define CET_1c0 0x40 // new face [1] is old face ~[0] #define CET_11 0x80 // new face [1] is old face [1] #define CET_1c1 0xC0 // new face [1] is old face ~[1] #define CET_INVALID 0xFF // invalid entry (out on two sides) #define _SetCET( _Face, _Crd0, _Crd1 ) (_Face)|(CET_0##_Crd0)|(CET_1##_Crd1) static UINT CubeEdgeTable[6][3][3] = { { { _SetCET( 0, 0, 1 ), _SetCET( 4, c0, 1 ), _SetCET( 5, c0, 1 ), }, { _SetCET( 2, c1, c0 ), CET_INVALID, CET_INVALID, }, { _SetCET( 3, 1, 0 ), CET_INVALID, CET_INVALID, }, }, { { _SetCET( 1, 0, 1 ), _SetCET( 5, c0, 1 ), _SetCET( 4, c0, 1 ), }, { _SetCET( 2, 1, 0 ), CET_INVALID, CET_INVALID, }, { _SetCET( 3, c1, c0 ), CET_INVALID, CET_INVALID, }, }, { { _SetCET( 2, 0, 1 ), _SetCET( 1, 1, 0 ), _SetCET( 0, c1, c0 ), }, { _SetCET( 5, c0, 1 ), CET_INVALID, CET_INVALID, }, { _SetCET( 4, 0, c1 ), CET_INVALID, CET_INVALID, }, }, { { _SetCET( 3, 0, 1 ), _SetCET( 1, c1, c0 ), _SetCET( 0, 1, 0 ), }, { _SetCET( 4, 0, c1 ), CET_INVALID, CET_INVALID, }, { _SetCET( 5, c0, 1 ), CET_INVALID, CET_INVALID, }, }, { { _SetCET( 4, 0, 1 ), _SetCET( 1, c0, 1 ), _SetCET( 0, c0, 1 ), }, { _SetCET( 2, 0, c1 ), CET_INVALID, CET_INVALID, }, { _SetCET( 3, 0, c1 ), CET_INVALID, CET_INVALID, }, }, { { _SetCET( 5, 0, 1 ), _SetCET( 0, c0, 1 ), _SetCET( 1, c0, 1 ), }, { _SetCET( 2, c0, 1 ), CET_INVALID, CET_INVALID, }, { _SetCET( 3, c0, 1 ), CET_INVALID, CET_INVALID, }, }, }; //----------------------------------------------------------------------------- // // DoCubeRemap - Interprets the edge table and munges coords and face. // //----------------------------------------------------------------------------- void DoCubeRemap( INT32 iCrd[], INT32 iCrdMax[], D3DCUBEMAP_FACES& Face, UINT uOut0, UINT uOut1) { UINT Table = CubeEdgeTable[Face][uOut1][uOut0]; _ASSERT( Table != CET_INVALID, "Illegal cube map lookup" ); INT32 iCrdIn[2]; iCrdIn[0] = iCrd[0]; iCrdIn[1] = iCrd[1]; switch ( Table & CET_0MASK ) { default: case CET_00: iCrd[0] = iCrdIn[0]; break; case CET_0c0: iCrd[0] = iCrdMax[0]-iCrdIn[0]; break; case CET_01: iCrd[0] = iCrdIn[1]; break; case CET_0c1: iCrd[0] = iCrdMax[1]-iCrdIn[1]; break; } switch ( Table & CET_1MASK ) { default: case CET_10: iCrd[1] = iCrdIn[0]; break; case CET_1c0: iCrd[1] = iCrdMax[0]-iCrdIn[0]; break; case CET_11: iCrd[1] = iCrdIn[1]; break; case CET_1c1: iCrd[1] = iCrdMax[1]-iCrdIn[1]; break; } Face = (D3DCUBEMAP_FACES)(Table & CET_FACEMASK); } //----------------------------------------------------------------------------- // // Computes level of detail for cube mapping, looks better if // we err on the side of fuzziness. // //----------------------------------------------------------------------------- void ComputeCubeCoverage( const FLOAT (*fGradients)[2], FLOAT& fLOD ) { // compute length of coverage in U and V axis FLOAT fLenX = RR_LENGTH( fGradients[0][0], fGradients[1][0] ); FLOAT fLenY = RR_LENGTH( fGradients[0][1], fGradients[1][1] ); FLOAT fCoverage; #if 0 // take average since one length can be pathologically small // for large areas of triangles when cube mapping fCoverage = (fLenX+fLenY)/2; #else // use the MAX of the lengths fCoverage = MAX(fLenX,fLenY); #endif // take log2 of coverage for LOD fLOD = RR_LOG2(fCoverage); } /////////////////////////////////////////////////////////////////////////////// // end