NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

SkinLightmapRenderer.py (23617B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\paperDoll\SkinLightmapRenderer.py
      2 from __future__ import print_function
      3 from __future__ import absolute_import
      4 import trinity
      5 import blue
      6 import telemetry
      7 import weakref
      8 import itertools
      9 import log
     10 from . import commonClientFunctions as pdCcf
     11 from eve.common.script.paperDoll import paperDollCommonFunctions as pdCf
     12 
     13 def LogError(*args):
     14     log.Log(' '.join(map(strx, args)), log.LGERR)
     15 
     16 
     17 class SkinLightmapRenderer:
     18     renderJob = None
     19     scene = None
     20     instances = set({})
     21 
     22     @staticmethod
     23     def Scene():
     24         if SkinLightmapRenderer.scene is None or SkinLightmapRenderer.scene.object is None:
     25             jobs = trinity.renderJobs.recurring
     26             for job in jobs:
     27                 if 'paperdoll' in job.name.lower():
     28                     return job.scene.object
     29 
     30             return
     31         return SkinLightmapRenderer.scene.object
     32 
     33     LIGHTMAP_RENDERER_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFLightmapUnwrap_Single.fx'
     34     LIGHTMAP_RENDERER_DOUBLE_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFLightmapUnwrap_Double.fx'
     35     STRETCHMAP_RENDERER_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFStretchmap.fx'
     36     LIGHTMAP_SCATTER_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/LightmapScatter.fx'
     37     LIGHTMAP_EXPAND_EFFECT = 'res:/Graphics/Effect/Utility/Compositing/ExpandOpaqueNoMask.fx'
     38     BECKMANN_LOOKUP_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFBeckmannLookup.fx'
     39     LIGHTMAP_APPLICATION_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFLightmapApplication_Single.fx'
     40     LIGHTMAP_APPLICATION_DOUBLE_EFFECT = 'res:/Graphics/Effect/Managed/Interior/Avatar/SkinnedAvatarBRDFLightmapApplication_Double.fx'
     41 
     42     class Mesh:
     43 
     44         def __init__(self):
     45             self.origEffect = {}
     46             self.stretchmapRenderEffect = {}
     47             self.lightmapRenderEffect = {}
     48             self.lightmapApplyEffect = {}
     49             self.liveParameters = []
     50 
     51         def ExtractOrigEffect(self, mesh):
     52             for area in itertools.chain(mesh.opaqueAreas, mesh.decalAreas):
     53                 pdCcf.AddWeakBlue(self, 'origEffect', area, area.effect)
     54 
     55             return len(self.origEffect) > 0
     56 
     57     @staticmethod
     58     def CreateRenderTarget(width, height, format, useRT = False):
     59         return trinity.TriTextureRes(trinity.Tr2RenderTarget(width, height, 1, format))
     60 
     61     @telemetry.ZONE_METHOD
     62     def CreateRenderTargets(self, lightmapFormat):
     63         try:
     64             self.lightmap = self.CreateRenderTarget(self.lightmapSize[0], self.lightmapSize[1], lightmapFormat)
     65             self.stretchmap = self.CreateRenderTarget(min(1024, self.lightmapSize[0]), min(1024, self.lightmapSize[1]), lightmapFormat)
     66             self.scatteredLightmap = self.CreateRenderTarget(self.lightmapSize[0], self.lightmapSize[1], lightmapFormat)
     67             self.depth = trinity.Tr2DepthStencil(self.lightmapSize[0], self.lightmapSize[1], trinity.DEPTH_STENCIL_FORMAT.D24S8)
     68         except Exception:
     69             self.lightmap = None
     70             self.stretchmap = None
     71             self.scatteredLightmap = None
     72             return False
     73 
     74         return True
     75 
     76     @telemetry.ZONE_METHOD
     77     def AddTex2D(self, effect, name, resourcePath = None):
     78         param = trinity.TriTextureParameter()
     79         param.name = name
     80         if resourcePath is not None:
     81             param.resourcePath = resourcePath
     82         effect.resources.append(param)
     83         return param
     84 
     85     def Print(self, msg):
     86         print('[Skin]', msg)
     87 
     88     def DebugPrint(self, msg):
     89         pass
     90 
     91     @telemetry.ZONE_METHOD
     92     def __init__(self, highQuality = True, useHDR = True):
     93         if True:
     94             raytraceWidth = 2048
     95             raytraceHeight = 2048
     96         else:
     97             raytraceWidth = 512
     98             raytraceHeight = 512
     99         self.raytracing = None
    100         self.refreshMeshes = False
    101         self.skinMapPath = None
    102         self.precomputeBeckmann = False
    103         self.useExpandOpaque = False
    104         self.lightmapEffectPreload = self.PreloadEffect(self.LIGHTMAP_RENDERER_EFFECT)
    105         self.lightmapDoubleEffectPreload = self.PreloadEffect(self.LIGHTMAP_RENDERER_DOUBLE_EFFECT)
    106         self.stretchmapEffectPreload = self.PreloadEffect(self.STRETCHMAP_RENDERER_EFFECT)
    107         self.lightmapApplicationEffectPreload = self.PreloadEffect(self.LIGHTMAP_APPLICATION_EFFECT)
    108         self.lightmapApplicationDoubleEffectPreload = self.PreloadEffect(self.LIGHTMAP_APPLICATION_DOUBLE_EFFECT)
    109         self.lightmapScatterEffect = self.PreloadEffect(self.LIGHTMAP_SCATTER_EFFECT)
    110         self.lightmapExpandEffect = self.PreloadEffect(self.LIGHTMAP_EXPAND_EFFECT)
    111         if self.precomputeBeckmann:
    112             self.beckmannLookupEffect = self.PreloadEffect(self.BECKMANN_LOOKUP_EFFECT)
    113         self.skinnedObject = None
    114         self.paramLightmap = self.AddTex2D(self.lightmapScatterEffect, 'Lightmap')
    115         self.paramStretchmap = self.AddTex2D(self.lightmapScatterEffect, 'Stretchmap')
    116         self.paramNoisemap = self.AddTex2D(self.lightmapScatterEffect, 'Noisemap', 'res:/Texture/Global/noise.png')
    117         self.paramSkinMap = self.AddTex2D(self.lightmapScatterEffect, 'SkinMap')
    118         self.paramExpandSource = self.AddTex2D(self.lightmapExpandEffect, 'Texture')
    119         self.paramDiffuseScattermap = self.AddTex2D(self.lightmapApplicationEffectPreload, 'DiffuseScatterMap')
    120         self.paramDiffusemap = self.AddTex2D(self.lightmapApplicationEffectPreload, 'DiffuseMap')
    121         if self.precomputeBeckmann:
    122             self.paramBeckmannLookup = self.AddTex2D(self.lightmapApplicationEffectPreload, 'BeckmannLookup')
    123         if highQuality:
    124             self.lightmapSize = (2048, 2048)
    125         else:
    126             self.lightmapSize = (512, 512)
    127         lightmapFormat = []
    128         if useHDR:
    129             lightmapFormat = [trinity.PIXEL_FORMAT.R16G16B16A16_FLOAT]
    130         lightmapFormat.append(trinity.PIXEL_FORMAT.B8G8R8X8_UNORM)
    131         lightmapFormat.append(trinity.PIXEL_FORMAT.B8G8R8A8_UNORM)
    132         lightmapFormat.append(trinity.PIXEL_FORMAT.R8G8B8A8_UNORM)
    133         self.lightmapRenderJob = None
    134         setupOK = False
    135         for format in lightmapFormat:
    136             if self.CreateRenderTargets(format):
    137                 setupOK = True
    138                 break
    139 
    140         if not setupOK:
    141             self.lightmapSize = (self.lightmapSize[0] / 2, self.lightmapSize[1] / 2)
    142             for format in lightmapFormat:
    143                 if self.CreateRenderTargets(format):
    144                     setupOK = True
    145                     break
    146 
    147             if setupOK:
    148                 self.Print('Warning: needed to downsize the lightmap resolution')
    149             else:
    150                 self.Print("Error: couldn't find any lightmap render format that works :(")
    151                 return
    152         if self.precomputeBeckmann:
    153             try:
    154                 self.beckmannLookup = self.CreateRenderTarget(256, 256, trinity.PIXEL_FORMAT.R8_UNORM)
    155                 self.RenderBeckmannLookup()
    156             except Exception:
    157                 self.Print("[Skin] Error: can't create specular lookup table texture")
    158                 return
    159 
    160         self.meshes = {}
    161         SkinLightmapRenderer.instances.add(weakref.ref(self))
    162 
    163     @telemetry.ZONE_METHOD
    164     def __del__(self):
    165         newSet = set({})
    166         for instance in SkinLightmapRenderer.instances:
    167             if instance():
    168                 newSet.add(instance)
    169 
    170         SkinLightmapRenderer.instances = newSet
    171         self.StopRendering()
    172         self.RestoreShaders()
    173 
    174     @staticmethod
    175     def PreloadEffect(path):
    176         effect = trinity.Tr2Effect()
    177         effect.effectFilePath = path
    178         while not effect.effectResource.isPrepared:
    179             pdCf.Yield()
    180 
    181         effect.RebuildCachedData()
    182         return effect
    183 
    184     @telemetry.ZONE_METHOD
    185     def CreateExtraMaps(self, effect):
    186         try:
    187             effect.StartUpdate()
    188             res = self.FindResource(effect, 'DiffuseScatterMap')
    189             if res is None:
    190                 res = self.AddTex2D(effect, 'DiffuseScatterMap')
    191             res.SetResource(self.lightmap if self.useExpandOpaque else self.scatteredLightmap)
    192             res = self.FindResource(effect, 'BeckmannLookup')
    193             if res is None:
    194                 if self.precomputeBeckmann:
    195                     res = self.AddTex2D(effect, 'BeckmannLookup')
    196                 else:
    197                     res = self.AddTex2D(effect, 'BeckmannLookup', 'res:/Texture/Global/beckmannSpecular.dds')
    198             if self.precomputeBeckmann:
    199                 res.SetResource(self.beckmannLookup)
    200         finally:
    201             effect.EndUpdate()
    202 
    203     @staticmethod
    204     def FindResource(effect, name):
    205         for r in effect.resources:
    206             if r.name == name:
    207                 return r
    208 
    209     @staticmethod
    210     def DuplicateResource(targetEffect, sourceEffect, name):
    211         p = trinity.TriTextureParameter()
    212         p.name = name
    213         p.SetResource(SkinLightmapRenderer.FindResource(sourceEffect, name).resource)
    214         targetEffect.resources.append(p)
    215 
    216     @staticmethod
    217     def DuplicateEffect(fx, newPath, dupResources = True):
    218         newEffect = trinity.Tr2Effect()
    219         newEffect.effectFilePath = newPath
    220         newEffect.parameters.extend(fx.parameters)
    221         newEffect.name = fx.name
    222         if dupResources:
    223             for r in fx.resources:
    224                 SkinLightmapRenderer.DuplicateResource(newEffect, fx, r.name)
    225 
    226         newEffect.PopulateParameters()
    227         newEffect.RebuildCachedData()
    228         return newEffect
    229 
    230     @telemetry.ZONE_METHOD
    231     def AddMesh(self, mesh):
    232         if mesh is None or self.lightmap is None:
    233             return
    234 
    235         def SetupSkinMap():
    236             for a in itertools.chain(mesh.opaqueAreas, mesh.decalAreas):
    237                 if a.effect is not None and a.effect.resources is not None:
    238                     for e in a.effect.resources:
    239                         if e.name == 'SkinMap':
    240                             self.skinMapPath = e.resourcePath
    241                             self.paramSkinMap.resourcePath = self.skinMapPath
    242                             return
    243 
    244         if self.skinMapPath is None:
    245             SetupSkinMap()
    246         m = self.Mesh()
    247         if not m.ExtractOrigEffect(mesh):
    248             return
    249         for areaRef, fx in m.origEffect.iteritems():
    250             if areaRef.object is None:
    251                 continue
    252             if pdCcf.IsBeard(areaRef.object):
    253                 pdCcf.AddWeakBlue(m, 'lightmapRenderEffect', areaRef.object, None)
    254                 pdCcf.AddWeakBlue(m, 'lightmapApplyEffect', areaRef.object, fx)
    255                 pdCcf.AddWeakBlue(m, 'stretchmapRenderEffect', areaRef.object, None)
    256                 continue
    257             wasDouble = 'double' in fx.effectFilePath.lower()
    258             singleApply = self.LIGHTMAP_APPLICATION_EFFECT
    259             doubleApply = self.LIGHTMAP_APPLICATION_DOUBLE_EFFECT
    260             lightmapRenderEffect = SkinLightmapRenderer.DuplicateEffect(fx, self.LIGHTMAP_RENDERER_DOUBLE_EFFECT if wasDouble else self.LIGHTMAP_RENDERER_EFFECT)
    261             lightmapApplyEffect = SkinLightmapRenderer.DuplicateEffect(fx, doubleApply if wasDouble else singleApply)
    262             pdCcf.AddWeakBlue(m, 'lightmapRenderEffect', areaRef.object, lightmapRenderEffect)
    263             pdCcf.AddWeakBlue(m, 'lightmapApplyEffect', areaRef.object, lightmapApplyEffect)
    264             stretchmapRenderEffect = trinity.Tr2Effect()
    265             stretchmapRenderEffect.effectFilePath = self.STRETCHMAP_RENDERER_EFFECT
    266             stretchmapRenderEffect.parameters.extend(fx.parameters)
    267             pdCcf.AddWeakBlue(m, 'stretchmapRenderEffect', areaRef.object, stretchmapRenderEffect)
    268             m.liveParameters.append(filter(lambda r: r[0].name.startswith('WrinkleNormalStrength') or r[0].name.startswith('spotLight'), zip(fx.parameters, lightmapRenderEffect.parameters, lightmapApplyEffect.parameters)))
    269             self.CreateExtraMaps(lightmapApplyEffect)
    270             stretchmapRenderEffect.PopulateParameters()
    271             stretchmapRenderEffect.RebuildCachedData()
    272             lightmapApplyEffect.RebuildCachedData()
    273             if False:
    274                 self.DebugPrint('lightmapRenderEffect resources:')
    275                 for p in lightmapRenderEffect.resources:
    276                     self.DebugPrint(p.name)
    277 
    278                 self.DebugPrint('lightmapApplyEffect resources:')
    279                 for p in lightmapApplyEffect.resources:
    280                     self.DebugPrint(p.name)
    281 
    282         pdCcf.AddWeakBlue(self, 'meshes', mesh, m)
    283 
    284     @staticmethod
    285     def DoChangeEffect(attrName, meshes):
    286         for meshRef, meshData in meshes.iteritems():
    287             mesh = meshRef.object
    288             count = 0
    289             dictionary = getattr(meshData, attrName)
    290             for areaRef, effect in dictionary.iteritems():
    291                 if areaRef.object is not None:
    292                     areaRef.object.effect = effect
    293 
    294     def ChangeEffect(self, attrName, meshes = None):
    295         if meshes is None:
    296             meshes = self.meshes
    297         SkinLightmapRenderer.DoChangeEffect(attrName, meshes)
    298 
    299     @staticmethod
    300     def DoRestoreShaders(meshes):
    301         for meshRef, meshData in meshes.iteritems():
    302             mesh = meshRef.object
    303             for areaRef, effect in meshData.origEffect.iteritems():
    304                 if areaRef.object:
    305                     areaRef.object.effect = effect
    306 
    307     @telemetry.ZONE_METHOD
    308     def RestoreShaders(self, meshes = None):
    309         if meshes is None:
    310             meshes = self.meshes
    311         SkinLightmapRenderer.DoRestoreShaders(meshes)
    312 
    313     @staticmethod
    314     def IsScattering(mesh):
    315         if mesh.name.startswith('head'):
    316             for a in mesh.opaqueAreas:
    317                 if a.effect is not None:
    318                     name = a.effect.name.lower()
    319                     if name.startswith('c_skin') and 'tearduct' not in name:
    320                         return True
    321 
    322         return False
    323 
    324     @telemetry.ZONE_METHOD
    325     def BindLightmapShader(self, mesh):
    326         if SkinLightmapRenderer.IsScattering(mesh):
    327             self.AddMesh(mesh)
    328             return True
    329         return False
    330 
    331     @telemetry.ZONE_METHOD
    332     def SetSkinnedObject(self, skinnedObject):
    333         if self.skinnedObject != skinnedObject or self.refreshMeshes:
    334             self.skinnedObject = skinnedObject
    335             self.meshes = {}
    336         self.refreshMeshes = False
    337         if skinnedObject is None or skinnedObject.visualModel is None:
    338             return
    339         haveNewMeshes = False
    340         for mesh in skinnedObject.visualModel.meshes:
    341             for meshRef in self.meshes:
    342                 if meshRef.object == mesh:
    343                     break
    344             else:
    345                 haveNewMeshes = self.BindLightmapShader(mesh) or haveNewMeshes
    346 
    347         scene = SkinLightmapRenderer.Scene()
    348         for each in scene.dynamics:
    349             each.display = each == skinnedObject
    350 
    351         if haveNewMeshes:
    352             self.RenderStretchmap()
    353 
    354     @telemetry.ZONE_METHOD
    355     def Refresh(self, immediateUpdate = False):
    356         self.refreshMeshes = True
    357         if immediateUpdate:
    358             self.SetSkinnedObject(self.skinnedObject)
    359 
    360     @staticmethod
    361     def DoCopyMeshesToVisual(skinnedObject, meshList):
    362         if skinnedObject is None or skinnedObject.visualModel is None:
    363             return
    364         skinnedObject.visualModel.meshes.removeAt(-1)
    365         for mesh in meshList:
    366             skinnedObject.visualModel.meshes.append(mesh)
    367 
    368     @telemetry.ZONE_METHOD
    369     def CopyMeshesToVisual(self, meshList):
    370         SkinLightmapRenderer.DoCopyMeshesToVisual(self.skinnedObject, meshList)
    371 
    372     @telemetry.ZONE_METHOD
    373     def OnBeforeUnwrap(self):
    374         self.ChangeEffect('lightmapRenderEffect')
    375         scene = SkinLightmapRenderer.Scene()
    376         if hasattr(scene, 'updateShadowCubeMap'):
    377             scene.updateShadowCubeMap = False
    378         if self.skinnedObject is not None and self.skinnedObject.visualModel is not None:
    379             self.savedMeshes = self.skinnedObject.visualModel.meshes[:]
    380         filteredMeshes = [ ref.object for ref in self.meshes.iterkeys() if ref.object is not None ]
    381         self.CopyMeshesToVisual(filteredMeshes)
    382         for meshData in self.meshes.itervalues():
    383             for list in meshData.liveParameters:
    384                 for params in list:
    385                     params[1].value = params[0].value
    386                     params[2].value = params[0].value
    387 
    388         trinity.settings.SetValue('apexVisualizeOnly', True)
    389 
    390     @telemetry.ZONE_METHOD
    391     def OnAfterUnwrap(self):
    392         scene = SkinLightmapRenderer.Scene()
    393         if hasattr(scene, 'updateShadowCubeMap'):
    394             scene.updateShadowCubeMap = True
    395         if hasattr(self, 'savedMeshes'):
    396             self.CopyMeshesToVisual(self.savedMeshes)
    397             del self.savedMeshes
    398         for each in scene.dynamics:
    399             each.display = True
    400 
    401         trinity.settings.SetValue('apexVisualizeOnly', False)
    402 
    403     @telemetry.ZONE_METHOD
    404     def CreateRJ(self, target, name, depthClear = 0.0, setDepthStencil = True, clearColor = (0.125, 0.125, 0.125, 1.0), isRT = False):
    405         rj = trinity.CreateRenderJob(name)
    406         rj.PushRenderTarget(target.wrappedRenderTarget).name = name
    407         if setDepthStencil:
    408             rj.PushDepthStencil(self.depth)
    409         rj.Clear(clearColor, depthClear)
    410         rj.SetStdRndStates(trinity.RM_FULLSCREEN)
    411         vp = trinity.TriViewport()
    412         vp.x = 0
    413         vp.y = 0
    414         if False:
    415             rdesc = target.GetDesc()
    416             vp.width = rdesc['Width']
    417             vp.height = rdesc['Height']
    418         else:
    419             vp.width = target.width
    420             vp.height = target.height
    421         rj.SetViewport(vp)
    422         return rj
    423 
    424     @telemetry.ZONE_METHOD
    425     def RenderBeckmannLookup(self):
    426         rj = self.CreateRJ(self.beckmannLookup, 'Beckmann precalc')
    427         rj.SetDepthStencil(None)
    428         rj.RenderEffect(self.beckmannLookupEffect)
    429         rj.PopRenderTarget()
    430         rj.PopDepthStencil()
    431         rj.ScheduleOnce()
    432 
    433     @staticmethod
    434     def FuncWrapper(weakSelf, func):
    435         if weakSelf():
    436             func(weakSelf())
    437 
    438     @telemetry.ZONE_METHOD
    439     def AddCallback(self, func, name, rj = None):
    440         cb = trinity.TriStepPythonCB()
    441         weakSelf = weakref.ref(self)
    442         cb.SetCallback(lambda : SkinLightmapRenderer.FuncWrapper(weakSelf, func))
    443         cb.name = name
    444         rj = rj if rj is not None else self.lightmapRenderJob
    445         rj.steps.append(cb)
    446 
    447     @telemetry.ZONE_METHOD
    448     def RenderStretchmap(self):
    449         if SkinLightmapRenderer.Scene() is None:
    450             return
    451         rj = self.CreateRJ(self.stretchmap, 'Stretchmap precalc', depthClear=None, setDepthStencil=False)
    452         rj.PushDepthStencil(None)
    453         self.AddCallback(SkinLightmapRenderer.OnBeforeUnwrap, 'onBeforeUnwrap', rj)
    454         self.AddCallback(lambda weakSelf: weakSelf.ChangeEffect('stretchmapRenderEffect'), 'change to stretch effect', rj)
    455         rj.Update(SkinLightmapRenderer.Scene())
    456         rj.RenderScene(SkinLightmapRenderer.Scene())
    457         self.AddCallback(SkinLightmapRenderer.OnAfterUnwrap, 'onAfterUnwrap', rj)
    458         self.AddCallback(SkinLightmapRenderer.RestoreShaders, 'restoreShaders', rj)
    459         rj.PopDepthStencil()
    460         rj.PopRenderTarget()
    461         rj.ScheduleOnce()
    462 
    463     @telemetry.ZONE_METHOD
    464     def OnBeforeBlur(self):
    465         rj = self.lightmapRenderJob
    466         self.paramLightmap.SetResource(self.lightmap)
    467         self.paramStretchmap.SetResource(self.stretchmap)
    468 
    469     @telemetry.ZONE_METHOD
    470     def OnBeforeExpand(self):
    471         rj = self.lightmapRenderJob
    472         self.paramExpandSource.SetResource(self.scatteredLightmap)
    473 
    474     @telemetry.ZONE_METHOD
    475     def OnAfterExpand(self):
    476         rj = self.lightmapRenderJob
    477         self.ChangeEffect('lightmapApplyEffect')
    478 
    479     @telemetry.ZONE_METHOD
    480     def SetViewport(self, rj, width, height):
    481         vp = trinity.TriViewport()
    482         vp.x = 0
    483         vp.y = 0
    484         vp.width = width
    485         vp.height = height
    486         rj.SetViewport(vp)
    487 
    488     @telemetry.ZONE_METHOD
    489     def RenderLightmap(self):
    490         rj = self.CreateRJ(self.lightmap, 'Lightmap rendering', depthClear=None, setDepthStencil=False, clearColor=(0, 0, 0, 0))
    491         self.lightmapRenderJob = rj
    492         self.AddCallback(SkinLightmapRenderer.OnBeforeUnwrap, 'onBeforeUnwrap')
    493         doHighZOptimization = False
    494         if not doHighZOptimization:
    495             rj.PushDepthStencil(None).name = 'Depthbuffer = None'
    496         rj.RenderScene(SkinLightmapRenderer.Scene()).name = 'Unwrap light pass'
    497         self.AddCallback(SkinLightmapRenderer.OnAfterUnwrap, 'onAfterUnwrap')
    498         rj.SetRenderTarget(self.scatteredLightmap.wrappedRenderTarget).name = 'Set Scattermap RT'
    499         self.AddCallback(SkinLightmapRenderer.OnBeforeBlur, 'onBeforeBlur')
    500         rj.SetStdRndStates(trinity.RM_FULLSCREEN)
    501         rj.RenderEffect(self.lightmapScatterEffect).name = 'Light blur pass'
    502         if self.useExpandOpaque:
    503             rj.SetRenderTarget(self.lightmap.wrappedRenderTarget).name = 'Set Lightmap RT for expand'
    504             self.AddCallback(SkinLightmapRenderer.OnBeforeExpand, 'onBeforeExpand')
    505             rj.RenderEffect(self.lightmapExpandEffect).name = 'Expand lightmap'
    506         self.AddCallback(SkinLightmapRenderer.OnAfterExpand, 'onAfterExpand')
    507         rj.PopRenderTarget().name = 'PopRT - main scene rendering'
    508         if not doHighZOptimization:
    509             rj.PopDepthStencil()
    510         if SkinLightmapRenderer.renderJob is not None and SkinLightmapRenderer.renderJob.object is not None:
    511             step = trinity.TriStepRunJob()
    512             step.job = rj
    513             SkinLightmapRenderer.renderJob.object.steps.append(step)
    514         else:
    515             jobs = trinity.renderJobs.recurring
    516             for job in jobs:
    517                 if 'paperdoll' in job.name.lower():
    518                     step = trinity.TriStepRunJob(rj)
    519                     step.name = rj.name
    520                     job.steps.insert(len(job.steps) - 1, step)
    521                     break
    522             else:
    523                 rj.ScheduleRecurring()
    524 
    525     @staticmethod
    526     def SaveTarget(target, path, isRT = False):
    527         if type(target) == trinity.Tr2RenderTarget:
    528             trinity.Tr2HostBitmap(target).Save(path)
    529         else:
    530             target.Save(path)
    531 
    532     def IsRendering(self):
    533         return self.lightmapRenderJob is not None
    534 
    535     @telemetry.ZONE_METHOD
    536     def StartRendering(self):
    537         if self.lightmap is None:
    538             return
    539         if self.lightmapRenderJob is not None:
    540             return
    541         self.RenderLightmap()
    542 
    543     @telemetry.ZONE_METHOD
    544     def StopRendering(self):
    545         if self.lightmapRenderJob is None:
    546             return
    547         if SkinLightmapRenderer.renderJob is not None and SkinLightmapRenderer.renderJob.object is not None:
    548             for step in SkinLightmapRenderer.renderJob.object.steps:
    549                 if step.job == self.lightmapRenderJob:
    550                     SkinLightmapRenderer.renderJob.object.steps.remove(step)
    551                     break
    552 
    553         else:
    554             self.lightmapRenderJob.UnscheduleRecurring()
    555         self.lightmapRenderJob = None
    556 
    557     def CreateScatterStep(renderJob, scene, append = True):
    558         step = trinity.TriStepRunJob()
    559         step.name = 'Subsurface scattering'
    560         step.job = trinity.CreateRenderJob('Render scattering')
    561         if append:
    562             renderJob.steps.append(step)
    563         SkinLightmapRenderer.renderJob = blue.BluePythonWeakRef(step.job)
    564         SkinLightmapRenderer.scene = blue.BluePythonWeakRef(scene)
    565         return step