godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
88.89k stars 20.16k forks source link

Incorrect PBR Shading #40753

Closed mindinsomnia closed 2 years ago

mindinsomnia commented 4 years ago

Godot version: v3.2.2

OS/device including version: Windows 10 & Linux Mint 20, GLES3

Issue description: Godot's implementation of PBR shading seems to be producing incorrect results.

With a base colour of completely solid black, a surface should reflect no light, but for some reason, even with a base colour of complete blackness, a reflection is still present on surfaces.

Here are some videos to compare Godot vs Blender Eevee:

Here is a purely metallic material in Blender. Note as the base colour reaches completely black, the reflections on the surface completely disappear and the surface turns completely black.

Here is the same material in Godot. Even with a base colour of black, there are still reflections on the surface.

Also, when a material has no metallic property (metallic = 0.0) or specular property (specular = 0.0) it should not produce any reflections, the surface should be 'diffuse', because that's the only BSDF left when all metallic and specular reflection is removed.

Here is an example of a grey sphere:

Note in Blender, with a grey sphere, with no metallic or specular, the surface is purely diffuse, as it should be.

The same material in Godot still shows reflections. Even though this surface has a low roughness value, that does not mean it should show a reflection, because there is no specular or metallic property to induce one.

Steps to reproduce:

  1. Create a new project in Godot
  2. Set any HDRI background as your environment background
  3. Create a sphere
  4. Give the sphere a Spatial Material
  5. Experiment with properties and compare them to another PBR rendering software, such as Blender's Eevee or Cycles.

Minimal reproduction project: Test PBR Material.zip

clayjohn commented 4 years ago

We use a different PBR model from Blender. So some differences are expected and are completely acceptable. Remember, Godot is a game engine, we trade speed for physical accuracy wherever it makes sense.

A clear example is the specular property. I don't know what it represents in Blender, but in Godot specular is not used to control the strength of specular reflections, it is used to tweak the size of the specular lobe when calculating direct specular lighting from lights. So we don't expect that specular reflections will disappear when specular is 0.

mindinsomnia commented 4 years ago

I don't know what it represents in Blender, but in Godot specular is not used to control the strength of specular reflections,

This part seems somewhat counter intuitive to me.

_Sidenote: Blender's PBR model is based on Disney's Principal Shader work, as it is a fairly widely used model across VFX and game industry and used for broad compatibility and because it is very 'artist friendly' with intuitive parameters for materials, it's the same model used for Pixar's Renderman, Substance Painter, Unreal Engine and Unity. Information about it can be found here: https://clarissewiki.com/4.0/disney-principled.html_

I appreciate your feedback and thank you for your patience, I wish to explain why I feel this PBR model in Godot is problematic from my perspective as a professional 3D artist. I do not wish to sound like I'm complaining, I just wish to offer my perspective:

When for example, I export models from other software, the texture sets they export often include a specular texture map. I can say from experience, every 3D software I've ever used, if it has exported or used specular texture maps, they are used to control the strength of specular reflection. Everything from Blender to Three.js.

If for example you go to any number of free CC0 texture websites, such as texturehaven, the specular texture maps control specular reflection strength, where black = no specular. Example

Compounding this, even though 'specular' means something different in Godot, Godot does import specular texture maps for materials on model imports. This adds to the confusion from an artist perspective.

For example if I export a model from Blender in GLTF format with textures, and import it into Godot, if the GLTF asset includes specular textures, the specular textures are loaded in Godot during import and assigned to the specular texture property in SpatialMaterial, as if they serve the same function. But obviously they don't, so the resulting material looks very different.

This for me is an issue of interoperability with other software (Blender, Unity, Unreal Engine, Substance Painter...) It means I can't design materials or assets in other software with any sense of what they will look like in Godot, as Godot is interpretting the specular property in a very different way to everything else I use.

