FDeferredShadingSceneRenderer::FDeferredShadingSceneRenderer(const FSceneViewFamily* InViewFamily,FHitProxyConsumer* HitProxyConsumer)
	: FSceneRenderer(InViewFamily, HitProxyConsumer)
	, EarlyZPassMode(DDM_NonMaskedOnly)
	, TranslucentSelfShadowLayout(0, 0, 0, 0)
	, CachedTranslucentSelfShadowLightId(INDEX_NONE)
{
	if (FPlatformProperties::SupportsWindowedMode())
	{
		// Use a depth only pass if we are using full blown HQ lightmaps
		// Otherwise base pass pixel shaders will be cheap and there will be little benefit to rendering a depth only pass
		if (AllowHighQualityLightmaps() || !ViewFamily.EngineShowFlags.Lighting)
		{
			EarlyZPassMode = DDM_None;
		}
	}

	// Shader complexity requires depth only pass to display masked material cost correctly
	if (ViewFamily.EngineShowFlags.ShaderComplexity)
	{
		EarlyZPassMode = DDM_AllOccluders;
	}

	// developer override, good for profiling, can be useful as project setting
	{
		static const auto ICVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.EarlyZPass"));
		const int32 CVarValue = ICVar->GetValueOnGameThread();

		switch(CVarValue)
		{
			case 0: EarlyZPassMode = DDM_None; break;
			case 1: EarlyZPassMode = DDM_NonMaskedOnly; break;
			case 2: EarlyZPassMode = DDM_AllOccluders; break;
		}
	}
}
void FBasePassForwardOpaqueDrawingPolicyFactory::AddStaticMesh(FRHICommandList& RHICmdList, FScene* Scene, FStaticMesh* StaticMesh)
{
	// Determine the mesh's material and blend mode.
	const auto FeatureLevel = Scene->GetFeatureLevel();
	const FMaterial* Material = StaticMesh->MaterialRenderProxy->GetMaterial(FeatureLevel);
	const EBlendMode BlendMode = Material->GetBlendMode();

	// Only draw opaque materials.
	if( !IsTranslucentBlendMode(BlendMode) )
	{
		// following check moved from ProcessBasePassMeshForForwardShading to avoid passing feature level.
		check(!AllowHighQualityLightmaps(Scene->GetFeatureLevel()));

		ProcessBasePassMeshForForwardShading(
			RHICmdList,
			FProcessBasePassMeshParameters(
				*StaticMesh,
				Material,
				StaticMesh->PrimitiveSceneInfo->Proxy,
				true,
				false,
				ESceneRenderTargetsMode::DontSet,
				FeatureLevel
				),
			FDrawBasePassForwardShadingStaticMeshAction(Scene,StaticMesh)
			);
	}
}
Exemplo n.º 3
0
/**
 * Initializes system settings and included texture LOD settings.
 *
 * @param bSetupForEditor	Whether to initialize settings for Editor
 */
