You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp

2322 lines
91 KiB
C++

//=============================================================================
// (C) 2005 ATI Research, Inc., All rights reserved.
//=============================================================================
// modifications by Crytek GmbH
// modifications by Amazon
#include <ImageProcessing_precompiled.h>
#include "CCubeMapProcessor.h"
#include <AzCore/std/bind/bind.h>
#include <AzCore/std/string/string.h>
#define CP_PI 3.14159265358979323846
namespace ImageProcessingAtom
{
//------------------------------------------------------------------------------
// D3D cube map face specification
// mapping from 3D x,y,z cube map lookup coordinates
// to 2D within face u,v coordinates
//
// --------------------> U direction
// | (within-face texture space)
// | _____
// | | |
// | | +Y |
// | _____|_____|_____ _____
// | | | | | |
// | | -X | +Z | +X | -Z |
// | |_____|_____|_____|_____|
// | | |
// | | -Y |
// | |_____|
// |
// v V direction
// (within-face texture space)
//------------------------------------------------------------------------------
//Information about neighbors and how texture coorrdinates change across faces
// in ORDER of left, right, top, bottom (e.g. edges corresponding to u=0,
// u=1, v=0, v=1 in the 2D coordinate system of the particular face.
//Note this currently assumes the D3D cube face ordering and orientation
CPCubeMapNeighbor sg_CubeNgh[6][4] =
{
//XPOS face
{{CP_FACE_Z_POS, CP_EDGE_RIGHT },
{CP_FACE_Z_NEG, CP_EDGE_LEFT },
{CP_FACE_Y_POS, CP_EDGE_RIGHT },
{CP_FACE_Y_NEG, CP_EDGE_RIGHT }},
//XNEG face
{{CP_FACE_Z_NEG, CP_EDGE_RIGHT },
{CP_FACE_Z_POS, CP_EDGE_LEFT },
{CP_FACE_Y_POS, CP_EDGE_LEFT },
{CP_FACE_Y_NEG, CP_EDGE_LEFT }},
//YPOS face
{{CP_FACE_X_NEG, CP_EDGE_TOP },
{CP_FACE_X_POS, CP_EDGE_TOP },
{CP_FACE_Z_NEG, CP_EDGE_TOP },
{CP_FACE_Z_POS, CP_EDGE_TOP }},
//YNEG face
{{CP_FACE_X_NEG, CP_EDGE_BOTTOM},
{CP_FACE_X_POS, CP_EDGE_BOTTOM},
{CP_FACE_Z_POS, CP_EDGE_BOTTOM},
{CP_FACE_Z_NEG, CP_EDGE_BOTTOM}},
//ZPOS face
{{CP_FACE_X_NEG, CP_EDGE_RIGHT },
{CP_FACE_X_POS, CP_EDGE_LEFT },
{CP_FACE_Y_POS, CP_EDGE_BOTTOM },
{CP_FACE_Y_NEG, CP_EDGE_TOP }},
//ZNEG face
{{CP_FACE_X_POS, CP_EDGE_RIGHT },
{CP_FACE_X_NEG, CP_EDGE_LEFT },
{CP_FACE_Y_POS, CP_EDGE_TOP },
{CP_FACE_Y_NEG, CP_EDGE_BOTTOM }}
};
//3x2 matrices that map cube map indexing vectors in 3d
// (after face selection and divide through by the
// _ABSOLUTE VALUE_ of the max coord)
// into NVC space
//Note this currently assumes the D3D cube face ordering and orientation
#define CP_UDIR 0
#define CP_VDIR 1
#define CP_FACEAXIS 2
float sgFace2DMapping[6][3][3] = {
//XPOS face
{{ 0, 0, -1}, //u towards negative Z
{ 0, -1, 0}, //v towards negative Y
{1, 0, 0}}, //pos X axis
//XNEG face
{{0, 0, 1}, //u towards positive Z
{0, -1, 0}, //v towards negative Y
{-1, 0, 0}}, //neg X axis
//YPOS face
{{1, 0, 0}, //u towards positive X
{0, 0, 1}, //v towards positive Z
{0, 1 , 0}}, //pos Y axis
//YNEG face
{{1, 0, 0}, //u towards positive X
{0, 0 , -1}, //v towards negative Z
{0, -1 , 0}}, //neg Y axis
//ZPOS face
{{1, 0, 0}, //u towards positive X
{0, -1, 0}, //v towards negative Y
{0, 0, 1}}, //pos Z axis
//ZNEG face
{{-1, 0, 0}, //u towards negative X
{0, -1, 0}, //v towards negative Y
{0, 0, -1}}, //neg Z axis
};
//The 12 edges of the cubemap, (entries are used to index into the neighbor table)
// this table is used to average over the edges.
int32 sg_CubeEdgeList[12][2] = {
{CP_FACE_X_POS, CP_EDGE_LEFT},
{CP_FACE_X_POS, CP_EDGE_RIGHT},
{CP_FACE_X_POS, CP_EDGE_TOP},
{CP_FACE_X_POS, CP_EDGE_BOTTOM},
{CP_FACE_X_NEG, CP_EDGE_LEFT},
{CP_FACE_X_NEG, CP_EDGE_RIGHT},
{CP_FACE_X_NEG, CP_EDGE_TOP},
{CP_FACE_X_NEG, CP_EDGE_BOTTOM},
{CP_FACE_Z_POS, CP_EDGE_TOP},
{CP_FACE_Z_POS, CP_EDGE_BOTTOM},
{CP_FACE_Z_NEG, CP_EDGE_TOP},
{CP_FACE_Z_NEG, CP_EDGE_BOTTOM}
};
//Information about which of the 8 cube corners are correspond to the
// the 4 corners in each cube face
// the order is upper left, upper right, lower left, lower right
int32 sg_CubeCornerList[6][4] = {
{ CP_CORNER_PPP, CP_CORNER_PPN, CP_CORNER_PNP, CP_CORNER_PNN }, // XPOS face
{ CP_CORNER_NPN, CP_CORNER_NPP, CP_CORNER_NNN, CP_CORNER_NNP }, // XNEG face
{ CP_CORNER_NPN, CP_CORNER_PPN, CP_CORNER_NPP, CP_CORNER_PPP }, // YPOS face
{ CP_CORNER_NNP, CP_CORNER_PNP, CP_CORNER_NNN, CP_CORNER_PNN }, // YNEG face
{ CP_CORNER_NPP, CP_CORNER_PPP, CP_CORNER_NNP, CP_CORNER_PNP }, // ZPOS face
{ CP_CORNER_PPN, CP_CORNER_NPN, CP_CORNER_PNN, CP_CORNER_NNN } // ZNEG face
};
//--------------------------------------------------------------------------------------
// Convert cubemap face texel coordinates and face idx to 3D vector
// note the U and V coords are integer coords and range from 0 to size-1
// this routine can be used to generate a normalizer cube map
//--------------------------------------------------------------------------------------
void TexelCoordToVect(int32 a_FaceIdx, float a_U, float a_V, int32 a_Size, float *a_XYZ)
{
float nvcU, nvcV;
float tempVec[3];
//scale up to [-1, 1] range (inclusive)
nvcU = (2.0f * ((float)a_U + 0.5f) / a_Size ) - 1.0f;
nvcV = (2.0f * ((float)a_V + 0.5f) / a_Size ) - 1.0f;
//generate x,y,z vector (xform 2d NVC coord to 3D vector)
//U contribution
VM_SCALE3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_UDIR], nvcU);
//V contribution
VM_SCALE3(tempVec, sgFace2DMapping[a_FaceIdx][CP_VDIR], nvcV);
VM_ADD3(a_XYZ, tempVec, a_XYZ);
//add face axis
VM_ADD3(a_XYZ, sgFace2DMapping[a_FaceIdx][CP_FACEAXIS], a_XYZ);
//normalize vector
VM_NORM3(a_XYZ, a_XYZ);
}
//--------------------------------------------------------------------------------------
// Convert 3D vector to cubemap face texel coordinates and face idx
// note the U and V coords are integer coords and range from 0 to size-1
// this routine can be used to generate a normalizer cube map
//
// returns face IDX and texel coords
//--------------------------------------------------------------------------------------
void VectToTexelCoord(float *a_XYZ, int32 a_Size, int32 *a_FaceIdx, float *a_U, float *a_V )
{
float nvcU, nvcV;
float absXYZ[3];
float maxCoord;
float onFaceXYZ[3];
int32 faceIdx;
float u, v;
//absolute value 3
VM_ABS3(absXYZ, a_XYZ);
if( (absXYZ[0] >= absXYZ[1]) && (absXYZ[0] >= absXYZ[2]) )
{
maxCoord = absXYZ[0];
if(a_XYZ[0] >= 0) //face = XPOS
{
faceIdx = CP_FACE_X_POS;
}
else
{
faceIdx = CP_FACE_X_NEG;
}
}
else if ( (absXYZ[1] >= absXYZ[0]) && (absXYZ[1] >= absXYZ[2]) )
{
maxCoord = absXYZ[1];
if(a_XYZ[1] >= 0) //face = XPOS
{
faceIdx = CP_FACE_Y_POS;
}
else
{
faceIdx = CP_FACE_Y_NEG;
}
}
else // if( (absXYZ[2] > absXYZ[0]) && (absXYZ[2] > absXYZ[1]) )
{
maxCoord = absXYZ[2];
if(a_XYZ[2] >= 0) //face = XPOS
{
faceIdx = CP_FACE_Z_POS;
}
else
{
faceIdx = CP_FACE_Z_NEG;
}
}
//divide through by max coord so face vector lies on cube face
VM_SCALE3(onFaceXYZ, a_XYZ, 1.0f/maxCoord);
nvcU = VM_DOTPROD3(sgFace2DMapping[ faceIdx ][CP_UDIR], onFaceXYZ );
nvcV = VM_DOTPROD3(sgFace2DMapping[ faceIdx ][CP_VDIR], onFaceXYZ );
u = (a_Size - 1.0f) * 0.5f * (nvcU + 1.0f);
v = (a_Size - 1.0f) * 0.5f * (nvcV + 1.0f);
*a_FaceIdx = faceIdx;
*a_U = u;
*a_V = v;
}
//--------------------------------------------------------------------------------------
// gets texel ptr in a cube map given a direction vector, and an array of
// CImageSurfaces that represent the cube faces.
//
//--------------------------------------------------------------------------------------
CP_ITYPE *GetCubeMapTexelPtr(float *a_XYZ, CImageSurface *a_Surface)
{
float u, v;
int32 faceIdx;
//get face idx and u, v texel coordinate in face
VectToTexelCoord(a_XYZ, a_Surface[0].m_Width, &faceIdx, &u, &v );
u = VM_MIN((int32)u, a_Surface[0].m_Width - 1);
v = VM_MIN((int32)v, a_Surface[0].m_Width - 1);
return( a_Surface[faceIdx].GetSurfaceTexelPtr(u, v) );
}
//--------------------------------------------------------------------------------------
// returns a bilinear filtered texel value
//
//--------------------------------------------------------------------------------------
void GetCubeMapTexelBilinear(float *a_XYZ, CImageSurface *a_Surface, CP_ITYPE* result, int32 numChannels)
{
float u, v;
int32 faceIdx;
//get face idx and u, v texel coordinate in face
VectToTexelCoord(a_XYZ, a_Surface[0].m_Width, &faceIdx, &u, &v);
//sample the four points in the quad around this point
int32 uPoint = (int32)u;
int32 vPoint = (int32)v;
//top Left
int32 uQuad = (int32)uPoint;
int32 vQuad = (int32)vPoint;
CP_ITYPE* sampleTL = (a_Surface[faceIdx].GetSurfaceTexelPtr(uQuad, vQuad));
//top right
uQuad = VM_MIN(uPoint + 1, a_Surface[0].m_Width - 1);
vQuad = vPoint;
CP_ITYPE* sampleTR = (a_Surface[faceIdx].GetSurfaceTexelPtr(uQuad, vQuad));
//bottom left
uQuad = uPoint;
vQuad = VM_MIN(vPoint + 1, a_Surface[0].m_Width - 1);
CP_ITYPE* sampleBL = (a_Surface[faceIdx].GetSurfaceTexelPtr(uQuad, vQuad));
//bottom right
uQuad = VM_MIN(uPoint + 1, a_Surface[0].m_Width - 1);
vQuad = VM_MIN(vPoint + 1, a_Surface[0].m_Width - 1);
CP_ITYPE* sampleBR = (a_Surface[faceIdx].GetSurfaceTexelPtr(uQuad, vQuad));
//compute interpolated value
float uDelta = u - uPoint;
float vDelta = v - vPoint;
for (uint32 i = 0; i < (uint32)numChannels; i++)
{
float topValue = sampleTL[i] * (1.0f - uDelta) + sampleTR[i] * uDelta;
float bottomValue = sampleBL[i] * (1.0f - uDelta) + sampleBR[i] * uDelta;
result[i] = topValue * (1.0f - vDelta) + bottomValue * vDelta;
}
}
//--------------------------------------------------------------------------------------
// Compute solid angle of given texel in cubemap face for weighting taps in the
// kernel by the area they project to on the unit sphere.
//
// Note that this code uses an approximation to the solid angle, by treating the
// two triangles that make up the quad comprising the texel as planar. If more
// accuracy is required, the solid angle per triangle lying on the sphere can be
// computed using the sum of the interior angles - PI.
//
//--------------------------------------------------------------------------------------
float TexelCoordSolidAngle(int32 a_FaceIdx, float a_U, float a_V, int32 a_Size)
{
float cornerVect[4][3];
double cornerVect64[4][3];
float halfTexelStep = 0.5f; //note u, and v are in texel coords (where each texel is one unit)
double edgeVect0[3];
double edgeVect1[3];
double xProdVect[3];
double texelArea;
//compute 4 corner vectors of texel
TexelCoordToVect(a_FaceIdx, a_U - halfTexelStep, a_V - halfTexelStep, a_Size, cornerVect[0] );
TexelCoordToVect(a_FaceIdx, a_U - halfTexelStep, a_V + halfTexelStep, a_Size, cornerVect[1] );
TexelCoordToVect(a_FaceIdx, a_U + halfTexelStep, a_V - halfTexelStep, a_Size, cornerVect[2] );
TexelCoordToVect(a_FaceIdx, a_U + halfTexelStep, a_V + halfTexelStep, a_Size, cornerVect[3] );
VM_NORM3_UNTYPED(cornerVect64[0], cornerVect[0] );
VM_NORM3_UNTYPED(cornerVect64[1], cornerVect[1] );
VM_NORM3_UNTYPED(cornerVect64[2], cornerVect[2] );
VM_NORM3_UNTYPED(cornerVect64[3], cornerVect[3] );
//area of triangle defined by corners 0, 1, and 2
VM_SUB3_UNTYPED(edgeVect0, cornerVect64[1], cornerVect64[0] );
VM_SUB3_UNTYPED(edgeVect1, cornerVect64[2], cornerVect64[0] );
VM_XPROD3_UNTYPED(xProdVect, edgeVect0, edgeVect1 );
texelArea = 0.5f * sqrt( VM_DOTPROD3_UNTYPED(xProdVect, xProdVect ) );
//area of triangle defined by corners 1, 2, and 3
VM_SUB3_UNTYPED(edgeVect0, cornerVect64[2], cornerVect64[1] );
VM_SUB3_UNTYPED(edgeVect1, cornerVect64[3], cornerVect64[1] );
VM_XPROD3_UNTYPED(xProdVect, edgeVect0, edgeVect1 );
texelArea += 0.5f * sqrt( VM_DOTPROD3_UNTYPED(xProdVect, xProdVect ) );
return texelArea;
}
//--------------------------------------------------------------------------------------
//Builds a normalizer cubemap
//
// Takes in a cube face size, and an array of 6 surfaces to write the cube faces into
//
// Note that this normalizer cube map stores the vectors in unbiased -1 to 1 range.
// if _bx2 style scaled and biased vectors are needed, uncomment the SCALE and BIAS
// below
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::BuildNormalizerCubemap(int32 a_Size, CImageSurface *a_Surface )
{
int32 iCubeFace, u, v;
//iterate over cube faces
for(iCubeFace=0; iCubeFace<6; iCubeFace++)
{
a_Surface[iCubeFace].Clear();
a_Surface[iCubeFace].Init(a_Size, a_Size, 3);
//fast texture walk, build normalizer cube map
CP_ITYPE *texelPtr = a_Surface[iCubeFace].m_ImgData;
for(v=0; v < a_Surface[iCubeFace].m_Height; v++)
{
for(u=0; u < a_Surface[iCubeFace].m_Width; u++)
{
TexelCoordToVect(iCubeFace, (float)u, (float)v, a_Size, texelPtr);
//VM_SCALE3(texelPtr, texelPtr, 0.5f);
//VM_BIAS3(texelPtr, texelPtr, 0.5f);
texelPtr += a_Surface[iCubeFace].m_NumChannels;
}
}
}
}
//--------------------------------------------------------------------------------------
//Builds a normalizer cubemap, with the texels solid angle stored in the fourth component
//
//Takes in a cube face size, and an array of 6 surfaces to write the cube faces into
//
//Note that this normalizer cube map stores the vectors in unbiased -1 to 1 range.
// if _bx2 style scaled and biased vectors are needed, uncomment the SCALE and BIAS
// below
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::BuildNormalizerSolidAngleCubemap(int32 a_Size, CImageSurface *a_Surface )
{
//iterate over cube faces
for(int32 iCubeFace=0; iCubeFace<6; iCubeFace++)
{
a_Surface[iCubeFace].Clear();
a_Surface[iCubeFace].Init(a_Size, a_Size, 4); //First three channels for norm cube, and last channel for solid angle
}
//iterate over cube faces
for(int32 iCubeFace=0; iCubeFace<6; iCubeFace++)
{
const int32 height = a_Surface[iCubeFace].m_Height;
const int32 width = a_Surface[iCubeFace].m_Width;
for(int32 v=0; v<height; v++)
{
//fast texture walk, build normalizer cube map
CP_ITYPE *texelPtr = a_Surface[iCubeFace].m_ImgData + v * width * a_Surface[iCubeFace].m_NumChannels;
for(int32 u=0; u<width; u++)
{
TexelCoordToVect(iCubeFace, (float)u, (float)v, a_Size, texelPtr);
//VM_SCALE3(texelPtr, texelPtr, 0.5f);
//VM_BIAS3(texelPtr, texelPtr, 0.5f);
*(texelPtr + 3) = TexelCoordSolidAngle(iCubeFace, (float)u, (float)v, a_Size);
texelPtr += a_Surface[iCubeFace].m_NumChannels;
}
}
}
}
//--------------------------------------------------------------------------------------
//Clear filter extents for the 6 cube map faces
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::ClearFilterExtents(CBBoxInt32 *aFilterExtents)
{
int32 iCubeFaces;
for(iCubeFaces=0; iCubeFaces<6; iCubeFaces++)
{
aFilterExtents[iCubeFaces].Clear();
}
}
//--------------------------------------------------------------------------------------
//Define per-face bounding box filter extents
//
// These define conservative texel regions in each of the faces the filter can possibly
// process. When the pixels in the regions are actually processed, the dot product
// between the tap vector and the center tap vector is used to determine the weight of
// the tap and whether or not the tap is within the cone.
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::DetermineFilterExtents(float *a_CenterTapDir, int32 a_SrcSize, int32 a_BBoxSize,
CBBoxInt32 *a_FilterExtents )
{
int32 u, v;
int32 faceIdx;
int32 minU, minV, maxU, maxV;
int32 i;
//neighboring face and bleed over amount, and width of BBOX for
// left, right, top, and bottom edges of this face
int32 bleedOverAmount[4];
int32 bleedOverBBoxMin[4];
int32 bleedOverBBoxMax[4];
int32 neighborFace;
int32 neighborEdge;
//get face idx, and u, v info from center tap dir
float uFloat, vFloat;
VectToTexelCoord(a_CenterTapDir, a_SrcSize, &faceIdx, &uFloat, &vFloat);
u = (int32)uFloat;
v = (int32)vFloat;
//define bbox size within face
a_FilterExtents[faceIdx].Augment(u - a_BBoxSize, v - a_BBoxSize, 0);
a_FilterExtents[faceIdx].Augment(u + a_BBoxSize, v + a_BBoxSize, 0);
a_FilterExtents[faceIdx].ClampMin(0, 0, 0);
a_FilterExtents[faceIdx].ClampMax(a_SrcSize-1, a_SrcSize-1, 0);
//u and v extent in face corresponding to center tap
minU = a_FilterExtents[faceIdx].m_minCoord[0];
minV = a_FilterExtents[faceIdx].m_minCoord[1];
maxU = a_FilterExtents[faceIdx].m_maxCoord[0];
maxV = a_FilterExtents[faceIdx].m_maxCoord[1];
//bleed over amounts for face across u=0 edge (left)
bleedOverAmount[0] = (a_BBoxSize - u);
bleedOverBBoxMin[0] = minV;
bleedOverBBoxMax[0] = maxV;
//bleed over amounts for face across u=1 edge (right)
bleedOverAmount[1] = (u + a_BBoxSize) - (a_SrcSize-1);
bleedOverBBoxMin[1] = minV;
bleedOverBBoxMax[1] = maxV;
//bleed over to face across v=0 edge (up)
bleedOverAmount[2] = (a_BBoxSize - v);
bleedOverBBoxMin[2] = minU;
bleedOverBBoxMax[2] = maxU;
//bleed over to face across v=1 edge (down)
bleedOverAmount[3] = (v + a_BBoxSize) - (a_SrcSize-1);
bleedOverBBoxMin[3] = minU;
bleedOverBBoxMax[3] = maxU;
//compute bleed over regions in neighboring faces
for(i=0; i<4; i++)
{
if(bleedOverAmount[i] > 0)
{
neighborFace = sg_CubeNgh[faceIdx][i].m_Face;
neighborEdge = sg_CubeNgh[faceIdx][i].m_Edge;
//For certain types of edge abutments, the bleedOverBBoxMin, and bleedOverBBoxMax need to
// be flipped: the cases are
// if a left edge mates with a left or bottom edge on the neighbor
// if a top edge mates with a top or right edge on the neighbor
// if a right edge mates with a right or top edge on the neighbor
// if a bottom edge mates with a bottom or left edge on the neighbor
//Seeing as the edges are enumerated as follows
// left =0
// right =1
// top =2
// bottom =3
//
// so if the edge enums are the same, or the sum of the enums == 3,
// the bbox needs to be flipped
if( (i == neighborEdge) || ((i+neighborEdge) == 3) )
{
bleedOverBBoxMin[i] = (a_SrcSize-1) - bleedOverBBoxMin[i];
bleedOverBBoxMax[i] = (a_SrcSize-1) - bleedOverBBoxMax[i];
}
//The way the bounding box is extended onto the neighboring face
// depends on which edge of neighboring face abuts with this one
switch(sg_CubeNgh[faceIdx][i].m_Edge)
{
case CP_EDGE_LEFT:
a_FilterExtents[neighborFace].Augment(0, bleedOverBBoxMin[i], 0);
a_FilterExtents[neighborFace].Augment(bleedOverAmount[i], bleedOverBBoxMax[i], 0);
break;
case CP_EDGE_RIGHT:
a_FilterExtents[neighborFace].Augment( (a_SrcSize-1), bleedOverBBoxMin[i], 0);
a_FilterExtents[neighborFace].Augment( (a_SrcSize-1) - bleedOverAmount[i], bleedOverBBoxMax[i], 0);
break;
case CP_EDGE_TOP:
a_FilterExtents[neighborFace].Augment(bleedOverBBoxMin[i], 0, 0);
a_FilterExtents[neighborFace].Augment(bleedOverBBoxMax[i], bleedOverAmount[i], 0);
break;
case CP_EDGE_BOTTOM:
a_FilterExtents[neighborFace].Augment(bleedOverBBoxMin[i], (a_SrcSize-1), 0);
a_FilterExtents[neighborFace].Augment(bleedOverBBoxMax[i], (a_SrcSize-1) - bleedOverAmount[i], 0);
break;
}
//clamp filter extents in non-center tap faces to remain within surface
a_FilterExtents[neighborFace].ClampMin(0, 0, 0);
a_FilterExtents[neighborFace].ClampMax(a_SrcSize-1, a_SrcSize-1, 0);
}
//If the bleed over amount bleeds past the adjacent face onto the opposite face
// from the center tap face, then process the opposite face entirely for now.
//Note that the cases in which this happens, what usually happens is that
// more than one edge bleeds onto the opposite face, and the bounding box
// encompasses the entire cube map face.
if(bleedOverAmount[i] > a_SrcSize)
{
uint32 oppositeFaceIdx;
//determine opposite face
switch(faceIdx)
{
case CP_FACE_X_POS:
oppositeFaceIdx = CP_FACE_X_NEG;
break;
case CP_FACE_X_NEG:
oppositeFaceIdx = CP_FACE_X_POS;
break;
case CP_FACE_Y_POS:
oppositeFaceIdx = CP_FACE_Y_NEG;
break;
case CP_FACE_Y_NEG:
oppositeFaceIdx = CP_FACE_Y_POS;
break;
case CP_FACE_Z_POS:
oppositeFaceIdx = CP_FACE_Z_NEG;
break;
default: // CP_FACE_Z_NEG:
oppositeFaceIdx = CP_FACE_Z_POS;
break;
}
//just encompass entire face for now
a_FilterExtents[oppositeFaceIdx].Augment(0, 0, 0);
a_FilterExtents[oppositeFaceIdx].Augment((a_SrcSize-1), (a_SrcSize-1), 0);
}
}
minV=minV;
}
//--------------------------------------------------------------------------------------
//ProcessFilterExtents
// Process bounding box in each cube face
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::ProcessFilterExtents(float *a_CenterTapDir, float a_DotProdThresh,
CBBoxInt32 *a_FilterExtents, CImageSurface *a_NormCubeMap, CImageSurface *a_SrcCubeMap,
CP_ITYPE *a_DstVal, uint32 a_FilterType, bool a_bUseSolidAngleWeighting, float a_SpecularPower)
{
//accumulators are 64-bit floats in order to have the precision needed
// over a summation of a large number of pixels
double dstAccumFace[6][4];
double weightAccumFace[6];
const int32 nSrcChannels = a_SrcCubeMap[0].m_NumChannels;
//norm cube map and srcCubeMap have same face width
const int32 faceWidth = a_NormCubeMap[0].m_Width;
//amount to add to pointer to move to next scanline in images
const int32 normCubePitch = faceWidth * a_NormCubeMap[0].m_NumChannels;
const int32 srcCubePitch = faceWidth * a_SrcCubeMap[0].m_NumChannels;
//iterate over cubefaces
for(int32 iFaceIdx=0; iFaceIdx<6; iFaceIdx++ )
{
//dest accum
for(int32 k=0; k<m_NumChannels; k++)
{
dstAccumFace[iFaceIdx][k] = 0.0f;
}
weightAccumFace[iFaceIdx] = 0.0f;
//if bbox is non empty
if(a_FilterExtents[iFaceIdx].Empty() == false)
{
//pointers used to walk across the image surface to accumulate taps
CP_ITYPE *normCubeRowStartPtr;
CP_ITYPE *srcCubeRowStartPtr;
int32 uStart, uEnd;
int32 vStart, vEnd;
uStart = a_FilterExtents[iFaceIdx].m_minCoord[0];
vStart = a_FilterExtents[iFaceIdx].m_minCoord[1];
uEnd = a_FilterExtents[iFaceIdx].m_maxCoord[0];
vEnd = a_FilterExtents[iFaceIdx].m_maxCoord[1];
normCubeRowStartPtr = a_NormCubeMap[iFaceIdx].m_ImgData + (a_NormCubeMap[iFaceIdx].m_NumChannels *
((vStart * faceWidth) + uStart) );
srcCubeRowStartPtr = a_SrcCubeMap[iFaceIdx].m_ImgData + (a_SrcCubeMap[iFaceIdx].m_NumChannels *
((vStart * faceWidth) + uStart) );
//note that <= is used to ensure filter extents always encompass at least one pixel if bbox is non empty
for(int32 v = vStart; v <= vEnd; v++)
{
int32 normCubeRowWalk;
int32 srcCubeRowWalk;
normCubeRowWalk = 0;
srcCubeRowWalk = 0;
for(int32 u = uStart; u <= uEnd; u++)
{
//pointers used to walk across the image surface to accumulate taps
CP_ITYPE *texelVect;
CP_ITYPE tapDotProd; //dot product between center tap and current tap
//pointer to direction in cube map associated with texel
texelVect = (normCubeRowStartPtr + normCubeRowWalk);
//check dot product to see if texel is within cone
tapDotProd = VM_DOTPROD3(texelVect, a_CenterTapDir);
if( tapDotProd >= a_DotProdThresh )
{
CP_ITYPE weight;
//for now just weight all taps equally, but ideally
// weight should be proportional to the solid angle of the tap
if(a_bUseSolidAngleWeighting == true)
{ //solid angle stored in 4th channel of normalizer/solid angle cube map
weight = *(texelVect+3);
}
else
{ //all taps equally weighted
weight = 1.0f;
}
switch(a_FilterType)
{
case CP_FILTER_TYPE_COSINE_POWER:
{
if(tapDotProd > 0.0f)
{
weight *= pow(tapDotProd, a_SpecularPower) * tapDotProd;
}
else
{
weight = 0;
}
}
break;
case CP_FILTER_TYPE_CONE:
case CP_FILTER_TYPE_ANGULAR_GAUSSIAN:
{
//weights are in same lookup table for both of these filter types
weight *= m_FilterLUT[(int32)(tapDotProd * (m_NumFilterLUTEntries - 1))];
}
break;
case CP_FILTER_TYPE_COSINE:
{
if(tapDotProd > 0.0f)
{
weight *= tapDotProd;
}
else
{
weight = 0.0f;
}
}
break;
case CP_FILTER_TYPE_DISC:
default:
break;
}
//iterate over channels
for(int32 k=0; k<nSrcChannels; k++) //(aSrcCubeMap[iFaceIdx].m_NumChannels) //up to 4 channels
{
dstAccumFace[iFaceIdx][k] += weight * *(srcCubeRowStartPtr + srcCubeRowWalk);
srcCubeRowWalk++;
}
weightAccumFace[iFaceIdx] += weight; //accumulate weight
}
else
{
//step across source pixel
srcCubeRowWalk += nSrcChannels;
}
normCubeRowWalk += a_NormCubeMap[iFaceIdx].m_NumChannels;
}
normCubeRowStartPtr += normCubePitch;
srcCubeRowStartPtr += srcCubePitch;
}
}
}
// reduction to 1 value from 6 faces
double dstAccum[4];
double weightAccum;
//dest accum
for(int32 k=0; k<m_NumChannels; k++)
{
dstAccum[k] = 0.0f;
}
weightAccum = 0.0f;
for(int32 iFaceIdx=0; iFaceIdx<6; iFaceIdx++ )
{
//dest accum
for(int32 k=0; k<m_NumChannels; k++)
{
dstAccum[k] += dstAccumFace[iFaceIdx][k];
}
weightAccum += weightAccumFace[iFaceIdx];
}
//divide through by weights if weight is non zero
if(weightAccum != 0.0f)
{
for(int32 k=0; k<m_NumChannels; k++)
{
a_DstVal[k] = (float)(dstAccum[k] / weightAccum);
}
}
else
{ //otherwise sample nearest
CP_ITYPE *texelPtr;
texelPtr = GetCubeMapTexelPtr(a_CenterTapDir, a_SrcCubeMap);
for(int32 k=0; k<m_NumChannels; k++)
{
a_DstVal[k] = texelPtr[k];
}
}
}
//--------------------------------------------------------------------------------------
// Fixup cube edges
//
// average texels on cube map faces across the edges
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::FixupCubeEdges(CImageSurface *a_CubeMap, int32 a_FixupType, int32 a_FixupWidth)
{
int32 i, j, k;
int32 face;
int32 edge;
int32 neighborFace;
int32 neighborEdge;
int32 nChannels = a_CubeMap[0].m_NumChannels;
int32 size = a_CubeMap[0].m_Width;
CPCubeMapNeighbor neighborInfo;
CP_ITYPE* edgeStartPtr;
CP_ITYPE* neighborEdgeStartPtr;
int32 edgeWalk;
int32 neighborEdgeWalk;
//pointer walk to walk one texel away from edge in perpendicular direction
int32 edgePerpWalk;
int32 neighborEdgePerpWalk;
//number of texels inward towards cubeface center to apply fixup to
int32 fixupDist;
int32 iFixup;
// note that if functionality to filter across the three texels for each corner, then
CP_ITYPE *cornerPtr[8][3]; //indexed by corner and face idx
CP_ITYPE *faceCornerPtrs[4]; //corner pointers for face
int32 cornerNumPtrs[8]; //indexed by corner and face idx
int32 iCorner; //corner iterator
int32 iFace; //iterator for faces
int32 corner;
//if there is no fixup, or fixup width = 0, do nothing
if((a_FixupType == CP_FIXUP_NONE) ||
(a_FixupWidth == 0) )
{
return;
}
//special case 1x1 cubemap, average face colors
if( a_CubeMap[0].m_Width == 1 )
{
//iterate over channels
for(k=0; k<nChannels; k++)
{
CP_ITYPE accum = 0.0f;
//iterate over faces to accumulate face colors
for(iFace=0; iFace<6; iFace++)
{
accum += *(a_CubeMap[iFace].m_ImgData + k);
}
//compute average over 6 face colors
accum /= 6.0f;
//iterate over faces to distribute face colors
for(iFace=0; iFace<6; iFace++)
{
*(a_CubeMap[iFace].m_ImgData + k) = accum;
}
}
return;
}
//iterate over corners
for(iCorner = 0; iCorner < 8; iCorner++ )
{
cornerNumPtrs[iCorner] = 0;
}
//iterate over faces to collect list of corner texel pointers
for(iFace=0; iFace<6; iFace++ )
{
//the 4 corner pointers for this face
faceCornerPtrs[0] = a_CubeMap[iFace].m_ImgData;
faceCornerPtrs[1] = a_CubeMap[iFace].m_ImgData + ( (size - 1) * nChannels );
faceCornerPtrs[2] = a_CubeMap[iFace].m_ImgData + ( (size) * (size - 1) * nChannels );
faceCornerPtrs[3] = a_CubeMap[iFace].m_ImgData + ( (((size) * (size - 1)) + (size - 1)) * nChannels );
//iterate over face corners to collect cube corner pointers
for(i=0; i<4; i++ )
{
corner = sg_CubeCornerList[iFace][i];
cornerPtr[corner][ cornerNumPtrs[corner] ] = faceCornerPtrs[i];
cornerNumPtrs[corner]++;
}
}
//iterate over corners to average across corner tap values
for(iCorner = 0; iCorner < 8; iCorner++ )
{
for(k=0; k<nChannels; k++)
{
CP_ITYPE cornerTapAccum;
cornerTapAccum = 0.0f;
//iterate over corner texels and average results
for(i=0; i<3; i++ )
{
cornerTapAccum += *(cornerPtr[iCorner][i] + k);
}
//divide by 3 to compute average of corner tap values
cornerTapAccum *= (1.0f / 3.0f);
//iterate over corner texels and average results
for(i=0; i<3; i++ )
{
*(cornerPtr[iCorner][i] + k) = cornerTapAccum;
}
}
}
//maximum width of fixup region is one half of the cube face size
fixupDist = VM_MIN( a_FixupWidth, size / 2);
//iterate over the twelve edges of the cube to average across edges
for(i=0; i<12; i++)
{
face = sg_CubeEdgeList[i][0];
edge = sg_CubeEdgeList[i][1];
neighborInfo = sg_CubeNgh[face][edge];
neighborFace = neighborInfo.m_Face;
neighborEdge = neighborInfo.m_Edge;
edgeStartPtr = a_CubeMap[face].m_ImgData;
neighborEdgeStartPtr = a_CubeMap[neighborFace].m_ImgData;
edgeWalk = 0;
neighborEdgeWalk = 0;
//amount to pointer to sample taps away from cube face
edgePerpWalk = 0;
neighborEdgePerpWalk = 0;
//Determine walking pointers based on edge type
// e.g. CP_EDGE_LEFT, CP_EDGE_RIGHT, CP_EDGE_TOP, CP_EDGE_BOTTOM
switch(edge)
{
case CP_EDGE_LEFT:
// no change to faceEdgeStartPtr
edgeWalk = nChannels * size;
edgePerpWalk = nChannels;
break;
case CP_EDGE_RIGHT:
edgeStartPtr += (size - 1) * nChannels;
edgeWalk = nChannels * size;
edgePerpWalk = -nChannels;
break;
case CP_EDGE_TOP:
// no change to faceEdgeStartPtr
edgeWalk = nChannels;
edgePerpWalk = nChannels * size;
break;
case CP_EDGE_BOTTOM:
edgeStartPtr += (size) * (size - 1) * nChannels;
edgeWalk = nChannels;
edgePerpWalk = -(nChannels * size);
break;
}
//For certain types of edge abutments, the neighbor edge walk needs to
// be flipped: the cases are
// if a left edge mates with a left or bottom edge on the neighbor
// if a top edge mates with a top or right edge on the neighbor
// if a right edge mates with a right or top edge on the neighbor
// if a bottom edge mates with a bottom or left edge on the neighbor
//Seeing as the edges are enumerated as follows
// left =0
// right =1
// top =2
// bottom =3
//
//If the edge enums are the same, or the sum of the enums == 3,
// the neighbor edge walk needs to be flipped
if( (edge == neighborEdge) || ((edge + neighborEdge) == 3) )
{ //swapped direction neighbor edge walk
switch(neighborEdge)
{
case CP_EDGE_LEFT: //start at lower left and walk up
neighborEdgeStartPtr += (size - 1) * (size) * nChannels;
neighborEdgeWalk = -(nChannels * size);
neighborEdgePerpWalk = nChannels;
break;
case CP_EDGE_RIGHT: //start at lower right and walk up
neighborEdgeStartPtr += ((size - 1)*(size) + (size - 1)) * nChannels;
neighborEdgeWalk = -(nChannels * size);
neighborEdgePerpWalk = -nChannels;
break;
case CP_EDGE_TOP: //start at upper right and walk left
neighborEdgeStartPtr += (size - 1) * nChannels;
neighborEdgeWalk = -nChannels;
neighborEdgePerpWalk = (nChannels * size);
break;
case CP_EDGE_BOTTOM: //start at lower right and walk left
neighborEdgeStartPtr += ((size - 1)*(size) + (size - 1)) * nChannels;
neighborEdgeWalk = -nChannels;
neighborEdgePerpWalk = -(nChannels * size);
break;
}
}
else
{ //swapped direction neighbor edge walk
switch(neighborEdge)
{
case CP_EDGE_LEFT: //start at upper left and walk down
//no change to neighborEdgeStartPtr for this case since it points
// to the upper left corner already
neighborEdgeWalk = nChannels * size;
neighborEdgePerpWalk = nChannels;
break;
case CP_EDGE_RIGHT: //start at upper right and walk down
neighborEdgeStartPtr += (size - 1) * nChannels;
neighborEdgeWalk = nChannels * size;
neighborEdgePerpWalk = -nChannels;
break;
case CP_EDGE_TOP: //start at upper left and walk left
//no change to neighborEdgeStartPtr for this case since it points
// to the upper left corner already
neighborEdgeWalk = nChannels;
neighborEdgePerpWalk = (nChannels * size);
break;
case CP_EDGE_BOTTOM: //start at lower left and walk left
neighborEdgeStartPtr += (size) * (size - 1) * nChannels;
neighborEdgeWalk = nChannels;
neighborEdgePerpWalk = -(nChannels * size);
break;
}
}
//Perform edge walk, to average across the 12 edges and smoothly propagate change to
//nearby neighborhood
//step ahead one texel on edge
edgeStartPtr += edgeWalk;
neighborEdgeStartPtr += neighborEdgeWalk;
// note that this loop does not process the corner texels, since they have already been
// averaged across faces across earlier
for(j=1; j<(size - 1); j++)
{
//for each set of taps along edge, average them
// and rewrite the results into the edges
for(k = 0; k<nChannels; k++)
{
CP_ITYPE edgeTap, neighborEdgeTap, avgTap; //edge tap, neighborEdgeTap and the average of the two
CP_ITYPE edgeTapDev, neighborEdgeTapDev;
edgeTap = *(edgeStartPtr + k);
neighborEdgeTap = *(neighborEdgeStartPtr + k);
//compute average of tap intensity values
avgTap = 0.5f * (edgeTap + neighborEdgeTap);
//propagate average of taps to edge taps
(*(edgeStartPtr + k)) = avgTap;
(*(neighborEdgeStartPtr + k)) = avgTap;
edgeTapDev = edgeTap - avgTap;
neighborEdgeTapDev = neighborEdgeTap - avgTap;
//iterate over taps in direction perpendicular to edge, and
// adjust intensity values gradualy to obscure change in intensity values of
// edge averaging.
for(iFixup = 1; iFixup < fixupDist; iFixup++)
{
//fractional amount to apply change in tap intensity along edge to taps
// in a perpendicular direction to edge
CP_ITYPE fixupFrac = (CP_ITYPE)(fixupDist - iFixup) / (CP_ITYPE)(fixupDist);
CP_ITYPE fixupWeight = 0.0f;
switch(a_FixupType )
{
case CP_FIXUP_PULL_LINEAR:
{
fixupWeight = fixupFrac;
}
break;
case CP_FIXUP_PULL_HERMITE:
{
//hermite spline interpolation between 1 and 0 with both pts derivatives = 0
// e.g. smooth step
// the full formula for hermite interpolation is:
//
// [ 2 -2 1 1 ][ p0 ]
// [t^3 t^2 t 1 ][ -3 3 -2 -1 ][ p1 ]
// [ 0 0 1 0 ][ d0 ]
// [ 1 0 0 0 ][ d1 ]
//
// Where p0 and p1 are the point locations and d0, and d1 are their respective derivatives
// t is the parameteric coordinate used to specify an interpoltion point on the spline
// and ranges from 0 to 1.
// if p0 = 0 and p1 = 1, and d0 and d1 = 0, the interpolation reduces to
//
// p(t) = - 2t^3 + 3t^2
fixupWeight = ((-2.0 * fixupFrac + 3.0) * fixupFrac * fixupFrac);
}
break;
case CP_FIXUP_AVERAGE_LINEAR:
{
fixupWeight = fixupFrac;
//perform weighted average of edge tap value and current tap
// fade off weight linearly as a function of distance from edge
edgeTapDev =
(*(edgeStartPtr + (iFixup * edgePerpWalk) + k)) - avgTap;
neighborEdgeTapDev =
(*(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k)) - avgTap;
}
break;
case CP_FIXUP_AVERAGE_HERMITE:
{
fixupWeight = ((-2.0 * fixupFrac + 3.0) * fixupFrac * fixupFrac);
//perform weighted average of edge tap value and current tap
// fade off weight using hermite spline with distance from edge
// as parametric coordinate
edgeTapDev =
(*(edgeStartPtr + (iFixup * edgePerpWalk) + k)) - avgTap;
neighborEdgeTapDev =
(*(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k)) - avgTap;
}
break;
}
// vary intensity of taps within fixup region toward edge values to hide changes made to edge taps
*(edgeStartPtr + (iFixup * edgePerpWalk) + k) -= (fixupWeight * edgeTapDev);
*(neighborEdgeStartPtr + (iFixup * neighborEdgePerpWalk) + k) -= (fixupWeight * neighborEdgeTapDev);
}
}
edgeStartPtr += edgeWalk;
neighborEdgeStartPtr += neighborEdgeWalk;
}
}
}
//--------------------------------------------------------------------------------------
//Constructor
//--------------------------------------------------------------------------------------
CCubeMapProcessor::CCubeMapProcessor(void)
{
int32 i;
//If zero filtering threads are specified then all filtering is performed in the
// process that called the cubemap filtering routines.
//Otherwise, the filtering is performed in separate filtering threads that cubemap generates
m_NumFilterThreads = CP_INITIAL_NUM_FILTER_THREADS;
//clear all threads
for(i=0; i<CP_MAX_FILTER_THREADS; i++ )
{
m_bThreadInitialized[i] = false;
m_ThreadID[i] = 0;
}
m_InputSize = 0;
m_OutputSize = 0;
m_NumMipLevels = 0;
m_NumChannels = 0;
m_NumFilterLUTEntries = 0;
m_FilterLUT = NULL;
m_shutdownWorkerThreadSignal = false;
//Constructors are automatically called for m_InputSurface and m_OutputSurface arrays
}
//--------------------------------------------------------------------------------------
//destructor
//--------------------------------------------------------------------------------------
CCubeMapProcessor::~CCubeMapProcessor()
{
Clear();
}
//--------------------------------------------------------------------------------------
// Stop any currently running threads, and clear all allocated data from cube map
// processor.
//
// To use the cube map processor after calling Clear(....), you need to call Init(....)
// again
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::Clear(void)
{
int32 i, j;
TerminateActiveThreads();
for(i=0; i<CP_MAX_FILTER_THREADS; i++ )
{
m_bThreadInitialized[i] = false;
}
m_InputSize = 0;
m_OutputSize = 0;
m_NumMipLevels = 0;
m_NumChannels = 0;
//Iterate over faces for input images
for (j = 0; j < CP_MAX_MIPLEVELS; j++)
{
for (i = 0; i < 6; i++)
{
m_InputSurface[j][i].Clear();
}
}
//Iterate over mip chain, and allocate memory for mip-chain
for(j=0; j<CP_MAX_MIPLEVELS; j++)
{
//Iterate over faces for output images
for(i=0; i<6; i++)
{
m_OutputSurface[j][i].Clear();
}
}
m_NumFilterLUTEntries = 0;
CP_SAFE_DELETE_ARRAY( m_FilterLUT );
}
//--------------------------------------------------------------------------------------
// Terminates execution of active threads
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::TerminateActiveThreads(void)
{
int32 i;
//signal all the threads to terminate
m_shutdownWorkerThreadSignal = true;
for(i=0; i<CP_MAX_FILTER_THREADS; i++)
{
if( m_bThreadInitialized[i] == true)
{
m_ThreadHandle[i].join();
m_bThreadInitialized[i] = false;
m_Status = CP_STATUS_FILTER_TERMINATED;
}
}
//reset the shutdown signal
m_shutdownWorkerThreadSignal = false;
}
//--------------------------------------------------------------------------------------
//Init cube map processor
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::Init(int32 a_InputSize, int32 a_OutputSize, int32 a_MaxNumMipLevels, int32 a_NumChannels)
{
int32 i, j;
int32 mipLevelSize;
m_Status = CP_STATUS_READY;
//since input is being modified, terminate any active filtering threads
TerminateActiveThreads();
m_InputSize = a_InputSize;
m_OutputSize = a_OutputSize;
m_NumChannels = a_NumChannels;
m_NumMipLevels = a_MaxNumMipLevels;
//first miplevel size
mipLevelSize = m_OutputSize;
//Iterate over mip chain, and init CImageSurfaces for mip-chain
for(j=0; j<a_MaxNumMipLevels; j++)
{
//Iterate over faces
for(i=0; i<6; i++)
{
m_InputSurface[j][i].Init(mipLevelSize, mipLevelSize, a_NumChannels);
m_OutputSurface[j][i].Init(mipLevelSize, mipLevelSize, a_NumChannels);
}
//next mip level is half size
mipLevelSize >>= 1;
//terminate if mip chain becomes too small
if(mipLevelSize == 0)
{
return;
}
}
}
//--------------------------------------------------------------------------------------
//Copy and convert cube map face data from an external image/surface into this object
//
// a_FaceIdx = a value 0 to 5 speciying which face to copy into (one of the CP_FACE_? )
// a_Level = mip level to copy into
// a_SrcType = data type of image being copyed from (one of the CP_TYPE_? types)
// a_SrcNumChannels = number of channels of the image being copied from (usually 1 to 4)
// a_SrcPitch = number of bytes per row of the source image being copied from
// a_SrcDataPtr = pointer to the image data to copy from
// a_Degamma = original gamma level of input image to undo by degamma
// a_Scale = scale to apply to pixel values after degamma (in linear space)
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::SetInputFaceData(int32 a_FaceIdx, int32 a_MipIdx, int32 a_SrcType, int32 a_SrcNumChannels,
int32 a_SrcPitch, void *a_SrcDataPtr, float a_MaxClamp, float a_Degamma, float a_Scale)
{
//since input is being modified, terminate any active filtering threads
TerminateActiveThreads();
m_InputSurface[a_MipIdx][a_FaceIdx].SetImageDataClampDegammaScale( a_SrcType, a_SrcNumChannels, a_SrcPitch,
a_SrcDataPtr, a_MaxClamp, a_Degamma, a_Scale );
}
//--------------------------------------------------------------------------------------
//Copy and convert cube map face data from this object into an external image/surface
//
// a_FaceIdx = a value 0 to 5 speciying which face to copy into (one of the CP_FACE_? )
// a_Level = mip level to copy into
// a_DstType = data type of image to copy to (one of the CP_TYPE_? types)
// a_DstNumChannels = number of channels of the image to copy to (usually 1 to 4)
// a_DstPitch = number of bytes per row of the dest image to copy to
// a_DstDataPtr = pointer to the image data to copy to
// a_Scale = scale to apply to pixel values (in linear space) before gamma for output
// a_Gamma = gamma level to apply to pixels after scaling
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::GetInputFaceData(int32 a_FaceIdx, int32 a_MipIdx, int32 a_DstType, int32 a_DstNumChannels,
int32 a_DstPitch, void *a_DstDataPtr, float a_Scale, float a_Gamma)
{
m_InputSurface[a_MipIdx][a_FaceIdx].GetImageDataScaleGamma( a_DstType, a_DstNumChannels, a_DstPitch,
a_DstDataPtr, a_Scale, a_Gamma );
}
//--------------------------------------------------------------------------------------
//ChannelSwapInputFaceData
// swizzle data in first 4 channels for input faces
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::ChannelSwapInputFaceData(int32 a_Channel0Src, int32 a_Channel1Src,
int32 a_Channel2Src, int32 a_Channel3Src )
{
int32 iMip, iFace, u, v, k;
int32 size;
CP_ITYPE texelData[4];
int32 channelSrcArray[4];
//since input is being modified, terminate any active filtering threads
TerminateActiveThreads();
size = m_InputSize;
channelSrcArray[0] = a_Channel0Src;
channelSrcArray[1] = a_Channel1Src;
channelSrcArray[2] = a_Channel2Src;
channelSrcArray[3] = a_Channel3Src;
//Iterate over mips and faces for input images
for (iMip = 0; iMip < m_NumMipLevels; iMip++)
{
for (iFace = 0; iFace < 6; iFace++)
{
for (v = 0; v < size; v++)
{
for (u = 0; u < size; u++)
{
//get channel data
for (k = 0; k < m_NumChannels; k++)
{
texelData[k] = *(m_InputSurface[iMip][iFace].GetSurfaceTexelPtr(u, v) + k);
}
//repack channel data accoring to swizzle information
for (k = 0; k < m_NumChannels; k++)
{
*(m_InputSurface[iMip][iFace].GetSurfaceTexelPtr(u, v) + k) =
texelData[channelSrcArray[k]];
}
}
}
}
// prepare size for next mip level
size >>= 1;
}
}
//--------------------------------------------------------------------------------------
//ChannelSwapOutputFaceData
// swizzle data in first 4 channels for input faces
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::ChannelSwapOutputFaceData(int32 a_Channel0Src, int32 a_Channel1Src,
int32 a_Channel2Src, int32 a_Channel3Src )
{
int32 iFace, iMipLevel, u, v, k;
int32 size;
CP_ITYPE texelData[4];
int32 channelSrcArray[4];
//since output is being modified, terminate any active filtering threads
TerminateActiveThreads();
size = m_OutputSize;
channelSrcArray[0] = a_Channel0Src;
channelSrcArray[1] = a_Channel1Src;
channelSrcArray[2] = a_Channel2Src;
channelSrcArray[3] = a_Channel3Src;
//Iterate over faces for input images
for(iMipLevel=0; iMipLevel<m_NumMipLevels; iMipLevel++ )
{
for(iFace=0; iFace<6; iFace++)
{
for(v=0; v<m_OutputSurface[iMipLevel][iFace].m_Height; v++ )
{
for(u=0; u<m_OutputSurface[iMipLevel][iFace].m_Width; u++ )
{
//get channel data
for(k=0; k<m_NumChannels; k++)
{
texelData[k] = *(m_OutputSurface[iMipLevel][iFace].GetSurfaceTexelPtr(u, v) + k);
}
//repack channel data accoring to swizzle information
for(k=0; k<m_NumChannels; k++)
{
*(m_OutputSurface[iMipLevel][iFace].GetSurfaceTexelPtr(u, v) + k) = texelData[ channelSrcArray[k] ];
}
}
}
}
}
}
//--------------------------------------------------------------------------------------
//Copy and convert cube map face data out of this class into an external image/surface
//
// a_FaceIdx = a value 0 to 5 specifying which face to copy from (one of the CP_FACE_? )
// a_Level = mip level to copy from
// a_DstType = data type of image to copyed into (one of the CP_TYPE_? types)
// a_DstNumChannels = number of channels of the image to copyed into (usually 1 to 4)
// a_DstPitch = number of bytes per row of the source image to copyed into
// a_DstDataPtr = pointer to the image data to copyed into
// a_Scale = scale to apply to pixel values (in linear space) before gamma for output
// a_Gamma = gamma level to apply to pixels after scaling
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::GetOutputFaceData(int32 a_FaceIdx, int32 a_Level, int32 a_DstType,
int32 a_DstNumChannels, int32 a_DstPitch, void *a_DstDataPtr, float a_Scale, float a_Gamma )
{
switch(a_DstType)
{
case CP_VAL_UNORM8:
case CP_VAL_UNORM8_BGRA:
case CP_VAL_UNORM16:
case CP_VAL_FLOAT16:
case CP_VAL_FLOAT32:
{
m_OutputSurface[a_Level][a_FaceIdx].GetImageDataScaleGamma( a_DstType, a_DstNumChannels,
a_DstPitch, a_DstDataPtr, a_Scale, a_Gamma );
}
break;
default:
break;
}
}
//--------------------------------------------------------------------------------------
//Cube map filtering and mip chain generation.
// the cube map filtereing is specified using a number of parameters:
// Filtering per miplevel is specified using 2D cone angle (in degrees) that
// indicates the region of the hemisphere to filter over for each tap.
//
// Note that the top mip level is also a filtered version of the original input images
// as well in order to create mip chains for diffuse environment illumination.
// The cone angle for the top level is specified by a_BaseAngle. This can be used to
// generate mipchains used to store the resutls of preintegration across the hemisphere.
//
// Then the mip angle used to genreate the next level of the mip chain from the first level
// is a_InitialMipAngle
//
// The angle for the subsequent levels of the mip chain are specified by their parents
// filtering angle and a per-level scale and bias
// newAngle = oldAngle * a_MipAnglePerLevelScale;
//
//--------------------------------------------------------------------------------------
static float ComputeBaseFilterAngle(float cosinePower)
{
// Find angle for which: cos(a) ^ cosinePower = epsilon
const float epsilon = 0.000001f;
float angle = acosf(powf(epsilon, 1.0f / cosinePower));
angle *= 180.0f / (float)CP_PI;
angle *= 2.0f;
return angle;
}
inline float RadicalInverse2(uint32 bits)
{
// Van der Corput radical inverse in base 2
// Reverse bits
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
return float(bits) * 2.3283064365386963e-10; // float(bits) * 2^-32
}
inline void HammersleySequence(uint32 sampleIndex, uint32 sampleCount, float* vXi)
{
vXi[0] = float(sampleIndex) / float(sampleCount);
vXi[1] = RadicalInverse2(sampleIndex);
}
void ImportanceSampleGGX(float* vXi, float alphaRoughnessSqr, float* vNormal, float* vOut)
{
float phi = 2.0f * CP_PI * vXi[0];
float cosTheta = sqrtf((1.0f - vXi[1]) / ( 1.0f + (alphaRoughnessSqr - 1.0f) * vXi[1]));
float sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
float vH[3];
vH[0] = sinTheta * cosf(phi);
vH[1] = sinTheta * sinf(phi);
vH[2] = cosTheta;
float vUpVectorX[3] = {1.0f, 0.0f, 0.0f};
float vUpVectorZ[3] = {0.0f, 0.0f, 1.0f};
float vTangentX[3];
float vTangentY[3];
float vTempVec[3];
// Build local frame
VM_XPROD3(vTempVec, fabs(vNormal[2]) < 0.999f ? vUpVectorZ : vUpVectorX, vNormal);
VM_NORM3(vTangentX, vTempVec);
VM_XPROD3(vTangentY, vNormal, vTangentX);
// Convert from tangent to world space
vOut[0] = vTangentX[0] * vH[0] + vTangentY[0] * vH[1] + vNormal[0] * vH[2];
vOut[1] = vTangentX[1] * vH[0] + vTangentY[1] * vH[1] + vNormal[1] * vH[2];
vOut[2] = vTangentX[2] * vH[0] + vTangentY[2] * vH[1] + vNormal[2] * vH[2];
}
void CCubeMapProcessor::FilterCubeSurfacesGGX(int32 a_MipIdx, int32 a_SampleCount, int32 a_FaceIdxStart, int32 a_FaceIdxEnd, int32 a_ThreadIdx)
{
// we don't want to convolve mip0 as it's theoretically a perfect mirror with zero roughness
AZ_Assert(a_MipIdx > 0, "FilterCubeSurfacesGGX called for mip 0");
CImageSurface* dstCubeMap = m_OutputSurface[a_MipIdx];
const uint32 numChannels = VM_MIN(m_NumChannels, 4);
const int32 dstSize = dstCubeMap[0].m_Width;
const uint32 maxMipIndex = (uint32)m_NumMipLevels - 1;
// Convert smoothness to roughness (needs to match shader code)
// The roughness value in microfacet calculations (called "alpha" in the literature) does not give perceptually
// linear results. Disney found that squaring the roughness value before using it in microfacet equations causes
// the user-provided roughness parameter to be more perceptually linear.
// See Burley's Disney PBR: https://pdfs.semanticscholar.org/eeee/3b125c09044d3e2f58ed0e4b1b66a677886d.pdf
float smoothness = VM_MAX(1.0f - ((float)a_MipIdx / maxMipIndex), 0.0f);
float perceptualRoughness = 1.0f - smoothness;
float alphaRoughness = perceptualRoughness * perceptualRoughness;
float alphaRoughnessSqr = alphaRoughness * alphaRoughness;
//thread progress
m_ThreadProgress[a_ThreadIdx].m_StartFace = a_FaceIdxStart;
m_ThreadProgress[a_ThreadIdx].m_EndFace = a_FaceIdxEnd;
CP_ITYPE* sourceTexelA = new CP_ITYPE[numChannels];
CP_ITYPE* sourceTexelB = new CP_ITYPE[numChannels];
//process required faces
for(int32 iCubeFace = a_FaceIdxStart; iCubeFace <= a_FaceIdxEnd && !m_shutdownWorkerThreadSignal; iCubeFace++)
{
//iterate over dst cube map face texel
for(int32 v = 0; v < dstSize && !m_shutdownWorkerThreadSignal; v++)
{
CP_ITYPE *texelPtr = dstCubeMap[iCubeFace].m_ImgData + v * dstCubeMap[iCubeFace].m_NumChannels * dstSize;
m_ThreadProgress[a_ThreadIdx].m_CurrentFace = iCubeFace;
m_ThreadProgress[a_ThreadIdx].m_CurrentRow = v;
for (int32 u = 0; u < dstSize && !m_shutdownWorkerThreadSignal; u++)
{
float color[4] = { 0 };
float totalWeight = 0;
float vH[3];
float vL[3];
//assume normal and view vector to be vCenterTapDir
float vCenterTapDir[3];
TexelCoordToVect(iCubeFace, (float)u, (float)v, dstSize, vCenterTapDir);
for (uint32 i = 0; i < (uint32)a_SampleCount && !m_shutdownWorkerThreadSignal; i++)
{
float vXi[2];
HammersleySequence(i, a_SampleCount, vXi);
ImportanceSampleGGX(vXi, alphaRoughnessSqr, vCenterTapDir, vH);
float fVdotH = VM_DOTPROD3(vCenterTapDir, vH);
vL[0] = 2 * fVdotH * vH[0] - vCenterTapDir[0];
vL[1] = 2 * fVdotH * vH[1] - vCenterTapDir[1];
vL[2] = 2 * fVdotH * vH[2] - vCenterTapDir[2];
float fNdotL = VM_DOTPROD3(vCenterTapDir, vL);
if (fNdotL > 0)
{
//compute specular D term (must match shader BRDF)
float dh = alphaRoughnessSqr / (CP_PI * powf(fVdotH * fVdotH * (alphaRoughnessSqr - 1.0f) + 1.0f, 2.0f));
//calculate the PDF (probability distribution) of the sample to determine the best mip level.
//lower probability sample directions use a smaller mip so they cover a larger sample area, which will
//blend the sample values and reduce artifacts
float pdf = dh * fVdotH / (4.0f * fVdotH);
float solidAngleTexel = 4.0f * CP_PI / (6.0f * m_InputSurface[0][0].m_Width * m_InputSurface[0][0].m_Width);
float solidAngleSample = 1.0f / (a_SampleCount * pdf);
float mip = 0.5f * log2f(solidAngleSample / solidAngleTexel) + 1.0f;
//determine surrounding mip levels
uint32 mipA = floor(mip);
uint32 mipB = mipA + 1;
float lerp = 0.0f;
VM_CLAMP(lerp, mip - mipA, 0.0f, 1.0f);
if (mipA >= maxMipIndex)
{
mipA = mipB = maxMipIndex;
lerp = 0.0f;
}
//retrieve bilinear filtered texel from each mip
GetCubeMapTexelBilinear(vL, m_InputSurface[mipA], sourceTexelA, numChannels);
GetCubeMapTexelBilinear(vL, m_InputSurface[mipB], sourceTexelB, numChannels);
//interpolate each channel value from the two bilinear mip samples for trilinear filtering
for (uint32 k = 0; k < numChannels; k++)
{
color[k] += (((1.0f - lerp) * sourceTexelA[k]) + (lerp * sourceTexelB[k])) * fNdotL;
}
totalWeight += fNdotL;
}
}
for (uint32 k = 0; k < numChannels; k++)
{
texelPtr[k] = color[k] / totalWeight;
}
texelPtr += dstCubeMap[iCubeFace].m_NumChannels;
}
}
}
delete[] sourceTexelA;
delete[] sourceTexelB;
}
void CCubeMapProcessor::FilterCubeMapMipChain(float a_BaseFilterAngle, float a_InitialMipAngle, float a_MipAnglePerLevelScale,
int32 a_FilterType, int32 a_FixupType, int32 a_FixupWidth, bool a_bUseSolidAngle, float a_GlossScale, float a_GlossBias,
int32 a_SampleCountGGX)
{
int32 i;
float coneAngle;
if(a_FilterType == CP_FILTER_TYPE_COSINE_POWER || a_FilterType == CP_FILTER_TYPE_GGX)
{
// Don't filter top mipmap
a_BaseFilterAngle = 0;
}
//Build filter lookup tables based on the source miplevel size
PrecomputeFilterLookupTables(a_FilterType, m_InputSurface[0][0].m_Width, a_BaseFilterAngle);
//initialize thread progress
m_ThreadProgress[0].m_CurrentMipLevel = 0;
m_ThreadProgress[0].m_CurrentRow = 0;
m_ThreadProgress[0].m_CurrentFace = 0;
//Filter the top mip level (initial filtering used for diffuse or blurred specular lighting )
FilterCubeSurfaces(m_InputSurface[0], m_OutputSurface[0], a_BaseFilterAngle, a_FilterType, a_bUseSolidAngle,
0, //start at face 0
5, //end at face 5
0); //thread 0 is processing
m_ThreadProgress[0].m_CurrentMipLevel = 1;
m_ThreadProgress[0].m_CurrentRow = 0;
m_ThreadProgress[0].m_CurrentFace = 0;
FixupCubeEdges(m_OutputSurface[0], a_FixupType, a_FixupWidth);
//Cone angle start (for generating subsequent mip levels)
coneAngle = a_InitialMipAngle;
//generate subsequent mip levels
for(i=0; i<(m_NumMipLevels-1) && !m_shutdownWorkerThreadSignal; i++)
{
m_ThreadProgress[0].m_CurrentMipLevel = i+1;
m_ThreadProgress[0].m_CurrentRow = 0;
m_ThreadProgress[0].m_CurrentFace = 0;
if (a_FilterType == CP_FILTER_TYPE_GGX)
{
FilterCubeSurfacesGGX(i + 1,
a_SampleCountGGX,
0, //start at face 0
5, //end at face 5
0 //thread 0 is processing
);
}
else
{
CImageSurface* srcCubeImage = m_OutputSurface[i];
float specPow = 1.0f;
if(a_FilterType == CP_FILTER_TYPE_COSINE_POWER)
{
uint32 numMipsForGloss = m_NumMipLevels - 2; // Lowest used mip is 4x4
float gloss = VM_MAX(1.0f - (float)(i + 1) / (float)(numMipsForGloss - 1), 0.0f);
// Compute specular power (this must match shader code)
specPow = pow(2.0f, a_GlossScale * gloss + a_GlossBias);
// Blinn to Phong approximation: (R.E)^p == (N.H)^(4*p)
specPow /= 4.0f;
coneAngle = ComputeBaseFilterAngle(specPow);
srcCubeImage = m_InputSurface[0];
}
//Build filter lookup tables based on the source miplevel size
PrecomputeFilterLookupTables(a_FilterType, srcCubeImage->m_Width, coneAngle);
//filter cube surfaces
FilterCubeSurfaces(srcCubeImage, m_OutputSurface[i+1], coneAngle, a_FilterType, a_bUseSolidAngle,
0, //start at face 0
5, //end at face 5
0, //thread 0 is processing
specPow);
}
m_ThreadProgress[0].m_CurrentMipLevel = i+2;
m_ThreadProgress[0].m_CurrentRow = 0;
m_ThreadProgress[0].m_CurrentFace = 0;
FixupCubeEdges(m_OutputSurface[i+1], a_FixupType, a_FixupWidth);
coneAngle = coneAngle * a_MipAnglePerLevelScale;
}
m_Status = CP_STATUS_FILTER_COMPLETED;
}
//--------------------------------------------------------------------------------------
//Builds the following lookup tables prior to filtering:
// -normalizer cube map
// -tap weight lookup table
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::PrecomputeFilterLookupTables(uint32 a_FilterType, int32 a_SrcCubeMapWidth, float a_FilterConeAngle)
{
float srcTexelAngle;
int32 iCubeFace;
//angle about center tap that defines filter cone
float filterAngle;
//min angle a src texel can cover (in degrees)
srcTexelAngle = (180.0f / (float)CP_PI) * atan2f(1.0f, (float)a_SrcCubeMapWidth);
//filter angle is 1/2 the cone angle
filterAngle = a_FilterConeAngle / 2.0f;
//ensure filter angle is larger than a texel
if(filterAngle < srcTexelAngle)
{
filterAngle = srcTexelAngle;
}
//ensure filter cone is always smaller than the hemisphere
if(filterAngle > 90.0f)
{
filterAngle = 90.0f;
}
//build lookup table for tap weights based on angle between current tap and center tap
BuildAngleWeightLUT(a_SrcCubeMapWidth * 2, a_FilterType, filterAngle);
//clear pre-existing normalizer cube map
for(iCubeFace=0; iCubeFace<6; iCubeFace++)
{
m_NormCubeMap[iCubeFace].Clear();
}
//Normalized vectors per cubeface and per-texel solid angle
BuildNormalizerSolidAngleCubemap(a_SrcCubeMapWidth, m_NormCubeMap);
}
//--------------------------------------------------------------------------------------
//The key to the speed of these filtering routines is to quickly define a per-face
// bounding box of pixels which enclose all the taps in the filter kernel efficiently.
// Later these pixels are selectively processed based on their dot products to see if
// they reside within the filtering cone.
//
//This is done by computing the smallest per-texel angle to get a conservative estimate
// of the number of texels needed to be covered in width and height order to filter the
// region. the bounding box for the center taps face is defined first, and if the
// filtereing region bleeds onto the other faces, bounding boxes for the other faces are
// defined next
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::FilterCubeSurfaces(CImageSurface *a_SrcCubeMap, CImageSurface *a_DstCubeMap,
float a_FilterConeAngle, int32 a_FilterType, bool a_bUseSolidAngle, int32 a_FaceIdxStart,
int32 a_FaceIdxEnd, int32 a_ThreadIdx, float a_SpecularPower)
{
const int32 srcSize = a_SrcCubeMap[0].m_Width;
const int32 dstSize = a_DstCubeMap[0].m_Width;
//min angle a src texel can cover (in degrees)
const float srcTexelAngle = (180.0f / (float)CP_PI) * atan2f(1.0f, (float)srcSize);
//angle about center tap to define filter cone
float filterAngle;
//filter angle is 1/2 the cone angle
filterAngle = a_FilterConeAngle / 2.0f;
//ensure filter angle is larger than a texel
if(filterAngle < srcTexelAngle)
{
filterAngle = srcTexelAngle;
}
//ensure filter cone is always smaller than the hemisphere
if(filterAngle > 90.0f)
{
filterAngle = 90.0f;
}
//the maximum number of texels in 1D the filter cone angle will cover
// used to determine bounding box size for filter extents
//ensure conservative region always covers at least one texel
const int32 filterSize = AZ::GetMax((int32)ceil(filterAngle / srcTexelAngle), 1);
//dotProdThresh threshold based on cone angle to determine whether or not taps
// reside within the cone angle
const float dotProdThresh = cosf( ((float)CP_PI / 180.0f) * filterAngle );
//thread progress
m_ThreadProgress[a_ThreadIdx].m_StartFace = a_FaceIdxStart;
m_ThreadProgress[a_ThreadIdx].m_EndFace = a_FaceIdxEnd;
//process required faces
for(int32 iCubeFace = a_FaceIdxStart; iCubeFace <= a_FaceIdxEnd && !m_shutdownWorkerThreadSignal; iCubeFace++)
{
//iterate over dst cube map face texel
for(int32 v = 0; v < dstSize && !m_shutdownWorkerThreadSignal; v++)
{
CP_ITYPE *texelPtr = a_DstCubeMap[iCubeFace].m_ImgData + v * a_DstCubeMap[iCubeFace].m_NumChannels * dstSize;
m_ThreadProgress[a_ThreadIdx].m_CurrentFace = iCubeFace;
m_ThreadProgress[a_ThreadIdx].m_CurrentRow = v;
for(int32 u=0; u<dstSize && !m_shutdownWorkerThreadSignal; u++)
{
//CImageSurface normCubeMap[6]; //
CBBoxInt32 filterExtents[6]; //bounding box per face to specify region to process
// note that pixels within these regions may be rejected
// based on the
float centerTapDir[3]; //direction of center tap
//get center tap direction
TexelCoordToVect(iCubeFace, (float)u, (float)v, dstSize, centerTapDir );
//clear old per-face filter extents
ClearFilterExtents(filterExtents);
//define per-face filter extents
DetermineFilterExtents(centerTapDir, srcSize, filterSize, filterExtents );
//perform filtering of src faces using filter extents
ProcessFilterExtents(centerTapDir, dotProdThresh, filterExtents, m_NormCubeMap, a_SrcCubeMap, texelPtr, a_FilterType, a_bUseSolidAngle, a_SpecularPower);
texelPtr += a_DstCubeMap[iCubeFace].m_NumChannels;
}
}
}
}
//--------------------------------------------------------------------------------------
//starts a new thread to execute the filtering options
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::InitiateFiltering(float a_BaseFilterAngle, float a_InitialMipAngle,
float a_MipAnglePerLevelScale, int32 a_FilterType, int32 a_FixupType, int32 a_FixupWidth, bool a_bUseSolidAngle,
float a_GlossScale, float a_GlossBias, int32 a_SampleCountGGX)
{
//set filtering options in main class to determine
m_BaseFilterAngle = a_BaseFilterAngle;
m_InitialMipAngle = a_InitialMipAngle;
m_MipAnglePerLevelScale = a_MipAnglePerLevelScale;
//terminate preexisting threads if needed
TerminateActiveThreads();
//call filtering function from the current process
FilterCubeMapMipChain(a_BaseFilterAngle, a_InitialMipAngle, a_MipAnglePerLevelScale, a_FilterType,
a_FixupType, a_FixupWidth, a_bUseSolidAngle, a_GlossScale, a_GlossBias, a_SampleCountGGX);
}
//--------------------------------------------------------------------------------------
//build filter lookup table
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::BuildAngleWeightLUT([[maybe_unused]] int32 a_NumFilterLUTEntries, int32 a_FilterType, float a_FilterAngle)
{
int32 iLUTEntry;
CP_SAFE_DELETE_ARRAY( m_FilterLUT );
m_NumFilterLUTEntries = 4096; //a_NumFilterLUTEntries;
m_FilterLUT = new CP_ITYPE [m_NumFilterLUTEntries];
// note that CP_FILTER_TYPE_DISC weights all taps equally and does not need a lookup table
if( a_FilterType == CP_FILTER_TYPE_CONE )
{
//CP_FILTER_TYPE_CONE is a cone centered around the center tap and falls off to zero
// over the filtering radius
CP_ITYPE filtAngleRad = a_FilterAngle * CP_PI / 180.0f;
for(iLUTEntry=0; iLUTEntry<m_NumFilterLUTEntries; iLUTEntry++ )
{
CP_ITYPE angle = acos( (float)iLUTEntry / (float)(m_NumFilterLUTEntries - 1) );
CP_ITYPE filterVal;
filterVal = (filtAngleRad - angle) / filtAngleRad;
if(filterVal < 0)
{
filterVal = 0;
}
//note that gaussian is not weighted by 1.0 / (sigma* sqrt(2 * PI)) seen as weights
// weighted tap accumulation in filters is divided by sum of weights
m_FilterLUT[iLUTEntry] = filterVal;
}
}
else if( a_FilterType == CP_FILTER_TYPE_ANGULAR_GAUSSIAN )
{
//fit 3 standard deviations within angular extent of filter
CP_ITYPE stdDev = (a_FilterAngle * CP_PI / 180.0) / 3.0;
CP_ITYPE inv2Variance = 1.0 / (2.0 * stdDev * stdDev);
for(iLUTEntry=0; iLUTEntry<m_NumFilterLUTEntries; iLUTEntry++ )
{
CP_ITYPE angle = acos( (float)iLUTEntry / (float)(m_NumFilterLUTEntries - 1) );
CP_ITYPE filterVal;
filterVal = exp( -(angle * angle) * inv2Variance );
//note that gaussian is not weighted by 1.0 / (sigma* sqrt(2 * PI)) seen as weights
// weighted tap accumulation in filters is divided by sum of weights
m_FilterLUT[iLUTEntry] = filterVal;
}
}
}
//--------------------------------------------------------------------------------------
// WriteMipLevelIntoAlpha
//
// Writes the current mip level into alpha in order for 2.0 shaders that need to
// know the current mip-level
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::WriteMipLevelIntoAlpha(void)
{
int32 iFace, iMipLevel;
//since output is being modified, terminate any active filtering threads
TerminateActiveThreads();
//generate subsequent mip levels
for(iMipLevel = 0; iMipLevel < m_NumMipLevels; iMipLevel++)
{
//Iterate over faces for input images
for(iFace = 0; iFace < 6; iFace++)
{
m_OutputSurface[iMipLevel][iFace].ClearChannelConst(3, (float) (16.0f * (iMipLevel / 255.0f)) );
}
}
}
//--------------------------------------------------------------------------------------
// Horizonally flip input cube map faces
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::FlipInputCubemapFaces(void)
{
int32 iFace, iMip;
//since input is being modified, terminate any active filtering threads
TerminateActiveThreads();
//Iterate over faces for input images
for (iMip = 0; iMip < m_NumMipLevels; iMip++)
{
for (iFace = 0; iFace < 6; iFace++)
{
m_InputSurface[iMip][iFace].InPlaceHorizonalFlip();
}
}
}
//--------------------------------------------------------------------------------------
//Horizonally flip output cube map faces
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::FlipOutputCubemapFaces(void)
{
int32 iFace, iMipLevel;
//since output is being modified, terminate any active filtering threads
TerminateActiveThreads();
//Iterate over faces for input images
for(iMipLevel = 0; iMipLevel < m_NumMipLevels; iMipLevel++)
{
for(iFace = 0; iFace < 6; iFace++)
{
m_OutputSurface[iMipLevel][iFace].InPlaceHorizonalFlip();
}
}
}
//--------------------------------------------------------------------------------------
// test to see if filter thread is still active
//
//--------------------------------------------------------------------------------------
bool CCubeMapProcessor::IsFilterThreadActive(uint32 a_ThreadIdx)
{
if(m_bThreadInitialized[a_ThreadIdx] == false)
{
return false;
}
else
{
if(m_ThreadHandle[a_ThreadIdx].joinable())
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------
//estimate fraction completed of filter thread based on current conditions
//
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::EstimateFilterThreadProgress(SFilterProgress *a_FilterProgress)
{
float totalMipComputation = 0.0f; //time to compute all mip levels as a function of the time it takes
//to compute the top mip level
float progressMipComputation = 0.0f; //progress based on entirely computed mip levels
float currentMipComputation = 0.0f; //amount of computation it takes to process this entire mip level
float progressFaceComputation = 0.0f; //progress based on entirely computed faces for this mip level
float currentFaceComputation = 0.0f; //amount of computation it takes to process this entire face
float progressRowComputation = 0.0f; //progress based on entirely computed rows for this face
//estimated fraction of total computation time the current face will take
int32 i;
float filterAngle = 1.0f; //filter angle for given miplevel
int32 dstSize = 1; //destination cube map size of given mip level
int32 currentMipSize = 1; //size of mip level currently being processed
//compuate total compuation time as a function of the time
// cubemap processing for each miplevel is roughly O(n^2 * m^2)
// where n is the cube map size, and m is the filter size
// Each miplevel is half the size of the previous level,
// and the filter size in texels is roughly proportional to the
// (filter angle size * size of source cubemap texels are fetched from) ^2
// computation to generate base mip level (generated from input cube map)
if(m_BaseFilterAngle > 0.0f)
{
totalMipComputation = pow(m_InputSize * m_BaseFilterAngle , 2.0f) * (m_OutputSize * m_OutputSize);
}
else
{
totalMipComputation = pow(m_InputSize * 0.01f , 2.0f) * (m_OutputSize * m_OutputSize);
}
progressMipComputation = 0.0f;
if(a_FilterProgress->m_CurrentMipLevel > 0)
{
progressMipComputation = totalMipComputation;
}
//filtering angle for this miplevel
filterAngle = m_InitialMipAngle;
dstSize = m_OutputSize;
//computation for entire base mip level (if current level is base level)
if(a_FilterProgress->m_CurrentMipLevel == 0)
{
currentMipComputation = totalMipComputation;
currentMipSize = dstSize;
}
//compuatation to generate subsequent mip levels
for(i=1; i<m_NumMipLevels; i++)
{
float computation;
dstSize /= 2;
filterAngle *= m_MipAnglePerLevelScale;
if(filterAngle > 180)
{
filterAngle = 180;
}
//note src size is dstSize*2 since miplevels are generated from the subsequent level
computation = pow(dstSize * 2 * filterAngle, 2.0f) * (dstSize * dstSize);
totalMipComputation += computation;
//accumulate computation for completed mip levels
if(a_FilterProgress->m_CurrentMipLevel > i)
{
progressMipComputation = totalMipComputation;
}
//computation for entire current mip level
if(a_FilterProgress->m_CurrentMipLevel == i)
{
currentMipComputation = computation;
currentMipSize = dstSize;
}
}
//fraction of compuation time processing the entire current mip level will take
currentMipComputation /= totalMipComputation;
progressMipComputation /= totalMipComputation;
progressFaceComputation = currentMipComputation *
(float)(a_FilterProgress->m_CurrentFace - a_FilterProgress->m_StartFace) /
(float)(1 + a_FilterProgress->m_EndFace - a_FilterProgress->m_StartFace);
currentFaceComputation = currentMipComputation *
1.0f /
(1 + a_FilterProgress->m_EndFace - a_FilterProgress->m_StartFace);
progressRowComputation = currentFaceComputation *
((float)a_FilterProgress->m_CurrentRow / (float)currentMipSize);
//progress completed
a_FilterProgress->m_FractionCompleted =
progressMipComputation +
progressFaceComputation +
progressRowComputation;
if( a_FilterProgress->m_CurrentFace < 0)
{
a_FilterProgress->m_CurrentFace = 0;
}
if( a_FilterProgress->m_CurrentMipLevel < 0)
{
a_FilterProgress->m_CurrentMipLevel = 0;
}
if( a_FilterProgress->m_CurrentRow < 0)
{
a_FilterProgress->m_CurrentRow = 0;
}
}
//--------------------------------------------------------------------------------------
// Return string describing the current status of the cubemap processing threads
//
//--------------------------------------------------------------------------------------
WCHAR *CCubeMapProcessor::GetFilterProgressString(void)
{
WCHAR threadProgressString[CP_MAX_FILTER_THREADS][CP_MAX_PROGRESS_STRING];
int32 i;
for(i=0; i<m_NumFilterThreads; i++)
{
if(IsFilterThreadActive(i))
{
EstimateFilterThreadProgress(&(m_ThreadProgress[i]) );
azsnwprintf(threadProgressString[i],
CP_MAX_PROGRESS_STRING,
L"%5.2f%% Complete (Level %3d, Face %3d, Row %3d)",
100.0f * m_ThreadProgress[i].m_FractionCompleted,
m_ThreadProgress[i].m_CurrentMipLevel,
m_ThreadProgress[i].m_CurrentFace,
m_ThreadProgress[i].m_CurrentRow
);
}
else
{
azsnwprintf(threadProgressString[i],
CP_MAX_PROGRESS_STRING,
L"Ready");
}
}
if(m_NumFilterThreads == 2)
{ //display information about both threads
azsnwprintf(m_ProgressString,
CP_MAX_PROGRESS_STRING,
L"Thread0: %s \nThread1: %s",
threadProgressString[0],
threadProgressString[1]);
}
else
{ //only display information about one thread
azsnwprintf(m_ProgressString,
CP_MAX_PROGRESS_STRING,
L"Thread 0: %s ",
threadProgressString[0]);
}
return m_ProgressString;
}
//--------------------------------------------------------------------------------------
//get status of cubemap processor
//
//--------------------------------------------------------------------------------------
int32 CCubeMapProcessor::GetStatus(void)
{
return m_Status;
}
//--------------------------------------------------------------------------------------
//refresh status
// sets cubemap processor to ready state if not processing
//--------------------------------------------------------------------------------------
void CCubeMapProcessor::RefreshStatus(void)
{
if(m_Status != CP_STATUS_PROCESSING )
{
m_Status = CP_STATUS_READY;
}
}
} //namespace ImageProcessingAtom