Also, putting aside all that for a moment, shouldn't an albedo/base colour of solid black produce a solid black material? Even with a roughness of 0, it should be possible to have a solid black material with no specular highlight? Where is the specular coming from and how can an artist control it's strength? Should we not have a material property to directly control specular reflection strength?

From an artist, software compatibility and interoperability perspective, it would be much more convenient and easier for me if Godot's PBR shading was compatible with other industry software. Again I don't wish to sound like I'm complaining, I just wish to explain from my perspective why I feel this way.

clayjohn commented 4 years ago

My apologies. I seemed to have caused some confusion. Godot's PBR model is based on the Disney PBR model. In fact, we use many of the same methods that Unreal does.

To be more clear. I meant to say that one cannot expect exact parity between the way a model looks in Blender and the way a model looks in Godot. This is the same difference between Blender and any other game engine (including Unreal). When you take a material from a 3D DCC which has correctness as a priority it will look different in a game engine which has speed as a priority.

Thank you for clearing up what specular model blender uses. I thought they might still use a blended specular approach (where specular blends with the non-PBR style "specular"). You'll note from the Blender docs which you linked that specular controls the index of refraction (IOR) of the material, rather than the strength of the specular reflection. This is the same with Godot and it is why textures from places like textureHaven work with Godot.

Again my apologies. My comment earlier seemed like it made you think that Godot's PBR model was incompatible with the Disney model.

Now, back to your original post. I would be interested to see how other game engines handle specular metals with a base color of black. While we aim to be as similar as possible to Eevee, in many places we have to make tradeoffs. So the useful knowledge for us is, do other game engines make those tradeoffs, or do they match the behaviour of Eevee?

edit: I did some research to figure out what physically correct results should be. I came open the excellent Filament docs, we often turn to them to assist in our PBR implementation. They explain that in the real world, metals have a luminosity between 67% to 100%. Which correspond to sRGB colors between the 170-255 range https://google.github.io/filament/images/material_chart.jpg. The takeaway is that values outside this range are no longer based in reality as the model only holds for the data set it was designed for.

mindinsomnia commented 4 years ago

@clayjohn I can confirm so far that Unreal Engine materials produce the same result as Blender, when given a specular of 0 will not show any reflection. This is confirmed by their documentation here.

Quote from the page:

To update a Material's Specular, input a scalar value between 0 (non-reflective) and 1 (fully reflective).

Demo

They also describe how to use specular here in this section of their documentation.

Screenshot of the relevant section:

firefox_6MjugayEsJ

firefox_Feif2dDp8N

Sidenote: I don't know if this is still the case but in 2013 they did a Siggraph presentation explaining they used GGX for computing specular and described it as "well worth the cost", Blender also uses GGX for Eevee and Cycles. Link to presentation notes here.

And that is true for metals that those are the realistic values ranges for base colour for raw metals (with exceptions for special circumstances, such as materials that are made up of layers of materials, such as paint layers, etc).

For me the issue is more that when something is 0.0 metallic, 0.0 specular, it should no longer be showing a reflection.

That chart you linked does show what I am referring to, there they refer to specular as reflectance and with a value of 0.0, I can't see a reflection on the surface. Likewise with metallic at 0.0, there should be no metallic property to a surface and no reflection from that either.

clayjohn commented 4 years ago

For me the issue is more that when something is 0.0 metallic, 0.0 specular, it should no longer be showing a reflection.

That chart you linked does show what I am referring to, there they refer to specular as reflectance and with a value of 0.0, I can't see a reflection on the surface. Likewise with metallic at 0.0, there should be no metallic property to a surface and no reflection from that either.

I understand. Game engines separate specular reflections from various light sources (e.g. OmniLights, DirectionLight, and IBL lighting from the Sky). The specular reflection from each light source is added when the light is calculated. The light from the Sky (IBL) is unique though, it is precalculated into a LUT and then read at render time.

The BRDF for IBL lighting we use comes from Unreal. We use their analytic approximation that is intended for mobile. Being an approximation, it is not quite accurate, but it is very fast. For other light sources we use the more correct BRDFs (which dont need to be precomputed).

