Search Unity

Render scene without specular lighting ... RenderWithShader? Other ways?

Discussion in 'Shaders' started by a436t4ataf, Dec 30, 2019.

  1. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I'm looking for a way to disable specular lighting universally for a frame on a specific camera (I need that data for an effect later). This seems to be the kind of thing RenderWithShader was invented for - but the API seems incomplete (you can, for instance, only replace a single Shader :().

    Things I thought to try:

    1. RenderWithShader ... and supply a copy of the Standard shader, but with lighting re-written. This ... sort-of works, but almost no-one uses the Standard shader exclusively, so this fails with almost all scenes.
    2. Something something globally disable lighting somehow ... um. Doesn't seem to be a way to do this, as far as I can tell, in Unity? Maybe with SRPs, but not with standard/production Unity :).
    3. Rewrite EVERY SHADER EVER WRITTEN BY MANKIND, and copy/paste them into a 1,000,000 line long Uber-UBER-Shader, with a million sub passes (one for each shader in existence), and fix every single one of them by hand. (assuming you could get the shader compiler to accept this, which you can't. Or that you could merge all the properties for all of them, which you also can't. Or you could support all the pragmas for all of them, which - yep! - you can't)
    Option 3 seems to be the one Unity expects me to do in this situation, but of course it fails completely.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Okay, lets step back and ask a question.

    What is specular lighting?

    I'm not asking what is it in terms of lighting equations, or what it means in lighting models, or any of that really. What is it in terms of GPUs and shaders?

    It's just some code in a shader. A shader that's likely also doing some kind of diffuse lighting. But how does that differ from any other part of the shader? The short answer is it doesn't. It's all just shader code. The GPU doesn't know or care that one part is the diffuse, one part is the specular, and other parts are reflections, emission, maybe parallax effects, etc. It's just shader code that results in some output color. And most of the time shaders that do both don't bother to separate the two since there's no need to do so.


    Now some shaders do conveniently separate the diffuse and specular to some extent, and even have built in keywords to toggle the specular off. Unity's Standard shader, and any shader that uses the Standard lighting model, can have it's specular toggled off with the _SPECULARHIGHLIGHTS_OFF keyword. However unless the shader has a
    #pragma multi_compile _ _SPECULARHIGHLIGHTS_OFF
    it won't be something you can toggle on and off globally. Even for the Standard shader you'd have to be careful to include the variant in the shader variant collection since it's a
    #pragma shader_feature
    which are explicitly removed from the build otherwise.

    But conceivably you could do this:
    Code (csharp):
    1. Shader.EnableKeyword("_SPECULARHIGHLIGHTS_OFF");
    2. cam.targetTexture = targetRT;
    3. cam.Render();
    4. cam.targetTexture = null;
    5. Shader.DisableKeyword("_SPECULARHIGHLIGHTS_OFF");
    But that won't work on anything not using the Standard lighting model code, or that doesn't have the variant. For those you will indeed need to modify the existing shaders to add support for that keyword to disable the specular lighting.


    The old RenderWithShader method made more sense in the days when shaders were less complex. Unity still uses it to generate the depth normals texture when using the forward rendering path, which is a problem for any custom shaders. The more modern way of handling custom render passes is to actually include those extra passes in the shader file itself and give them unique LightMode tags, but there's no way to extend that system outside of the pre-existing ones in the built in rendering paths, so RenderWithShader remained as a stop gap. For the SRPs they've opened this up a lot and allow you to render using specific LightMode tags, but that still requires modifying every shader you want to use to add the variant.


    And that's really the crux of the issue. What you're asking for isn't something that can be easily appended to an existing rendering path / pipeline. Getting "just the diffuse lighting" puts you firmly in the realm of a custom rendering path / pipeline.


    The only other option I can think of is this: Limit this to opaque, deferred rendered objects. Add a command buffer on all lights that cause them to render a second time to a "diffuse only" buffer sampling from the same gbuffers. Obvious not perfect, but gets around any issues with UV offsets / animations / procedural texturing that RenderWithShader wouldn't work well with. Also limits you to only opaque objects using the Standard shading model, so any custom shading models or transparent objects would fail.
     
    a436t4ataf likes this.
  3. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I think of it as a "conceptually simple, but practically difficult", problem :).

    Thinking about RenderWithShader ... if it accepted a dictionary of "shader name" : "replacement shader", then it would at least be possible (but a huge amount of work), with the option of scaling-back to "partly working" for shaders you couldn't easily fix - but as far as I can tell, Unity doesn't even make it *possible*.

    (for anyone else reading, I see two reasons that RwithS isn't powerful enough here:

    1. RenderWithShader has no option to fallback to "don't replace shader X", it's only fallback mode is "dont RENDER object Y"
    2. RenderWithShader only allows one, universal, shader to do the replacing. Which means you'd have to fit all shaders into a single shader, which is more than the shader compiler / GPU (uniforms, etc) can handle.
    )

    ... this is a great idea!

    But, wait - what if: I hook "AfterLighting" with a commandbuffer, and deliberately zero the whole specular gbuffer ... Wouldn't that "disable" specular for everything in the deferred path?