Accessing indrect lighting with C++ ?

Hi,

I was thinking about a stealth game mechanic and I was wondering if it was possible to access the indirect lighting cache in any way to compute a sort of local lighting brightness. The goal would be to know if the player is exposed to any lighting and indirect lighting since the purpose is to dissimulate yourself in the shadows.

The best case I think would be to be able to query a bunch of the samples created by lightmass which are near the player. This way it will be possible to compute an average color value and therefor brightness.

It’s not possible to get at the indirect lighting cache because that’s on the rendering thread, but you can interpolate the indirect lighting yourself on the game thread, and then incorporate that into your gameplay. Check out FPrecomputedLightVolume and InterpolateIncidentRadiancePoint, which is exposed to other dll’s. To get the correct result you have to interpolate from all levels and combine, along the lines of this, except you want to iterate over levels in the world, each of which potentially has a PrecomputedLightVolumes.

   FSHVectorRGB2 AccumulatedIncidentRadiance;
	float AccumulatedDirectionalShadowing = 0;
	float AccumulatedWeight = 0;

	for (int32 VolumeIndex = 0; VolumeIndex < Scene->PrecomputedLightVolumes.Num(); VolumeIndex++)
	{
		const FPrecomputedLightVolume* PrecomputedLightVolume = Scene->PrecomputedLightVolumes[VolumeIndex];
		PrecomputedLightVolume->InterpolateIncidentRadiancePoint(
			Block.Min + Block.Size / 2, 
			AccumulatedWeight, 
			AccumulatedDirectionalShadowing,
			AccumulatedIncidentRadiance);
	}

	if (AccumulatedWeight > 0)
	{
		OutDirectionalShadowing = AccumulatedDirectionalShadowing / AccumulatedWeight;
		OutIncidentRadiance = AccumulatedIncidentRadiance / AccumulatedWeight;
	}
	else
	{
		OutIncidentRadiance = AccumulatedIncidentRadiance;
		OutDirectionalShadowing = AccumulatedDirectionalShadowing;
	}

I’m trying your code in my character class. Unfortunately it seems my build doesn’t know the array “PrecomputedLightVolumes” that should be present inside FSceneInterface.

I’m not sure to follow you by the way when you talk about “exposed to other dll’s”, what do you mean ? Also, when you talk about interpolating between levels you mean on the currently streamed levels loaded by the game ?

I was saying you need to change the above code so it iterates over the levels in the world instead of the precomputed light volumes in the scene. Level->PrecomputedLightVolume is the data you want to use for interpolation.

I’m not sure to follow you by the way when you talk about “exposed to other dll’s”, what do you mean ?

Your game module can only link calls to functions in engine that have been properly exported with ENGINE_API. InterpolateIncidentRadiancePoint is, so you are good to go.

Also, when you talk about interpolating between levels you mean on the currently streamed levels loaded by the game ?

Yes. You can have multiple levels loaded due to level streaming, and they can each have lighting samples. You need to interpolate between all of them to be seamless. The above code shows one way to do this, by accumulating the weight separately from the lighting and then normalizing.

Thanks for the answer, I’m still trying to convert your sample code to something usable in my Character class. This is what I have so far :

void AExedreCharacter::ComputeLightWeight()
{
	
	if(MeshBody != NULL)
	{
		float OutIncidentRadiance = 0.0;
		
		class ULevel* Level = MeshBody->GetComponentLevel();
		
		FSHVectorRGB2 AccumulatedIncidentRadiance;
		
		float AccumulatedWeight = 0;

		class FPrecomputedLightVolume* RadianceVolume = Level->PrecomputedLightVolume;

		RadianceVolume->InterpolateIncidentRadiance(
		GetActorLocation(), 
		AccumulatedWeight,
		AccumulatedIncidentRadiance);

		
		if (AccumulatedWeight > 0)
		{
			OutIncidentRadiance = AccumulatedIncidentRadiance / AccumulatedWeight;
		}
		else
		{
			OutIncidentRadiance = AccumulatedIncidentRadiance;
		}

            //log writing
		elog(OutIncidentRadiance);
	}
}

