Search Unity

[SOLVED] Flat shading

Discussion in 'Shaders' started by laurentlavigne, Jun 14, 2016.

  1. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I am trying to make a smooth mesh look flat, flattening the normals of each polygons with this shader but getting errors, can someone help out with GLSL (would use sorface shading but I don't think the flat attribute exists)

    Code (CSharp):
    1. Shader "Flattener" {
    2.     SubShader {
    3.         Pass {
    4.             GLSLPROGRAM
    5.  
    6.             #extension GL_EXT_gpu_shader4 : require
    7.             flat varying vec4 color;
    8.  
    9.             #ifdef VERTEX
    10.             void main()
    11.             {
    12.                 color = gl_Color;
    13.                 gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    14.             }
    15.             #endif
    16.  
    17.             #ifdef FRAGMENT
    18.             void main()
    19.             {
    20.                 gl_FragColor = color; // set the output fragment color
    21.             }
    22.             #endif
    23.  
    24.             ENDGLSL
    25.         }
    26.     }
    27. }
     
    megadavido likes this.
  2. LukaKotar

    LukaKotar

    Joined:
    Sep 25, 2011
    Posts:
    394
    What do the errors you are getting say? If there is more than one, it is usually the top one that matters.
     
  3. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    GLSL compilation failed:
    ERROR: 0:7: '' : extension 'GL_EXT_gpu_shader4' is not supported
    ERROR: 0:8: 'vec4' : syntax error syntax error
     
  4. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    Can you just not smooth the normals?
     
  5. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    I want smooth mesh to be flat shaded and this is the way in GLSL
     
  6. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    That extension being not supported seems very clear to me, don't know what you need help with. (did you try to remove that extension line? not sure what needs it, then again I really don't know much about glsl).
     
  7. LukaKotar

    LukaKotar

    Joined:
    Sep 25, 2011
    Posts:
    394
    I'm pretty sure vec4 is not valid in Unity. Change it to half4 or float4. As for the other error, it looks like GL_EXT_gpu_shader4 simply isn't supported, either by Unity or your computer.

    Also, can you describe what you mean by "flat shaded"? Try selecting your model, and in the import settings change "Normals" from "Import" to "Calculate", then mode the slider to 0. Is this what you want?
     
  8. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    these changes don't work.

    calculate normal to zero doubles up vertices to break the normal
     
  9. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    11,792
    try adding : #pragma only_renderers glcore
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I believe flat was added in OpenGL 3.0, maybe 3.1, but Unity might be trying to compile to 2.0.

    You might try adding:
    #version 130
    Or
    #version 140

    To force OpenGL 3.0 or 3.1 respectively. I assume those work in Unity with GLSLPROGRAM blocks, I don't know for sure as I rarely do direct GLSL or HLSL programming with in Unity.

    Alternately you could try writing it as a normal Unity shader within a CGPROGRAM and use the nointerpolation qualifier along with #pragma target 4.0
     
  11. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    THANKS bgolus ! Just adding nointerpolation in front of the color v2f does the magic!
    Most of my shaders are surface shader, nointerpolation gives a syntax error, do you know what the surface shader equivalent is?

    Code (CSharp):
    1. Shader "Diffuse With Shadows"
    2. {
    3.     Properties
    4.     {
    5.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         Pass
    10.         {
    11.             Tags {"LightMode"="ForwardBase"}
    12.             CGPROGRAM
    13.             #pragma vertex vert
    14.             #pragma fragment frag
    15.             #include "UnityCG.cginc"
    16.             #include "Lighting.cginc"
    17.  
    18.             // compile shader into multiple variants, with and without shadows
    19.             // (we don't care about any lightmaps yet, so skip these variants)
    20.             #pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
    21.             // shadow helper functions and macros
    22.             #include "AutoLight.cginc"
    23.  
    24.             struct v2f
    25.             {
    26.                 float2 uv : TEXCOORD0;
    27.                 SHADOW_COORDS(1) // put shadows data into TEXCOORD1
    28.                 nointerpolation fixed3 diff : COLOR0;
    29.                 nointerpolation fixed3 ambient : COLOR1;
    30.                 float4 pos : SV_POSITION;
    31.             };
    32.             v2f vert (appdata_base v)
    33.             {
    34.                 v2f o;
    35.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
    36.                 o.uv = v.texcoord;
    37.                 half3 worldNormal = UnityObjectToWorldNormal(v.normal);
    38.                 half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
    39.                 o.diff = nl * _LightColor0.rgb;
    40.                 o.ambient = ShadeSH9(half4(worldNormal,1));
    41.                 // compute shadows data
    42.                 TRANSFER_SHADOW(o)
    43.                 return o;
    44.             }
    45.  
    46.             sampler2D _MainTex;
    47.  
    48.             fixed4 frag (v2f i) : SV_Target
    49.             {
    50.                 fixed4 col = tex2D(_MainTex, i.uv);
    51.                 // compute shadow attenuation (1.0 = fully lit, 0.0 = fully shadowed)
    52.                 fixed shadow = SHADOW_ATTENUATION(i);
    53.                 // darken light's illumination with shadow, keep ambient intact
    54.                 fixed3 lighting = i.diff * shadow + i.ambient;
    55.                 col.rgb *= lighting;
    56.                 return col;
    57.             }
    58.             ENDCG
    59.         }
    60.  
    61.         // shadow casting support
    62.         UsePass "Legacy Shaders/VertexLit/SHADOWCASTER"
    63.     }
    64. }
     
    Last edited: Jun 15, 2016
    AlejMC, iamyoukou_unity and Marrt like this.
  12. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I don't believe there's a way to do it with surface shaders. Instead you'll likely have to take the generated vert/frag shader and modify that.
     
  13. laurentlavigne

    laurentlavigne

    Joined:
    Aug 16, 2012
    Posts:
    6,363
    The generated shader was too messy so I transcribed the surface shader to CG. Thanks for the help.
     
  14. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    350
    Hello, can anyone explain what happens when you use the keyword "nointerpolation", the entire triangle is filled with the value calculated for the first vertex?
     
  15. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Correct.
     
  16. mouurusai

    mouurusai

    Joined:
    Dec 2, 2011
    Posts:
    350
    Thank you)
     
  17. JamesArndt

    JamesArndt

    Joined:
    Dec 1, 2009
    Posts:
    2,932
    You do know that any of the Unlit shaders that are built into Unity flat shade models right?
     
  18. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Unlit shaders are, well, unlit, sometimes erroneously referred to as "flat shading" or "flatly lit", but unlit shaders aren't lit or really "shaded" at all. The flat shading being discussed here, and specifically the GLSL "flat" and HLSL "nointerpolation", allows for faceted surface shading without having faceted model normals. What the OP is referring to is the "PolyWorld" style faceted surface shading, like this:


    The example shader at the start is just testing the GLSL "flat" attribute designation rather than an attempt at the final flat shaded result.
     
    AlejMC and laurentlavigne like this.
  19. Fuegan

    Fuegan

    Joined:
    Dec 5, 2012
    Posts:
    20
    Does that mean it is possible to keep a low vertices count while having the flat style? A visually faceted cube (custom one not the Unity one) will still have 8 vertices instead of 24?

    I guess my real question behind it is more how good is it performance wise?
     
    Last edited: Jul 27, 2016
  20. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Yep, you can have every vertex welded, though there's a chance two polygons will end up with the same values if they share the same first vertex.

    Performance wise there should be zero performance difference vs the same smooth mesh with a normal shader, and plausibly though unlikely even a very (very, very) minor perf gain over traditional interpolated attributes. On very high poly objects it might even be noticeably faster than manually faceted surfaces.

    Unfortunately for the platforms that this would actually matter at these low poly counts, they don't support nointerpolation / flat (low end mobile) so it's kind of moot.
     
    Marrt, laurentlavigne and Fuegan like this.
  21. Fuegan

    Fuegan

    Joined:
    Dec 5, 2012
    Posts:
    20
    Thanks for the answer.

    It made me think though, if I'm correct the smoothed triangles use an average of the vertices normals with a weight based on distance right? Isn't there a way to have a non-weighted average? It will do some calculations but it would avoid the problem of multiple surfaces sharing the same first vertex.

    I looked for it a little but didn't find anything so it might be hardware restrictions and in any case I'm curious why.
     
  22. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    By default values passed from the vertex shader to the pixel shader use perspective corrected barycentric interpolation, basically the average weighted by normalized distance as you said. This is done at the hardware level and there's no direct control over it apart from if the graphics API supports various modifiers like nointerpolation (what this thread is about), noperspective (if used on texture UVs it'll make them look warped like they did on the original PlayStation and early arcades), or centroid (which has to do with MSAA, it is not the center of the triangle). Those are the only options and there's no way to change them or extend them.

    You can however work around them. Neither the vertex shader nor the fragment shader have access to the data you need for this as the vertex shader only knows of the data of that single vertex, and the fragment only knows the final interpolated value. Geometry shaders are able to get all of the data from the 3 vertices of a triangle at once and you can modify the values that eventually get interpolated. In that way you could take all 3 vertex normals and average them and then spit out a new triangle to replace the original one with unique vertices all using the same normal. Even better you don't actually need normal on your mesh at all at that point, you can calculate the actual normal of the surface and use that.

    However on old hardware geometry shaders either aren't supported or are really slow such that it's possible a pre-faceted mesh will be noticeably faster. On newer hardware it'll be a bit of a wash, but the pre-faceted mesh will likely still be faster. The nointerpolation should be faster than either of them.
     
  23. Fuegan

    Fuegan

    Joined:
    Dec 5, 2012
    Posts:
    20
    Thanks for all the clarification bgolus.
     
  24. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
    I am sorry to jump this old thread, but @bgolus could you let me know if I understand your explanation correctly?

    There are currently 3 ways to do faceted "flat shading":

    1) split vertices (aka pre-faceted, through model import or duplicating vertices during procedural generation)
    2) geometry shader (GLSL, requires "#pragma target 4.0")
    2.1) through interpolation qualifier "flat" (using one of vertex)
    2.2) using all 3 vertices and calculate normal manually
    3) interpolation modifier (HLSL, requires "#pragma target 4.0")
    3.1) through nointerpolation (using one of vertex)

    If I understand correctly, option 1 is the only way for iOS (which uses OpenGL ES 3.0 thus doesn't support geometry shader, and "#pragma target 4.0" requirement blocks Metal).

    Am I right? Just trying to lay it out there, so others from Google search don't have to repeat my search...
     
    ModLunar likes this.
  25. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You're mostly accurate.

    1 - works on every device and target.
    2 - requires #pragma target 4.0 (DX11, OpenGL 3.2, OpenGL ES 3.1+AEP aka recent android phones, and recent consoles.
    2.1 - we'll get back to this one, but basically no.
    2.2 - yep
    3.0 & 3.1 - Should work with #pragma target 3.5 (DX11, OpenGl 3.2, OpenGL ES 3.0, Metal, basically the same as target 4.0 but without geometry shaders so includes metal and es 3.0)

    The interpolation qualifiers flat and nointerpolation are the same thing, the difference is one is used by OpenGL and the other by DirectX. Since Unity's shader lab is CG/HLSL based we use the nointerpolation qualifier. When unity does its magic to translate the shader to there others languages it should be capable of converting the HLSL "nointerpolation" to "flat" for OpenGL, OpenGL ES, and for Metal. However Unity's converters/translators aren't always complete or perfect and might be missing some features. In that case you could write the GLSL directly and use the "flat" qualifier yourself. For iOS Metal you might have to put in a bug if it doesn't work.

    Now let's go back to point "2.1" again more directly. When data is passed from the vertex shader to the geometry shader they is no interpolation, so no interpolation qualifier is needed, and if one exists it'll just be ignored. When outputting a tri from the geometry shader you could use an interpolation qualifier, but in this case there's no point since you can just set the same normal for all three vertices. That normal could be the first vertex's normal, which would emulate nointerpolation, or the average of the three vertices which is nice and cheap, or the actual surface's normal which is a little more expensive to calculate but there's some perf savings from no longer needing to have normals on the model itself.

    Now to answer your last question directly, though it's answered in the wall of text above, #pragma target 3.5 should allow for option 3 on iOS, but it might not just because of Unity not implementing it, but option 1 is guaranteed to work on everything.
     
    ModLunar and bitinn like this.
  26. bitinn

    bitinn

    Joined:
    Aug 20, 2016
    Posts:
    961
  27. PrakNep

    PrakNep

    Joined:
    Oct 24, 2016
    Posts:
    17
    Hmmm... I've spent much time trying to do stuffs related to this flat shading, and just stumbled upon this forum post =]
    My tree model got from 331 verts to 925 verts when I made it flat through import settings @_@ ... And its a mobile game sooooo...
    Is there any way I could achieve flat shading on OpenGL ES 2 devices through a shader?
    I dont need any shadows or any lights. Is it impossible to achieve a faceted look on OpenGL ES 2.0 devices without bumping up the verts number? *me sobbing* I could have 3 trees instead of 1 if I could do the flat shading through a shader instead of making the model faceted :p
     
  28. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Nope!


    Well, maybe.

    It's possible that some GLES 2.0 devices that have support for GL_OES_standard_derivatives could use the derivatives technique to calculate the normal, but this isn't universally supported. Unity used to have a way to explicitly request OpenGL extensions in shaders, but I don't know if it works anymore, or if it's needed.

    You can try this shader and see if it runs at all:
    https://forum.unity.com/threads/flat-lighting-without-separate-smoothing-groups.280183/#post-3696988
     
    Last edited: Dec 10, 2018
  29. PrakNep

    PrakNep

    Joined:
    Oct 24, 2016
    Posts:
    17
    Thanks a lot for your reply =]
    My test results are... Works perfectly on Open GL ES 3 devices, but not on ES 2 ones ^^' I believe my device doesnt have support for GL_OES_standard_derivatives, and there must be other devices like mine too and i would lose those devices :/ ... Pics attached =] The good one is from my ES 3 device, the bad one from my ES 2 ^ Screenshot_20181211-022552.png Screenshot_2018-12-11-02-24-02.png
     
  30. PrakNep

    PrakNep

    Joined:
    Oct 24, 2016
    Posts:
    17
  31. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I'm familiar with that article.

    As discussed above, the problem is OpenGL ES 2.0 doesn't support any of the features needed to do flat shading purely in a shader. The only universal solution is the mesh based one.
     
  32. PrakNep

    PrakNep

    Joined:
    Oct 24, 2016
    Posts:
    17
    hmmm okku ^^ ill see
    thanks =]
     
  33. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    I wonder if you could bake out a custom normal map that flattens the normals. That may not be the most performant option, but it should work on most platforms and might be a win for high vertex count objects where the extra split verts would require more memory than the normal maps.
    The edges might not look perfect due to filtering.
    It would be free if you are using normal maps anyway.
     
  34. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Indeed, using normal maps to get something like flat shading is possible, but the edges between faces will always be slightly rounded unless you're using a very high resolution normal map, or are insetting the UVs on each face, at which point you're creating seams and thus unique vertices anyway. You'll also never quite get perfectly flat faces, they'll always have a little bit of lumpiness to them from the lack of accuracy in 8 bpc normal maps.
     
  35. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Just a quick Update on this Thread since i always seem to bump into it again.

    A lot of Android devices support GL3.0 and above now
    https://developer.android.com/about/dashboards/index.html#OpenGL


    So using the "nointerpolation" keyword seems to be kind of ok now.

    I do some Terrainchunk generation for mobile and used vertex splitting for flat shading. Each triangle is using 3 separate vertices in this approach. Per chunk i got:
    64x64 quads = 8192 triangles = 24576 vertices​
    This is my main bottleneck. I also can't do LOD-switching within these view distances

    No, with nointerpolation i can use triangle strips. Strips, because need at least 1 vertex per triangle to store the modified normal. This yields about 8k of vertices per chunk, a threefold reduction.

    upload_2019-11-29_22-39-51.png

    Code (CSharp):
    1. private void RecalculateStripNormals(){
    2.  
    3.         // provoking vertices that need their normal set:
    4.         //        ▼__▼__▼__▼__
    5.         //        | /| /| /| /|
    6.         //        |/_|/_|/_|/_|
    7.         //           ▲  ▲  ▲  ▲
    8.         //
    9.         //        ▼__▼__▼__▼__
    10.         //        | /| /|\ | /|
    11.         //        |/_|/_|_\|/_|
    12.         //           ▲  ▲  ▲  ▲
    13.          
    14.         for (int vi = 0; vi < stripTriangles.Length; vi+=3 ) {
    15.             stripNormals[stripTriangles[vi+0]]    = Normal( ref stripVertices[triangles[vi+0]], ref stripVertices[triangles[vi+1]], ref stripVertices[triangles[vi+2]]);
    16.         }    
    17.         mesh.normals = stripNormals;
    18.     }

    The next step would be the the flat keyword in geometry shaders supported in gl 3.2 upwards, but i did not consider it yet. https://www.khronos.org/registry/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.pdf
     
  36. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Once you get to OpenGLES 3.0 you can use derivatives, which works on any arbitrary mesh with no preprocessing and is way cheaper than geometry shaders.
     
  37. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    Exactly the kind of response i was hoping to get!

    So i can get away with a normal shared mesh AND save even more performance? Lets look this up... ok, looks like a longer tinker session for me. The ddx,ddy stuff is done in the fragment shader. I also need to combine this stuff with my vertex based radial fog distance, otherwise i get this rotating fog wall again...

    Thanks for the info Ben!
     
  38. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    It seems like many devices support nointerpolation. It has been a big savings for us
     
  39. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    I came back from some testing. Once i use ddx & ddy (=derivatives) in the fragment shader, my performance still takes too much of a hit (60fps->40fps). Even if i do nothing else there and just use the normal as color:
    upload_2019-12-1_14-15-46.png

    So currently the vertex strip with nointerpolation seems to be the fastest way for mobile flat shading. But i am by no means a shader-guy so i am open to any suggestions in this matter.



    But with interpolation i got another problem now. Somehow the provoking vertex seems to switch on Android compared to the Editor, as if some mesh optimization is done on the smartphone that changes the vertex order in triangles[].
    upload_2019-12-1_14-26-51.png
     
  40. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Ouch. Mobile GPU performance is always a bit of a crapshoot, but that is way worse than I would have expected. :(

    There’s a bunch of build time mesh optimization settings that might be causing issues. Try looking for those and disabling as many as you can find.
     
  41. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    I produce the meshes at runtime. Maybe this is a shader issue, can i somehow get the editor to show shaders as they would look on an opengles3 target? Editor looks fine but as soon as i put this thing on smartphone(s) the triangles use the wrong normals.

    Android must perform something on it or is unable to use no interpolation because i see the unused zero-normals that i generate in the mesh-strips.

    Same mesh in:
    • Inspector
    • with some legacy specular shader
    • with the nointerpolation shader (has correct lighting)
    upload_2019-12-1_23-1-22.png


    This is my shader btw.
    Code (CSharp):
    1. Shader "InfinityTerrain/VertexLitEDITOPTIMIZED" {
    2. Properties {
    3. _MainTex ("Base (RGB)", 2D) = "white" { }
    4. }
    5. SubShader {
    6. LOD 80
    7. Tags { "RenderType"="Opaque" }
    8. Pass {
    9.   Tags { "LIGHTMODE"="Vertex" "RenderType"="Opaque" }
    10. CGPROGRAM
    11. #pragma vertex vert
    12. #pragma fragment frag
    13. //#pragma target 2.0
    14.  
    15. #pragma target 3.0
    16.  
    17.  
    18. #include "UnityCG.cginc"
    19. #pragma multi_compile_fog
    20. #define USING_FOG (defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2))
    21.  
    22. // ES2.0/WebGL/3DS can not do loops with non-constant-expression iteration counts :(
    23. #if defined(SHADER_API_GLES)
    24.   #define LIGHT_LOOP_LIMIT 8
    25. #elif defined(SHADER_API_N3DS)
    26.   #define LIGHT_LOOP_LIMIT 4
    27. #else
    28.   #define LIGHT_LOOP_LIMIT unity_VertexLightParams.x
    29. #endif
    30.  
    31. // Some ES3 drivers (e.g. older Adreno) have problems with the light loop
    32. #if defined(SHADER_API_GLES3) && !defined(SHADER_API_DESKTOP) && (defined(SPOT) || defined(POINT))
    33.   #define LIGHT_LOOP_ATTRIBUTE UNITY_UNROLL
    34. #else
    35.   #define LIGHT_LOOP_ATTRIBUTE
    36. #endif
    37. #define ENABLE_SPECULAR (!defined(SHADER_API_N3DS))
    38.  
    39. // Compile specialized variants for when positional (point/spot) and spot lights are present
    40. #pragma multi_compile __ POINT SPOT
    41.  
    42. // Compute illumination from one light, given attenuation
    43. //fixed3 computeLighting (int idx, fixed3 dirToLight, fixed3 eyeNormal, fixed3 viewDir, fixed4 diffuseColor, fixed shininess, fixed atten, inout fixed3 specColor) {
    44. fixed3 computeLighting(int idx, fixed3 dirToLight, fixed3 eyeNormal, fixed3 viewDir, fixed4 diffuseColor,                    fixed atten ) {
    45.   fixed NdotL = max(dot(eyeNormal, dirToLight), 0.0);
    46.   // diffuse
    47.   fixed3 color = NdotL * diffuseColor.rgb * unity_LightColor[idx].rgb;
    48.   return color * atten;
    49. }
    50.  
    51. // Compute attenuation & illumination from one light
    52. //fixed3 computeOneLight(int idx, float3 eyePosition, fixed3 eyeNormal, fixed3 viewDir, fixed4 diffuseColor, fixed shininess, inout fixed3 specColor) {
    53. fixed3 computeOneLight(int idx, float3 eyePosition, fixed3 eyeNormal, fixed3 viewDir, fixed4 diffuseColor) {
    54.   float3 dirToLight = unity_LightPosition[idx].xyz;
    55.   fixed att = 1.0;
    56.   #if defined(POINT) || defined(SPOT)
    57.     dirToLight -= eyePosition * unity_LightPosition[idx].w;
    58.     // distance attenuation
    59.     float distSqr = dot(dirToLight, dirToLight);
    60.     att /= (1.0 + unity_LightAtten[idx].z * distSqr);
    61.     if (unity_LightPosition[idx].w != 0 && distSqr > unity_LightAtten[idx].w) att = 0.0; // set to 0 if outside of range
    62.     distSqr = max(distSqr, 0.000001); // don't produce NaNs if some vertex position overlaps with the light
    63.     dirToLight *= rsqrt(distSqr);
    64.     #if defined(SPOT)
    65.       // spot angle attenuation
    66.       fixed rho = max(dot(dirToLight, unity_SpotDirection[idx].xyz), 0.0);
    67.       fixed spotAtt = (rho - unity_LightAtten[idx].x) * unity_LightAtten[idx].y;
    68.       att *= saturate(spotAtt);
    69.     #endif
    70.   #endif
    71.   att *= 0.5; // passed in light colors are 2x brighter than what used to be in FFP
    72. //return min (computeLighting (idx, dirToLight, eyeNormal, viewDir, diffuseColor, shininess, att, specColor), 1.0);
    73.   return min(computeLighting(idx, dirToLight, eyeNormal, viewDir, diffuseColor,                 att           ), 1.0);
    74. }
    75.  
    76. // uniforms
    77. int4 unity_VertexLightParams; // x: light count, y: zero, z: one (y/z needed by d3d9 vs loop instruction)
    78. float4 _MainTex_ST;
    79.  
    80. // vertex shader input data
    81. struct appdata {
    82.   float3 pos : POSITION;
    83.   float3 normal : NORMAL;
    84.   float3 uv0 : TEXCOORD0;
    85.   UNITY_VERTEX_INPUT_INSTANCE_ID
    86. };
    87.  
    88. // vertex-to-fragment interpolators
    89. struct v2f {
    90.     nointerpolation fixed4 color : COLOR0;
    91.   float2 uv0 : TEXCOORD0;
    92.   #if USING_FOG
    93.     fixed fog : TEXCOORD1;
    94.   #endif
    95.   float4 pos : SV_POSITION;
    96.   UNITY_VERTEX_OUTPUT_STEREO
    97. };
    98.  
    99. // vertex shader
    100. v2f vert (appdata IN) {
    101.   v2f o;
    102.   UNITY_SETUP_INSTANCE_ID(IN);
    103.   UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    104.   fixed4 color = fixed4(0,0,0,1.1);
    105.   float3 eyePos = UnityObjectToViewPos(IN.pos);//float3 eyePos = mul (UNITY_MATRIX_MV, float4(IN.pos,1)).xyz;
    106.   fixed3 eyeNormal = normalize (mul ((float3x3)UNITY_MATRIX_IT_MV, IN.normal).xyz);
    107.   fixed3 viewDir = 0.0;
    108.   // lighting
    109.   fixed3 lcolor = fixed4(0,0,0,1).rgb + fixed4(1,1,1,1).rgb * glstate_lightmodel_ambient.rgb;
    110.  
    111.  
    112.   fixed3 specColor = 0.0;
    113.   fixed shininess = 0 * 128.0;
    114.   LIGHT_LOOP_ATTRIBUTE for (int il = 0; il < LIGHT_LOOP_LIMIT; ++il) {
    115.    // lcolor += computeOneLight(il, eyePos, eyeNormal, viewDir, fixed4(1,1,1,1), shininess, specColor);
    116.     lcolor += computeOneLight(il, eyePos, eyeNormal, viewDir, fixed4(1, 1, 1, 1)                     );
    117.   }
    118.  
    119.   color.rgb = lcolor.rgb;
    120.   color.a = fixed4(1,1,1,1).a;
    121.   o.color = saturate(color);
    122.   // compute texture coordinates
    123.   o.uv0 = IN.uv0.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    124.   // fog
    125.   #if USING_FOG
    126.     float fogCoord = length(eyePos.xyz); // radial fog distance
    127.     UNITY_CALC_FOG_FACTOR_RAW(fogCoord);
    128.     o.fog = saturate(unityFogFactor);
    129.   #endif
    130.   // transform position
    131.   o.pos = UnityObjectToClipPos(IN.pos);
    132.   return o;
    133. }
    134.  
    135. // textures
    136. sampler2D _MainTex;
    137.  
    138. // fragment shader
    139. fixed4 frag (v2f IN) : SV_Target {
    140.   fixed4 col;
    141.   fixed4 tex, tmp0, tmp1, tmp2;
    142.   // SetTexture #0
    143.   tex = tex2D (_MainTex, IN.uv0.xy);
    144.  
    145.   col.rgb = tex * IN.color;
    146.   col *= 2;
    147.   col.a = fixed4(0,0,0,0).a;
    148.   // fog
    149.   #if USING_FOG
    150.     col.rgb = lerp (unity_FogColor.rgb, col.rgb, IN.fog);
    151.   #endif
    152.   return col;
    153. }
    154.  
    155. // texenvs
    156. //! TexEnv0: 02010103 01060004 [_MainTex]
    157. ENDCG
    158. }
    159. }
    160. }
     
    Last edited: Dec 1, 2019
  42. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    I’d be more interested in how you’re generating the mesh. The shader for no interpolation stuff is relatively uninteresting, and what vertex is the ”primary” is totally up to the GPU (and should be defined by the API) and not something you can do anything about in the shader.

    I’d assume you’re right that it’s something with how you’re generating the mesh and the platform differences there, but honestly I have no idea.
     
  43. Marrt

    Marrt

    Joined:
    Feb 7, 2012
    Posts:
    613
    You were right! This was it. I thought i already tested this by cycling through the triangle rotations. BUT i still wrote the normal into the first vertex of any given triangle everytime. Now i added a button to cycle into which vertex the normal gets written and:

    Android seems to use the last vertex as provoking vertex

    But can we be sure that this is the same for all devices?
    The docs say you can choose the index, but where would i put that?
    "void glProvokingVertex(GLenum provokeMode);"
    https://www.khronos.org/opengl/wiki/Primitive#Provoking_vertex





    Anyway, thanks for your continued help in these shader topics, Ben.
    To conclude what we found:

    Flat shading 2019:
    for Mobile (Opengles 3.0 or greater):
    • Option 1: VertexSplitting: if you use vertex shaders and manage to keep Verts below 200k, you can hold 60fps on mid-end devices (e.g. my Samsung A3 2017)
    • Option 2: nointerpolation: Fastest because you use one third of the vertices compared to above. But you need one dedicated vertex in each triangle that stores the normal for that triangle only. So it requires mesh tinkering
    Code (CSharp):
    1. public    static int stripProvokingVertex = 2;//vertex 0,1 or 2 from triangle
    2.      public void RecalculateStripNormals(){
    3.  
    4.          // provoking vertices that need their normal set:
    5.          //        ▼__▼__▼__▼__
    6.          //        | /| /| /| /|
    7.          //        |/_|/_|/_|/_|
    8.          //           ▲  ▲  ▲  ▲
    9.          //
    10.          //        ▼__▼__▼__▼__
    11.          //        | /| /|\ | /|
    12.          //        |/_|/_|_\|/_|
    13.          //           ▲  ▲  ▲  ▲
    14.            
    15.          for (int vi = 0; vi < stripTriangles.Length; vi+=3 ) {
    16.              stripNormals[stripTriangles[vi +stripProvokingVertex]]    = Normal( ref stripVertices[triangles[vi+0]], ref stripVertices[triangles[vi+1]], ref stripVertices[triangles[vi+2]]);
    17.          }
    18.          mesh.normals = stripNormals;
    19.      }

    for Desktop:
    • Use ddx/ddy, works with any mesh and you are most likely doing stuff in the fragment shader already
    fixed3 posddx = ddx(IN.posWorld.xyz);
    fixed3 posddy = ddy(IN.posWorld.xyz);
    fixed3 derivedNormal = cross(normalize(posddx), normalize(posddy));
     
    Last edited: Dec 2, 2019
    DavidSWu and kideternal like this.
  44. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    We use nointerpolation and set the Desktop Graphics API to GLES 3.2 that way we vertex 2 is always the provoking vertex.
    Unfortunately, we switched to metal for IOS, so now we modify our "Flat" preprocessor to use either DX style (vertex #1 is provoking) or GL style (vertex #2 is provoking) depending on the platform.
    It is a bit of a pain, but the performance savings are great. Flat shading is even cheaper than smooth shading in many cases because we get fewer split verts.

    IOS and Vulkan use DX style. You can actually change the provoking vertex in Vulkan, GLES, and Metal, but I was unable to get that to work in Unity. It may be an extension that is not always supported :(
    A problem is that we would need 2 versions of meshes if we wanted to support Vulkan and GLES on Android. Fortunately, Vulkan appears to be too buggy to use so we don't have to worry about that yet.
    Android AAB's (or whatever they are called) should be the solution to that problem.
     
    kideternal likes this.
  45. CoCoNutti

    CoCoNutti

    Joined:
    Nov 30, 2009
    Posts:
    513
    This is what I do:
     
  46. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    Unfortunately, that method will increase your vertex count significantly.
     
  47. DavidSWu

    DavidSWu

    Joined:
    Jun 20, 2016
    Posts:
    183
    set the Desktop Graphics API to GLES 3.2 (i.e. player settings, Windows)
     
    Marrt likes this.
  48. kideternal

    kideternal

    Joined:
    Nov 20, 2014
    Posts:
    82
    I'm looking to do something similar on my project. How does your preprocessor work? Is it something you can share? I'm considering writing a Blender plugin, but it feels like reinventing the wheel.

    Blender 2.82 has a nice "Weighted Normal" modifier, but that can introduce undesirable artifacts. I have to modify thousands of models, so automation will be necessary. Ideally a preprocessor would look at Edge Splits and merge their vertices while preserving their Face normals. Preserving Quads during model import introduces UV issues, but it would be nice if a preprocessor assigned normals based on Quads...
     
    Last edited: Apr 7, 2020