LightMapper

From Unify Community Wiki
Jump to: navigation, search

Author: Alex Vendelbo Ringgaard (Talzor)

Description

A simple light mapper for Unity, that tries to match Unitys point lights

Usage

Place the script in the Editor folder and run the wizard from GameObject/Lightmap Wizard.

The light mapper only supports point lights and while it doesn't match them completely it's usually a fairly good approximation and it's something to do until UT makes their own.

C# - LightMapper.cs

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;
 
///Lightmapper based on texel to light raycast @see http://www.unifycommunity.com/wiki/index.php?title=PixelLightMapper#Usage for more info
public class PixelLightMapper : ScriptableWizard {	
	public enum LightmapMode {
		ALL,				//Lightmap all objects in the scene with a lightmap
		SELECTION,			//Lightmap all objects in the scene with a lightmap which is selected
		SELECTION_OR_CHILD,	//Lightmap all objects in the scene with a lightmap which is selected or a child of a selected transform
	}
 
	public LightmapMode lightmapMode = LightmapMode.ALL; ///<What objects should be lightmapped
	public int smoothing = 1;				///<How much should the final lightmaps be smoothed
	public bool saveLightMaps = false;		///<Should the lightmaps be saved after being generated. If false all lightmaps will revert on reimport
	public bool alphaLookup = false;		///<Should the lightmapper try to look at the alpha of surfaces
	public bool noDirectionalShadow = false;///<Always apply directional light regardsless of colliders (Good for indoor rooms)
	public LayerMask layerMask = -1;		///<What layers to raycast the light agains
	public Color ambientColor = Color.black;///<Color added to all texels
 
	[MenuItem("GameObject/Lightmap Wizard (Pixel)")]
	public static void CreateWizard() {
		DisplayWizard("Generate Lightmaps (Pixel based)", typeof(PixelLightMapper), "Run", "Reset");	
	}
 
	private const int falloffStart = 60; ///<Inside this angle (-ray.direction/hit.normal) full light will be used
	private const int falloffEnd = 90; ///<At this angle (-ray.direction/hit.normal) the surface recieves no light (Between it will use lerp)
	private const float spotFalloff = 2.5f; ///<The outer range where spot light begins to falloff
 