My guess is the slight bit of remaining specular reflection is caused by this approximation. If so, the solution would be to replace it with a proper LUT.

mindinsomnia commented 4 years ago

I appreciate the effort for the explanation, and you offering me your time and patience on this!

That solution you suggest sounds great.

lawnjelly commented 4 years ago

Isn't this just a confusion about the difference between specular workflow and roughness / metallic workflow?

https://marmoset.co/posts/pbr-texture-conversion/

Also, when a material has no metallic property (metallic = 0.0) or specular property (specular = 0.0) it should not produce any reflections, the surface should be 'diffuse', because that's the only BSDF left when all metallic and specular reflection is removed.

But afaik godot spatial material doesn't have a specular property?

mindinsomnia commented 4 years ago

@lawnjelly I don't believe this issue is related to that.

I understand there are different PBR workflows, one based around metallic and the other based on specular and that can cause issues of it's own but I don't believe that is the cause of this issue.

In this situation, this is all metallic / roughness workflow in Godot/Blender/UE/etc, so it's not the conversion issue. Godot's spatial material does have a specular property, there's some videos up in my original post showing the effect of it's slider and comparisons to Blender Eevee, and further down images from Unreal Engine.

lawnjelly commented 4 years ago

Ah ignore me, clayjohn has pointed out there is a specular option in metallic for f0, just not a specular texture map, I get the confusion now.

mindinsomnia commented 4 years ago

@clayjohn suggested solution sounds great to me, I'd be happy for that.

Edit: Ignore this, see following message.

I just tested your theory clay and I think you're absolutely right. I used one of my sandbox levels, turned off the sky light and all sources of environment light, reduced it to direct lights only, and created a flat black sphere. Base colour black, specular 0.0, and metallic 0.0, roughness of 0.0. With all environment lights turned off, even with an omnilight sitting right on top of it, just above the surface, I couldn't get any specular highlight, it comes out as solid black as it should:

without environment lights

The moment I add a light probe however, now it's showing a specular reflection, this is the same material, still completely black, 0.0 specular, 0.0 metallic, roughness of 0.0:

without light probe

mindinsomnia commented 4 years ago

Correction, obviously the specular won't show for the omnilight if I have a roughness of 0.0, because omnilights in Godot have no 'size' so they won't show unless a surface is at least a bit rough. I increased roughness to 0.1 and now it does show.

Base colour black (0.0, 0.0, 0.0), metallic 0.0, specular 0.0 and roughness of 0.1 with an omnilight, no environment lights or light probes:

no environment light

Surely that specular highlight shouldn't be there.

clayjohn commented 4 years ago

I just did a comparison between Godot, Unreal, and Blender. It is interesting to note that in Unreal when you reduce specular the specular reflections slowly fade until you hit 0.002, anything below that and the specular just snaps off. In Blender the specular reflections slowly fade until they disappear. I wonder if Unreal is just running a check internally if (specular < 0.002) {specular_light = vec3(0.0);} That would explain how we are getting different results even when we use the same approximation they do.

mindinsomnia commented 4 years ago

@clayjohn Maybe it's a performance optimisation?

For anyone else reading this, they also have demos on their documentation, pre-rendered images with sliders below them to see what the properties are meant to do.

Experimenting with Godot's specular property and omni lights, 0.0 specular in Godot "feels" like around '0.25' in Blender Eevee in comparison.

clayjohn commented 4 years ago

@mindinsomnia Thanks for the link! It's making me think more and more that Unreal is using specular as an easy way of reducing the strength of specular reflections. Note the linked article Everything is shiny and the tip in the documentation you linked

For very diffuse Materials, you may be inclined to set this to zero. Resist! All Materials have specular, see this post for examples [5] . What you really want to do for very diffuse Materials is make them rough.

