Announcement

Collapse
No announcement yet.

[TUTORIAL] Fog Of War

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    [TUTORIAL] Fog Of War

    ------------------------------------
    **Update 09-02-2015
    Updated material graph (SceneTexture:Color->SceneTexture:PostProcessInput0) and LevelInfo's construction script blueprint to work with 4.9. Happy fogging!

    ------------------------------------
    **Update 01-23-2015
    Raise your chainswords towards Terra and bless the God-Emperor, there are updates!
    1. The solution now calculates the visibility-data on a separate thread. The frame times turned terrible for large FOW-textures with the synchronized solution.
    2. Unveiling of visible texels now uses line-of-sight calculations, thus only the texels visible to the pawns will be unveiled.
    3. Increased visibility resolution to 50UU/texel for more detailed fogging.
    4. Improved performance greatly by only blurring the texels visible at the moment. For sparsely visible levels the calculation time went from ~400ms to ~3ms
    5. Tweaked the visibility-updating to make the thread run whenever the blending of the last frame is finished. Blending-times between the data for the last and the current frame is lowered accordingly.
    6. Added "shroud of darkness", meaning that areas that are previously visited, but not currently in sight, are darkened.
    7. Added a little bit of extra blurring in the material by sampling 4 locations (though I'm not sure if it works properly, at least the result isn't very apparent).

    The code and pictures of the tutorial below has been altered to reflect the changes.

    Notable issues/considerations/topics of discussion/future work with the current solution:
    1. Since I'm now using a resolution of 50UU/texel, levels are restricted to 512x512m (assuming 100UU/m)when using a 1024x1024 texture. I tried using 2k textures, though this creates a major hiccup when the UpdateTextureRegions()-method is called.
      With the 1k-texture there are no noticeable hiccups on my rig, though it is outfitted with a 980GTX. Lower-end hardware might therefore also experience hiccups with 1k-textures. It is of course possible decrease the visibility resolution (by changing the value of the field SamplesPerMeter in FogOfWarManager.h) for bigger levels, but at the cost of reduced resolution.
    2. Shutting down the worker-thread properly turned out to be non-trivial. I have occasionally experienced a crash when exiting the game because the actors whose positions we are calculating visibility data from are already deconstructed. Being a C++ rookie I've tried to fix this several times though I'm not completely sure the thread deconstruction is done properly.
    3. The solution is not tailored for multiplayer games.
    4. Additional performance improvements could definitely be implemented. I haven't tested the solution with hundreds of actors/dense level-geometry and long-sight ranges, though I expect it to run fairly poor because of the way line of sight-visibility is done.
      Because I'm lazy, I just perform a trace against every ws-point within the actor's sight radius sampled at every half-meter. This is ridiculously inefficient because all of the inner points of the sight circle are potentially unnecessary, and it gets worse as the sight radius increases. What I should have done is create a "rasterized" circle
      centred around the actor using something like Bresenham's midpoint circle algorithm to determine the boundary texels of the circle. We could then trace from origo to each boundary point, and unveil just the texels from the hit-position and back to origo using Bresenham's line-drawing algorithm.
    5. There is still a little blockiness when projecting the texture onto the level. I guess it's possible to increase the resolution for small levels or do more blurring in the material, however the graph gets terribly big and I haven't figured out if it's possible to do separable gaussian blur using the current UE pp-material solution.
      A better solution (overall?) would probably be to make the FOW-texture follow the camera and just display a section of the underlying visibility data. However, that would call for a large rewrite of the current solution and has to deal with issues like camera teleportation.


    I guess that's it for now, please feel free to comment, give feedback, and ask for help if you're unable to get the tutorial working.

    Cheers,
    Isvulfe

    ------------------------------------


    **Original file
    **CASE FILE 112:67:A:AA6:Xad
    **Subject: Verbal transcription of holo-diary MN74-X92
    **Author identity: Transmechanic Gamecrafter Isvulfe, Adeptus Mechanicus
    **Title: A challenge arises
    **Day one, 7 049 243.M41


    My level was running but like the God-Emperor himself, my pawns could see it all, which is indeed heresy.
    To rectify this vile insult to the Emperor, a system must be created. A system to shield the areas of the unveiled to the pawns, or they would forever be corrupted by the taint of heresy.

    In the dark of the night, only aided by the light of my servo-skull, I researched. Browsing the libraries of Forum Unrealis and the Hub of Answers to see if any great Tech-Priests or scribes had
    knowledge to share on the matter. There were hints and whispers of ancient techniques, blueprints and code ciphered in the Language of the Machine God, but no solution appeared beyond the realm of hypothesises.
    My mission became clear to me. Alone I had to face this challenge. I undertook the Rite of Caffeinemaking, then closed my mind from distractions, and started working.

    **Title: A plan is outlined
    **Day seven, 7 049 243.M41


    The Engine Gods of Epic are indeed to be praised. All the tools were there, I just didn't know where to look. Alas! Had I the bionics of a Magos my work would have been swifter.
    No matter, a plan has formed:
    1. A dynamic texture must be formed. This will be fashioned as described by the great Artisan Rama here: https://wiki.unrealengine.com/Dynamic_Textures .
      The texture will be black as the taint of Chaos for regions within the Fog of War, and white as the God-Emperor's glory for regions discovered by the pawns.
    2. The dynamic texture will be used in a post-process material projected onto the level. One texel will correspond to vision data of a hundred Units Unrealis square. The material will make use of
      the node named "AbsoluteWorldPosition" as UV-coordinates for the dynamic texture to project it onto the game world in the xy-plane. It will the be multiplied with the magnificent colors from the rendered scene, using the node of SceneTexture:SceneColor.
      It is of paramount importance to do so before the Mapping of Tones, or results will look undesirable.
    3. While the generation of the texture must be ciphered in the Language of the Machine God, commonly known as C++, I must utilise the power of Blueprint to push the texture from code
      into the Editorium to have the power to use it in the post-process material. The grand blueprint-node of "SetTextureParameterValue" will be utilised.


    One should note that the solution is limited to doing Fog of War on the xy-plane. An area unveiled for any z-value will unveil it for all z-values.

    **Title: Struggeling with the Language of the Machine God
    **Day twelve, 7 049 243.M41

    Entering into the realm of the Language of the Machine God is indeed a challenge for the novice. Trained in the arts of Java, the ancient glyphs of C++ appear grotesque, almost heretical to the untrained mind.
    One day it might unhinge my sanity for good. While the great Artisan Rama possesses unfathomable powers, he appears to sometimes forget the limitations of his inferiors. A crucial detail should be mentioned if one is to follow
    his design for Dynamic Textures. Having tried to compile these ciphers the Machine Spirit became wrath with me and spat out errors of the linker.

    For many nights I struggeled. The wrath of the Machine Spirit's linker thwarted my every attempt to please it, until I discovered the following rite:
    1. Open the file named MyProject.Build.cs
    2. Locate the cipher-line PublicDependencyModuleNames.AddRange(new string[] { "Core", "Engine"...
    3. Add cipher-strings "RHI", "RenderCore" to the array.
    4. The Machine Spirit is willing.


    The following conclusion has been reached: The Engine Gods of Epic has structured their divine creation in a modular fashion. Each shard of functionality is
    compiled into a .dll-file. To make the mighty Tool of Building aware of which modules we depend upon, explicit includes must be provided in the Build.cs-file.

    **Title: Alterations
    **Day twenty, 12 050 243.M41

    Code-ciphers, post-process material and blueprints are completed. Additional changes alters original plan:
    1. A resolution of one texel per 100 Units Unrealis reveals fairly blocky results when texture is black/white. Separable blur of the Grand Logus Gauss is done in code-cipher to
      make result more pleasing to the unaugemented eye.
    2. FOW-calculation is not done on every tick, but scheduled every 0.25 seconds. This keeps the Machine Spirit willing to solve other problems such as planetary bombardment trajectories or warp-travel coordinates.
    3. To avoid popping when the Fog Of War-texture is updated, we save the texture from the last update and blend between the former result and the new one in the post-process material.


    Detailed inner workings:
    Observe resulting header code-ciphers named FogOfWarManager.h:
    Code:
    #pragma once
    
    #include "GameFramework/Actor.h"
    #include "FogOfWarWorker.h"
    #include "FogOfWarManager.generated.h"
    
    /**
     * 
     */
    
    UCLASS() 
    class RPGTEST_API AFogOfWarManager : public AActor
    {
    	GENERATED_BODY()	
    	AFogOfWarManager(const FObjectInitializer & FOI);
    	virtual ~AFogOfWarManager();
    	virtual void BeginPlay() override;			
    	virtual void Tick(float DeltaSeconds) override;
    public:	
    	//Triggers a update in the blueprint
    	UFUNCTION(BlueprintNativeEvent)
    	void OnFowTextureUpdated(UTexture2D* currentTexture, UTexture2D* lastTexture);
    	
    	//Register an actor to influence the FOW-texture
    	void RegisterFowActor(AActor* Actor);
    
    	//Stolen from https://wiki.unrealengine.com/Dynamic_Textures
    	void UpdateTextureRegions(
    		UTexture2D* Texture, 
    		int32 MipIndex, 
    		uint32 NumRegions, 
    		FUpdateTextureRegion2D* Regions, 
    		uint32 SrcPitch, 
    		uint32 SrcBpp, 
    		uint8* SrcData, 
    		bool bFreeData);
    
    	//How far will an actor be able to see
    	//CONSIDER: Place it on the actors to allow for individual sight-radius
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = FogOfWar)
    	float SightRange = 9.0f;
    
    	//The number of samples per 100 unreal units
    	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = FogOfWar)
    	float SamplesPerMeter = 2.0f;
    
    	//If the last texture blending is done
    	UPROPERTY(BlueprintReadWrite)
    	bool bIsDoneBlending;
    
    	//Should we blur? It takes up quite a lot of CPU time...
    	UPROPERTY(EditAnywhere)
    	bool bIsBlurEnabled = true;
    
    	//The size of our textures
    	uint32 TextureSize = 1024;
    
    	//Array containing what parts of the map we've unveiled.
    	UPROPERTY()
    	TArray<bool> UnfoggedData;
    
    	//Temp array for horizontal blur pass
    	UPROPERTY()
    	TArray<uint8> HorizontalBlurData;
    
    	//Our texture data (result of vertical blur pass)
    	UPROPERTY()
    	TArray<FColor> TextureData;
    
    	//Our texture data from the last frame
    	UPROPERTY()
    	TArray<FColor> LastFrameTextureData;
    
    	//Check to see if we have a new FOW-texture.
    	bool bHasFOWTextureUpdate = false;
    
    	//Blur size
    	uint8 blurKernelSize = 15;
    
    	//Blur kernel
    	UPROPERTY()
    	TArray<float> blurKernel;
    
    	//Store the actors that will be unveiling the FOW-texture.
    	UPROPERTY()
    	TArray<AActor*> FowActors;
    
    	//DEBUG: Time it took to update the fow texture
    	float fowUpdateTime = 0;
    
    	//Getter for the working thread
    	bool GetIsBlurEnabled();
    		
    private:	
    	void UpdateFowTexture();
    	
    	//Triggers the start of a new FOW-texture-update
    	void StartFOWTextureUpdate();	
    	
    	//Our dynamically updated texture
    	UPROPERTY()
    	UTexture2D* FOWTexture;
    
    	//Texture from last update. We blend between the two to do a smooth unveiling of newly discovered areas.
    	UPROPERTY()
    	UTexture2D* LastFOWTexture;	
    	
    	//Texture regions	
    	FUpdateTextureRegion2D* textureRegions;	
    
    	//Our fowupdatethread		
    	AFogOfWarWorker* FowThread;
    };
    Observe resulting body code-ciphers FogOfWarManager.cpp:
    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "RpgTest.h"
    #include "FogOfWarManager.h"
    
    AFogOfWarManager::AFogOfWarManager(const FObjectInitializer &FOI) : Super(FOI) {	
    	PrimaryActorTick.bCanEverTick = true;
    	textureRegions = new FUpdateTextureRegion2D(0, 0, 0, 0, TextureSize, TextureSize);		
    	
    	//15 Gaussian samples. Sigma is 2.0.
    	//CONSIDER: Calculate the kernel instead, more flexibility...
    	blurKernel.Init(0.0f, blurKernelSize);
    	blurKernel[0] = 0.000489f;
    	blurKernel[1] = 0.002403f;
    	blurKernel[2] = 0.009246f;
    	blurKernel[3] = 0.02784f;
    	blurKernel[4] = 0.065602f;
    	blurKernel[5] = 0.120999f;
    	blurKernel[6] = 0.174697f;
    	blurKernel[7] = 0.197448f;
    	blurKernel[8] = 0.174697f;
    	blurKernel[9] = 0.120999f;
    	blurKernel[10] = 0.065602f;
    	blurKernel[11] = 0.02784f;
    	blurKernel[12] = 0.009246f;
    	blurKernel[13] = 0.002403f;
    	blurKernel[14] = 0.000489f;
    }
    
    AFogOfWarManager::~AFogOfWarManager() {
    	if (FowThread) {		
    		FowThread->ShutDown();
    	}
    }
    
    void AFogOfWarManager::BeginPlay() {	
    	Super::BeginPlay();
    	bIsDoneBlending = true;
    	AFogOfWarManager::StartFOWTextureUpdate();
    }
    
    void AFogOfWarManager::Tick(float DeltaSeconds) {
    	Super::Tick(DeltaSeconds);	
    	if (FOWTexture && LastFOWTexture && bHasFOWTextureUpdate && bIsDoneBlending) {		
    		LastFOWTexture->UpdateResource();
    		UpdateTextureRegions(LastFOWTexture, (int32)0, (uint32)1, textureRegions, (uint32)(4 * TextureSize), (uint32)4, (uint8*)LastFrameTextureData.GetData(), false);		
    		FOWTexture->UpdateResource();
    		UpdateTextureRegions(FOWTexture, (int32)0, (uint32)1, textureRegions, (uint32)(4 * TextureSize), (uint32)4, (uint8*)TextureData.GetData(), false);		
    		bHasFOWTextureUpdate = false;
    		bIsDoneBlending = false;
    		//Trigger the blueprint update
    		OnFowTextureUpdated(FOWTexture, LastFOWTexture);		
    	}
    }
    
    void AFogOfWarManager::StartFOWTextureUpdate() {	
    	if (!FOWTexture) {
    		FOWTexture = UTexture2D::CreateTransient(TextureSize, TextureSize);
    		LastFOWTexture = UTexture2D::CreateTransient(TextureSize, TextureSize);
    		int arraySize = TextureSize * TextureSize;
    		TextureData.Init(FColor(0, 0, 0, 255), arraySize);
    		LastFrameTextureData.Init(FColor(0, 0, 0, 255), arraySize);
    		HorizontalBlurData.Init(0, arraySize);
    		UnfoggedData.Init(false, arraySize);		
    		FowThread = new AFogOfWarWorker(this);
    	}	
    }
    
    void AFogOfWarManager::OnFowTextureUpdated_Implementation(UTexture2D* currentTexture, UTexture2D* lastTexture) {
    	//Handle in blueprint
    }
    
    void AFogOfWarManager::RegisterFowActor(AActor* Actor) {
    	FowActors.Add(Actor);
    }
    
    bool AFogOfWarManager::GetIsBlurEnabled() {
    	return bIsBlurEnabled;
    }
    
    void AFogOfWarManager::UpdateTextureRegions(UTexture2D* Texture, int32 MipIndex, uint32 NumRegions, FUpdateTextureRegion2D* Regions, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData)
    {
    	if (Texture && Texture->Resource)
    	{
    		struct FUpdateTextureRegionsData
    		{
    			FTexture2DResource* Texture2DResource;
    			int32 MipIndex;
    			uint32 NumRegions;
    			FUpdateTextureRegion2D* Regions;
    			uint32 SrcPitch;
    			uint32 SrcBpp;
    			uint8* SrcData;
    		};
    
    		FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;
    
    		RegionData->Texture2DResource = (FTexture2DResource*)Texture->Resource;
    		RegionData->MipIndex = MipIndex;
    		RegionData->NumRegions = NumRegions;
    		RegionData->Regions = Regions;
    		RegionData->SrcPitch = SrcPitch;
    		RegionData->SrcBpp = SrcBpp;
    		RegionData->SrcData = SrcData;
    
    		ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
    			UpdateTextureRegionsData,
    			FUpdateTextureRegionsData*, RegionData, RegionData,
    			bool, bFreeData, bFreeData,
    			{
    				for (uint32 RegionIndex = 0; RegionIndex < RegionData->NumRegions; ++RegionIndex)
    				{
    					int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
    					if (RegionData->MipIndex >= CurrentFirstMip)
    					{
    						RHIUpdateTexture2D(
    							RegionData->Texture2DResource->GetTexture2DRHI(),
    							RegionData->MipIndex - CurrentFirstMip,
    							RegionData->Regions[RegionIndex],
    							RegionData->SrcPitch,
    							RegionData->SrcData
    							+ RegionData->Regions[RegionIndex].SrcY * RegionData->SrcPitch
    							+ RegionData->Regions[RegionIndex].SrcX * RegionData->SrcBpp
    							);
    					}
    				}
    				if (bFreeData)
    				{
    					FMemory::Free(RegionData->Regions);
    					FMemory::Free(RegionData->SrcData);
    				}
    				delete RegionData;
    			});
    	}
    }

    Observe resulting header code-ciphers named FogOfWarWorker.h:
    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    /**
     * Worker thread for updating the fog of war data.
     */
    class AFogOfWarManager;
    
    class AFogOfWarWorker : public FRunnable
    {	
    	//Thread to run the FRunnable on
    	FRunnableThread* Thread;	
    
    	//Pointer to our manager
    	AFogOfWarManager* Manager;
    
    	//Thread safe counter 
    	FThreadSafeCounter StopTaskCounter;	
    
    public:
    	AFogOfWarWorker();
    	AFogOfWarWorker(AFogOfWarManager* manager);
    	virtual ~AFogOfWarWorker();
    
    	//FRunnable interface
    	virtual bool Init();
    	virtual uint32 Run();
    	virtual void Stop();		
    
    	//Method to perform work
    	void UpdateFowTexture();
    
    	bool bShouldUpdate = false;
    
    	void ShutDown();
    };

    Observe resulting body code-ciphers FogOfWarWorker.cpp:
    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "RpgTest.h"
    
    AFogOfWarWorker::AFogOfWarWorker() {}
    
    AFogOfWarWorker::AFogOfWarWorker(AFogOfWarManager* manager){
    	Manager = manager;
    	Thread = FRunnableThread::Create(this, TEXT("AFogOfWarWorker"), 0U, TPri_BelowNormal);
    }
    
    AFogOfWarWorker::~AFogOfWarWorker() {
    	delete Thread;
    	Thread = NULL; 
    }
    
    void AFogOfWarWorker::ShutDown() {
    	Stop();
    	Thread->WaitForCompletion();	
    }
    
    bool AFogOfWarWorker::Init() {
    	if (Manager) {
    		Manager->GetWorld()->GetFirstPlayerController()->ClientMessage("Fog of War worker thread started");
    		return true;
    	}
    	return false;
    }
    
    uint32 AFogOfWarWorker::Run() {
    	FPlatformProcess::Sleep(0.03f);
    	while (StopTaskCounter.GetValue() == 0) {
    		float time;
    		if (Manager && Manager->GetWorld()) {
    			time = Manager->GetWorld()->TimeSeconds;
    		}
    		if (!Manager->bHasFOWTextureUpdate) {
    			UpdateFowTexture();
    			if (Manager && Manager->GetWorld()) {
    				Manager->fowUpdateTime = Manager->GetWorld()->TimeSince(time);
    			}
    		}
    		FPlatformProcess::Sleep(0.1f);
    	}
    	return 0;
    }
    
    void AFogOfWarWorker::UpdateFowTexture() {
    	Manager->LastFrameTextureData = TArray<FColor>(Manager->TextureData);
    	uint32 halfTextureSize = Manager->TextureSize / 2;
    	int signedSize = (int)Manager->TextureSize; //For convenience....
    	TSet<FVector2D> currentlyInSight;
    	TSet<FVector2D> texelsToBlur;
    	int sightTexels = Manager->SightRange * Manager->SamplesPerMeter;
    	float dividend = 100.0f / Manager->SamplesPerMeter;	
    
    	for (auto Itr(Manager->FowActors.CreateIterator()); Itr; Itr++) {
    		//Find actor position
    		if(!*Itr) return;		
    		FVector position = (*Itr)->GetActorLocation();		
    		
    		//We divide by 100.0 because 1 texel equals 1 meter of visibility-data.
    		int posX = (int)(position.X / dividend) + halfTextureSize;
    		int posY = (int)(position.Y / dividend) + halfTextureSize;
    		float integerX, integerY;
    
    		FVector2D fractions = FVector2D(modf(position.X / 50.0f, &integerX), modf(position.Y / 50.0f, &integerY));
    		FVector2D textureSpacePos = FVector2D(posX, posY);
    		int size = (int)Manager->TextureSize;
    
    		FCollisionQueryParams queryParams(FName(TEXT("FOW trace")), false, (*Itr));
    		int halfKernelSize = (Manager->blurKernelSize - 1) / 2;
    		
    		//Store the positions we want to blur
    		for (int y = posY - sightTexels - halfKernelSize; y <= posY + sightTexels + halfKernelSize; y++) {
    			for (int x = posX - sightTexels - halfKernelSize; x <= posX + sightTexels + halfKernelSize; x++) {
    				if (x > 0 && x < size && y > 0 && y < size) {
    					texelsToBlur.Add(FIntPoint(x, y));
    				}
    			}
    		}
    		
    		//Unveil the positions our actors are currently looking at
    		for (int y = posY - sightTexels; y <= posY + sightTexels; y++) {
    			for (int x = posX - sightTexels; x <= posX + sightTexels; x++) {
    				//Kernel for radial sight
    				if (x > 0 && x < size && y > 0 && y < size) {
    					FVector2D currentTextureSpacePos = FVector2D(x, y);
    					int length = (int)(textureSpacePos - currentTextureSpacePos).Size();
    					if (length <= sightTexels) {
    						FVector currentWorldSpacePos = FVector(
    							((x - (int)halfTextureSize)) * dividend,
    							((y - (int)halfTextureSize)) * dividend,
    							position.Z);
    
    						//CONSIDER: This is NOT the most efficient way to do conditional unfogging. With long view distances and/or a lot of actors affecting the FOW-data
    						//it would be preferrable to not trace against all the boundary points and internal texels/positions of the circle, but create and cache "rasterizations" of
    						//viewing circles (using Bresenham's midpoint circle algorithm) for the needed sightranges, shift the circles to the actor's location
    						//and just trace against the boundaries. 
    						//We would then use Manager->GetWorld()->LineTraceSingle() and find the first collision texel. Having found the nearest collision
    						//for every ray we would unveil all the points between the collision and origo using Bresenham's Line-drawing algorithm.
    						//However, the tracing doesn't seem like it takes much time at all (~0.02ms with four actors tracing circles of 18 texels each),
    						//it's the blurring that chews CPU..
    						if (!Manager->GetWorld()->LineTraceTest(position, currentWorldSpacePos, ECC_WorldStatic, queryParams)) {							
    							//Unveil the positions we are currently seeing
    							Manager->UnfoggedData[x + y * Manager->TextureSize] = true;
    							//Store the positions we are currently seeing.
    							currentlyInSight.Add(FVector2D(x, y));
    						}
    					}
    				}
    			}
    		}
    	}	
    
    	if (Manager->GetIsBlurEnabled()) {
    		//Horizontal blur pass
    		int offset = floorf(Manager->blurKernelSize / 2.0f);		 
    		for (auto Itr(texelsToBlur.CreateIterator()); Itr; ++Itr) {
    			int x = (Itr)->IntPoint().X;
    			int y = (Itr)->IntPoint().Y;
    			float sum = 0;
    			for (int i = 0; i < Manager->blurKernelSize; i++) {
    				int shiftedIndex = i - offset;
    				if (x + shiftedIndex >= 0 && x + shiftedIndex <= signedSize - 1) {
    					if (Manager->UnfoggedData[x + shiftedIndex + (y * signedSize)]) {
    						//If we are currently looking at a position, unveil it completely
    						if (currentlyInSight.Contains(FVector2D(x + shiftedIndex, y))) {
    							sum += (Manager->blurKernel[i] * 255);
    						}
    						//If this is a previously discovered position that we're not currently looking at, put it into a "shroud of darkness".							
    						else {
    							sum += (Manager->blurKernel[i] * 100);
    						}
    					}
    				}
    			}
    			Manager->HorizontalBlurData[x + y * signedSize] = (uint8)sum;
    		}
    		
    
    		//Vertical blur pass
    		for (auto Itr(texelsToBlur.CreateIterator()); Itr; ++Itr) {
    			int x = (Itr)->IntPoint().X;
    			int y = (Itr)->IntPoint().Y;
    			float sum = 0;
    			for (int i = 0; i < Manager->blurKernelSize; i++) {
    				int shiftedIndex = i - offset;
    				if (y + shiftedIndex >= 0 && y + shiftedIndex <= signedSize - 1) {
    					sum += (Manager->blurKernel[i] * Manager->HorizontalBlurData[x + (y + shiftedIndex) * signedSize]);
    				}
    			}
    			Manager->TextureData[x + y * signedSize] = FColor((uint8)sum, (uint8)sum, (uint8)sum, 255);
    		}		
    	}
    	else {
    		for (int y = 0; y < signedSize; y++) {
    			for (int x = 0; x < signedSize; x++) {
    
    				if (Manager->UnfoggedData[x + (y * signedSize)]) {
    					if (currentlyInSight.Contains(FVector2D(x, y))) {
    						Manager->TextureData[x + y * signedSize] = FColor((uint8)255, (uint8)255, (uint8)255, 255);
    					}
    					else {
    						Manager->TextureData[x + y * signedSize] = FColor((uint8)100, (uint8)100, (uint8)100, 255);
    					}
    				}
    			}
    		}
    	}
    	Manager->bHasFOWTextureUpdate = true;
    }
    
    void AFogOfWarWorker::Stop() {
    	StopTaskCounter.Increment();
    }

    Observe image of blueprint named LevelInfo construction-script. Note that the blueprint is based upon FogOfWarManager and contains an unbounded post-process component.
    Click image for larger version

