Exposing the BRDF Models to the Material Editor?

Poking around at the Unreal Shader Files, is it possible to expose to different PBR BRDF models that are written into the engine to the material editor, so that materials can select them per-material? I’m looking inside BRDF.usf right now in the engine source, and multiple models are there. I assume that we can simply write our own models in here and change the #define PHYSICS_### flags to use that shader when we compile? Is that feasible?

I’m looking into ways to make the renderer much more flexible. Our options are extremely locked down at the moment. This would also be a great way to bypass the current issues with foliage shading, which are forcing us to use very expensive SSS methods.

No this cannot be exposed to the Material Editor as the BRDF functions are used during the deferred lighting stage, which is done in screen space regardless of the material type. The only thing the material can do is provide inputs into these functions such as Roughness. You can certainly define your own BRDF functions, but then you have to change the one currently in effect. Which means you would not be able to use it just for foliage.

What you could do if you are willing to put in the effort is define a new Lighting ID for say Foliage, and expose that to the Material Editor (In the same manner as Subsurface and Preintegrated Skin shading is done). Then providing the necessary code in the DeferredLightingCommon.usf to perform your custom dynamic lighting. I originally did this for my hair rendering before I moved away from the deferred renderer, so I do know how to set that up if you want to travel down that path.

I did originally look at that idea so I’d be eager to hear more :slight_smile: It’s a shame you can’t change the models, some for example are much better at simulating hard/glossy surfaces than others (e.g. Oren-Nayar vs Cook-Torrence).

I was hunting for the information about creating custom Lighting ID’s but didn’t get very far, feel free to share anything you may know about them that could help :slight_smile: Even at that stage, is it still impossible to change the BRDF model for that material only? For example, having Translucent Surfaces that use Cook-Torrence, and Pre-Integrated skin using Oren-Nayar. (Just for the sake of example).

Also, you say you’ve moved away from the deferred renderer which I’m assuming means you’ve worked on the forward renderer a little? Do you have things like Light Vectors and custom lighting models working? My project isn’t going to be flooded with dynamic lights and reflections, so maybe a switch would benefit this project too.

I’ll put something together over the weekend for you to take a look through, i still have the deferred code I was using, just not using it anymore.

Yeah you dont set the BRDF model for a material only, the BRDF is dependent on the lighting model, You wouldn’t have Pre-Integrated skin using Oren-Nayar for example, you would have a new lighting model called Pre-Integrated Skin Oren-Nayar (Or some other fancy name) that would apply Oren-Nayar to that lighting model and you would use that in place of your Pre-Integrated Skin lighting model. Then in the deferred shading shader, you would check for your new lighting model and then light that section with Oren-Nayar. But you have to be careful with how many lighting ID’s you might want to add, since it is just a float that is encoded between 0.f-1.f, and its just a simple (LightingID / 4.f) at the moment (4 being the max lighting ID) so you could effectively have 10, but this also adds to the complexity of the deferred shader.

This is what I have found during my investigations, if anyone from Epic or otherwise wants to speak up and check that my understanding is correct, that would be great.

Well I render in the translucency stage now as I needed alpha. So yeah I have implemented a kind of forward renderer for the hair, it only takes into consideration the primary directional light source and then composites the final color in the scene texture, due to this I will have to limit the number of dynamic lights that can influence the hair (Im thinking 1 directional light + The 4 closest point/spot lights). But it does use a custom lighting model. The hair is lit using the kajiya model with dual highlights. But as of right now its completely hardcoded for only the hair mesh and its still in the deferred renderer itself, just in the translucency stage.

Awesome information, the trickiest part is figuring out what sits where, which files to look in etc. Being new to C++ and programming in general makes it slightly harder but I can piece together what’s happening with basic knowledge.

But yeah, would be great to see some more detail on this kind of thing. Epic’s Trello road-map does have a Parallel rendering implementation at the highest priority right now but I just want to see the rendered becoming much more flexible.

So as promised here is a quick rundown of adding a new lighting model ID. In the code below, the new lighting model ID will be called Foliage

====================================================================================================================================
DeferredShadingCommon.usf

[Line 128] or [Line 253 in v4.2]
This is where you define new lighting models. Make sure to update the MAX any time you add a new one


#define LIGHTINGMODELID_FOLIAGE 4
#define LIGHTINDMODELID_NUM 5

[Line 212] or [Line 340 in v4.2]
This line encodes the lighting model ID into the range of 0.f - 1.f for storing in the GBuffer


OutGBufferA.a = Data.LightingModelId / ( (float) LIGHTINGMODELID_NUM - 1.f );

[Line 247] or [Line 374 in v4.2]
This function decodes the lighting model ID from the GBuffer


int DecodeLightingModelId( float4 InGBufferA )
{
	return (int) ( InGBufferA.a * ( (float) LIGHTINGMODELID_NUM - 0.001f ) );
}

====================================================================================================================================
Engine\Source\Runtime\Engine\Classes\Engine\EngineTypes.h

[Line 109]
Engine side definition of the Material Lighting Model. This allows it to be selectable in the Material Editor


MLM_Foliage UMETA( DisplayName = "Foliage" ),
MLM_MAX

====================================================================================================================================
Engine\Source\Runtime\Engine\Private\Materials\MaterialShared.cpp

[Line 1131] or [Line 1146 in v4.2]
Ensure that the shader compiler sets our new custom lighting model if selected