	void OnWizardCreate() {
		//Find all lights
		Light[] lights = Object.FindObjectsOfType(typeof (Light)) as Light[];
 
		//Find all renderers with a lightmap that should be lightmapped and all lightmaps that will be used (This is primarily done to show the progress bar)
		List<Renderer> models = new List<Renderer>(); //List of all renderers with a lightmap
		List<Texture2D> lightmaps = new List<Texture2D>(); //List of all lightmaps that will be used
		foreach (Renderer renderer in Object.FindObjectsOfType(typeof (Renderer))) {
			if (renderer.sharedMaterial.HasProperty("_LightMap")) {
				if ((lightmapMode == LightmapMode.ALL) ||
					(lightmapMode == LightmapMode.SELECTION && Selected(renderer.transform)) ||
					(lightmapMode == LightmapMode.SELECTION_OR_CHILD && SelectedOrChild(renderer.transform))
				) {
					Texture2D lightmap = renderer.sharedMaterial.GetTexture("_LightMap") as Texture2D;
 
					if (lightmap) {
						models.Add(renderer);
 
						if (!lightmaps.Contains(lightmap)) {
							lightmaps.Add(lightmap);
						}
					}
				}
			}
		}
 
		//This is just for the progressbar
		int totalSteps = lightmaps.Count + models.Count + lightmaps.Count; //Clear + Lightmap + Smoothing
		int currentStep = 0;
 
		//Clear all lightmaps
		for (int lightmapIndex = 0; lightmapIndex < lightmaps.Count; lightmapIndex++) {
			Texture2D lightmap = lightmaps[lightmapIndex];
 
			EditorUtility.DisplayProgressBar(
				"Lightmapping", 
				string.Format("Clearing lightmaps: {0} ({1} of {2})", lightmap.name, lightmapIndex+1, lightmaps.Count), 
				currentStep++ / (float) totalSteps
			);
 
			lightmap.SetPixels(new Color[lightmap.width*lightmap.height]); //Set the lightmap to all black
		}
 
		for (int rendererIndex = 0; rendererIndex < models.Count; rendererIndex++) {
			Renderer renderer = models[rendererIndex];
 
			EditorUtility.DisplayProgressBar(
				"Lightmapping", 
				string.Format("Calculating Light: {0} ({1} of {2})", renderer.name, rendererIndex+1, models.Count), 
				currentStep++ / (float) totalSteps
			);
 
			Mesh mesh = ((MeshFilter) renderer.GetComponent(typeof (MeshFilter))).sharedMesh;
			Texture2D lightmap = renderer.sharedMaterial.GetTexture("_LightMap") as Texture2D;
			Texture2D mainTex = renderer.sharedMaterial.mainTexture as Texture2D; //For alpha lookup
 
			if (lightmap.format == TextureFormat.Alpha8 || lightmap.format == TextureFormat.ARGB32 || lightmap.format == TextureFormat.RGB24) {
				int[] triangles = mesh.triangles;
				Vector3[] vertices = mesh.vertices;
				Vector3[] normals = mesh.normals;
				Vector2[] mainUV = mesh.uv; //Used for alphaLookup
				Vector2[] lightUV = mesh.uv2.Length > 0 ? mesh.uv2 : mesh.uv;
 
				//Transform vertices and normals to global coords
				Transform t = renderer.transform;
				for (int vert = 0; vert < vertices.Length; vert++) {
					vertices[vert] = t.TransformPoint(vertices[vert]);	
				}
				for (int normal = 0; normal < normals.Length; normal++) {
					normals[normal] = t.TransformDirection(normals[normal]);	
				}
 
				for (int i = 0; i < triangles.Length; i += 3) {
					//Vertice indexs
					int vi0 = triangles[i];
					int vi1 = triangles[i+1];
					int vi2 = triangles[i+2];
 
					//Lightmap UV coords [pixel]
					Vector2 pUV0 = new Vector2(lightUV[vi0].x * lightmap.width, lightUV[vi0].y * lightmap.height);
					Vector2 pUV1 = new Vector2(lightUV[vi1].x * lightmap.width, lightUV[vi1].y * lightmap.height);
					Vector2 pUV2 = new Vector2(lightUV[vi2].x * lightmap.width, lightUV[vi2].y * lightmap.height);
 
					Triangle2D pUVTriangle = new Triangle2D(pUV0, pUV1, pUV2);
 
					//mainTex UV coords [pixel]. These are only used (and computed) if alphaLookup is true (AND there is a mainTex)
					Vector2 pMainUV0 = alphaLookup && mainTex ? new Vector2(mainUV[vi0].x * mainTex.width, mainUV[vi0].y * mainTex.height) : Vector2.zero;
					Vector2 pMainUV1 = alphaLookup && mainTex ? new Vector2(mainUV[vi1].x * mainTex.width, mainUV[vi1].y * mainTex.height) : Vector2.zero;
					Vector2 pMainUV2 = alphaLookup && mainTex ? new Vector2(mainUV[vi2].x * mainTex.width, mainUV[vi2].y * mainTex.height) : Vector2.zero;
 
					//Square of pixels around triangle
					Vector4 bounds = new Vector4(
						Mathf.Floor(Mathf.Min(Mathf.Min(pUV0.x, pUV1.x), pUV2.x)),
						Mathf.Ceil(Mathf.Max(Mathf.Max(pUV0.x, pUV1.x), pUV2.x)),
						Mathf.Floor(Mathf.Min(Mathf.Min(pUV0.y, pUV1.y), pUV2.y)),
						Mathf.Ceil(Mathf.Max(Mathf.Max(pUV0.y, pUV1.y), pUV2.y))
					);
 
					for (float x = bounds.x; x < bounds.y; x++) {
						for (float y = bounds.z; y < bounds.w; y++) {
							Vector2 texel = new Vector2(x + 0.5f, y + 0.5f); //Use the middel of the texel
 
							//Check if the texel is inside the triangle
							if (pUVTriangle.IsPointInside(texel)) {
								Vector3 bCoords = pUVTriangle.PointToBaryCentric(texel);
 
								Vector3 worldPosition = vertices[vi0] * bCoords.x + vertices[vi1] * bCoords.y + vertices[vi2] * bCoords.z;
								Vector3 worldNormal = normals[vi0] * bCoords.x + normals[vi1] * bCoords.y + normals[vi2] * bCoords.z;
								Vector3 raycastOffset = worldNormal.normalized * 0.0001f; ///Vector added to worldposition when raycasting so that objects hit themself more consistently
 
								Color texelColor = lightmap.GetPixel((int) x, (int) y);
 
								//The modifier based on the surfaces alpha
								float alpha = 1;
								if (alphaLookup) {
									if (mainTex && (mainTex.format == TextureFormat.Alpha8 || mainTex.format == TextureFormat.ARGB32)) {
										Vector2 mainTexel = bCoords.x * pMainUV0 + bCoords.y * pMainUV1 + bCoords.z * pMainUV2;
 
										alpha = mainTex.GetPixel((int) mainTexel.x, (int) mainTexel.y).a;
									}
								}
 
								foreach (Light light in lights) {
									Transform lightTransform = light.transform;
									float alphaMod = alpha;
 
									//POINT LIGHT
									if (light.type == LightType.Point) {
										Vector3 toLight = lightTransform.position-worldPosition;
										float distanceToLight = toLight.magnitude;
 
										if (light.range > distanceToLight && !Obstructed(worldPosition+raycastOffset, toLight, distanceToLight, ref alphaMod)) {
											float angleMod = AngleMod(toLight, worldNormal);
											float attenuationMod = light.attenuate ? 2*Mathf.Pow(Mathf.Exp(-distanceToLight/light.range), 5) : 1;
 
											texelColor += light.color * light.intensity * angleMod * attenuationMod * alphaMod;
										}
									}
									//SPOT LIGHT
									else if (light.type == LightType.Spot) {
										Vector3 toLight = lightTransform.position-worldPosition;
										float distanceToLight = toLight.magnitude;
 
										float angleToSpot = Vector3.Angle(-toLight, lightTransform.forward);
 
										if (angleToSpot < light.spotAngle/2) {
											float projectedDistance = Mathf.Cos(angleToSpot*Mathf.Deg2Rad) * distanceToLight; //Distance to light (projected onto light.forward)
 
											if (light.range > projectedDistance && !Obstructed(worldPosition+raycastOffset, toLight, distanceToLight, ref alphaMod)) {
												//Edge falloff
												float edgeMod = (spotFalloff - Mathf.Max(angleToSpot - ( (light.spotAngle/2) - spotFalloff ), 0)) / spotFalloff;
 
												float angleMod = AngleMod(toLight, worldNormal);
												float attenuationMod = light.attenuate ? Mathf.Max(1-Mathf.Pow(projectedDistance/light.range, 3), 0) : 1;
 
												texelColor += light.color * light.intensity * edgeMod * angleMod * attenuationMod * alphaMod;
											}
										}
									}
									//DIRECTIONAL
									else if (light.type == LightType.Directional) {
										if (Vector3.Angle(lightTransform.forward, worldNormal) > 90) {
											if (noDirectionalShadow || !Obstructed(worldPosition+raycastOffset, -lightTransform.forward, Mathf.Infinity, ref alphaMod)) {
												float angleMod = AngleMod(-lightTransform.forward, worldNormal);
 
												texelColor += light.color * light.intensity * angleMod * alphaMod;
											}
										}												
									}
								} //END foreach light
 
								lightmap.SetPixel((int) x, (int) y, texelColor);
							}						
						}	
					}
				}
			} //END if (format)
			else {
				Debug.LogError(string.Format("Unsupported format of lightmap in object {0}", renderer.name));	
			}
		} //END foreach (renderer)
 
		//Smooth and save lightmaps
		for (int lightmapIndex = 0; lightmapIndex < lightmaps.Count; lightmapIndex++) {
			Texture2D lightmap = lightmaps[lightmapIndex];
 
			EditorUtility.DisplayProgressBar(
				"Lightmapping", 
				string.Format("Smoothing: {0} ({1} of {2})", lightmap.name, lightmapIndex+1, lightmaps.Count), 
				currentStep++ / (float) totalSteps
			);
 
			SmoothLightMaps(lightmap);
 
			lightmap.Apply();
 
			//Save to disk
			if (saveLightMaps) {
				File.WriteAllBytes(EditorUtility.GetAssetPath(lightmap), lightmap.EncodeToPNG());
			}
		}
 
		EditorUtility.ClearProgressBar();			
	}
 