Name:	level_info_construction_script.png
Views:	1
Size:	87.1 KB
ID:	1160582

    Observer image of blueprint event graph
    Click image for larger version

Name:	level_info_event_graph2.png
Views:	1
Size:	236.0 KB
ID:	1143277

    Observe image of material
    Click image for larger version

Name:	pp_blendable_material_02.png
Views:	1
Size:	351.8 KB
ID:	1160583
    Last edited by Isvulfe; 02-16-2017, 09:46 AM. Reason: Update 4.15
    Tutorial - Fog of War

    #3


    The Emperor will be pleased.
    Rule#21: Be polite, be professional, but have a plan to kill everyone you meet.

    Comment


      #4
      Yes! thank you very much!
      This is a really cool effect and with many uses in different kinds of games
      "sic parvis magna"
      @Meguido

      Comment


        #5
        Your post is written very nice But I would appreciate a blueprint only solution.
        Easy to use UMG Mini Map on the UE4 Marketplace.
        Forum thread: https://forums.unrealengine.com/show...-Plug-and-Play

        Comment


          #6
          Originally posted by John Alcatraz View Post
          Your post is written very nice But I would appreciate a blueprint only solution.
          Thank you! I guess it's possible to do fog of war purely in blueprints, however this solution requires a little bit of c++ for the dynamic texture creation and update. I guess it depends a bit on you background, and if you've never touched a line of code I understand your reluctance towards c++-based projects. On the other hand it's quite amazing how easy Epic has made it to setup the programming environment and to get you ready to code. Knowing only a little c++ will get you really far. If you have any questions on how to get the tutorial up and running, I'll be happy to get help you out.
          Tutorial - Fog of War

          Comment


            #7
            Hey Isvulfe, would you know how to integrate this into a multiplayer environment - I was going to calculate it on the server but surely sending a packet update whenever the targets FOW changes would be too intense. Should I just calculate it clientside and on the server at the same time, but use the servers authority for whether units are visible or not.

            Comment


              #8
              Originally posted by Aussiemandias View Post
              Hey Isvulfe, would you know how to integrate this into a multiplayer environment - I was going to calculate it on the server but surely sending a packet update whenever the targets FOW changes would be too intense. Should I just calculate it clientside and on the server at the same time, but use the servers authority for whether units are visible or not.
              Aussiemandias!
              That's an excellent question, though having no experience with multiplayer games in UE4, I might not be well suited to give a very meaningful answer. However, partially as you proposed, I think that doing the FOW-texture update (the contents of the tutorial) solely on the client should work just fine. The responsibility of the server would then be to give an array of visible enemy units to each client per tick. This solution decouples the FOW and the hiding/unveiling of the enemy units.

              I would also consider letting the server calculate which texels in the FOW-texture that are visible to each client, and replicate these (not as a two-dimensional array but as a List/Set of visible texel coordinates). The FOW-textures will still only reside on the clients, and the blurring and texture-updating would be carried out clientside based on the visibility-data from the server. However, here is where your concern about packet-size comes into play. If you are making an RTS with 200 units per player where each unit has a long sight range, the number of texels visible to each client could get quite large. Thus, I would avoid replicating visibility-data to the clients if you know that the amount of visible texels will get large.

              Note that the tutorial in its current state isn't doing any kind of visibility-blocking neither from static nor dynamic geometry (there's a TODO in the code). Since writing the tutorial I have changed the FOW-system quite a bit, and my current solution
              does visibility-blocking and performs updates on the texture in a separate thread, which is necessary to get acceptable frame times with larger FOW-textures. If I'm able to find the time, I will try to update the tutorial during the weekend. If you want to, I could then get back to you with some pointers on how I would change the code to make it work in a multiplayer environment.

              Cheers,
              Isvulfe
              Tutorial - Fog of War

              Comment


                #9
                Updated the tutorial with new goodies.
                Tutorial - Fog of War

                Comment


                  #10
                  Originally posted by Isvulfe View Post
                  Updated the tutorial with new goodies.
                  Awesome, you're the man! I'm going to experiment with multiplayer and post anything that works. I agree with you about having a simple array of visibile units passed to each client. Would be small and efficient.

                  Comment


                    #11
                    Actually I'm having a little trouble getting this to work. I've registered my actor to the FOW manager and placed one in the world, but it's all black. Could you upload the material blueprints since I think I may have messed up there.

                    Comment


                      #12
                      Allright, here is the material and material instance: https://drive.google.com/file/d/0B2D...ew?usp=sharing
                      My material parameter collection with a 1k map where 1 texel=50UU looks like this:
                      Click image for larger version

Name:	material_parameters.png
Views:	1
Size:	43.0 KB
ID:	1065584
                      Tutorial - Fog of War

                      Comment


                        #13
                        Thanks! I got mine working, I just had to put in those values in the image above.

                        Comment


                          #14
                          Originally posted by Aussiemandias View Post
                          Thanks! I got mine working, I just had to put in those values in the image above.
                          That's great! Good luck with your project.
                          Tutorial - Fog of War

                          Comment


                            #15
                            I did some work on this for mutliplayer, not as hard as I thought but the issue is performance related, mostly because...
                            1. The server is calculating visible pixels for 2 FOWManagers (1 for each team)
                            2. Each client has their own version too, so if I test this on my rig I'm essentially running 4 FOW managers


                            I solved some of this by only running blurring on the client (server doesn't care for visuals!). I could network UnfoggedData and remove the LineTraceTest from the client too, I'll see how that goes.

                            EDIT: Now that I think about it, it's only an issue for me developing. Players connected to the server will only be running one instance of it. Still, it would be useful to seperate the visibility calculation from the display/blurring.

                            Last edited by Aussiemandias; 01-25-2015, 09:27 PM.

                            Comment

                            Working...
                            X