Unfortunately I don’t pass the compilation. FPrecomputedLightVolume give me an error, the call to InterpolateIncidentRadiance() should be correct, but the compile is complaining. I don’t see why because this is what I have in PrecomputedLightVolume.h :

   /** Interpolates incident radiance to Position. */
	ENGINE_API void InterpolateIncidentRadiance(
		const FVector& Position, 
		float& AccumulatedWeight,
		FSHVectorRGB2& AccumulatedIncidentRadiance) const;
	
	/** Interpolates incident radiance to Position. */
	ENGINE_API void InterpolateIncidentRadiance(
		const FBoxCenterAndExtent& BoundingBox, 
		const FIntVector& QueryCellDimensions,
		const FIntVector& DestCellDimensions,
		const FIntVector& DestCellPosition,
		TArray& AccumulatedWeights,
		TArray& AccumulatedIncidentRadiance) const;

Also, the FSHVectorRGB2 / float is also refused by the compiler. Since this struct (I believe) is not explained anywhere, I have no idea how to access its members.

The compiler log :

D:\Dropbox\Software\notepad\bin>echo OFF

"Building Rocket (Exedre), please wait..."

Parsing headers for Exedre
Code generation finished for Exedre and took 1,969
Module.Exedre.cpp
C:\Rocket\Projects\Exedre\Source\Exedre\Private\ExedreCharacter.cpp(464) : error
 C2027: use of undefined type 'FPrecomputedLightVolume'
        c:\rocket\engine\intermediate\builddata\include\engine\../../../../Sourc
e/Runtime/Engine/Classes/Engine/Level.h(346) : see declaration of 'FPrecomputedL
ightVolume'
C:\Rocket\Projects\Exedre\Source\Exedre\Private\ExedreCharacter.cpp(464) : error
 C2227: left of '->InterpolateIncidentRadiance' must point to class/struct/union
/generic type
C:\Rocket\Projects\Exedre\Source\Exedre\Private\ExedreCharacter.cpp(472) : error
 C2440: '=' : cannot convert from 'TSHVectorRGB' to 'float'
        with
        [
            MaxSHOrder=2
        ]
        No user-defined-conversion operator available that can perform this conv
ersion, or the operator cannot be called
C:\Rocket\Projects\Exedre\Source\Exedre\Private\ExedreCharacter.cpp(476) : error
 C2440: '=' : cannot convert from 'FSHVectorRGB2' to 'float'
        No user-defined-conversion operator available that can perform this conv
ersion, or the operator cannot be called
-------- End Detailed Actions Stats --------------------------------------------
---------------
ERROR: UBT ERROR: Failed to produce item: C:\Rocket\Projects\Exedre\Binaries\Win
64\RocketEditor-Exedre.pdb
Cumulative action seconds (8 processors): 0,00 building projects, 10,06 compilin
g, 0,00 creating app bundles, 0,00 generating debug info, 0,00 linking, 0,00 oth
er
UBT execution time: 15,63 seconds

Appuyez sur une touche pour continuer...

The first error means you need to include the appropriate header file to get access to the type FPrecomputedLightVolume, like so

#include "PrecomputedLightVolume.h"

Let me know what the errors are after fixing that.

Ha, I never thought I had to include the header since the rest of the time the compiler seems to find everything by itself. Thanks !

Now my only and current error is the same as above about the FSHVectorRGB2 conversion to float.

The conversion error is because you made OutIncidentRadiance a float. FSHVectorRGB2 is a directional HDR color representation so it can’t be converted straight to a float. If you only care about brightness in the end (no color) you should do

FSHVector2 Ambient = FSHVector2::AmbientFunction();
OutIncidentRadiance  = Dot(AccumulatedIncidentRadiance, Ambient).GetLuminance();

(I didn’t actually compile this so might be a typing error)

Again, thanks a lot for the help. It compiled fine ! :slight_smile:

I’m not sure to fully understand everything so I will ask some more questions (especially because I’m not familiar with the HDR pipeline) :

  • Why do you do a dot product between the indirect lighting and the ambient lighting instead of adding them together ? Isn’t the ambient lighting added on the rest of the lighting normally ?

  • Regarding you code above, why is the Dot product so low even when under some strong lighting ? What would be the best to compute a value more human readable ?

  • I took a look inside some of my books and google, but I wasn’t able to find some help to understand a bit better what the following means : typedef TSHVector<2> FSHVector2; . I understand the keyword typedef, but not the <>. From the rest of SHMath.h it looks like an “order”, but what does that means exactly ? Is that a sort of array ?

