LOS effect optimization/changes:

- Rendering the shadows instead of light volumes to avoid the expensive raycasts needed to calculate the light volume.
- The LOS shadows are now rendered in two passes (fully obscured + penumbra) instead of each convex hull taking up 2 passes.

TODO: fix linux
This commit is contained in:
Joonas Rikkonen
2018-03-03 20:23:27 +02:00
parent e15fad6986
commit da7ea779b7
7 changed files with 224 additions and 187 deletions

View File

@@ -34,7 +34,7 @@ namespace Barotrauma
{
healthBar.Flash();
damageOverlayTimer = MathHelper.Clamp(amount * 0.1f, 0.2f, 5.0f);
damageOverlayTimer = MathHelper.Clamp(amount * 0.1f, 0.2f, 1.0f);
}
public static void AddToGUIUpdateList(Character character)

View File

@@ -6,41 +6,6 @@ using System.Diagnostics;
namespace Barotrauma.Lights
{
/*class CachedShadow : IDisposable
{
public VertexBuffer ShadowBuffer;
public Vector2 LightPos;
public int ShadowVertexCount, PenumbraVertexCount;
public CachedShadow(VertexPositionColor[] shadowVertices, Vector2 lightPos, int shadowVertexCount, int penumbraVertexCount)
{
//var ShadowVertices = new VertexPositionColor [shadowVertices.Count()];
//shadowVertices.CopyTo(ShadowVertices, 0);
ShadowBuffer = new VertexBuffer(GameMain.CurrGraphicsDevice, VertexPositionColor.VertexDeclaration, 6*2, BufferUsage.None);
ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length);
ShadowVertexCount = shadowVertexCount;
PenumbraVertexCount = penumbraVertexCount;
LightPos = lightPos;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
ShadowBuffer.Dispose();
}
}*/
class ConvexHullList
{
private List<ConvexHull> list;
@@ -107,27 +72,30 @@ namespace Barotrauma.Lights
class ConvexHull
{
public static List<ConvexHullList> HullLists = new List<ConvexHullList>();
static BasicEffect shadowEffect;
static BasicEffect penumbraEffect;
//private Dictionary<LightSource, CachedShadow> cachedShadows;
public VertexBuffer ShadowBuffer;
public static BasicEffect shadowEffect;
public static BasicEffect penumbraEffect;
Segment[] segments = new Segment[4];
SegmentPoint[] vertices = new SegmentPoint[4];
SegmentPoint[] losVertices = new SegmentPoint[4];
//private Vector2[] vertices;
//private Vector2[] losVertices;
private bool[] backFacing;
private bool[] ignoreEdge;
private VertexPositionColor[] shadowVertices;
private VertexPositionTexture[] penumbraVertices;
int shadowVertexCount;
public VertexPositionColor[] ShadowVertices
{
get { return shadowVertices; }
}
public VertexPositionTexture[] PenumbraVertices
{
get { return penumbraVertices; }
}
public int shadowVertexCount;
private Entity parentEntity;
@@ -399,8 +367,14 @@ namespace Barotrauma.Lights
}
}
private void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true)
public void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true)
{
Vector3 offset = Vector3.Zero;
if (parentEntity != null && parentEntity.Submarine != null)
{
offset = new Vector3(parentEntity.Submarine.DrawPosition.X, parentEntity.Submarine.DrawPosition.Y, 0.0f);
}
shadowVertexCount = 0;
var vertices = los ? losVertices : this.vertices;
@@ -463,7 +437,7 @@ namespace Barotrauma.Lights
//one vertex on the hull
shadowVertices[i] = new VertexPositionColor();
shadowVertices[i].Color = los ? Color.Black : Color.Transparent;
shadowVertices[i].Position = vertexPos;
shadowVertices[i].Position = vertexPos+ offset;
//one extruded by the light direction
shadowVertices[j] = new VertexPositionColor();
@@ -472,7 +446,7 @@ namespace Barotrauma.Lights
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0);
L2P.Normalize();
shadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000;
shadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000 + offset;
svCount += 2;
currentIndex = (currentIndex + 1) % 4;
@@ -486,12 +460,18 @@ namespace Barotrauma.Lights
private void CalculatePenumbraVertices(int startingIndex, int endingIndex, Vector2 lightSourcePos, bool los)
{
Vector3 offset = Vector3.Zero;
if (parentEntity != null && parentEntity.Submarine != null)
{
offset = new Vector3(parentEntity.Submarine.DrawPosition.X, parentEntity.Submarine.DrawPosition.Y, 0.0f);
}
for (int n = 0; n < 4; n += 3)
{
Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f);
penumbraVertices[n] = new VertexPositionTexture();
penumbraVertices[n].Position = penumbraStart;
penumbraVertices[n].Position = penumbraStart + offset;
penumbraVertices[n].TextureCoordinate = new Vector2(0.0f, 1.0f);
//penumbraVertices[0].te = fow ? Color.Black : Color.Transparent;
@@ -506,7 +486,7 @@ namespace Barotrauma.Lights
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
penumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000;
penumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
if (los)
{
@@ -531,7 +511,7 @@ namespace Barotrauma.Lights
{
List<ConvexHull> list = new List<ConvexHull>();
foreach (ConvexHullList chList in ConvexHull.HullLists)
foreach (ConvexHullList chList in HullLists)
{
Vector2 lightPos = position;
if (ParentSub == null)
@@ -581,85 +561,7 @@ namespace Barotrauma.Lights
return list;
}
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, LightSource light, Matrix transform, bool los = true)
{
if (!Enabled) return;
Vector2 lightSourcePos = light.Position;
if (parentEntity != null && parentEntity.Submarine != null)
{
if (light.ParentSub == null)
{
lightSourcePos -= parentEntity.Submarine.Position;
}
else if (light.ParentSub != parentEntity.Submarine)
{
lightSourcePos += (light.ParentSub.Position-parentEntity.Submarine.Position);
}
}
CalculateShadowVertices(lightSourcePos, los);
ShadowBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, 6 * 2, BufferUsage.None);
ShadowBuffer.SetData(shadowVertices, 0, shadowVertices.Length);
graphicsDevice.SetVertexBuffer(ShadowBuffer);
shadowVertexCount = shadowVertices.Length;
DrawShadows(graphicsDevice, cam, transform, los);
}
public void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Vector2 lightSourcePos, Matrix transform, bool los = true)
{
if (!Enabled) return;
if (parentEntity != null && parentEntity.Submarine != null) lightSourcePos -= parentEntity.Submarine.Position;
CalculateShadowVertices(lightSourcePos, los);
DrawShadows(graphicsDevice, cam, transform, los);
}
private void DrawShadows(GraphicsDevice graphicsDevice, Camera cam, Matrix transform, bool los = true)
{
Vector3 offset = Vector3.Zero;
if (parentEntity != null && parentEntity.Submarine != null)
{
offset = new Vector3(parentEntity.Submarine.DrawPosition.X, parentEntity.Submarine.DrawPosition.Y, 0.0f);
}
if (shadowVertexCount>0)
{
shadowEffect.World = Matrix.CreateTranslation(offset) * transform;
if (los)
{
shadowEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, shadowVertices, 0, shadowVertexCount * 2 - 2, VertexPositionColor.VertexDeclaration);
}
else
{
shadowEffect.CurrentTechnique.Passes[0].Apply();
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleStrip, 0, shadowVertexCount * 2 - 2);
}
}
if (los)
{
penumbraEffect.World = shadowEffect.World;
penumbraEffect.CurrentTechnique.Passes[0].Apply();
#if WINDOWS
graphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVertices, 0, 2, VertexPositionTexture.VertexDeclaration);
#endif
}
}
public void Remove()
{
var chList = HullLists.Find(x => x.Submarine == parentEntity.Submarine);

View File

@@ -38,7 +38,7 @@ namespace Barotrauma.Lights
BasicEffect lightEffect;
public Effect losEffect
public Effect LosEffect
{
get; private set;
}
@@ -52,9 +52,8 @@ namespace Barotrauma.Lights
public bool LightingEnabled = true;
public bool ObstructVision;
LightSource losSource;
private Sprite visionCircle;
private Texture2D visionCircle;
private Dictionary<Hull, Color> hullAmbientLights;
private Dictionary<Hull, Color> smoothedHullAmbientLights;
@@ -67,8 +66,8 @@ namespace Barotrauma.Lights
AmbientLight = new Color(20, 20, 20, 255);
//visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
visionCircle = new Sprite("Content/Lights/visioncircle.png", new Vector2(0.2f, 0.5f));
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
//visionCircle = new Sprite("Content/Lights/visioncircle.png", new Vector2(0.2f, 0.5f));
var pp = graphics.PresentationParameters;
@@ -78,13 +77,10 @@ namespace Barotrauma.Lights
RenderTargetUsage.DiscardContents);
losTexture = new RenderTarget2D(graphics, (int)(GameMain.GraphicsWidth*lightmapScale), (int)(GameMain.GraphicsHeight*lightmapScale), false, SurfaceFormat.Color, DepthFormat.None);
losSource = new LightSource(Vector2.Zero, GameMain.GraphicsWidth, Color.White, null, false);
losSource.texture = new Texture2D(graphics, 1, 1);
losSource.texture.SetData(new Color[] { Color.White });// fill the texture with white
#if WINDOWS
losEffect = content.Load<Effect>("losshader");
LosEffect = content.Load<Effect>("losshader");
#else
losEffect = content.Load<Effect>("losshader_opengl");
#endif
@@ -241,43 +237,88 @@ namespace Barotrauma.Lights
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
{
if (!LosEnabled || ViewTarget == null) return;
if (!LosEnabled && !ObstructVision) return;
graphics.SetRenderTarget(losTexture);
//--------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null, null, cam.Transform * Matrix.CreateScale(new Vector3(lightmapScale, lightmapScale, 1.0f)));
graphics.Clear(Color.Black);
if (ObstructVision)
{
graphics.Clear(Color.Black);
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
diff.Y = -diff.Y;
float rotation = MathUtils.VectorToAngle(diff);
Vector2 scale = new Vector2(MathHelper.Clamp(diff.Length() / 256.0f, 2.0f, 5.0f), 2.0f) * 0.3f;
Vector2 scale = new Vector2(
MathHelper.Clamp(diff.Length() / 256.0f, 2.0f, 5.0f), 2.0f);
visionCircle.size = new Vector2(visionCircle.SourceRect.Width * scale.X, visionCircle.SourceRect.Height * scale.Y);
losSource.overrideLightTexture = visionCircle;
losSource.Rotation = rotation;
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
new Vector2(LightSource.LightTexture.Width * 0.2f, LightSource.LightTexture.Height / 2), scale, SpriteEffects.None, 0.0f);
}
else
{
losSource.overrideLightTexture = null;
graphics.Clear(Color.White);
}
graphics.BlendState = BlendState.Additive;
Vector2 pos = ViewTarget.Position;
losSource.Position = pos;
losSource.NeedsRecalculation = true;
losSource.ParentSub = ViewTarget.Submarine;
Matrix transform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
spriteBatch.End();
losSource.Draw(spriteBatch, lightEffect, transform);
graphics.BlendState = BlendState.AlphaBlend;
//--------------------------------------
if (LosEnabled && ViewTarget != null)
{
Vector2 pos = ViewTarget.WorldPosition;
Rectangle camView = new Rectangle(cam.WorldView.X, cam.WorldView.Y - cam.WorldView.Height, cam.WorldView.Width, cam.WorldView.Height);
Matrix shadowTransform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
var convexHulls = ConvexHull.GetHullsInRange(viewTarget.Position, cam.WorldView.Width*0.75f, viewTarget.Submarine);
if (convexHulls != null)
{
List<VertexPositionColor> shadowVerts = new List<VertexPositionColor>();
List<VertexPositionTexture> penumbraVerts = new List<VertexPositionTexture>();
foreach (ConvexHull convexHull in convexHulls)
{
if (!convexHull.Enabled || !convexHull.Intersects(camView)) continue;
Vector2 relativeLightPos = pos;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position;
convexHull.CalculateShadowVertices(relativeLightPos, true);
//convert triangle strips to a triangle list
for (int i = 0; i < convexHull.shadowVertexCount * 2 - 2; i++)
{
if (i % 2 == 0)
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
}
else
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
}
}
penumbraVerts.AddRange(convexHull.PenumbraVertices);
}
ConvexHull.shadowEffect.World = shadowTransform;
ConvexHull.shadowEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, shadowVerts.ToArray(), 0, shadowVerts.Count / 3, VertexPositionColor.VertexDeclaration);
ConvexHull.penumbraEffect.World = shadowTransform;
ConvexHull.penumbraEffect.CurrentTechnique.Passes[0].Apply();
graphics.DrawUserPrimitives(PrimitiveType.TriangleList, penumbraVerts.ToArray(), 0, penumbraVerts.Count / 3, VertexPositionTexture.VertexDeclaration);
}
}
graphics.SetRenderTarget(null);
}
@@ -361,19 +402,9 @@ namespace Barotrauma.Lights
return hullAmbientLight;
}
public void DrawLightMap(SpriteBatch spriteBatch, Effect effect)
{
if (!LightingEnabled) return;
spriteBatch.Begin(SpriteSortMode.Deferred, CustomBlendStates.Multiplicative, null, null, null, null);
spriteBatch.Draw(lightMap, new Rectangle(0,0,GameMain.GraphicsWidth,GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
}
public void ClearLights()
{
lights.Clear();
losSource.Reset();
}
}
@@ -393,10 +424,16 @@ namespace Barotrauma.Lights
MultiplyWithAlpha = new BlendState();
MultiplyWithAlpha.ColorDestinationBlend = MultiplyWithAlpha.AlphaDestinationBlend = Blend.One;
MultiplyWithAlpha.ColorSourceBlend = MultiplyWithAlpha.AlphaSourceBlend = Blend.DestinationAlpha;
LOS = new BlendState();
LOS.ColorSourceBlend = LOS.AlphaSourceBlend = Blend.Zero;
LOS.ColorDestinationBlend = LOS.AlphaDestinationBlend = Blend.InverseSourceColor;
LOS.ColorBlendFunction = LOS.AlphaBlendFunction = BlendFunction.Add;
}
public static BlendState Multiplicative { get; private set; }
public static BlendState WriteToAlpha { get; private set; }
public static BlendState MultiplyWithAlpha { get; private set; }
public static BlendState LOS { get; private set; }
}
}

