Advertisement
christophermcasey

Unity & Futile 2D Lighting Layer

Dec 1st, 2013
2,260
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 9.09 KB | None | 0 0
  1. using System;
  2. using UnityEngine;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5.  
  6. // YOUTUBE LINK OF THIS CODE IN-GAME:
  7. // http://www.youtube.com/watch?v=KR7JQj0JetU&hd=1
  8.  
  9. /*
  10.  * Author: Christopher M. Casey
  11.  * http://www.christophermcasey.com/
  12.  * This class provides a lighting layer that cooperates with Futile.
  13.  * The lighting mesh works by overlaying the screen with a variable-resolution
  14.  * mesh that is mapped to a flat white texture with a multiplicative shader.
  15.  * Every frame, vertex colors are updated based on the visible lights in the
  16.  * scene. This allows for fast, nice-looking 2D lighting.
  17.  */
  18.  
  19. // We don't inherit any Futile classes because it is not necessary; we want to operate
  20. // independently from Futile's batching system. This layer will be the only one using
  21. // its material and it overlays everything else, so it is easier just to add on top of
  22. // Futile rather than inherit from it.
  23.  
  24. public class HNSLightingLayer
  25. {
  26.  
  27.     // singleton (only one of these ever)
  28.     public static HNSLightingLayer instance;
  29.  
  30.     // this is the color of all unlit space -- not totally black but very close
  31.     // use a getter-only property because we can't have const Color, but this should never be set
  32.     private static Color __darknessColor = new Color(0.05f, 0.05f, 0.05f, 1f);
  33.     private static Color _darknessColor
  34.     {
  35.         get { return __darknessColor; }
  36.     }
  37.  
  38.     // This is the size of each grid square or quad within the light mesh.
  39.     // Smaller values look good but are slower, higher values look bad but are faster.
  40.     // Tweak this for performance!
  41.     private const float _lightGridRes = 16.0f;
  42.  
  43.     // This is used every update to calculate which lights are visible.
  44.     private List<HNSLight> _visibleLights = new List<HNSLight>();
  45.  
  46.     // GameObject and its components...
  47.     GameObject _gameObject;
  48.     Transform _transform;
  49.     MeshFilter _meshFilter;
  50.     MeshRenderer _meshRenderer;
  51.     Mesh _mesh;
  52.     Material _material;
  53.  
  54.     // Use this to update vertex colors every frame
  55.     Color32[] _vertColors;
  56.  
  57.     // width, height, and width * height of vertices in this mesh
  58.     int _lightGridVertW;
  59.     int _lightGridVertH;
  60.     int _lightGridVertSz;
  61.  
  62.     // Offset for ensuring that this mesh's vertices always align with the same world space coordinates.
  63.     // If we don't use this, artifacts occur as vertex coordinates change relative to world position of
  64.     // lights and shadows.
  65.     Vector2 _offVector = Vector2.zero;
  66.  
  67.     // private constructor to support singleton pattern
  68.     private HNSLightingLayer()
  69.     {
  70.         // number of quads or grid squares that will be in the mesh
  71.         int lightGridW = Mathf.CeilToInt(Futile.screen.width / _lightGridRes)+1;
  72.         int lightGridH = Mathf.CeilToInt(Futile.screen.height / _lightGridRes)+1;
  73.         int lightGridSz = lightGridW * lightGridH;
  74.  
  75.         // number of actual vertices that will be in the mesh
  76.         _lightGridVertW = lightGridW+1;
  77.         _lightGridVertH = lightGridH+1;
  78.         _lightGridVertSz = _lightGridVertW * _lightGridVertH;
  79.  
  80.         // actual mesh data
  81.         Vector3[] verts = new Vector3[_lightGridVertSz];
  82.         Vector2[] uvs = new Vector2[_lightGridVertSz];
  83.         _vertColors = new Color32[_lightGridVertSz];
  84.         int[] tris = new int[lightGridSz*2*3];
  85.  
  86.         // fill vertex data - this will make a mesh that overlaps the whole screen
  87.         for (int i=0; i<_lightGridVertSz; i++)
  88.         {
  89.             float col = i % _lightGridVertW;
  90.             float row = i / _lightGridVertW;
  91.  
  92.             verts[i] = new Vector2(
  93.                 col * _lightGridRes - Futile.screen.halfWidth,
  94.                 row * _lightGridRes - Futile.screen.halfHeight);
  95.         }
  96.  
  97.         // since we are just using a plain white texture, UVs don't matter, give it something safe.
  98.         for (int i=0; i<_lightGridVertSz; i++)
  99.         {
  100.             uvs[i] = new Vector2(0.5f, 0.5f);
  101.         }
  102.  
  103.         // these get updated every frame so they don't matter right now, give it something safe.
  104.         for (int i=0; i<_lightGridVertSz; i++)
  105.         {
  106.             _vertColors[i] = _darknessColor;
  107.         }
  108.  
  109.         // fill up triangles. clockwise winding order.
  110.         for (int i=0; i<tris.Length; i+=6)
  111.         {
  112.             int vcol = (i/6) % lightGridW;
  113.             int vrow = (i/6) / lightGridW;
  114.  
  115.             tris[i  ] = vrow * _lightGridVertW + vcol;
  116.             tris[i+1] = (vrow+1) * _lightGridVertW + vcol;
  117.             tris[i+2] = (vrow+1) * _lightGridVertW + (vcol+1);
  118.  
  119.             tris[i+3] = vrow * _lightGridVertW + vcol;
  120.             tris[i+4] = (vrow+1) * _lightGridVertW + (vcol+1);
  121.             tris[i+5] = vrow * _lightGridVertW + (vcol+1);
  122.         }
  123.  
  124.         // boilerplate GameObject setup: make a GameObject, give it a MeshFilter, MeshRenderer,
  125.         // Material, etc.
  126.  
  127.         _gameObject = new GameObject("HNSLightingLayer");
  128.         _transform = _gameObject.transform;
  129.  
  130.         _transform.parent = Futile.instance.gameObject.transform; // add _gameObject to the scene!
  131.        
  132.         _meshFilter = _gameObject.AddComponent<MeshFilter>();
  133.         _meshRenderer = _gameObject.AddComponent<MeshRenderer>();
  134.         _meshRenderer.castShadows = false;
  135.         _meshRenderer.receiveShadows = false;
  136.        
  137.         _mesh = _meshFilter.mesh;
  138.  
  139.         _material = new Material(FShader.Multiplicative.shader); // special sauce, multiplicative blending
  140.         _material.mainTexture = Futile.atlasManager.GetElementWithName("Futile_White").atlas.texture;
  141.         _material.renderQueue = Futile.baseRenderQueueDepth+999999; // this will go on top of EVERYTHING.
  142.  
  143.         _meshRenderer.renderer.material = _material;
  144.  
  145.         // since we're uploading new colors every frame, this is a dynamic mesh
  146.         _mesh.MarkDynamic();
  147.  
  148.         // -----
  149.         // give the mesh all its data, which was calculated already above
  150.         _mesh.vertices = verts;
  151.         _mesh.uv = uvs;
  152.         _mesh.colors32 = _vertColors;
  153.         _mesh.triangles = tris;
  154.         // -----
  155.  
  156.         // large values ensure this is never culled by Unity
  157.         _mesh.bounds = new Bounds(Vector3.zero, new Vector3(9999999999, 9999999999, 9999999999));
  158.  
  159.         // this may not be strictly necessary, but doesn't seem to hurt.
  160.         _mesh.Optimize();
  161.     }
  162.  
  163.     // Singleton constructor (should only ever be one of these lighting meshes!)
  164.  
  165.     public static HNSLightingLayer Create()
  166.     {
  167.         if (instance == null)
  168.         {
  169.             return (instance = new HNSLightingLayer());
  170.         }
  171.         else
  172.         {
  173.             return instance;
  174.         }
  175.     }
  176.  
  177.     // This is called by the game update loop every time lights need to be updated (probably once per frame)
  178.  
  179.     public void Update()
  180.     {
  181.         // Set modulus offset for whole lighting layer (keeps mesh vertices at the same world space
  182.         // location, avoid flickering artifacts)
  183.  
  184.         _offVector.Set(-(HNSGame.camera.x % _lightGridRes), -(HNSGame.camera.y % _lightGridRes));
  185.         _gameObject.transform.position = _offVector;
  186.  
  187.         // Only process the lights that are currently visible (their radius intersects with camera)
  188.         // AND are in the active room
  189.  
  190.         _visibleLights.Clear();
  191.        
  192.         foreach (HNSLight light in HNSLight.allInstances)
  193.         {
  194.             if (HNSRoom.activeRoom.bounds.Contains(light.position) &&
  195.                 light.circle.CheckIntersectWithRect(HNSGame.camera))
  196.             {
  197.                 _visibleLights.Add(light);
  198.             }
  199.         }
  200.  
  201.         // Need some variables for lighting color loop...
  202.  
  203.         float distRatio, sum, avgR, avgG, avgB;
  204.         Vector2 currentVert = Vector2.zero;
  205.         float[] weights = new float[_visibleLights.Count];
  206.         Rect smallRoomBounds = HNSRoom.activeRoom.bounds.CloneWithExpansion(-64f);
  207.  
  208.         // Lighting color loop, calculates color at each vertex of our lighting mesh
  209.        
  210.         for (int i=0; i<_lightGridVertSz; i++)
  211.         {
  212.             // This is the position of the current vertex in screen space
  213.             currentVert.Set(
  214.                 (i % _lightGridVertW) * _lightGridRes + _offVector.x,
  215.                 (i / _lightGridVertW) * _lightGridRes + _offVector.y);
  216.  
  217.             // Here we iterate through every visible light and calculate color for this vertex
  218.             // based on its location as a weighted average of its distance from every visible light!
  219.  
  220.             distRatio = 1f;
  221.             sum = 0f;
  222.             avgR = 0f;
  223.             avgG = 0f;
  224.             avgB = 0f;
  225.            
  226.             for (int j=0; j<_visibleLights.Count; ++j)
  227.             {
  228.                 HNSLight light = _visibleLights[j];
  229.                 float tmp = Mathf.Clamp01(
  230.                     Vector2.Distance(
  231.                     light.position-HNSGame.camera.GetPosition(),currentVert) / light.radius);
  232.                 distRatio *= tmp;
  233.                 sum += 1f-tmp;
  234.                 weights[j] = 1f-tmp;
  235.             }
  236.            
  237.             if (sum == 0f) sum = 0.000001f; // no dividing by zero!
  238.            
  239.             for (int j=0; j<weights.Length; ++j)
  240.             {
  241.                 weights[j] /= sum;
  242.             }
  243.            
  244.             for (int j=0; j<_visibleLights.Count; ++j)
  245.             {
  246.                 Color clr = _visibleLights[j].color;
  247.                 avgR += clr.r * weights[j];
  248.                 avgG += clr.g * weights[j];
  249.                 avgB += clr.b * weights[j];
  250.             }
  251.  
  252.             // We have a color for this vertex!
  253.  
  254.             _vertColors[i] = Color.Lerp(new Color(avgR,avgG,avgB,1f), _darknessColor, distRatio);
  255.  
  256.             // This ensures that areas outside of the currently active room are always darkened.
  257.             // Can be turned into better-looking occlusion easily, but right now we only care about
  258.             // the current room.
  259.  
  260.             Vector2 currentVertWorldSpace = currentVert + HNSGame.camera.GetPosition();
  261.             if (!smallRoomBounds.Contains(currentVertWorldSpace))
  262.             {
  263.                 Vector2 closestPt =
  264.                     smallRoomBounds.GetClosestInteriorPoint(currentVertWorldSpace);
  265.                 distRatio =
  266.                     Mathf.Clamp01(Vector2.Distance(closestPt,currentVertWorldSpace)/96.0f);
  267.                 _vertColors[i] = Color.Lerp(_vertColors[i], _darknessColor, distRatio);
  268.             }
  269.         }
  270.  
  271.         // All colors are now calculated, register them with the mesh.
  272.  
  273.         _mesh.colors32 = _vertColors;
  274.     }
  275. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement