imposterCapture.cpp
Engine/source/util/imposterCapture.cpp
Classes:
class
A material hook used to hold imposter generation rendering materials for an object.
Detailed Description
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "util/imposterCapture.h" 26 27#include "gfx/bitmap/gBitmap.h" 28#include "core/color.h" 29#include "renderInstance/renderPassManager.h" 30#include "renderInstance/renderMeshMgr.h" 31#include "materials/materialManager.h" 32#include "materials/materialFeatureTypes.h" 33#include "materials/customMaterialDefinition.h" 34#include "ts/tsShapeInstance.h" 35#include "scene/sceneManager.h" 36#include "scene/sceneRenderState.h" 37#include "lighting/lightInfo.h" 38#include "gfx/gfxTransformSaver.h" 39#include "gfx/gfxDebugEvent.h" 40#include "core/stream/fileStream.h" 41 42 43/// A material hook used to hold imposter generation 44/// rendering materials for an object. 45class ImposterCaptureMaterialHook : public MatInstanceHook 46{ 47public: 48 49 ImposterCaptureMaterialHook(); 50 51 // MatInstanceHook 52 virtual ~ImposterCaptureMaterialHook(); 53 virtual const MatInstanceHookType& getType() const { return Type; } 54 55 /// The material hook type. 56 static const MatInstanceHookType Type; 57 58 void init( BaseMatInstance *mat ); 59 60 static BaseMatInstance* getDiffuseInst( BaseMatInstance *inMat ) 61 { return _getOrCreateHook( inMat )->mDiffuseMatInst; } 62 63 static BaseMatInstance* getNormalsInst( BaseMatInstance *inMat ) 64 { return _getOrCreateHook( inMat )->mNormalsMatInst; } 65 66protected: 67 68 static void _overrideFeatures( ProcessedMaterial *mat, 69 U32 stageNum, 70 MaterialFeatureData &fd, 71 const FeatureSet &features ); 72 73 static ImposterCaptureMaterialHook* _getOrCreateHook( BaseMatInstance *inMat ); 74 75 /// 76 BaseMatInstance *mDiffuseMatInst; 77 78 /// 79 BaseMatInstance *mNormalsMatInst; 80}; 81 82 83const MatInstanceHookType ImposterCaptureMaterialHook::Type( "ImposterCapture" ); 84 85 86ImposterCaptureMaterialHook::ImposterCaptureMaterialHook() 87 : mDiffuseMatInst( NULL ), 88 mNormalsMatInst( NULL ) 89{ 90} 91 92ImposterCaptureMaterialHook::~ImposterCaptureMaterialHook() 93{ 94 SAFE_DELETE( mDiffuseMatInst ); 95 SAFE_DELETE( mNormalsMatInst ); 96} 97 98void ImposterCaptureMaterialHook::init( BaseMatInstance *inMat ) 99{ 100 // We cannot capture impostors on custom materials 101 // as we don't know how to get just diffuse and just 102 // normals rendering. 103 if ( dynamic_cast<CustomMaterial*>( inMat->getMaterial() ) ) 104 return; 105 106 // Tweak the feature data to include just what we need. 107 FeatureSet features; 108 features.addFeature( MFT_VertTransform ); 109 features.addFeature( MFT_DiffuseMap ); 110 features.addFeature( MFT_OverlayMap ); 111 features.addFeature( MFT_DetailMap ); 112 features.addFeature( MFT_DiffuseColor ); 113 features.addFeature( MFT_AlphaTest ); 114 features.addFeature( MFT_IsTranslucent ); 115 116 const String &matName = inMat->getMaterial()->getName(); 117 118 mDiffuseMatInst = MATMGR->createMatInstance( matName ); 119 mDiffuseMatInst->getFeaturesDelegate().bind( &ImposterCaptureMaterialHook::_overrideFeatures ); 120 mDiffuseMatInst->init( features, inMat->getVertexFormat() ); 121 122 features.addFeature( MFT_IsBC3nm ); 123 features.addFeature( MFT_NormalMap ); 124 features.addFeature( MFT_NormalsOut ); 125 features.addFeature( MFT_AccuMap ); 126 mNormalsMatInst = MATMGR->createMatInstance( matName ); 127 mNormalsMatInst->getFeaturesDelegate().bind( &ImposterCaptureMaterialHook::_overrideFeatures ); 128 mNormalsMatInst->init( features, inMat->getVertexFormat() ); 129} 130 131void ImposterCaptureMaterialHook::_overrideFeatures( ProcessedMaterial *mat, 132 U32 stageNum, 133 MaterialFeatureData &fd, 134 const FeatureSet &features ) 135{ 136 if ( features.hasFeature( MFT_NormalsOut) ) 137 fd.features.addFeature( MFT_NormalsOut ); 138 139 fd.features.addFeature( MFT_ForwardShading ); 140 fd.features.addFeature( MFT_Imposter ); 141} 142 143ImposterCaptureMaterialHook* ImposterCaptureMaterialHook::_getOrCreateHook( BaseMatInstance *inMat ) 144{ 145 ImposterCaptureMaterialHook *hook = inMat->getHook<ImposterCaptureMaterialHook>(); 146 if ( !hook ) 147 { 148 // Create a hook and initialize it using the incoming material. 149 hook = new ImposterCaptureMaterialHook; 150 hook->init( inMat ); 151 inMat->addHook( hook ); 152 } 153 154 return hook; 155} 156 157 158ImposterCapture::ImposterCapture() 159: mDl( 0 ), 160 mDim( 0 ), 161 mRadius( 0.0f ), 162 mCenter( Point3F( 0, 0, 0 ) ), 163 mBlackBmp( NULL ), 164 mWhiteBmp( NULL ), 165 mState( NULL ), 166 mShapeInstance( NULL ), 167 mRenderTarget( NULL ), 168 mRenderPass( NULL ), 169 mMeshRenderBin( NULL ) 170{ 171} 172 173ImposterCapture::~ImposterCapture() 174{ 175 AssertFatal( !mShapeInstance, "ImposterCapture destructor - TSShapeInstance hasn't been cleared!" ); 176} 177 178void ImposterCapture::_colorAverageFilter( U32 dimensions, const U8 *inBmpBits, U8 *outBmpBits ) 179{ 180 LinearColorF color; 181 U32 count = 0; 182 U32 index, index2; 183 184 for ( S32 y = 0; y < dimensions; y++ ) 185 { 186 for( S32 x = 0; x < dimensions; x++ ) 187 { 188 // We only blend on transparent pixels. 189 index = ( ( y * dimensions ) + x ) * 4; 190 if ( inBmpBits[index+3] > 84 ) 191 { 192 outBmpBits[index+0] = inBmpBits[index+0]; 193 outBmpBits[index+1] = inBmpBits[index+1]; 194 outBmpBits[index+2] = inBmpBits[index+2]; 195 outBmpBits[index+3] = inBmpBits[index+3]; //hack 196 continue; 197 } 198 199 color.set(0,0,0); 200 count = 0; 201 202 for ( S32 fy = y-6; fy <= y+6; fy++ ) 203 { 204 for ( S32 fx = x-6; fx <= x+6; fx++ ) 205 { 206 if ( fy >= 0 && fy < (dimensions-1) && 207 fx >= 0 && fx < (dimensions-1) ) 208 { 209 index2 = ( ( fy * dimensions ) + fx ) * 4; 210 if ( inBmpBits[index2+3] > 84 ) 211 { 212 color.red += inBmpBits[index2+0]; 213 color.green += inBmpBits[index2+1]; 214 color.blue += inBmpBits[index2+2]; 215 ++count; 216 } 217 } 218 } 219 } 220 221 outBmpBits[index+0] = (U8)( (F32)color.red / (F32)count ); 222 outBmpBits[index+1] = (U8)( (F32)color.green / (F32)count ); 223 outBmpBits[index+2] = (U8)( (F32)color.blue / (F32)count ); 224 outBmpBits[index+3] = 0; 225 } 226 } 227} 228 229void ImposterCapture::_renderToTexture( GFXTexHandle texHandle, GBitmap *outBitmap, const ColorI &color ) 230{ 231 GFXDEBUGEVENT_SCOPE( ImposterCapture_RenderToTexture, ColorI::RED ); 232 PROFILE_SCOPE( ImposterCapture_RenderToTexture ); 233 234 mRenderTarget->attachTexture( GFXTextureTarget::Color0, texHandle ); 235 mRenderTarget->attachTexture( GFXTextureTarget::DepthStencil, mDepthBuffer ); 236 GFX->setActiveRenderTarget( mRenderTarget ); 237 238 GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, color, 1.0f, 0 ); 239 240 mShapeInstance->render( mRData, mDl, 1.0f ); 241 242 mState->getRenderPass()->renderPass( mState ); 243 244 mRenderTarget->resolve(); 245 246 texHandle->copyToBmp( outBitmap ); 247} 248 249void ImposterCapture::_separateAlpha( GBitmap *imposterOut ) 250{ 251 PROFILE_START(TSShapeInstance_snapshot_sb_separate); 252 253 // TODO: Remove all this when we get rid of the 'render on black/white'. 254 255 // now separate the color and alpha channels 256 GBitmap *bmp = new GBitmap; 257 bmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8A8); 258 U8 * wbmp = (U8*)mWhiteBmp->getBits(0); 259 U8 * bbmp = (U8*)mBlackBmp->getBits(0); 260 U8 * dst = (U8*)bmp->getBits(0); 261 262 const U32 pixCount = mDim * mDim; 263 264 // simpler, probably faster... 265 for ( U32 i=0; i < pixCount; i++ ) 266 { 267 // Shape on black background is alpha * color, shape on white 268 // background is alpha * color + (1-alpha) * 255 we want 255 * 269 // alpha, or 255 - (white - black). 270 // 271 // JMQ: or more verbosely: 272 // cB = alpha * color + (0 * (1 - alpha)) 273 // cB = alpha * color 274 // cW = alpha * color + (255 * (1 - alpha)) 275 // cW = cB + (255 * (1 - alpha)) 276 // solving for alpha 277 // cW - cB = 255 * (1 - alpha) 278 // (cW - cB)/255 = (1 - alpha) 279 // alpha = 1 - (cW - cB)/255 280 // since we want alpha*255, multiply through by 255 281 // alpha * 255 = 255 - cW - cB 282 U32 alpha = 255 - (wbmp[i*3+0] - bbmp[i*3+0]); 283 alpha += 255 - (wbmp[i*3+1] - bbmp[i*3+1]); 284 alpha += 255 - (wbmp[i*3+2] - bbmp[i*3+2]); 285 286 if ( alpha != 0 ) 287 { 288 F32 floatAlpha = ((F32)alpha)/(3.0f*255.0f); 289 dst[i*4+0] = (U8)(bbmp[i*3+0] / floatAlpha); 290 dst[i*4+1] = (U8)(bbmp[i*3+1] / floatAlpha); 291 dst[i*4+2] = (U8)(bbmp[i*3+2] / floatAlpha); 292 293 // Before we assign the alpha we "fizzle" the value 294 // if its greater than 84. This causes the imposter 295 // to dissolve instead of popping into view. 296 alpha /= 3; 297 dst[i*4+3] = (U8)alpha; 298 } 299 else 300 { 301 dst[i*4+0] = dst[i*4+1] = dst[i*4+2] = dst[i*4+3] = 0; 302 } 303 } 304 305 PROFILE_END(); // TSShapeInstance_snapshot_sb_separate 306 307 PROFILE_START(TSShapeInstance_snapshot_sb_filter); 308 309 // We now run a special kernel filter over the image that 310 // averages color into the transparent areas. This should 311 // in essence give us a border around the edges of the 312 // imposter silhouette which fixes the artifacts around the 313 // alpha test billboards. 314 U8* dst2 = (U8*)imposterOut->getBits(0); 315 316 _colorAverageFilter( mDim, dst, dst2 ); 317 318 if ( 0 ) 319 { 320 FileStream fs; 321 if ( fs.open( "./imposterout.png", Torque::FS::File::Write ) ) 322 imposterOut->writeBitmap( "png", fs ); 323 324 fs.close(); 325 326 if ( fs.open( "./temp.png", Torque::FS::File::Write ) ) 327 bmp->writeBitmap( "png", fs ); 328 329 fs.close(); 330 } 331 332 333 PROFILE_END(); // TSShapeInstance_snapshot_sb_filter 334 335 delete bmp; 336} 337 338 339void ImposterCapture::_convertDXT5nm( GBitmap *normalsOut ) 340{ 341 PROFILE_SCOPE(ImposterCapture_ConvertDXT5nm); 342 343 U8 *bits = (U8*)normalsOut->getBits(0); 344 const U32 pixCount = mDim * mDim; 345 U8 x, y, z; 346 347 // Encoding in object space DXT5 which moves 348 // one of the coords to the alpha channel for 349 // improved precision.... in our case z. 350 351 for ( U32 i=0; i < pixCount; i++ ) 352 { 353 x = bits[i*4+0]; 354 y = bits[i*4+1]; 355 z = bits[i*4+2]; 356 357 bits[i*4+0] = x; 358 bits[i*4+1] = y; 359 bits[i*4+2] = 0; 360 bits[i*4+3] = z; 361 } 362} 363 364void ImposterCapture::begin( TSShapeInstance *shapeInst, 365 S32 dl, 366 S32 dim, 367 F32 radius, 368 const Point3F ¢er ) 369{ 370 mShapeInstance = shapeInst; 371 mDl = dl; 372 mDim = dim; 373 mRadius = radius; 374 mCenter = center; 375 376 mBlackTex.set( mDim, mDim, GFXFormatR8G8B8A8_SRGB, &GFXRenderTargetSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 377 mWhiteTex.set( mDim, mDim, GFXFormatR8G8B8A8_SRGB, &GFXRenderTargetSRGBProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 378 mNormalTex.set( mDim, mDim, GFXFormatR8G8B8A8, &GFXRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 379 mDepthBuffer.set( mDim, mDim, GFXFormatD24S8, &GFXZTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) ); 380 381 // copy the black render target data into a bitmap 382 mBlackBmp = new GBitmap; 383 mBlackBmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8); 384 385 // copy the white target data into a bitmap 386 mWhiteBmp = new GBitmap; 387 mWhiteBmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8); 388 389 // Setup viewport and frustrum to do orthographic projection. 390 RectI viewport( 0, 0, mDim, mDim ); 391 GFX->setViewport( viewport ); 392 GFX->setOrtho( -mRadius, mRadius, -mRadius, mRadius, 1, 20.0f * mRadius ); 393 394 // Position camera looking out the X axis. 395 MatrixF cameraMatrix( true ); 396 cameraMatrix.setColumn( 0, Point3F( 0, 0, 1 ) ); 397 cameraMatrix.setColumn( 1, Point3F( 1, 0, 0 ) ); 398 cameraMatrix.setColumn( 2, Point3F( 0, 1, 0 ) ); 399 400 // setup scene state required for TS mesh render...this is messy and inefficient; 401 // should have a mode where most of this is done just once (and then 402 // only the camera matrix changes between snapshots). 403 // note that we use getFrustum here, but we set up an ortho projection above. 404 // it doesn't seem like the scene state object pays attention to whether the projection is 405 // ortho or not. this could become a problem if some code downstream tries to 406 // reconstruct the projection matrix using the dimensions and doesn't 407 // realize it should be ortho. at the moment no code is doing that. 408 F32 left, right, top, bottom, nearPlane, farPlane; 409 bool isOrtho; 410 GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho ); 411 Frustum frust( isOrtho, left, right, top, bottom, nearPlane, farPlane, cameraMatrix ); 412 413 // Set up render pass. 414 415 mRenderPass = new RenderPassManager(); 416 mRenderPass->assignName( "DiffuseRenderPass" ); 417 mMeshRenderBin = new RenderMeshMgr(); 418 mRenderPass->addManager( mMeshRenderBin ); 419 420 // Set up scene state. 421 422 mState = new SceneRenderState( 423 gClientSceneGraph, 424 SPT_Diffuse, 425 SceneCameraState( viewport, frust, GFX->getWorldMatrix(),GFX->getProjectionMatrix() ), 426 mRenderPass, 427 false 428 ); 429 430 // Set up our TS render state. 431 mRData.setSceneState( mState ); 432 mRData.setCubemap( NULL ); 433 mRData.setFadeOverride( 1.0f ); 434 435 // set gfx up for render to texture 436 GFX->pushActiveRenderTarget(); 437 mRenderTarget = GFX->allocRenderToTextureTarget(); 438 439} 440 441void ImposterCapture::capture( const MatrixF &rotMatrix, 442 GBitmap **imposterOut, 443 GBitmap **normalMapOut ) 444{ 445 GFXTransformSaver saver; 446 447 // this version of the snapshot function renders the shape to a black texture, then to white, then reads bitmaps 448 // back for both renders and combines them, restoring the alpha and color values. this is based on the 449 // TGE implementation. it is not fast due to the copy and software combination operations. the generated bitmaps 450 // are upside-down (which is how TGE generated them...) 451 452 (*imposterOut) = new GBitmap( mDim, mDim, false, GFXFormatR8G8B8A8 ); 453 (*normalMapOut) = new GBitmap( mDim, mDim, false, GFXFormatR8G8B8A8 ); 454 455 // The object to world transform. 456 MatrixF centerMat( true ); 457 centerMat.setPosition( -mCenter ); 458 MatrixF objMatrix( rotMatrix ); 459 objMatrix.mul( centerMat ); 460 GFX->setWorldMatrix( objMatrix ); 461 462 // The view transform. 463 MatrixF view( EulerF( M_PI_F / 2.0f, 0, M_PI_F ), Point3F( 0, 0, -10.0f * mRadius ) ); 464 mRenderPass->assignSharedXform( RenderPassManager::View, view ); 465 466 mRenderPass->assignSharedXform( RenderPassManager::Projection, GFX->getProjectionMatrix() ); 467 468 // Render the diffuse pass. 469 mRenderPass->clear(); 470 mMeshRenderBin->getMatOverrideDelegate().bind( ImposterCaptureMaterialHook::getDiffuseInst ); 471 _renderToTexture( mBlackTex, mBlackBmp, ColorI(0, 0, 0, 0) ); 472 _renderToTexture( mWhiteTex, mWhiteBmp, ColorI(255, 255, 255, 255) ); 473 474 // Now render the normals. 475 mRenderPass->clear(); 476 mMeshRenderBin->getMatOverrideDelegate().bind( ImposterCaptureMaterialHook::getNormalsInst ); 477 _renderToTexture( mNormalTex, *normalMapOut, ColorI(0, 0, 0, 0) ); 478 479 480 _separateAlpha( *imposterOut ); 481 _convertDXT5nm( *normalMapOut ); 482 483 if ( 0 ) 484 { 485 // Render out the bitmaps for debug purposes. 486 FileStream fs; 487 if ( fs.open( "./blackbmp.png", Torque::FS::File::Write ) ) 488 mBlackBmp->writeBitmap( "png", fs ); 489 490 fs.close(); 491 492 if ( fs.open( "./whitebmp.png", Torque::FS::File::Write ) ) 493 mWhiteBmp->writeBitmap( "png", fs ); 494 495 fs.close(); 496 497 if ( fs.open( "./normalbmp.png", Torque::FS::File::Write ) ) 498 (*normalMapOut)->writeBitmap( "png", fs ); 499 500 fs.close(); 501 502 if ( fs.open( "./finalimposter.png", Torque::FS::File::Write ) ) 503 (*imposterOut)->writeBitmap( "png", fs ); 504 505 fs.close(); 506 } 507} 508 509void ImposterCapture::end() 510{ 511 GFX->popActiveRenderTarget(); 512 513 mBlackTex.free(); 514 mWhiteTex.free(); 515 mNormalTex.free(); 516 517 mShapeInstance = NULL; 518 519 mRenderTarget = NULL; 520 mMeshRenderBin = NULL; // Deleted by mRenderPass 521 SAFE_DELETE( mState ); 522 SAFE_DELETE( mRenderPass ); 523 SAFE_DELETE( mBlackBmp ); 524 SAFE_DELETE( mWhiteBmp ); 525} 526 527