	void OnWizardOtherButton() {
		lightmapMode = LightmapMode.ALL;
		smoothing = 1;
		saveLightMaps = false;
		alphaLookup = false;
		noDirectionalShadow = false;
		layerMask = -1;
		ambientColor = Color.black;
	}
 
	///Compute the intensity modifier from the angle of the surface
	private float AngleMod(Vector3 forward, Vector3 normal) {
		float angle = Vector3.Angle(forward, normal);
 
		return Mathf.Max(((falloffEnd-falloffStart) - Mathf.Max(angle-falloffStart, 0)), 0) / (falloffEnd-falloffStart);
	}
 
	private bool Obstructed(Vector3 from, Vector3 direction, float distance, ref float alphaMod) {
		RaycastHit[] hits = Physics.RaycastAll(from, direction, distance, layerMask);
 
		foreach (RaycastHit hit in hits) {
			Collider collider = hit.collider;
			Renderer renderer = collider.renderer;
 
			if (!collider.isTrigger && renderer.enabled) {
				if (alphaLookup) {
					Texture2D mainTex = renderer.sharedMaterial.mainTexture as Texture2D; //For alpha lookup
					if (mainTex && (mainTex.format == TextureFormat.Alpha8 || mainTex.format == TextureFormat.ARGB32)) {
						alphaMod *= 1f - mainTex.GetPixel((int) (hit.textureCoord.x*mainTex.width), (int) (hit.textureCoord.y*mainTex.height)).a;						
					}
					else {
						return true; //The texture isn't an alpha texture (or no texture isn't set)
					}
				}
				else {
					return true;
				}	
			}
		}
 
		return false;
	}
 