View File

@@ -231,19 +231,26 @@ namespace Barotrauma
graphics.SetRenderTarget(null);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.None, null, null, null);
if (GameMain.LightManager.LosEnabled && Character.Controlled!=null)
{
float r = Math.Min(CharacterHUD.damageOverlayTimer * 0.5f, 0.5f);
spriteBatch.Draw(renderTargetBackground, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
Color.Lerp(GameMain.LightManager.AmbientLight * 0.5f, Color.Red, r));
spriteBatch.End();
GameMain.LightManager.losEffect.CurrentTechnique = GameMain.LightManager.losEffect.Techniques["LosShader"];
GameMain.LightManager.losEffect.Parameters["xTexture"].SetValue(renderTargetFinal);
GameMain.LightManager.losEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.losTexture);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.None, null, GameMain.LightManager.losEffect, null);
}
spriteBatch.Draw(renderTargetFinal, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
spriteBatch.End();
if (GameMain.LightManager.LosEnabled && Character.Controlled!=null)
{
GameMain.LightManager.LosEffect.CurrentTechnique = GameMain.LightManager.LosEffect.Techniques["LosShader"];
GameMain.LightManager.LosEffect.Parameters["xTexture"].SetValue(renderTargetBackground);
GameMain.LightManager.LosEffect.Parameters["xLosTexture"].SetValue(GameMain.LightManager.losTexture);
//convert the los color to HLS and make sure the luminance of the color is always the same regardless
//of the ambient light color and the luminance of the damage overlight color
float r = Math.Min(CharacterHUD.damageOverlayTimer * 0.5f, 0.5f);
Vector3 losColorHls = Color.Lerp(GameMain.LightManager.AmbientLight, Color.Red, r).RgbToHLS();
losColorHls.Y = 0.1f;
Color losColor = ToolBox.HLSToRGB(losColorHls);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, GameMain.LightManager.LosEffect, null);
spriteBatch.Draw(renderTargetBackground, new Rectangle(0, 0, spriteBatch.GraphicsDevice.Viewport.Width, spriteBatch.GraphicsDevice.Viewport.Height), losColor);
spriteBatch.End();
}
}
}
}

