Jump to content
  • entries
    9
  • comments
    52
  • views
    25,572

PBR Lighting in Leadwerks


Rastar

7,722 views

 Share

blog-0828579001455037487.jpg

Physically-based Rendering (PBR), often also called Physically-based Shading (PBS), has taken the world of game engines and content creation tools by storm over the last couple of years. And for good reason: Art assets can be produced in a much more predictable and consistent way, since its properties directly relate to (measurable) real-world data, like the diffuse color of a material or the luminous flux (in lumen) of a light source. Even better, those assets also behave predictably and cosistently under varying lighting conditions. And since I really like the results of PBR pipelines (and love fooling around with shaders) I had a go (actually a second one) at implementing PBR in Leadwerks.

 

So what is (different in) PBR? There are many good resources on this around (e.g. the tutorials by Marmoset http://www.marmoset.co/toolbag/learn), but in a nutshell:

 

- Special care is taken to make the reflection by a material energy-conserving. Meaning: There can't be more light coming out of a material than went in (unless it is an emitter, of course).

- The properties of a material are modeled after its real-world cousin. This is especially obvious for metals: Metals have no diffuse reflection (which is actually the main contributor to any material's reflection in a classical pipeline). Physically, the diffuse reflection of a material consists of light that is absorbed and then re-emitted (with the diffuse color of the respective material). Metals don't do that (much) - any light hitting a material is being reflected right away, never entering its surface. As a consequence, the diffuse color (usuall called albedo) of a metal is pitch black.

- Everything is shiny: Even non-glossy non-metals do have a (low) amount of reflectivity, being especially apparent at glazing angles (Fresnel effect). Most materials don't have a colored reflection, athough some metals do (like gold or copper).

 

You will find two main workflows for PBR pipelines: specular-gloss and metalness-roughness. These are basically just two different ways of specifying a material's properties, one giving more artistic freedom and less artifact (specular-gloss), with the other being slightly more intuitive and memory/bandwidth friendly (metalness-roughness). Since I don't have access to the setup of the G buffers in Leadwerks' deferred renderer, I went with the metalness-roughness variant since I could squeeze that into the current setup.

 

Apart from modifying the lighting shaders to use the different rendering algorithm (I used the GGX specular lighting), it was important to include indirect specular lighting because otherwise metals would be mostly black. The standard way to do this is to use special cubemaps (I created mine using https://www.knaldtech.com/lys/). I also added a simple form of diffuse IBL (image-based lighting) using spherical harmonics.

 

Some other things are important when adopting a PBR lighting algorithm:

 

- Use linear space. PC monitors actually use gamma space, which is why most texture files are also encoded in gamma space. The problem here is that adding several colors in gamma space gives an incorrect result (for a more detailed description see http://http.developer.nvidia.com/GPUGems3/gpugems3_ch24.html). Therefore, textures have to be converted to linear space, and the rendering result must be converted back to gamma space for displaying on a monitor.

- The accuracy of an 8bit per channel frame buffer (which is what Leadwerks currently uses) does not yield best results, 16bits per channel would be preferable. And some textures (cubemaps, e.g.) should actually be in 16bit formats as well.

 

But enough chit-chat, what does all this actually look like?

 

First of all, we have to "usual" diffuse lighting, here by a directional light

 

http://images.akamai.steamusercontent.com/ugc/318997058028854065/DDFD846082BF282E8CC7279CBF5FE022A03EE367/

 

and its corresponding specular companion

 

http://images.akamai.steamusercontent.com/ugc/318997058028856863/0BD31B453D6ABD8B16E7435CBD9EE86649D92C4D/

 

Together with the diffuse ambient term

 

http://images.akamai.steamusercontent.com/ugc/318997058028860663/961C026CA6DB56192FFB356C9F6BCA53BB893CAC/

 

and the indirect specular

 

http://images.akamai.steamusercontent.com/ugc/318997058028863634/8925ED33DDDD375EC0161B843F465BCCD3AC737E/

 

this adds up to the total lighting seen at the beginning of the page

 

http://images.akamai.steamusercontent.com/ugc/318997058028866463/8771AB07CB9F5D472F034A4EC83EA4A96A82EA06/

 

So, what's next? Well, actually I'm not convinced I have done all this right - there are some artifacts I have to look at, and I'm sure if the calibration is right. Also I need to write a cubemap generator to create those textures in-engine.

 

Stay tuned!

  • Upvote 12
 Share

17 Comments


Recommended Comments

How does this affect performance with scene rendering?

 

There's obviously more texture color lookups, a few more multiplication operations, and more memory used by the textures.

Link to comment

I can't give you a direct comparison. It should be slower, but not by an awful lot. Apart from the ambient lighting there aren't more texture lookups, and that would be an unfair comparison since it's an additionl feature. But yes, the shader calculations are more involved (more dot product, a pow() for the Schlick approximation etc.). My code isn't yet optimized, and some optimizations would actually need changes to the Leadwerks engine:

  • the conversion of textures from linear space to gamma can be don eby the GPU using its sRGB sampling. Right now I have to transform the colors using pow(col, 2.2) and back using pow(col, 1/2.2)
  • I am currently doing the ambient lighting in the materials because I don't know how to pass the required data to the lighting shaders,

Link to comment
I am currently doing the ambient lighting in the materials because I don't know how to pass the required data to the lighting shaders,

 

Isn't the ambient lighting color just passed as a uniform in the lighting shaders?

directionallight.shader:

uniform vec4 ambientlight;
...
...
sampleoutput = (diffuse * lightcolor + specular) * attenuation + emission + diffuse * ambientlight;

 

Can you not just pass your data in place of the inherent ambientlight uniform?

Link to comment
Can you not just pass your data in place of the inherent ambientlight uniform?

 

I am using spherical harmonics (think Marmoset Skyshop) which have 9 float parameters, so the 4 channels of the ambient color aren't sufficient. Come to think of it: I am currently using just one cubemap, but there could be many in the scene, so the coefficients (and reflection cubemap values) would depend on the object's position. It might be best to keep these calculations in the materials.

Link to comment

This is amazing! Can you make the same shot with your shader and with default. I suppose your base color texture isn't the same as diffuse but still.

Link to comment

That's actually not too easy, because the assets have to be so different. I tried the following: Used a barrel model from Dexsoft's Industrial3 collection and applied a rusted steel texture from gametextures.com (that is available in both classic and PBR variants):

 

Classic

 

http://images.akamai.steamusercontent.com/ugc/318997198276597375/6C91BE3851233BE9365238615B71AC72E1012060/

 

 

and PBR

 

http://images.akamai.steamusercontent.com/ugc/318997198276599144/73A9480C1634DF36C7D0BE8E3DBBAF46143C8179/

  • Upvote 1
Link to comment

I think you should enable specular reflection in the first image to get an accurate comparison. A plain diffuse texture with no normal map or specular reflection is going to look very flat.

  • Upvote 2
Link to comment

Actually I did, but in all those back and forth changes between the two pipelines I must have made a mistake, I created a clean new project and did the "classic" shot again:

 

http://images.akamai.steamusercontent.com/ugc/318997198277429861/AA729F838AEFA16B397DE94A706744F4ED18E51C/

 

But the direct comparison is really difficult. E.g. in this new shot the light intensity is higher (1.2 instead of 0.8) than in the PBR shots to show some highlights.

Link to comment

The shiny metal barrel looks better in the pbr shot but the rust looks better in the classic shot. If you could get the rust to look more dry and gritty on the shiny barrel it would be best of both.

Link to comment

The shiny metal barrel looks better in the pbr shot but the rust looks better in the classic shot. If you could get the rust to look more dry and gritty on the shiny barrel it would be best of both.

Just need to apply roughness map or something like this, I'm sure it will be possible.

Great work!

  • Upvote 1
Link to comment

If possible, can you post the textures used for the red marble and the rusted metal? I would like to see a more direct comparison just using the inherent LE materials and shaders.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...