	public void SmoothLightMaps(Texture2D lightmap) {
		Color[] pixels = lightmap.GetPixels(0);
		Color[] newPixels = new Color[pixels.Length];
 
		for (int width = 0; width < lightmap.width; width++) {
			for (int height = 0; height < lightmap.height; height++) {
				int pixelsBlended = 0;
				Color blendedColor = new Color(0, 0, 0, 1);
 
				for (int offsetW = -smoothing; offsetW <= smoothing; offsetW++) {
					for (int offsetH = -smoothing; offsetH <= smoothing; offsetH++) {
						//If inside the texture
						if (width + offsetW >= 0 && width + offsetW < lightmap.width &&
							height + offsetH >= 0 && height + offsetH < lightmap.height) {
 
							Color pixelColor = pixels[(height+offsetH)*lightmap.width + (width+offsetW)];
							if (pixelColor.r > 0 || pixelColor.g > 0 || pixelColor.b > 0) { //Ignore texels that are black (this all but kill edge artifacts)
								blendedColor += pixelColor;
								pixelsBlended++;
							}
						}
					}	
				}
 
				newPixels[height*lightmap.width + width] = (blendedColor/(pixelsBlended > 0 ? pixelsBlended : 1)) + ambientColor;				
			}						
		}
 
		lightmap.SetPixels(newPixels, 0);		
	}
 
	///@return True if t is selected
	private bool Selected(Transform t) {
		foreach (Transform selectedTransform in Selection.transforms) {
			if (t == selectedTransform) {
				return true;	
			}
		}
		return false;
	}
 
	///@return True if t is selected or a child of a selected transform	
	private bool SelectedOrChild(Transform t) {
		foreach (Transform selectedTransform in Selection.transforms) {
			if (ChildOf(t, selectedTransform)) {
				return true;	
			}
		}
		return false;
	}
	///@return True if child if a child of parent
	private bool ChildOf(Transform child, Transform parent) {
		if (child == parent) {
			return true;	
		}
		else if (child.parent != null) {
			return ChildOf(child.parent, parent);
		}
		else {
			return false;	
		}
	}
 
	public struct Triangle2D {
		Vector2 vert0;
		Vector2 vert1;
		Vector2 vert2;
 
		public Triangle2D(Vector2 vert0, Vector2 vert1, Vector2 vert2) {
			this.vert0 = vert0;
			this.vert1 = vert1;
			this.vert2 = vert2;
		}
 
		public Vector3 PointToBaryCentric(Vector2 point) {
			float A, B, C, G, H, I;
 
			A = vert0.x - vert2.x;
			B = vert1.x - vert2.x;
			C = vert2.x - point.x;
			G = vert0.y - vert2.y;
			H = vert1.y - vert2.y;
			I = vert2.y - point.y;
 
			float w1, w2, w3;
			w1 = (B*I - C*H) / (A*H - B*G);
			w2 = (A*I - C*G) / (B*G - A*H);
			w3 = 1 - w1 - w2;
 
			return new Vector3(w1, w2, w3);
		}
 
		public bool IsPointInside(Vector2 point) {
			Vector3 weights = PointToBaryCentric(point);
			return (weights.x >= 0 && weights.y >= 0 && weights.z >= 0);
		}	
	}
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Toolbox