A specular value of 0 doesnt really make sense in a PBR system. My guess is that Unreal has taken advantage of this and changed the behaviour of specular when it is outside physically normal values. By sticking to the plain PBR equations, Godot looks different.

That being said, this issue seems to be converging with https://github.com/godotengine/godot/issues/33616 where the user wanted a setting to disable specular altogether (even though that would not be physically correct). The solution reduz and I discussed was adding a specular_disabled property. But given the discussion here, it may be better to hijack the specular property itself.

mindinsomnia commented 4 years ago

I understand where you're coming from on this. It's a balancing act in my opinion.

On the one hand, as a 3D artist I've learnt the rules of PBR and I ensure my materials adhere to (or at least appear to adhere to) the rules, such as one of the first rules I learnt, 'everything is shiny'. But with art, knowing the rules is just as important as knowing when to break them.

There is value in my opinion in Unreal and Blender's approach in permitting the rule range of specular values, even if going down to 0.0 is usually not physically correct for most surfaces.

There are times when you just want to precisely control the amount of specular being added to something to achieve the exact look you're going for. Sometimes it's because you are going for something hyper real, fantastical, stylised, cartoony, etc, other times it might be just because you're trying to 'cheat', like if you have a deep cavity in your textured surface that due to self shadowing shouldn't be receiving any specular reflections, or at least greatly reduced specular highlights:

L3GrFTK2ZJ

Or if you're trying to fake a particular realistic complex lighting interaction and can't quite get there while operating within the rules. Or whatever the reason, there's usually something that comes up, eventually you end up having to dial something in manually with the specular slider.

Calinou commented 4 years ago

@clayjohn Isn't there already a specular mode property in StandardMaterial3D? IIRC, it has a Disabled mode since SpatialMaterial was added in 3.0.

clayjohn commented 4 years ago

@Calinou it only applies to the specular blob, not specular from IBL. So we need something more.

christianclavet commented 3 years ago

Correction, obviously the specular won't show for the omnilight if I have a roughness of 0.0, because omnilights in Godot have no 'size' so they won't show unless a surface is at least a bit rough. I increased roughness to 0.1 and now it does show.

Base colour black (0.0, 0.0, 0.0), metallic 0.0, specular 0.0 and roughness of 0.1 with an omnilight, no environment lights or light probes:

no environment light

Surely that specular highlight shouldn't be there.

Hi, Please can you check the roughness value?

A low value like 0, will make the object reflect the environment because its become really smooth.

If you want to check this correctly in Blender, you need to have a HDRI background, so when the value is low, you will see reflection like in the screenshot.

If you increase the roughness value to a high value, it will diffuse the environment from the reflection make it appear "rough", hence you should not see these reflection spots from the surface. All this discussion seem to be really how Godot PBR model treat the roughness channel.

If you want to diffuse the specular spot, increase the roughness. Want to make it real smooth and shiny reduce it...

For the values in the metallic channel, change the appearance of the surface, as a low value, will make it look soft like plastic/cloth, and a high value will make it look harder, like metal/glass.

SIsilicon commented 3 years ago

@christianclavet I believe the point of this issue is to show the inconsistency of Godot's specular property with other PBR renderers. In the other engines, realtime or offline, reducing the specular property to 0 disables specular contributions all together! From direct lights, reflection probes, and the environment. Increasing the roughness won't help much, if at all. It just diffuses the specular over a larger area.

mindinsomnia commented 3 years ago

@christianclavet I believe the point of this issue is to show the inconsistency of Godot's specular property with other PBR renderers. In the other engines, realtime or offline, reducing the specular property to 0 disables specular contributions all together! From direct lights, reflection probes, and the environment. Increasing the roughness won't help much, if at all. It just diffuses the specular over a larger area.

You are absolutely correct, that is indeed precisely the point of this issue and all I was trying to convey.

Calinou commented 2 years ago

Closing in favor of https://github.com/godotengine/godot-proposals/issues/3792, as feature proposals are now tracked on the Godot proposals repository.