Torque3D Documentation / _generateds / imposterCapture.cpp

imposterCapture.cpp

Engine/source/util/imposterCapture.cpp

More...

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 &center )
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