Why do you do a dot product between the indirect lighting and the ambient lighting instead of adding them together

FSHVector2::AmbientFunction() makes a ‘shape’, in this case a shape that has uniform intensity in all directions because we want to know the average lighting. We could also have made a cosine lobe around a normal to do normal mapped lighting. The dot product then answers the question ‘how much of the captured lighting (AccumulatedIncidentRadiance) coincides with the shape?’

Regarding you code above, why is the Dot product so low even when under some strong lighting ?

I’m not completely sure, but I might have missed a constant scaling factor. You can try multiplying by a tweakable until it looks acceptable for your purposes.

I took a look inside some of my books and google, but I wasn’t able to find some help to understand a bit better what the following means : typedef TSHVector<2> FSHVector2; . I understand the keyword typedef, but not the <>. From the rest of SHMath.h it looks like an “order”, but what does that means exactly ? Is that a sort of array ?

FSHVector2 is a 2 band spherical harmonic, which has four floats total (V0 is ambient term, V1-3 are the first directional band). Using spherical harmonics can involve a lot of complex math but the concept of what they do is simple - just think of them as a low resolution cubemap. They store directional information. The more bands it has, the more resolution in the cubemap. Our indirect lighting samples only have 2 bands to reduce memory usage.

As to the C++ question, <2> is the template parameter of the template class TSHVector, which specifies how many bands (orders) there are. TSHVector is implemented to support different numbers of bands, so we don’t have to duplicate a bunch of code to support 2 or 3 bands. FVector2D vs FVector and FVector4 could have been implemented this way too but they are not.

Thanks for the explanation. I had to multiply the Luminance result by 150000 to get something close to 1 in very bright areas. That seems a bit high.

I was wondering if it was possible to access the direct lighting too ? I’m not sure of how UE4 compute dynamic lighting, but in UE3 dynamic object were using SH too isn’t it ? Is that possible to access this SH and do a similar luminance computing ?

I would like to avoid to compute the visibility of lights around the player by using a lot of traces.

150000 is crazy, I’m not sure what’s going on.

I was wondering if it was possible to access the direct lighting too ? I’m not sure of how UE4 compute dynamic lighting, but in UE3 dynamic object were using SH too isn’t it ? Is that possible to access this SH and do a similar luminance computing ?

Unfortunately no, only static lights will have their direct lighting in the lighting samples. You would have to trace rays to lights to get their shadowing. Dynamic light environments in UE3 no longer exist in UE4, all stationary and movable lights have their contribution calculated dynamically.

Last time on this topic (I think). I got everything I need working. I’m using Light->LightComponent->GetDirectIntensity() to know if a light is touching/affecting the player. It works with almost every lights, but not on directional lights (at least not the one considered as the sun in my scene). Is that normal ?

Is the light visible and affecting the world? The only thing special about the directional light version of that function is that it returns black if the light has bVisible=false or bAffectsWorld=false

Okay I made a mistake, the function works with the directional light. However, because of a bug I get a lighting information superior to 0 even in the shadow.

I’m talking about this bug : https://rocket.unrealengine.com/questions/4142/advanced-shadowlighting-settings-.html

My previous answer got removed ? :\

Well anyway, the function works fine but because of the current lighting leak the function return a wrong value when the player is inside a shadow. I will cast a classic Trace to work around that problem for the moment.

Okay I don’t know why but I can’t add any new comment below the current ones (they get removed automatically).

Is the light visible and affecting the world? The only thing special about the directional light version of that function is that it returns black if the light has bVisible=false or bAffectsWorld=false

I made a mistake, the function works fine but because of the lighting leak currently in my build the function returns a wrong value when the player is inside a shadow. I will work around that by using a LineTrace check.
I’m talking about this leak : https://rocket.unrealengine.com/questions/4142/advanced-shadowlighting-settings-.html