You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

342 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Duality;
using Duality.Editor;
using Duality.Resources;
using Duality.Drawing;
using Duality.Properties;
using DynamicLighting.Properties;
// Note:
//
// Rewrite dynamic lighting to not reverse-engineer a vertices world position in the shader code.
// --> Instead, add an additional worldPosition attribute to the vertex format.
// --> This is the only way to allow flexible lighting when using different perspective modes.
namespace DynamicLighting
{
/// <summary>
/// A source of light. Supported light types are directional, ambient, point and spot.
/// To create directional or ambient lights, add this Component to a GameObject without Transform.
/// </summary>
[EditorHintCategory(CoreResNames.CategoryGraphics)]
[EditorHintImage(DynLightResNames.IconResourceLight)]
public class Light : Component
{
private class DeviceLightInfo
{
public List<Light> PriorizedLights;
public int FrameId;
}
private static DeviceLightInfo nullDeviceInfo = null;
private static Dictionary<IDrawDevice,DeviceLightInfo> deviceInfo = new Dictionary<IDrawDevice,DeviceLightInfo>();
public const int MaxVisible = 8;
private Vector3 dir = Vector3.UnitZ;
private ColorRgba color = ColorRgba.White;
private ColorRgba ambientColor = ColorRgba.White;
private float intensity = 1.0f;
private float ambientIntensity = 0.0f;
private float range = 1500.0f;
private float spotFocus = 0.0f;
/// <summary>
/// [GET / SET] The direction this Light points to. Used in spot and directional light. Set this to zero
/// in order to create a point light.
/// </summary>
public Vector3 Direction
{
get { return this.dir; }
set
{
this.dir = value;
if (this.dir != Vector3.Zero) this.dir.Normalize();
}
}
/// <summary>
/// [GET / SET] The Lights main color.
/// </summary>
[EditorHintFlags(MemberFlags.AffectsOthers)]
public ColorRgba Color
{
get { return this.color; }
set
{
ColorHsva hsva = value.ToHsva();
if (hsva.V != 1.0f || hsva.A != 1.0f)
{
this.intensity = hsva.V * hsva.A;
hsva.V = 1.0f;
hsva.A = 1.0f;
}
this.color = hsva.ToRgba();
}
}
/// <summary>
/// [GET / SET] The Lights intensity.
/// </summary>
public float Intensity
{
get { return this.intensity; }
set { this.intensity = value; }
}
/// <summary>
/// [GET / SET] The Lights ambient color value. Ambient light is used as base value in directional lighting.
/// </summary>
[EditorHintFlags(MemberFlags.AffectsOthers)]
public ColorRgba AmbientColor
{
get { return this.ambientColor; }
set
{
ColorHsva hsva = value.ToHsva();
if (hsva.V != 1.0f || hsva.A != 1.0f)
{
this.ambientIntensity = hsva.V * hsva.A;
hsva.V = 1.0f;
hsva.A = 1.0f;
}
this.ambientColor = hsva.ToRgba();
}
}
/// <summary>
/// [GET / SET] The Lights ambient intensity. Ambient light is used as base value in directional lighting.
/// </summary>
public float AmbientIntensity
{
get { return this.ambientIntensity; }
set { this.ambientIntensity = value; }
}
/// <summary>
/// [GET / SET] The Lights range. Only applies to point and spot lights.
/// </summary>
[EditorHintIncrement(10.0f)]
public float Range
{
get { return this.range; }
set { this.range = value; }
}
/// <summary>
/// [GET / SET] The Lights spot focus. The higher this value is, the smaller the spot radius.
/// </summary>
[EditorHintRange(1.0f, 100.0f)]
public float SpotFocus
{
get { return this.spotFocus; }
set { this.spotFocus = value; }
}
[EditorHintFlags(MemberFlags.Invisible)]
public bool IsDirectional
{
get { return this.GameObj == null || this.GameObj.Transform == null; }
}
[EditorHintFlags(MemberFlags.Invisible)]
public bool IsSpot
{
get { return !this.IsDirectional && this.dir != Vector3.Zero; }
}
public bool IsVisibleTo(IDrawDevice device)
{
if (this.IsDirectional) return true;
if (this.range <= 0.0f) return false;
if (this.intensity <= 0.0f) return false;
float uniformScale = this.GameObj.Transform.Scale;
if (uniformScale <= 0.0f) return false;
Vector3 pos = this.GameObj.Transform.Pos;
if (device.IsCoordInView(pos, this.range * uniformScale)) return true;
if (device.IsCoordInView(pos - Vector3.UnitZ * this.range * 0.5f * uniformScale, this.range * uniformScale)) return true;
if (device.IsCoordInView(pos + Vector3.UnitZ * this.range * uniformScale, this.range * uniformScale)) return true;
return false;
}
public int CalcPriority(IDrawDevice device)
{
if (!this.IsDirectional)
{
float uniformScale = this.GameObj.Transform.Scale;
Vector3 pos = this.GameObj.Transform.Pos;
float scale = 1.0f;
device.PreprocessCoords(ref pos, ref scale);
float planarDist = (this.GameObj.Transform.Pos.Xy - device.RefCoord.Xy).Length;
float rangeFactor = 1.0f / (this.range * uniformScale);
float distFactor = (MathF.Min(scale, 1.0f) * planarDist);
float spotFactor;
if (this.dir != Vector3.Zero)
spotFactor = 0.5f * (1.0f + Vector3.Dot((device.RefCoord - this.GameObj.Transform.Pos).Normalized, this.dir));
else
spotFactor = 1.0f;
return MathF.RoundToInt(1000000.0f * spotFactor * distFactor * MathF.Pow(rangeFactor, 1.5f) * MathF.Pow(1.0f / this.intensity, 0.5f));
}
else
{
return MathF.RoundToInt(100.0f * MathF.Pow(1.0f / this.intensity, 0.5f));
}
}
private static DeviceLightInfo UpdateLighting(IDrawDevice device)
{
DeviceLightInfo info;
if (device == null)
{
info = nullDeviceInfo;
if (info != null && info.FrameId == Time.FrameCount) return info;
if (info == null)
{
info = new DeviceLightInfo();
nullDeviceInfo = info;
}
info.FrameId = Time.FrameCount;
info.PriorizedLights = Scene.Current.FindComponents<Light>().Where(l => l.Active).ToList();
}
else
{
if (deviceInfo.TryGetValue(device, out info) && info != null && info.FrameId == Time.FrameCount) return info;
if (info == null)
{
info = new DeviceLightInfo();
deviceInfo[device] = info;
}
info.FrameId = Time.FrameCount;
info.PriorizedLights = Scene.Current.FindComponents<Light>().Where(l => l.Active).Where(l => l.IsVisibleTo(device)).ToList();
info.PriorizedLights.StableSort((Light a, Light b) => a.CalcPriority(device) - b.CalcPriority(device));
}
return info;
}
public static void SetupLighting(IDrawDevice device, BatchInfo material)
{
DeviceLightInfo info = UpdateLighting(device);
// Prepare shader dara
float[] _lightPos = new float[4 * MaxVisible];
float[] _lightDir = new float[4 * MaxVisible];
float[] _lightColor = new float[3 * MaxVisible];
int _lightCount = MathF.Min(MaxVisible, info.PriorizedLights.Count);
int i = 0;
foreach (Light light in info.PriorizedLights)
{
if (light.Disposed) continue;
Vector3 dir;
Vector3 pos;
float uniformScale;
bool directional = light.IsDirectional;
if (directional)
{
dir = light.dir;
pos = Vector3.Zero;
uniformScale = 1.0f;
}
else
{
dir = light.dir;
pos = light.GameObj.Transform.Pos;
uniformScale = light.GameObj.Transform.Scale;
MathF.TransformCoord(ref dir.X, ref dir.Y, light.GameObj.Transform.Angle);
}
if (directional)
{
_lightPos[i * 4 + 0] = (float)light.ambientColor.R * light.ambientIntensity / 255.0f;
_lightPos[i * 4 + 1] = (float)light.ambientColor.G * light.ambientIntensity / 255.0f;
_lightPos[i * 4 + 2] = (float)light.ambientColor.B * light.ambientIntensity / 255.0f;
_lightPos[i * 4 + 3] = 0.0f;
}
else
{
_lightPos[i * 4 + 0] = pos.X;
_lightPos[i * 4 + 1] = pos.Y;
_lightPos[i * 4 + 2] = pos.Z;
_lightPos[i * 4 + 3] = light.range * uniformScale;
}
_lightDir[i * 4 + 0] = dir.X;
_lightDir[i * 4 + 1] = dir.Y;
_lightDir[i * 4 + 2] = dir.Z;
_lightDir[i * 4 + 3] = dir == Vector3.Zero ? 0.0f : MathF.Max(light.spotFocus, 1.0f);
_lightColor[i * 3 + 0] = (float)light.color.R * light.intensity / 255.0f;
_lightColor[i * 3 + 1] = (float)light.color.G * light.intensity / 255.0f;
_lightColor[i * 3 + 2] = (float)light.color.B * light.intensity / 255.0f;
i++;
if (i >= _lightCount) break;
}
if (i + 1 < _lightCount) _lightCount = i + 1;
material.SetUniform("_lightCount", _lightCount);
material.SetUniform("_lightPos", _lightPos);
material.SetUniform("_lightDir", _lightDir);
material.SetUniform("_lightColor", _lightColor);
}
public static void GetLightAtWorldPos(Vector3 worldPos, out Vector4 lightColor, float translucency = 0.0f)
{
DeviceLightInfo info = UpdateLighting(null);
lightColor = Vector4.UnitW;
foreach (Light light in info.PriorizedLights)
{
if (light.Disposed) continue;
if (light.IsDirectional)
{
float translucencyFactor = Vector3.Dot(light.dir, Vector3.UnitZ);
translucencyFactor = translucencyFactor + 0.5f + 1.5f * translucency;
translucencyFactor = MathF.Sign(translucencyFactor) * MathF.Pow(MathF.Abs(translucencyFactor), 0.5f);
translucencyFactor = MathF.Clamp(translucencyFactor, 0.0f, 1.0f);
Vector3 color = new Vector3(
(float)light.color.R * light.intensity / 255.0f,
(float)light.color.G * light.intensity / 255.0f,
(float)light.color.B * light.intensity / 255.0f);
Vector3.Multiply(ref color, translucencyFactor, out color);
lightColor += new Vector4(color);
}
else
{
Vector3 lightPos = light.GameObj.Transform.Pos;
Vector3 lightVec = lightPos - worldPos;
float dist = lightVec.Length;
Vector3 lightVecNorm = lightVec / dist;
float spotExp = light.dir == Vector3.Zero ? 0.0f : MathF.Max(light.spotFocus, 1.0f);
Vector3 lightDir = light.dir;
float uniformScale = light.GameObj.Transform.Scale;
MathF.TransformCoord(ref lightDir.X, ref lightDir.Y, light.GameObj.Transform.Angle);
float attenFactor = 1.0f - MathF.Min(dist / (light.range * uniformScale), 1.0f);
float spotFactor = MathF.Pow(MathF.Max(Vector3.Dot(lightVecNorm, -lightDir), 0.000001f), spotExp);
float translucencyFactor = Vector3.Dot(lightVecNorm, -Vector3.UnitZ);
translucencyFactor = translucencyFactor + 0.5f + 1.5f * translucency;
translucencyFactor = MathF.Sign(translucencyFactor) * MathF.Pow(MathF.Abs(translucencyFactor), 0.5f);
translucencyFactor = MathF.Clamp(translucencyFactor, 0.0f, 1.0f);
Vector3 color = new Vector3(
(float)light.color.R * light.intensity / 255.0f,
(float)light.color.G * light.intensity / 255.0f,
(float)light.color.B * light.intensity / 255.0f);
Vector3.Multiply(ref color, attenFactor * spotFactor * translucencyFactor, out color);
lightColor += new Vector4(color);
}
}
}
}
}