Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

GPU Instanced Deferred Lights

Discussion in 'Shaders' started by Manufacture43, Sep 17, 2019.

  1. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Hello,

    In our game, we put lights on our enemy and friendly shots so that they highlight the environments and the other players. A very nice effect that obviously has the drawback of adding dozens of small lights to the scene. Using the deferred shader, that shouldn't be a problem right? It's the whole point of the thing after all.

    As it turns out, Unity doesn't use GPU instancing to render point lights in the deferred renderer. This is problematic on the Switch as those dozens of draw calls have a very high performance impact on the render thread. To solve the issue, I wrote a custom light renderer that uses instancing so that there is just one draw call for all the lights.

    This works great, except for one thing: it lights all the pixels instead of just the necessary pixels as the original light shader of unity does. This has obviously a huge performance impact. My problem is that I have no idea which values I should use for the Stencil test. I tried the ones I found in the regular deferred shader:
    Code (CSharp):
    1.             Stencil
    2.             {
    3.                 Ref [_StencilNonBackground]
    4.                 ReadMask [_StencilNonBackground]
    5.                 CompBack Equal
    6.                 CompFront Equal
    7.             }
    They seem to work in the editor but not on the Switch. Any idea how to get the right stencil values ?

    Editor: (stencil ref is 128, looks correct)
    editor.png

    Switch: (stencil ref is 0, probably wrong, takes to 2ms to render so very probably wrong)
    switch.png

    Shader:
    Code (CSharp):
    1. Shader "CustomLights/InstancedLight"
    2. {
    3.     SubShader
    4.     {
    5.         Tags { "Queue" = "Transparent-1" }
    6.  
    7.         Pass
    8.         {
    9.             Fog { Mode Off }
    10.             ZWrite Off
    11.             ZTest LEqual
    12.             Blend One One
    13.             Cull Back
    14.  
    15.             Stencil
    16.             {
    17.                 Ref [_StencilNonBackground]
    18.                 ReadMask [_StencilNonBackground]
    19.                 CompBack Equal
    20.                 CompFront Equal
    21.             }
    22.  
    23.             CGPROGRAM
    24.             #pragma target 3.0
    25.             #pragma vertex vert_deferred_instanced
    26.             #pragma fragment frag
    27.             #pragma multi_compile_lightpass
    28.             #pragma multi_compile_instancing UNITY_HDR_ON
    29.             #pragma instancing_options nolodfade nolightprobe nolightmap
    30.  
    31.             #pragma exclude_renderers nomrt
    32.  
    33.             #include "UnityCG.cginc"
    34.             #include "UnityDeferredLibrary.cginc"
    35.             #include "UnityPBSLighting.cginc"
    36.             #include "UnityStandardUtils.cginc"
    37.             #include "UnityGBuffer.cginc"
    38.             #include "UnityStandardBRDF.cginc"
    39.  
    40.             sampler2D _CameraGBufferTexture0;
    41.             sampler2D _CameraGBufferTexture1;
    42.             sampler2D _CameraGBufferTexture2;
    43.  
    44.             UNITY_INSTANCING_BUFFER_START(Props)
    45.                 UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    46.             UNITY_INSTANCING_BUFFER_END(Props)
    47.  
    48.             struct appdata
    49.             {
    50.                 float4 vertex : POSITION;
    51.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    52.             };
    53.  
    54.             struct unity_v2f_deferred_instanced {
    55.                 float4 pos : SV_POSITION;
    56.                 float4 uv : TEXCOORD0;
    57.                 float3 ray : TEXCOORD1;
    58.                 half4 color : COLOR;
    59.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    60.             };
    61.  
    62.             unity_v2f_deferred_instanced vert_deferred_instanced(appdata v)
    63.             {
    64.                 unity_v2f_deferred_instanced o;
    65.  
    66.                 UNITY_SETUP_INSTANCE_ID(v);
    67.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    68.  
    69.                 o.pos = UnityObjectToClipPos(v.vertex);
    70.                 o.uv = ComputeScreenPos(o.pos);
    71.                 o.ray = UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1);
    72.                 o.color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    73.  
    74.                 return o;
    75.             }
    76.  
    77.             // Common lighting data calculation (direction, attenuation, ...)
    78.             void DeferredCalculateLightParams(
    79.                 unity_v2f_deferred_instanced i,
    80.                 out float3 outWorldPos,
    81.                 out float2 outUV,
    82.                 out half3 outLightDir,
    83.                 out float outAtten,
    84.                 out float outFadeDist)
    85.             {
    86.                 i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
    87.                 float2 uv = i.uv.xy / i.uv.w;
    88.  
    89.                 // read depth and reconstruct world position
    90.                 float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
    91.                 depth = Linear01Depth(depth);
    92.                 float4 vpos = float4(i.ray * depth, 1);
    93.                 float3 wpos = mul(unity_CameraToWorld, vpos).xyz;
    94.  
    95.                 float fadeDist = UnityComputeShadowFadeDistance(wpos, vpos.z);
    96.  
    97.                 float3 lightPos = float3(unity_ObjectToWorld[0][3], unity_ObjectToWorld[1][3], unity_ObjectToWorld[2][3]);
    98.                 float3 tolight = wpos - lightPos;
    99.                 half3 lightDir = -normalize(tolight);
    100.  
    101.                 float att = dot(tolight, tolight) * i.color.w; // color.w is inversed squared range. ie 1/(r*r)
    102.                 float atten = tex2D(_LightTextureB0, att.rr).r;
    103.  
    104.                 atten *= UnityDeferredComputeShadow(tolight, fadeDist, uv);
    105.  
    106.                 outWorldPos = wpos;
    107.                 outUV = uv;
    108.                 outLightDir = lightDir;
    109.                 outAtten = atten;
    110.                 outFadeDist = fadeDist;
    111.             }
    112.  
    113.             half4 frag(unity_v2f_deferred_instanced i) : SV_Target
    114.             {
    115.                 UNITY_SETUP_INSTANCE_ID(i);
    116.  
    117.                 float3 wpos;
    118.                 float2 uv;
    119.                 float atten, fadeDist;
    120.  
    121.                 UnityLight light;
    122.                 UNITY_INITIALIZE_OUTPUT(UnityLight, light);
    123.  
    124.                 DeferredCalculateLightParams(i, wpos, uv, light.dir, atten, fadeDist);
    125.  
    126.                 light.color = i.color.rgb * atten;
    127.  
    128.                 // unpack Gbuffer
    129.                 half4 gbuffer0 = tex2D(_CameraGBufferTexture0, uv);
    130.                 half4 gbuffer1 = tex2D(_CameraGBufferTexture1, uv);
    131.                 half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);
    132.                 UnityStandardData data = UnityStandardDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2);
    133.  
    134.                 float3 eyeVec = normalize(wpos - _WorldSpaceCameraPos);
    135.                 half oneMinusReflectivity = 1 - SpecularStrength(data.specularColor.rgb);
    136.  
    137.                 UnityIndirect ind;
    138.                 UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind);
    139.                 ind.diffuse = 0;
    140.                 ind.specular = 0;
    141.  
    142.                 return UNITY_BRDF_PBS(data.diffuseColor, data.specularColor, oneMinusReflectivity,
    143.                     data.smoothness, data.normalWorld, -eyeVec, light, ind);
    144.             }
    145.  
    146.             ENDCG
    147.         }
    148.     }
    149. }
    150.  
     
    Last edited: Sep 17, 2019
  2. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    What you have isn't working correctly. Not even on desktop. There's a reason why the lights aren't instanced. Light's volume geometry has to be rendered twice. With different shader each time. The idea is to only shade pixels within light's volume.

    First you render front faces and update stencil buffer for every visible pixel. Then you render back faces and you use stencil from previous draw call and zbuffer to isolate pixels within light's volume. And then finally you shade only those remaining pixels. There are multiple variants of this technique. Here's one of them explained in more detail. I don't know which technique exactly is unity using but the basic idea is the same for all of them. You have to render the mesh twice. You can't use instancing. Your solution can work visually but it will always be inefficient. You're shading pixels outside of light's volume.

    This technique is at least a decade old and nobody is using it anymore. There are lot more efficient methods but they are also more difficult to write. Look for "Tile-based deferred shading" or "Clustered deferred shading". Both were specifically designed to handle large number of lights.
     
  3. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Your technique is interesting. I don't see why it couldn't work with instancing though, we could totally do two instanced draw calls.

    However, this is not beneficial here if I understand correctly. Unity already marks pixels that should be lit in the stencil buffer. Since most of our scenes are lightmapped, that's not that many pixels. So when we render the sphere of our instanced lights, all we need is to shade those marked pixels, and not all the pixels of the sphere.

    This is all we need to achieve good performance here.

    + We don't handle all the weird cases of camera in volume, etc, just because we don't have them :)
     
  4. Michal_

    Michal_

    Joined:
    Jan 14, 2015
    Posts:
    365
    That would work only if there are no collisions of the volumes in screen space.

    Yes, and they are doing it by rendering the volume twice. See the picture. There is only one light in the scene but two draw calls of the same geometry.

    Screenshot_147.png
     
  5. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Funny, that stencil write shader already has instancing support.
     
  6. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Might not work in every case, but I got away with this:
    Code (CSharp):
    1. Shader "CustomLights/InstancedLight"
    2. {
    3.     SubShader
    4.     {
    5.         Tags { "Queue" = "Transparent-1" }
    6.  
    7.         // Stencil is marked with bit 4 (value 16) just like Unity does
    8.         // https://docs.unity3d.com/Manual/SL-Stencil.html Deferred rendering path
    9.  
    10.         Pass // mark stencil bit 4 for geometry inside volume
    11.         {
    12.             Cull Front
    13.             ZTest LEqual
    14.             ZWrite Off
    15.             ColorMask 0
    16.             Stencil
    17.             {
    18.                 Ref 16
    19.                 WriteMask 16
    20.                 ZFail Replace
    21.             }
    22.  
    23.             CGPROGRAM
    24.             #pragma vertex vert
    25.             #pragma fragment frag
    26.             #pragma target 2.0
    27.             #pragma multi_compile_instancing
    28.             #pragma instancing_options nolodfade nolightprobe nolightmap
    29.  
    30.             #include "UnityCG.cginc"
    31.  
    32.             struct a2v {
    33.                 float4 pos : POSITION;
    34.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    35.             };
    36.  
    37.             struct v2f {
    38.                 float4 vertex : SV_POSITION;
    39.                 UNITY_VERTEX_OUTPUT_STEREO
    40.             };
    41.  
    42.             v2f vert(a2v v)
    43.             {
    44.                 v2f o;
    45.                 UNITY_SETUP_INSTANCE_ID(v);
    46.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    47.                 o.vertex = UnityObjectToClipPos(v.pos);
    48.                 return o;
    49.             }
    50.  
    51.             fixed4 frag() : SV_Target { return 0; }
    52.             ENDCG
    53.         }
    54.  
    55.         Pass // do lighting on marked pixels in volume that are not background or lightmapped
    56.         {
    57.             Fog { Mode Off }
    58.             ZTest Less
    59.             ZWrite Off
    60.             Blend One One
    61.             Cull Back
    62.             Stencil
    63.             {
    64.                 Ref 16
    65.                 ReadMask 16
    66.                 Comp Equal
    67.             }
    68.  
    69.             CGPROGRAM
    70.             #pragma target 3.0
    71.             #pragma vertex vert_deferred_instanced
    72.             #pragma fragment frag
    73.             #pragma multi_compile_lightpass
    74.             #pragma multi_compile_instancing UNITY_HDR_ON
    75.             #pragma instancing_options nolodfade nolightprobe nolightmap
    76.  
    77.             #pragma exclude_renderers nomrt
    78.  
    79.             #include "UnityCG.cginc"
    80.             #include "UnityDeferredLibrary.cginc"
    81.             #include "UnityPBSLighting.cginc"
    82.             #include "UnityStandardUtils.cginc"
    83.             #include "UnityGBuffer.cginc"
    84.             #include "UnityStandardBRDF.cginc"
    85.  
    86.             sampler2D _CameraGBufferTexture0;
    87.             sampler2D _CameraGBufferTexture1;
    88.             sampler2D _CameraGBufferTexture2;
    89.  
    90.             UNITY_INSTANCING_BUFFER_START(Props)
    91.                 UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color)
    92.             UNITY_INSTANCING_BUFFER_END(Props)
    93.  
    94.             struct appdata
    95.             {
    96.                 float4 vertex : POSITION;
    97.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    98.             };
    99.  
    100.             struct unity_v2f_deferred_instanced {
    101.                 float4 pos : SV_POSITION;
    102.                 float4 uv : TEXCOORD0;
    103.                 float3 ray : TEXCOORD1;
    104.                 half4 color : COLOR;
    105.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    106.             };
    107.  
    108.             unity_v2f_deferred_instanced vert_deferred_instanced(appdata v)
    109.             {
    110.                 unity_v2f_deferred_instanced o;
    111.  
    112.                 UNITY_SETUP_INSTANCE_ID(v);
    113.                 UNITY_TRANSFER_INSTANCE_ID(v, o);
    114.  
    115.                 o.pos = UnityObjectToClipPos(v.vertex);
    116.                 o.uv = ComputeScreenPos(o.pos);
    117.                 o.ray = UnityObjectToViewPos(v.vertex) * float3(-1, -1, 1);
    118.                 o.color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
    119.  
    120.                 return o;
    121.             }
    122.  
    123.             // Common lighting data calculation (direction, attenuation, ...)
    124.             void DeferredCalculateLightParams(
    125.                 unity_v2f_deferred_instanced i,
    126.                 out float3 outWorldPos,
    127.                 out float2 outUV,
    128.                 out half3 outLightDir,
    129.                 out float outAtten,
    130.                 out float outFadeDist)
    131.             {
    132.                 i.ray = i.ray * (_ProjectionParams.z / i.ray.z);
    133.                 float2 uv = i.uv.xy / i.uv.w;
    134.  
    135.                 // read depth and reconstruct world position
    136.                 float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, uv);
    137.                 depth = Linear01Depth(depth);
    138.                 float4 vpos = float4(i.ray * depth, 1);
    139.                 float3 wpos = mul(unity_CameraToWorld, vpos).xyz;
    140.  
    141.                 float fadeDist = UnityComputeShadowFadeDistance(wpos, vpos.z);
    142.  
    143.                 float3 lightPos = float3(unity_ObjectToWorld[0][3], unity_ObjectToWorld[1][3], unity_ObjectToWorld[2][3]);
    144.                 float3 tolight = wpos - lightPos;
    145.                 half3 lightDir = -normalize(tolight);
    146.  
    147.                 float att = dot(tolight, tolight) * i.color.w; // color.w is inversed squared range. ie 1/(r*r)
    148.                 float atten = tex2D(_LightTextureB0, att.rr).r;
    149.  
    150.                 atten *= UnityDeferredComputeShadow(tolight, fadeDist, uv);
    151.  
    152.                 outWorldPos = wpos;
    153.                 outUV = uv;
    154.                 outLightDir = lightDir;
    155.                 outAtten = atten;
    156.                 outFadeDist = fadeDist;
    157.             }
    158.  
    159.             half4 frag(unity_v2f_deferred_instanced i) : SV_Target
    160.             {
    161.                 UNITY_SETUP_INSTANCE_ID(i);
    162.  
    163.                 float3 wpos;
    164.                 float2 uv;
    165.                 float atten, fadeDist;
    166.  
    167.                 UnityLight light;
    168.                 UNITY_INITIALIZE_OUTPUT(UnityLight, light);
    169.  
    170.                 DeferredCalculateLightParams(i, wpos, uv, light.dir, atten, fadeDist);
    171.  
    172.                 light.color = i.color.rgb * atten;
    173.  
    174.                 // unpack Gbuffer
    175.                 half4 gbuffer0 = tex2D(_CameraGBufferTexture0, uv);
    176.                 half4 gbuffer1 = tex2D(_CameraGBufferTexture1, uv);
    177.                 half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);
    178.                 UnityStandardData data = UnityStandardDataFromGbuffer(gbuffer0, gbuffer1, gbuffer2);
    179.  
    180.                 float3 eyeVec = normalize(wpos - _WorldSpaceCameraPos);
    181.                 half oneMinusReflectivity = 1 - SpecularStrength(data.specularColor.rgb);
    182.  
    183.                 UnityIndirect ind;
    184.                 UNITY_INITIALIZE_OUTPUT(UnityIndirect, ind);
    185.                 ind.diffuse = 0;
    186.                 ind.specular = 0;
    187.  
    188.                 return UNITY_BRDF_PBS(data.diffuseColor, data.specularColor, oneMinusReflectivity,
    189.                     data.smoothness, data.normalWorld, -eyeVec, light, ind);
    190.             }
    191.  
    192.             ENDCG
    193.         }
    194.  
    195.         Pass // set stencil bit 4 back to zero
    196.         {
    197.             Cull Front
    198.             ZTest LEqual
    199.             ZWrite Off
    200.             ColorMask 0
    201.             Stencil
    202.             {
    203.                 WriteMask 16
    204.                 Pass Zero
    205.             }
    206.  
    207.             CGPROGRAM
    208.             #pragma vertex vert
    209.             #pragma fragment frag
    210.             #pragma target 2.0
    211.             #pragma multi_compile_instancing
    212.             #pragma instancing_options nolodfade nolightprobe nolightmap
    213.  
    214.             #include "UnityCG.cginc"
    215.  
    216.             struct a2v {
    217.                 float4 pos : POSITION;
    218.                 UNITY_VERTEX_INPUT_INSTANCE_ID
    219.             };
    220.  
    221.             struct v2f {
    222.                 float4 vertex : SV_POSITION;
    223.                 UNITY_VERTEX_OUTPUT_STEREO
    224.             };
    225.  
    226.             v2f vert(a2v v)
    227.             {
    228.                 v2f o;
    229.                 UNITY_SETUP_INSTANCE_ID(v);
    230.                 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
    231.                 o.vertex = UnityObjectToClipPos(v.pos);
    232.                 return o;
    233.             }
    234.  
    235.             fixed4 frag() : SV_Target { return 0; }
    236.             ENDCG
    237.         }
    238.     }
    239. }
    240.  
    The last pass is to reset the stencil buffer light bit. The regular implementation does it in the lighting pass so that subsequent lights are ok. Since it's instanced, I separated it. And I actually don't even do it because my instanced lights are after all unity lights, there's no lights after!

    It's obviously not as efficient as with separate light passes, because it lights some unnecessary pixels for some lights, but it's still a hell of a lot faster than the default implementation. At least in our use case.

    Thanks for the link explaining the stencil trick!
     
    Last edited: Sep 19, 2019
    Baggers_ and Michal_ like this.
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,321
    The idea with the stencil is to confine each light to only the pixels it covers. If you draw the same stencil value for all lights, then you loose a lot of that as now each light is being applied to pixels that any light covers (within the screen coverage of your light’s mesh). This was the point @Michal_ was trying to make.

    Though, to be fair that still potentially reduces the total pixels shaded considerably.
     
    Baggers_ likes this.
  8. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    And it does! In our case, we have many small lights that
    - are very close to each other => mostly the same shaded pixels for all
    - and a few scattered that don't overlap => same pixels as if rendered separately

    That won't work for everyone, obviously.

    I'm sharing so that anyone that might end up in a similar situation can benefit.
     
  9. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    In our case, we dumped the stencil, and we clip the pixels instead.
    GPU Instanciated lights!
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,321
    Do you mean you're calling clip() in the shader to skip pixels outside of the range of the light?
    I.E.:
    clip(lightRange - length(lightPos - scenePos));


    On modern GPUs this should work relatively well, though potentially slower than the stencil method.
     
  11. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Yes, and it turns hundred of lights into 1 draw call. Might be slightly longer on GPU, but probably a lot faster on CPU.
     
  12. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    The advantage of the stencil method is that it doesn't even run the pixel shader for pixels that won't be lit because the sphere of the light doesn't touch the geometry that is behind. (ie. much faster when the light doesn't light anything)
     
  13. Julian-S

    Julian-S

    Joined:
    May 31, 2013
    Posts:
    73
    @Manufacture43 @LightStriker

    Just came across this thread, and it seems up the alley of what I'm trying to do, however I'm relatively new to shaders.

    I am using deferred rendering as I have lots of moving units in the scene, each with their own point lights. Thing is, I'm also using BlendOp Max in the deferred shader, and therefore the lights don't stack in intensity much. Often the units are close together, and their lights overlap, but it doesn't actually change the lighting of the overlapping pixels.

    What I'd like to do is make Unity not bother to do another lighting pass on pixels from overlapping point lights. Perhaps I'm wrong in thinking so, but are either of your solutions along the lines of achieving that?
     
  14. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    No, not at all. The only way I'm thinking one could achieve this would be to increment the value written by the lights to the stencil buffer and have the stencil test be set to GREATER. But that would eat up a lot of bits in the stencil buffer so it would only allow for very few lights and nothing left for any other use. Doesn't seem practical.

    Or maybe have a second depth buffer just for lights? but that would probably yield weird results.
     
  15. bufothejig

    bufothejig

    Joined:
    Nov 24, 2013
    Posts:
    13
    @Manafacture43 I am having a similar problem while porting my game to Switch. Everything works fine on PC, PS4, XB1 (released 2 years ago), but my instanced point lights are messed up.

    They all have flickering black squares where the light is.

    I found your thread and took a look at your shader.

    My question is how do I use the shader?

    I just updated my project from Unity 2017 to Unity 2019 so I could do the Switch port, so I'm not familiar with the new render pipelines, is that how I need to use this?

    I've been digging through Unity documentation for awhile and I can't find a clear example of "this is how you use a custom point light shader".

    Any help would be appreciated, thanks!
     
  16. Manufacture43

    Manufacture43

    Joined:
    Apr 21, 2017
    Posts:
    140
    Sorry I just saw your message. I have a custom light component that registers itself into a general list. That list then builds a commandbuffer that just sets properties and DrawMeshInstanced twice (first pass to mark pixels, second pass to light) in the CameraEvent.AfterLighting pass.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Rendering;
    3. using System.Collections.Generic;
    4.  
    5. [RequireComponent(typeof(Camera))]
    6. public class InstancedLightRenderer : MonoBehaviour
    7. {
    8.     public int MaxLights = 256;
    9.     public Mesh UnitSphere;
    10.     public Material Material;
    11.  
    12.     public static List<InstancedLight> lights = new List<InstancedLight>(256);
    13.  
    14.     CommandBuffer commands;
    15.  
    16.     MaterialPropertyBlock properties;
    17.  
    18.     ExpandableArray<Matrix4x4> matrices;
    19.     List<Vector4> colors;
    20.  
    21.     int colorID;
    22.  
    23.     void Awake()
    24.     {
    25.         commands = new CommandBuffer();
    26.         commands.name = "Instanced Lights";
    27.  
    28.         matrices = new ExpandableArray<Matrix4x4>(0, MaxLights);
    29.         colors = new List<Vector4>(MaxLights);
    30.  
    31.         properties = new MaterialPropertyBlock();
    32.         colorID = Shader.PropertyToID("_Color");
    33.         colors.AddMany(Color.black, MaxLights); // pre fill array to maximum instances
    34.         properties.SetVectorArray(colorID, colors);
    35.     }
    36.  
    37.     void OnEnable()
    38.     {
    39.         GetComponent<Camera>().AddCommandBuffer(CameraEvent.AfterLighting, commands);
    40.     }
    41.  
    42.     void OnDisable()
    43.     {
    44.         GetComponent<Camera>().RemoveCommandBuffer(CameraEvent.AfterLighting, commands);
    45.     }
    46.  
    47.     void OnPreRender()
    48.     {
    49.         matrices.Clear();
    50.         colors.Clear();
    51.  
    52.         commands.Clear();
    53.  
    54.         int count = Mathf.Min(MaxLights, lights.Count);
    55.         for (int i = 0; i < count; i++)
    56.         {
    57.             InstancedLight light = lights[i];
    58.             float s = light.Range * 2f;
    59.             Vector3 scale = new Vector3() { x = s, y = s, z = s };
    60.             matrices.Add() = Matrix4x4.TRS(light.transform.position, Quaternion.identity, scale);
    61.             colors.Add(new Vector4() { x = light.Color.r, y = light.Color.g, z = light.Color.b, w = 1f / (light.Range * light.Range) });
    62.         }
    63.  
    64.         properties.SetVectorArray(colorID, colors);
    65.         commands.DrawMeshInstanced(UnitSphere, 0, Material, 0, matrices.Data, count, properties); // mark pixels that should be lit
    66.         commands.DrawMeshInstanced(UnitSphere, 0, Material, 1, matrices.Data, count, properties); // light
    67.         //commands.DrawMeshInstanced(UnitSphere, 0, Material, 2, matrices.Data, count, properties); // unmark pixels, not necessary since there is no lights drawn after
    68.     }
    69. }
    70.  
     
    Invertex likes this.
  17. blueivy

    blueivy

    Joined:
    Mar 4, 2013
    Posts:
    627