View File

@@ -6,6 +6,91 @@ namespace Barotrauma
{
public static partial class ToolBox
{
// Convert an RGB value into an HLS value.
public static Vector3 RgbToHLS(this Color color)
{
double h, l, s;
// Convert RGB to a 0.0 to 1.0 range.
double double_r = color.R / 255.0;
double double_g = color.G / 255.0;
double double_b = color.B / 255.0;
// Get the maximum and minimum RGB components.
double max = double_r;
if (max < double_g) max = double_g;
if (max < double_b) max = double_b;
double min = double_r;
if (min > double_g) min = double_g;
if (min > double_b) min = double_b;
double diff = max - min;
l = (max + min) / 2;
if (Math.Abs(diff) < 0.00001)
{
s = 0;
h = 0; // H is really undefined.
}
else
{
if (l <= 0.5) s = diff / (max + min);
else s = diff / (2 - max - min);
double r_dist = (max - double_r) / diff;
double g_dist = (max - double_g) / diff;
double b_dist = (max - double_b) / diff;
if (double_r == max) h = b_dist - g_dist;
else if (double_g == max) h = 2 + r_dist - b_dist;
else h = 4 + g_dist - r_dist;
h = h * 60;
if (h < 0) h += 360;
}
return new Vector3((float)h, (float)l, (float)s);
}
// Convert an HLS value into an RGB value.
public static Color HLSToRGB(Vector3 hls)
{
double h = hls.X, l = hls.Y, s = hls.Z;
double p2;
if (l <= 0.5) p2 = l * (1 + s);
else p2 = l + s - l * s;
double p1 = 2 * l - p2;
double double_r, double_g, double_b;
if (s == 0)
{
double_r = l;
double_g = l;
double_b = l;
}
else
{
double_r = QqhToRgb(p1, p2, h + 120);
double_g = QqhToRgb(p1, p2, h);
double_b = QqhToRgb(p1, p2, h - 120);
}
// Convert RGB to the 0 to 255 range.
return new Color((byte)(double_r * 255.0), (byte)(double_g * 255.0), (byte)(double_b * 255.0));
}
private static double QqhToRgb(double q1, double q2, double hue)
{
if (hue > 360) hue -= 360;
else if (hue < 0) hue += 360;
if (hue < 60) return q1 + (q2 - q1) * hue / 60;
if (hue < 180) return q2;
if (hue < 240) return q1 + (q2 - q1) * (240 - hue) / 60;
return q1;
}
public static string LimitString(string str, ScalableFont font, int maxWidth)
{
if (maxWidth <= 0 || string.IsNullOrWhiteSpace(str)) return "";

View File

@@ -7,10 +7,16 @@ sampler LosSampler = sampler_state { Texture = <xLosTexture>; };
float4 main(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float4 sampleColor = xTexture.Sample(TextureSampler, texCoord);
float4 losColor = xLosTexture.Sample(LosSampler, texCoord);
float4 sample = xTexture.Sample(TextureSampler, texCoord);
float4 outColor = float4(sample.x*losColor.x, sample.y*losColor.x, sample.z*losColor.x, losColor.x);
float obscureAmount = 1.0f - losColor.r;
float4 outColor = float4(
sampleColor.r * color.r,
sampleColor.g * color.g,
sampleColor.b * color.b,
obscureAmount);
return outColor;
}