case MLM_Foliage: OutEnvironment.SetDefine( TEXT( "MATERIAL_LIGHTINGMODEL_FOLIAGE" ), TEXT( "1" ) ); break;
default:

====================================================================================================================================
BasePassPixelShader.usf

[Line 711] or [Line 731 in v4.2]
Set the Lighting Model ID based on the Material Lighting Model selected


#elif MATERIAL_LIGHTINGMODEL_FOLIAGE
   Data.LightingModelId = LIGHTINGMODELID_FOLIAGE;
#else

====================================================================================================================================
DeferredLightingCommon.usf

[Line 262] or [Line 353 in v4.2]

GetDynamicLighting
This function is in charge of calculating lighting for a given position, normal, etc. and this is where you would
calculate your new lighting based on the LightingModelId

====================================================================================================================================

26e06ad8ed9b6be420e842ee0d10950b11c86ad2.jpeg
You should see a new option in the material editor for your new lighting model



To ensure your new lighting model ID is being applied, you can use the Lighting Model buffer visualization. Your new lighting model will be white. If you plan on having addition lighting models, you will need to update MaterialTemplate.usf at Line 797 to include new colors to be able to differentiate between the different models (Otherwise they will all appear white)


Here is an example of applying Oren-Nayar diffuse to the new lighting model, compared to the existing Default Lit lighting model

Wow!

I really like your end result !

Any chance of Oren_Nayar diffuse /anything ? It looks very good :slight_smile:

Great work!

I cannot take any credit for the Oren Nayar lighting, the code is already present in the shaders. All I did was use it in a custom lighting model. All credit to Epic.

I can the above changes as they are required in the engine code, but for now the above will do as I am looking into a way to possibly expose the ability to create custom lighting models in the editor, creating a new material function (like a light function) that you would assign an ID that could be selected in your material which would change the way it is lit during the deferred shading pass. This will require a fair amount of tinkering and the end result will not be able to be a . I will upload the source to so it can just be dropped in and compiled once I am done. (This may be a fair while away though as I have a number of other projects to complete prior).

You just need to change single #define in BRDF.usf
It’s hard to miss:



// Diffuse model
// 0: Lambert
// 1: Burley
// 2: Oren-Nayar
#define PHYSICAL_DIFFUSE	2


The you just start your game, and wait till all shaders recompile.

Except that changes it for all materials. (If that doesn’t matter, then yes that is the preferred method). Where as the above code allows you to only change it for certain materials.

In any case thanks for tutorial! Un 4.2 version the line numbers are bit different in shader files though, so if anyone else wondering:

DeferredShadingCommon.usf
It start from line 253.

BasePassPixelShader.usf
Line 723

DeferredLightingCommon.usf
Line 353.

Could you also post code that you added in GetDynamicLighting function ?
I looked at it, and honestly I started doubting myself after seeing all those #if #endif BRANCH defines.

Thanks for listing those numbers. I am using v4.1.1 still. I have updated the original post to include them.

Okay first I just duplicated the PointLightDiffuse function and changed it to use Oren Nayar as such:



float3 PointLightDiffuseOrenNayar( FScreenSpaceData ScreenSpaceData, float3 VectorToLight, float3 V, half3 N )
{
	FGBufferData InGBufferData = ScreenSpaceData.GBuffer;

	float3 L = normalize( VectorToLight );

	float3 H = normalize(V + L);
	float NoL = saturate( dot(N, L) );
	float NoV = saturate( dot(N, V) );
	float VoH = saturate( dot(V, H) );
	
	return Diffuse_OrenNayar( InGBufferData.DiffuseColor, InGBufferData.Roughness, NoV, NoL, VoH );
} 


Then in the GetDynamicLighting function where it calculates the DiffuseLighting variable I changed it from:



float3 DiffuseLighting = PointLightDiffuse( ScreenSpaceData, L, -CameraVector, N );


to



float3 DiffuseLighting = float3( 0.f, 0.f, 0.f );
if( LightingModelId == LIGHTINGMODELID_FOLIAGE )
{
	DiffuseLighting = PointLightDiffuseOrenNayar( ScreenSpaceData, L, -CameraVector, N );
}
else
{
	DiffuseLighting = PointLightDiffuse( ScreenSpaceData, L, -CameraVector, N );
}


And thats it. Remember this was just a quick solution to show a different lighting model, there are better ways to do the lighting calculations.

Ok thank you very much this!

I just want to tinker around bit, and yours solution is given perfect starting point to get something going (;.

You my friend… are my new favourite person :smiley:

Have you guys had any luck implementing this in 4.4? The code files have changes considerably with the advent of the new Shading Models. I’m trying to add a custom Minnaert BRDF model for a custom shader called ‘Moon’, but there’s no more ‘Diffuse Lighting’ function to switch out the shader model.

I haven’t looked at the shader source in a while, but will take a look soon and let you know.

In 4.3 some of the gbuffers were reorganized to allow for a byte worth of shading model ids - 256. We’re using a range to implement a custom toon shading model.

This thread is really helpfull.
I also follow the same way to update shader code for custom lighting model, such as Cook-Torrance lighting.

Roughness = 1

Nice work Transformers! Out of curiosity did you do that in 4.5 or 4.6? The code seems to have changed a fair bit, hopefully will be able to update his tutorial when he gets around to it :slight_smile:

I did it in 4.51
Maybe the feature would handle this part without recompile editor.