void FSystemSettings::Initialize( bool bSetupForEditor )
{
	TestBitFieldFunctions();

	RegisterShowFlagConsoleVariables();

	// Load the settings that will be the default for every other compat level, and the editor, and the other split screen levels.
	FSystemSettingsData DefaultSettings;
	DefaultSettings.LoadFromIni(GetSectionName(false), GEngineIni, false);

	(FSystemSettingsData&)(*this) = DefaultSettings;
	LoadFromIni();

	ApplyOverrides();

	bInitialUseHighQualityLightmaps = AllowHighQualityLightmaps();

	IConsoleManager::Get().RegisterConsoleVariableSink(FConsoleCommandDelegate::CreateRaw(this, &FSystemSettings::CVarSink));

	// intialize a critical texture streaming value used by texture loading, etc
	int32 MinTextureResidentMipCount = 1;
	verify(GConfig->GetInt(TEXT("TextureStreaming"), TEXT("MinTextureResidentMipCount"), MinTextureResidentMipCount, GEngineIni));
	UTexture2D::SetMinTextureResidentMipCount(MinTextureResidentMipCount);
}
void FIndirectLightingCache::InterpolateBlock(
	FScene* Scene, 
	const FIndirectLightingCacheBlock& Block, 
	TArray<float>& AccumulatedWeight, 
	TArray<FSHVectorRGB2>& AccumulatedIncidentRadiance)
{
	const FBoxCenterAndExtent BlockBoundingBox(Block.Min + Block.Size / 2, Block.Size / 2);
	const FVector HalfTexelWorldOffset = BlockBoundingBox.Extent / FVector(Block.TexelSize);	

	if (GCacheLimitQuerySize && Block.TexelSize > 2)
	{
		for (int32 VolumeIndex = 0; VolumeIndex < Scene->PrecomputedLightVolumes.Num(); VolumeIndex++)
		{
			const FPrecomputedLightVolume* PrecomputedLightVolume = Scene->PrecomputedLightVolumes[VolumeIndex];

			// Compute the target query size
			// We will try to split up the allocation into groups that are smaller than this before querying the octree
			// This prevents very large objects from finding all the samples in the level in their octree search
			const float WorldTargetSize = PrecomputedLightVolume->GetNodeLevelExtent(GCacheQueryNodeLevel) * 2;

			const FVector WorldCellSize = Block.Size / FVector(Block.TexelSize);

			// Number of cells to increment by for query blocks
			FIntVector NumStepCells;
			NumStepCells.X = FMath::Max(1, FMath::FloorToInt(WorldTargetSize / WorldCellSize.X));
			NumStepCells.Y = FMath::Max(1, FMath::FloorToInt(WorldTargetSize / WorldCellSize.Y));
			NumStepCells.Z = FMath::Max(1, FMath::FloorToInt(WorldTargetSize / WorldCellSize.Z));
			FIntVector NumQueryStepCells(0, 0, 0);

			// World space size to increment by for query blocks
			const FVector WorldStepSize = FVector(NumStepCells) * WorldCellSize;
			FVector QueryWorldStepSize(0, 0, 0);

			check(NumStepCells.X > 0 && NumStepCells.Y > 0 && NumStepCells.Z > 0);

			// This will track the position in cells of the query block being built
			FIntVector CellIndex(0, 0, 0);

			// This will track the min world position of the query block being built
			FVector MinPosition = Block.Min;

			for (MinPosition.Z = Block.Min.Z, CellIndex.Z = 0; 
				CellIndex.Z < Block.TexelSize;
				MinPosition.Z += WorldStepSize.Z, CellIndex.Z += NumStepCells.Z)
			{
				QueryWorldStepSize.Z = WorldStepSize.Z;
				NumQueryStepCells.Z = NumStepCells.Z;

				// If this is the last query block in this dimension, adjust both the world space and cell sizes to match
				if (CellIndex.Z + NumStepCells.Z > Block.TexelSize)
				{
					QueryWorldStepSize.Z = Block.Min.Z + Block.Size.Z - MinPosition.Z;
					NumQueryStepCells.Z = Block.TexelSize - CellIndex.Z;
				}

				for (MinPosition.Y = Block.Min.Y, CellIndex.Y = 0; 
					CellIndex.Y < Block.TexelSize;
					MinPosition.Y += WorldStepSize.Y, CellIndex.Y += NumStepCells.Y)
				{
					QueryWorldStepSize.Y = WorldStepSize.Y;
					NumQueryStepCells.Y = NumStepCells.Y;

					if (CellIndex.Y + NumStepCells.Y > Block.TexelSize)
					{
						QueryWorldStepSize.Y = Block.Min.Y + Block.Size.Y - MinPosition.Y;
						NumQueryStepCells.Y = Block.TexelSize - CellIndex.Y;
					}

					for (MinPosition.X = Block.Min.X, CellIndex.X = 0; 
						CellIndex.X < Block.TexelSize;
						MinPosition.X += WorldStepSize.X, CellIndex.X += NumStepCells.X)
					{
						QueryWorldStepSize.X = WorldStepSize.X;
						NumQueryStepCells.X = NumStepCells.X;

						if (CellIndex.X + NumStepCells.X > Block.TexelSize)
						{
							QueryWorldStepSize.X = Block.Min.X + Block.Size.X - MinPosition.X;
							NumQueryStepCells.X = Block.TexelSize - CellIndex.X;
						}

						FVector BoxExtent = QueryWorldStepSize / 2;
						// Use a 0 query extent in dimensions that only have one cell, these become point queries
						BoxExtent.X = NumQueryStepCells.X == 1 ? 0 : BoxExtent.X;
						BoxExtent.Y = NumQueryStepCells.Y == 1 ? 0 : BoxExtent.Y;
						BoxExtent.Z = NumQueryStepCells.Z == 1 ? 0 : BoxExtent.Z;

						// Build a bounding box for the query block
						const FBoxCenterAndExtent BoundingBox(MinPosition + BoxExtent + HalfTexelWorldOffset, BoxExtent);

						checkSlow(CellIndex.X < Block.TexelSize && CellIndex.Y < Block.TexelSize && CellIndex.Z < Block.TexelSize);
						checkSlow(CellIndex.X + NumQueryStepCells.X <= Block.TexelSize
							&& CellIndex.Y + NumQueryStepCells.Y <= Block.TexelSize
							&& CellIndex.Z + NumQueryStepCells.Z <= Block.TexelSize);

						// Interpolate from the SH volume lighting samples that Lightmass computed
						PrecomputedLightVolume->InterpolateIncidentRadianceBlock(
							BoundingBox,
							NumQueryStepCells,
							FIntVector(Block.TexelSize),
							CellIndex,
							AccumulatedWeight,
							AccumulatedIncidentRadiance);
					}
				}
			}
		}
	}
	else
	{
		for (int32 VolumeIndex = 0; VolumeIndex < Scene->PrecomputedLightVolumes.Num(); VolumeIndex++)
		{
			const FPrecomputedLightVolume* PrecomputedLightVolume = Scene->PrecomputedLightVolumes[VolumeIndex];
			check(PrecomputedLightVolume);
			check(PrecomputedLightVolume->IsUsingHighQualityLightMap() == AllowHighQualityLightmaps(Scene->GetFeatureLevel()));
			// Interpolate from the SH volume lighting samples that Lightmass computed
			// Query using the bounds of all the samples in this block
			// There will be a performance cliff for large objects which end up intersecting with the entire octree
			PrecomputedLightVolume->InterpolateIncidentRadianceBlock(
				FBoxCenterAndExtent(BlockBoundingBox.Center + HalfTexelWorldOffset, BlockBoundingBox.Extent), 
				FIntVector(Block.TexelSize), 
				FIntVector(Block.TexelSize), 
				FIntVector(0), 
				AccumulatedWeight, 
				AccumulatedIncidentRadiance);
		}
	}	
}
bool FLightMapDensityDrawingPolicyFactory::DrawDynamicMesh(
	FRHICommandList& RHICmdList, 
	const FViewInfo& View,
	ContextType DrawingContext,
	const FMeshBatch& Mesh,
	bool bBackFace,
	bool bPreFog,
	const FPrimitiveSceneProxy* PrimitiveSceneProxy,
	FHitProxyId HitProxyId
	)
{
	bool bDirty = false;

	const auto FeatureLevel = View.GetFeatureLevel();
	const FMaterialRenderProxy* MaterialRenderProxy = Mesh.MaterialRenderProxy;
	const FMaterial* Material = MaterialRenderProxy->GetMaterial(FeatureLevel);
	const EBlendMode BlendMode = Material->GetBlendMode();
	const FMeshDrawingRenderState DrawRenderState(Mesh.DitheredLODTransitionAlpha);

	const bool bMaterialMasked = Material->IsMasked();
	const bool bMaterialModifiesMesh = Material->MaterialModifiesMeshPosition_RenderThread();
	if (!bMaterialMasked && !bMaterialModifiesMesh)
	{
		// Override with the default material for opaque materials that are not two sided
		MaterialRenderProxy = GEngine->LevelColorationLitMaterial->GetRenderProxy(false);
	}

	bool bIsLitMaterial = (Material->GetShadingModel() != MSM_Unlit);
	/*const */FLightMapInteraction LightMapInteraction = (Mesh.LCI && bIsLitMaterial) ? Mesh.LCI->GetLightMapInteraction(FeatureLevel) : FLightMapInteraction();
	// force simple lightmaps based on system settings
	bool bAllowHighQualityLightMaps = AllowHighQualityLightmaps(FeatureLevel) && LightMapInteraction.AllowsHighQualityLightmaps();

	if (bIsLitMaterial && PrimitiveSceneProxy && (LightMapInteraction.GetType() == LMIT_Texture))
	{
		// Should this object be texture lightmapped? Ie, is lighting not built for it??
		bool bUseDummyLightMapPolicy = Mesh.LCI == NULL || Mesh.LCI->GetLightMapInteraction(FeatureLevel).GetType() != LMIT_Texture;
		if (!bUseDummyLightMapPolicy)
		{
			if (bAllowHighQualityLightMaps)
			{
				TLightMapDensityDrawingPolicy< TUniformLightMapPolicy<LMP_HQ_LIGHTMAP> > DrawingPolicy(View, Mesh.VertexFactory, MaterialRenderProxy, TUniformLightMapPolicy<LMP_HQ_LIGHTMAP>(), BlendMode);
				RHICmdList.BuildAndSetLocalBoundShaderState(DrawingPolicy.GetBoundShaderStateInput(FeatureLevel));
				DrawingPolicy.SetSharedState(RHICmdList, &View, TLightMapDensityDrawingPolicy< FUniformLightMapPolicy >::ContextDataType());
				for (int32 BatchElementIndex = 0; BatchElementIndex < Mesh.Elements.Num(); BatchElementIndex++)
				{
					DrawingPolicy.SetMeshRenderState(RHICmdList, View,PrimitiveSceneProxy,Mesh,BatchElementIndex,bBackFace,DrawRenderState,
						Mesh.LCI,
						TLightMapDensityDrawingPolicy<FUniformLightMapPolicy>::ContextDataType()
						);
					DrawingPolicy.DrawMesh(RHICmdList, Mesh,BatchElementIndex);
				}
				bDirty = true;
			}
			else
			{
				TLightMapDensityDrawingPolicy< TUniformLightMapPolicy<LMP_LQ_LIGHTMAP> > DrawingPolicy(View, Mesh.VertexFactory, MaterialRenderProxy, TUniformLightMapPolicy<LMP_LQ_LIGHTMAP>(), BlendMode);
				RHICmdList.BuildAndSetLocalBoundShaderState(DrawingPolicy.GetBoundShaderStateInput(FeatureLevel));
				DrawingPolicy.SetSharedState(RHICmdList, &View, TLightMapDensityDrawingPolicy< FUniformLightMapPolicy >::ContextDataType());
				for (int32 BatchElementIndex = 0; BatchElementIndex < Mesh.Elements.Num(); BatchElementIndex++)
				{
					DrawingPolicy.SetMeshRenderState(RHICmdList, View,PrimitiveSceneProxy,Mesh,BatchElementIndex,bBackFace,DrawRenderState,
						Mesh.LCI,
						TLightMapDensityDrawingPolicy<FUniformLightMapPolicy>::ContextDataType()
						);
					DrawingPolicy.DrawMesh(RHICmdList, Mesh,BatchElementIndex);
				}
				bDirty = true;
			}
		}
		else
		{
			TLightMapDensityDrawingPolicy<TUniformLightMapPolicy<LMP_DUMMY> > DrawingPolicy(View, Mesh.VertexFactory, MaterialRenderProxy, TUniformLightMapPolicy<LMP_DUMMY>(), BlendMode);
			RHICmdList.BuildAndSetLocalBoundShaderState(DrawingPolicy.GetBoundShaderStateInput(FeatureLevel));
			DrawingPolicy.SetSharedState(RHICmdList, &View, TLightMapDensityDrawingPolicy<FUniformLightMapPolicy >::ContextDataType());
			for (int32 BatchElementIndex = 0; BatchElementIndex < Mesh.Elements.Num(); BatchElementIndex++)
			{
				DrawingPolicy.SetMeshRenderState(RHICmdList, View,PrimitiveSceneProxy,Mesh,BatchElementIndex,bBackFace,DrawRenderState,
					Mesh.LCI,
					TLightMapDensityDrawingPolicy<FUniformLightMapPolicy>::ContextDataType()
					);
				DrawingPolicy.DrawMesh(RHICmdList, Mesh,BatchElementIndex);
			}
			bDirty = true;
		}
	}
	else
	{
		TLightMapDensityDrawingPolicy<TUniformLightMapPolicy<LMP_NO_LIGHTMAP> > DrawingPolicy(View, Mesh.VertexFactory, MaterialRenderProxy, TUniformLightMapPolicy<LMP_NO_LIGHTMAP>(), BlendMode);
		RHICmdList.BuildAndSetLocalBoundShaderState(DrawingPolicy.GetBoundShaderStateInput(FeatureLevel));
		DrawingPolicy.SetSharedState(RHICmdList, &View, TLightMapDensityDrawingPolicy<TUniformLightMapPolicy<LMP_NO_LIGHTMAP> >::ContextDataType());
		for (int32 BatchElementIndex = 0; BatchElementIndex < Mesh.Elements.Num(); BatchElementIndex++)
		{
			DrawingPolicy.SetMeshRenderState(RHICmdList, View,PrimitiveSceneProxy,Mesh,BatchElementIndex,bBackFace,DrawRenderState,
				Mesh.LCI,
				TLightMapDensityDrawingPolicy<FUniformLightMapPolicy>::ContextDataType()
				);
			DrawingPolicy.DrawMesh(RHICmdList, Mesh,BatchElementIndex);
		}
		bDirty = true;
	}

	return bDirty;
}
void GetPrecomputedLightingParameters(
	ERHIFeatureLevel::Type FeatureLevel,
	FPrecomputedLightingParameters& Parameters, 
	const FIndirectLightingCache* LightingCache, 
	const FIndirectLightingCacheAllocation* LightingAllocation, 
	const FLightCacheInterface* LCI
	)
{
	// FCachedVolumeIndirectLightingPolicy, FCachedPointIndirectLightingPolicy
	{
		if (LightingAllocation)
		{
			Parameters.IndirectLightingCachePrimitiveAdd = LightingAllocation->Add;
			Parameters.IndirectLightingCachePrimitiveScale = LightingAllocation->Scale;
			Parameters.IndirectLightingCacheMinUV = LightingAllocation->MinUV;
			Parameters.IndirectLightingCacheMaxUV = LightingAllocation->MaxUV;
			Parameters.PointSkyBentNormal = LightingAllocation->CurrentSkyBentNormal;
			Parameters.DirectionalLightShadowing = LightingAllocation->CurrentDirectionalShadowing;

			for (uint32 i = 0; i < sizeof(FSHVectorRGB2) / sizeof(FVector4); ++i)
			{
				Parameters.IndirectLightingSHCoefficients[i] = LightingAllocation->SingleSamplePacked[i];
			}

			Parameters.IndirectLightingSHSingleCoefficient = FVector4(LightingAllocation->SingleSamplePacked[0].X, LightingAllocation->SingleSamplePacked[1].X, LightingAllocation->SingleSamplePacked[2].X)
					* FSHVector2::ConstantBasisIntegral * .5f; //@todo - why is .5f needed to match directional?
		}
		else
		{
			Parameters.IndirectLightingCachePrimitiveAdd = FVector(0, 0, 0);
			Parameters.IndirectLightingCachePrimitiveScale = FVector(1, 1, 1);
			Parameters.IndirectLightingCacheMinUV = FVector(0, 0, 0);
			Parameters.IndirectLightingCacheMaxUV = FVector(1, 1, 1);
			Parameters.PointSkyBentNormal = FVector4(0, 0, 1, 1);
			Parameters.DirectionalLightShadowing = 1;

			for (uint32 i = 0; i < sizeof(FSHVectorRGB2) / sizeof(FVector4); ++i)
			{
				Parameters.IndirectLightingSHCoefficients[i] = FVector4(0, 0, 0, 0);
			}

			Parameters.IndirectLightingSHSingleCoefficient = FVector4(0, 0, 0, 0);
		}

		// If we are using FCachedVolumeIndirectLightingPolicy then InitViews should have updated the lighting cache which would have initialized it
		// However the conditions for updating the lighting cache are complex and fail very occasionally in non-reproducible ways
		// Silently skipping setting the cache texture under failure for now
		if (FeatureLevel >= ERHIFeatureLevel::SM4 && LightingCache && LightingCache->IsInitialized())
		{
			Parameters.IndirectLightingCacheTexture0 = const_cast<FIndirectLightingCache*>(LightingCache)->GetTexture0().ShaderResourceTexture;
			Parameters.IndirectLightingCacheTexture1 = const_cast<FIndirectLightingCache*>(LightingCache)->GetTexture1().ShaderResourceTexture;
			Parameters.IndirectLightingCacheTexture2 = const_cast<FIndirectLightingCache*>(LightingCache)->GetTexture2().ShaderResourceTexture;

			Parameters.IndirectLightingCacheTextureSampler0 = TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
			Parameters.IndirectLightingCacheTextureSampler1 = TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
			Parameters.IndirectLightingCacheTextureSampler2 = TStaticSamplerState<SF_Bilinear,AM_Clamp,AM_Clamp,AM_Clamp>::GetRHI();
		}
		else
		if (FeatureLevel >= ERHIFeatureLevel::ES3_1)
		{
			Parameters.IndirectLightingCacheTexture0 = GBlackVolumeTexture->TextureRHI;
			Parameters.IndirectLightingCacheTexture1 = GBlackVolumeTexture->TextureRHI;
			Parameters.IndirectLightingCacheTexture2 = GBlackVolumeTexture->TextureRHI;

			Parameters.IndirectLightingCacheTextureSampler0 = GBlackVolumeTexture->SamplerStateRHI;
			Parameters.IndirectLightingCacheTextureSampler1 = GBlackVolumeTexture->SamplerStateRHI;
			Parameters.IndirectLightingCacheTextureSampler2 = GBlackVolumeTexture->SamplerStateRHI;
		}
		else
		{
			Parameters.IndirectLightingCacheTexture0 = GBlackTexture->TextureRHI;
			Parameters.IndirectLightingCacheTexture1 = GBlackTexture->TextureRHI;
			Parameters.IndirectLightingCacheTexture2 = GBlackTexture->TextureRHI;

			Parameters.IndirectLightingCacheTextureSampler0 = GBlackTexture->SamplerStateRHI;
			Parameters.IndirectLightingCacheTextureSampler1 = GBlackTexture->SamplerStateRHI;
			Parameters.IndirectLightingCacheTextureSampler2 = GBlackTexture->SamplerStateRHI;
		}
	}

	// TDistanceFieldShadowsAndLightMapPolicy
	const FShadowMapInteraction ShadowMapInteraction = LCI ? LCI->GetShadowMapInteraction() : FShadowMapInteraction(); 
	if (ShadowMapInteraction.GetType() == SMIT_Texture)
	{
		const UShadowMapTexture2D* ShadowMapTexture = ShadowMapInteraction.GetTexture();
		Parameters.ShadowMapCoordinateScaleBias = FVector4(ShadowMapInteraction.GetCoordinateScale(), ShadowMapInteraction.GetCoordinateBias());
		Parameters.StaticShadowMapMasks = FVector4(ShadowMapInteraction.GetChannelValid(0), ShadowMapInteraction.GetChannelValid(1), ShadowMapInteraction.GetChannelValid(2), ShadowMapInteraction.GetChannelValid(3));
		Parameters.InvUniformPenumbraSizes = ShadowMapInteraction.GetInvUniformPenumbraSize();
		Parameters.StaticShadowTexture = ShadowMapTexture ? ShadowMapTexture->TextureReference.TextureReferenceRHI.GetReference() : GWhiteTexture->TextureRHI;
		Parameters.StaticShadowTextureSampler = ShadowMapTexture ? ShadowMapTexture->Resource->SamplerStateRHI : GWhiteTexture->SamplerStateRHI;
	}
	else
	{
		Parameters.StaticShadowMapMasks = FVector4(1, 1, 1, 1);
		Parameters.InvUniformPenumbraSizes = FVector4(0, 0, 0, 0);
		Parameters.StaticShadowTexture = GWhiteTexture->TextureRHI;
		Parameters.StaticShadowTextureSampler = GWhiteTexture->SamplerStateRHI;
	}

	// TLightMapPolicy
	const FLightMapInteraction LightMapInteraction = LCI ? LCI->GetLightMapInteraction(FeatureLevel) : FLightMapInteraction();
	if (LightMapInteraction.GetType() == LMIT_Texture)
	{
		const bool bAllowHighQualityLightMaps = AllowHighQualityLightmaps(FeatureLevel) && LightMapInteraction.AllowsHighQualityLightmaps();

		// Vertex Shader
		const FVector2D LightmapCoordinateScale = LightMapInteraction.GetCoordinateScale();
		const FVector2D LightmapCoordinateBias = LightMapInteraction.GetCoordinateBias();
		Parameters.LightMapCoordinateScaleBias = FVector4(LightmapCoordinateScale.X, LightmapCoordinateScale.Y, LightmapCoordinateBias.X, LightmapCoordinateBias.Y);

		// Pixel Shader
		const ULightMapTexture2D* LightMapTexture = LightMapInteraction.GetTexture(bAllowHighQualityLightMaps);
		const ULightMapTexture2D* SkyOcclusionTexture = LightMapInteraction.GetSkyOcclusionTexture();
		const ULightMapTexture2D* AOMaterialMaskTexture = LightMapInteraction.GetAOMaterialMaskTexture();

		Parameters.LightMapTexture = LightMapTexture ? LightMapTexture->TextureReference.TextureReferenceRHI.GetReference() : GBlackTexture->TextureRHI;
		Parameters.SkyOcclusionTexture  = SkyOcclusionTexture ? SkyOcclusionTexture->TextureReference.TextureReferenceRHI.GetReference() : GWhiteTexture->TextureRHI;
		Parameters.AOMaterialMaskTexture  = AOMaterialMaskTexture ? AOMaterialMaskTexture->TextureReference.TextureReferenceRHI.GetReference() : GBlackTexture->TextureRHI;

		Parameters.LightMapSampler = LightMapTexture ? LightMapTexture->Resource->SamplerStateRHI : GBlackTexture->SamplerStateRHI;
		Parameters.SkyOcclusionSampler = SkyOcclusionTexture ? SkyOcclusionTexture->Resource->SamplerStateRHI : GWhiteTexture->SamplerStateRHI;
		Parameters.AOMaterialMaskSampler = AOMaterialMaskTexture ? AOMaterialMaskTexture->Resource->SamplerStateRHI : GBlackTexture->SamplerStateRHI;

		const uint32 NumCoef = bAllowHighQualityLightMaps ? NUM_HQ_LIGHTMAP_COEF : NUM_LQ_LIGHTMAP_COEF;
		const FVector4* Scales = LightMapInteraction.GetScaleArray();
		const FVector4* Adds = LightMapInteraction.GetAddArray();
		for (uint32 CoefIndex = 0; CoefIndex < NumCoef; ++CoefIndex)
		{
			Parameters.LightMapScale[CoefIndex] = Scales[CoefIndex];
			Parameters.LightMapAdd[CoefIndex] = Adds[CoefIndex];
		}
	}
	else
	{
		// Vertex Shader
		Parameters.LightMapCoordinateScaleBias = FVector4(1, 1, 0, 0);

		// Pixel Shader
		Parameters.LightMapTexture = GBlackTexture->TextureRHI;
		Parameters.SkyOcclusionTexture  = GWhiteTexture->TextureRHI;
		Parameters.AOMaterialMaskTexture  = GBlackTexture->TextureRHI;

		Parameters.LightMapSampler = GBlackTexture->SamplerStateRHI;
		Parameters.SkyOcclusionSampler = GWhiteTexture->SamplerStateRHI;
		Parameters.AOMaterialMaskSampler = GBlackTexture->SamplerStateRHI;

		const uint32 NumCoef = FMath::Max<uint32>(NUM_HQ_LIGHTMAP_COEF, NUM_LQ_LIGHTMAP_COEF);
		for (uint32 CoefIndex = 0; CoefIndex < NumCoef; ++CoefIndex)
		{
			Parameters.LightMapScale[CoefIndex] = FVector4(1, 1, 1, 1);
			Parameters.LightMapAdd[CoefIndex] = FVector4(0, 0, 0, 0);
		}
	}
}
FArchive& operator<<(FArchive& Ar,FPrecomputedLightVolume& Volume)
{
	if (Ar.IsCountingMemory())
	{
		const int32 AllocatedBytes = Volume.GetAllocatedBytes();
		Ar.CountBytes(AllocatedBytes, AllocatedBytes);
	}
	else if (Ar.IsLoading())
	{
		Ar << Volume.bInitialized;
		if (Volume.bInitialized)
		{
			FBox Bounds;
			Ar << Bounds;
			float SampleSpacing = 0.0f;
			Ar << SampleSpacing;
			Volume.Initialize(Bounds);
			TArray<FVolumeLightingSample> HighQualitySamples;
			// Deserialize samples as an array, and add them to the octree
			Ar << HighQualitySamples;

			if (FPlatformProperties::SupportsHighQualityLightmaps() 
				&& (GIsEditor || AllowHighQualityLightmaps(GMaxRHIFeatureLevel)))
			{
				for(int32 SampleIndex = 0; SampleIndex < HighQualitySamples.Num(); SampleIndex++)
				{
					Volume.AddHighQualityLightingSample(HighQualitySamples[SampleIndex]);
				}
			}

			TArray<FVolumeLightingSample> LowQualitySamples;

			if (Ar.UE4Ver() >= VER_UE4_VOLUME_SAMPLE_LOW_QUALITY_SUPPORT)
			{
				Ar << LowQualitySamples;
			}

			if (FPlatformProperties::SupportsLowQualityLightmaps() 
				&& (GIsEditor || !AllowHighQualityLightmaps(GMaxRHIFeatureLevel)))
			{
				for(int32 SampleIndex = 0; SampleIndex < LowQualitySamples.Num(); SampleIndex++)
				{
					Volume.AddLowQualityLightingSample(LowQualitySamples[SampleIndex]);
				}
			}

			Volume.FinalizeSamples();
		}
	}
	else if (Ar.IsSaving())
	{
		Ar << Volume.bInitialized;
		if (Volume.bInitialized)
		{
			Ar << Volume.Bounds;
			float SampleSpacing = 0.0f;
			Ar << SampleSpacing;

			TArray<FVolumeLightingSample> HighQualitySamples;

			if (!Ar.IsCooking() || Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::HighQualityLightmaps))
			{
				// Gather an array of samples from the octree
				for(FLightVolumeOctree::TConstIterator<> NodeIt(Volume.HighQualityLightmapOctree); NodeIt.HasPendingNodes(); NodeIt.Advance())
				{
					const FLightVolumeOctree::FNode& CurrentNode = NodeIt.GetCurrentNode();

					FOREACH_OCTREE_CHILD_NODE(ChildRef)
					{
						if(CurrentNode.HasChild(ChildRef))
						{
							NodeIt.PushChild(ChildRef);
						}
					}

					for (FLightVolumeOctree::ElementConstIt ElementIt(CurrentNode.GetElementIt()); ElementIt; ++ElementIt)
					{
						const FVolumeLightingSample& Sample = *ElementIt;
						HighQualitySamples.Add(Sample);
					}
				}
			}
			
			Ar << HighQualitySamples;

			TArray<FVolumeLightingSample> LowQualitySamples;

			if (!Ar.IsCooking() || Ar.CookingTarget()->SupportsFeature(ETargetPlatformFeatures::LowQualityLightmaps))
			{
				// Gather an array of samples from the octree
				for(FLightVolumeOctree::TConstIterator<> NodeIt(Volume.LowQualityLightmapOctree); NodeIt.HasPendingNodes(); NodeIt.Advance())
				{
					const FLightVolumeOctree::FNode& CurrentNode = NodeIt.GetCurrentNode();

					FOREACH_OCTREE_CHILD_NODE(ChildRef)
					{
						if(CurrentNode.HasChild(ChildRef))
						{
							NodeIt.PushChild(ChildRef);
						}
					}

					for (FLightVolumeOctree::ElementConstIt ElementIt(CurrentNode.GetElementIt()); ElementIt; ++ElementIt)
					{
						const FVolumeLightingSample& Sample = *ElementIt;
						LowQualitySamples.Add(Sample);
					}
				}
			}

			Ar << LowQualitySamples;
		}