SHARE
TWEET

Unity & Futile 2D Lighting Layer

christophermcasey Dec 1st, 2013 2,021 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. }
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top