Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Standard (Specular Setup) Shader

Discussion in 'Shaders' started by llamagod, Dec 23, 2018.

  1. llamagod

    llamagod

    Joined:
    Sep 27, 2015
    Posts:
    76
    I'm trying to implement 2D Dynamic Sprite Lighting with normals and specularity as described in this tutorial: https://www.mrfoxandfriends.com/blog/devstories2 in another engine than Unity.

    So I'm trying to figure out how the Standard (Specular Setup) shader works, what calculations it performs. But when I downloaded the built-in shaders and looked inside there were references to a ton of other files and references within those and references within those and so on. It's very hard to read and bounce between files.

    I tried writing my own shader to achieve the same effect, but it doesn't give the same result.
    A normal map and a specularity map is generated using Sprite DLight.
    colorMap is the original sprite, normalMap.rgb is the normal and normalMap.a is the specularity from the specularity map.
    lightPosition has a z coordinate to position it further away from the scene.
    Code (CSharp):
    1. float4 PointLightShader(VertexToPixel PSIn) : COLOR0
    2. {
    3.     float4 colorMap = tex2D(ColorMapSampler, PSIn.TexCoord);
    4.     clip(colorMap.a - 0.001);
    5.  
    6.     float3 pixelPosition = float3(PSIn.WorldPos, 0);
    7.     float3 lightDirection = lightPosition - pixelPosition;
    8.  
    9.     float coneAttenuation = saturate(1.0f - length(lightDirection) / lightDecay);
    10.  
    11.     float4 normalColor = tex2D(NormalMapSampler, PSIn.TexCoord);
    12.  
    13.     float3 normal = normalColor.rgb;
    14.     float3 normalTangent = (2.0f * normal) - 1.0f;
    15.  
    16.     float3 lightDirNorm = normalize(lightDirection);
    17.     float3 eyeVec = float3(0, 0, 1);
    18.        
    19.     float amount = max(dot(normalTangent, lightDirNorm), 0);
    20.                
    21.     float3 reflect = normalize(2 * amount * normalTangent - lightDirNorm);
    22.     float specular = min(pow(saturate(dot(reflect, eyeVec)), 10), amount);
    23.                
    24.     return (colorMap * amount * coneAttenuation * lightColor * lightStrength) + (specular * coneAttenuation * normalColor.a);
    25. }

    How would I need to modify my code to make it work like the example?
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Unity’s Standard shader is significantly more complex than your basic phong. It’s a physically based shading model using Cook-Torrance GGX specular and the “Disney Diffuse” Oren-Nayer approximation for diffuse.

    If those words are unknown to you, there’s a lot of learning in your future.

    Or you can try putting together a basic Surface Shader and looking at the generated shader code of that to see how it interfaces with the Standard shading model’s BRDF functions.
     
  3. llamagod

    llamagod

    Joined:
    Sep 27, 2015
    Posts:
    76
    Thanks for the answer. I do know what you mean by basic phong and I first thought that was what Unity was using, or some variation at least. But I haven't heard of the other terms, I'll check it out. I read the generated code and it wasn't very helpful. There are a ton of references and I usually hit a wall with the parameters simply being passed on to somewhere else through output, like o.Normal = normal for example.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Most of the important code is in the BDRF functions in UnityStandardBRDF.cginc. Specifically those related to BDRF1. BRDF3 is a normalized Blinn Phong which may look a little more like Code you’re use to, and is what Unity uses on most mobile platforms.
     
  5. llamagod

    llamagod

    Joined:
    Sep 27, 2015
    Posts:
    76
    Thanks, I can see stuff like DisneyDiffuse is in there. Looks good although might be a bit overkill for 2D diffuse + specular lighting considering the view vector is always (0, 0, 1) for example.
     
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Most people don’t bother with diffuse models beyond Lambert (ie: dot product) as most people don’t notice the difference. It adds something for very rough surfaces, but almost all other cases is nearly indistinguishable from Lambert.
     
  7. llamagod

    llamagod

    Joined:
    Sep 27, 2015
    Posts:
    76
    What do you mean by Lambert?
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Lambert is the generic diffuse shading that 99% of real time lighting uses.

    float diffuse = saturate(dot(-lightDir, normal));

    Phong is Lambert, but using a reflection of the view direction and normal instead of just the normal, and then applying a power to the result.
     
  9. llamagod

    llamagod

    Joined:
    Sep 27, 2015
    Posts:
    76
    I see. I don't think I need anything as fancy as Unity's standard shader actually. Sprite Lamp uses a simple Phong with some extra things. I think Sprite DLight uses something similar to generate the preview which is what I'm going for.

    This is the Sprite Lamp shader only with the parts that I'm interested in (without cel shading, ambient occlusion etc.): https://pastebin.com/yExae5Uv

    This is the whole shader: https://pastebin.com/GcrRmVzj

    Here's what the dev wrote about it: https://www.snakehillgames.com/sprite-lamps-basic-shader/

    Tried adapting my code to this. It's better, but still off somehow, especially the specular lighting is not powerful enough.
    Code (CSharp):
    1. float4 PointLightShader(VertexToPixel PSIn) : COLOR0
    2. {
    3.     float4 colorMap = tex2D(ColorMapSampler, PSIn.TexCoord);
    4.     clip(colorMap.a - 0.001);
    5.     float3 pixelPosition = float3(PSIn.WorldPos, 0);
    6.     float3 lightDirection = lightPosition - pixelPosition;
    7.     float coneAttenuation = saturate(1.0f - length(lightDirection) / lightDecay);
    8.     float4 normalColor = tex2D(NormalMapSampler, PSIn.TexCoord);
    9.     float3 normal = normalColor.rgb;
    10.     float3 normalTangent = (2.0f * normal) - 1.0f;
    11.     float3 lightDirNorm = normalize(lightDirection);
    12.     float3 eyeVec = float3(0, 0, 1);
    13.  
    14.  
    15.     float diffuse = dot(normalTangent, lightDirNorm);
    16.     float amount = saturate(diffuse * 1000.0);
    17.     diffuse = saturate(diffuse);
    18.            
    19.     float3 reflect = normalize(2 * amount * normalTangent - lightDirNorm);
    20.     float specular = pow(saturate(dot(reflect, eyeVec)), 5) * amount * 0.5 * normalColor.a;
    21.            
    22.     colorMap = colorMap * diffuse * coneAttenuation * lightColor * lightStrength + (specular * coneAttenuation * specularStrength);
    23. }
     
    Last edited: Dec 25, 2018