Browse files

Reimplement voxel rendering with a FBO.

  • Loading branch information...
1 parent 2215f74 commit c5337cdcf33f20d36c14af7c0537d8e924bc1900 @pchote pchote committed Jun 12, 2013
View
15 OpenRA.FileFormats/Graphics/HvaReader.cs
@@ -19,7 +19,7 @@ public class HvaReader
{
public readonly uint FrameCount;
public readonly uint LimbCount;
- float[] Transforms;
+ public readonly float[] Transforms;
public HvaReader(Stream s)
{
@@ -48,19 +48,6 @@ public HvaReader(Stream s)
}
}
- public float[] TransformationMatrix(uint limb, uint frame)
- {
- if (frame >= FrameCount)
- throw new ArgumentOutOfRangeException("frame", "Only {0} frames exist.".F(FrameCount));
- if (limb >= LimbCount)
- throw new ArgumentOutOfRangeException("limb", "Only {1} limbs exist.".F(LimbCount));
-
- var t = new float[16];
- Array.Copy(Transforms, 16*(LimbCount*frame + limb), t, 0, 16);
-
- return t;
- }
-
public static HvaReader Load(string filename)
{
using (var s = File.OpenRead(filename))
View
2 OpenRA.Game/Graphics/Renderer.cs
@@ -49,7 +49,7 @@ public Renderer()
WorldSpriteRenderer = new SpriteRenderer(this, device.CreateShader("shp"));
WorldRgbaSpriteRenderer = new SpriteRenderer(this, device.CreateShader("rgba"));
WorldLineRenderer = new LineRenderer(this, device.CreateShader("line"));
- WorldVoxelRenderer = new VoxelRenderer(this, device.CreateShader("vxl"), device.CreateShader("vxlshadow"));
+ WorldVoxelRenderer = new VoxelRenderer(this, device.CreateShader("vxl"));
LineRenderer = new LineRenderer(this, device.CreateShader("line"));
WorldQuadRenderer = new QuadRenderer(this, device.CreateShader("line"));
RgbaSpriteRenderer = new SpriteRenderer(this, device.CreateShader("rgba"));
View
115 OpenRA.Game/Graphics/Voxel.cs
@@ -27,19 +27,22 @@ struct Limb
public class Voxel
{
- Limb[] limbs;
- HvaReader hva;
- VoxelLoader loader;
+ Limb[] limbData;
+ float[] transforms;
- float[][] transform, lightDirection, groundNormal;
- float[] groundZ;
+ public readonly uint Frames;
+ public readonly uint Limbs;
public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva)
{
- this.hva = hva;
- this.loader = loader;
+ if (vxl.LimbCount != hva.LimbCount)
+ throw new InvalidOperationException("Voxel and hva limb counts don't match");
- limbs = new Limb[vxl.LimbCount];
+ transforms = hva.Transforms;
+ Frames = hva.FrameCount;
+ Limbs = hva.LimbCount;
+
+ limbData = new Limb[vxl.LimbCount];
for (var i = 0; i < vxl.LimbCount; i++)
{
var vl = vxl.Limbs[i];
@@ -48,53 +51,20 @@ public Voxel(VoxelLoader loader, VxlReader vxl, HvaReader hva)
l.Bounds = (float[])vl.Bounds.Clone();
l.Size = (byte[])vl.Size.Clone();
l.RenderData = loader.GenerateRenderData(vxl.Limbs[i]);
- limbs[i] = l;
+ limbData[i] = l;
}
-
- transform = new float[vxl.LimbCount][];
- lightDirection = new float[vxl.LimbCount][];
- groundNormal = new float[vxl.LimbCount][];
- groundZ = new float[vxl.LimbCount];
- }
-
- // Extract the rotation components from a matrix and apply them to a vector
- static float[] ExtractRotationVector(float[] mtx, WVec vec)
- {
- var tVec = Util.MatrixVectorMultiply(mtx, new float[] {vec.X, vec.Y, vec.Z, 1});
- var tOrigin = Util.MatrixVectorMultiply(mtx, new float[] {0,0,0,1});
- tVec[0] -= tOrigin[0]*tVec[3]/tOrigin[3];
- tVec[1] -= tOrigin[1]*tVec[3]/tOrigin[3];
- tVec[2] -= tOrigin[2]*tVec[3]/tOrigin[3];
-
- // Renormalize
- var w = (float)Math.Sqrt(tVec[0]*tVec[0] + tVec[1]*tVec[1] + tVec[2]*tVec[2]);
- tVec[0] /= w;
- tVec[1] /= w;
- tVec[2] /= w;
- tVec[3] = 1f;
-
- return tVec;
- }
-
- public void Draw(VoxelRenderer r, float[] lightAmbientColor, float[] lightDiffuseColor,
- int colorPalette, int normalsPalette)
- {
- for (var i = 0; i < limbs.Length; i++)
- r.Render(loader, limbs[i].RenderData, transform[i], lightDirection[i],
- lightAmbientColor, lightDiffuseColor, colorPalette, normalsPalette);
}
- public void DrawShadow(VoxelRenderer r, int shadowPalette)
+ public float[] TransformationMatrix(uint limb, uint frame)
{
- for (var i = 0; i < limbs.Length; i++)
- r.RenderShadow(loader, limbs[i].RenderData, transform[i], lightDirection[i],
- groundNormal[i], groundZ[i], shadowPalette);
- }
+ if (frame >= Frames)
+ throw new ArgumentOutOfRangeException("frame", "Only {0} frames exist.".F(Frames));
+ if (limb >= Limbs)
+ throw new ArgumentOutOfRangeException("limb", "Only {1} limbs exist.".F(Limbs));
- float[] TransformationMatrix(uint limb, uint frame)
- {
- var l = limbs[limb];
- var t = hva.TransformationMatrix(limb, frame);
+ var l = limbData[limb];
+ var t = new float[16];
+ Array.Copy(transforms, 16*(Limbs*frame + limb), t, 0, 16);
// Fix limb position
t[12] *= l.Scale*(l.Bounds[3] - l.Bounds[0]) / l.Size[0];
@@ -108,46 +78,16 @@ float[] TransformationMatrix(uint limb, uint frame)
return t;
}
- static readonly WVec forward = new WVec(1024,0,0);
- static readonly WVec up = new WVec(0,0,1024);
- public void PrepareForDraw(WorldRenderer wr, WPos pos, IEnumerable<WRot> rotations,
- WRot camera, uint frame, float scale, WRot lightSource)
+ public VoxelRenderData RenderData(uint limb)
{
- // Calculate the shared view matrix components
- var pxPos = wr.ScreenPosition(pos);
- var posMtx = Util.TranslationMatrix(pxPos.X, pxPos.Y, pxPos.Y);
- var scaleMtx = Util.ScaleMatrix(scale, scale, scale);
- var rotMtx = rotations.Reverse().Aggregate(Util.MakeFloatMatrix(camera.AsMatrix()),
- (a,b) => Util.MatrixMultiply(a, Util.MakeFloatMatrix(b.AsMatrix())));
-
- // Each limb has its own transformation matrix
- for (uint i = 0; i < limbs.Length; i++)
- {
- var t = TransformationMatrix(i, frame);
- transform[i] = Util.MatrixMultiply(rotMtx, t);
- transform[i] = Util.MatrixMultiply(scaleMtx, transform[i]);
- transform[i] = Util.MatrixMultiply(posMtx, transform[i]);
-
- // Transform light direction into limb-space
- var undoPitch = Util.MakeFloatMatrix(new WRot(camera.Pitch, WAngle.Zero, WAngle.Zero).AsMatrix());
- var lightTransform = Util.MatrixMultiply(Util.MatrixInverse(transform[i]), undoPitch);
-
- lightDirection[i] = ExtractRotationVector(lightTransform, forward.Rotate(lightSource));
- groundNormal[i] = ExtractRotationVector(Util.MatrixInverse(t), up);
-
- // Hack: Extract the ground z position independently of y.
- groundZ[i] = (wr.ScreenPosition(pos).Y - wr.ScreenZPosition(pos, 0)) / 2;
- }
+ return limbData[limb].RenderData;
}
- public uint Frames { get { return hva.FrameCount; }}
- public uint LimbCount { get { return (uint)limbs.Length; }}
-
public float[] Size
{
get
{
- return limbs.Select(a => a.Size.Select(b => a.Scale*b).ToArray())
+ return limbData.Select(a => a.Size.Select(b => a.Scale*b).ToArray())
.Aggregate((a,b) => new float[]
{
Math.Max(a[0], b[0]),
@@ -162,14 +102,15 @@ public float[] Bounds(uint frame)
var ret = new float[] {float.MaxValue,float.MaxValue,float.MaxValue,
float.MinValue,float.MinValue,float.MinValue};
- for (uint j = 0; j < limbs.Length; j++)
+ for (uint j = 0; j < Limbs; j++)
{
+ var l = limbData[j];
var b = new float[]
{
0, 0, 0,
- (limbs[j].Bounds[3] - limbs[j].Bounds[0]),
- (limbs[j].Bounds[4] - limbs[j].Bounds[1]),
- (limbs[j].Bounds[5] - limbs[j].Bounds[2])
+ (l.Bounds[3] - l.Bounds[0]),
+ (l.Bounds[4] - l.Bounds[1]),
+ (l.Bounds[5] - l.Bounds[2])
};
// Calculate limb bounding box
View
145 OpenRA.Game/Graphics/VoxelRenderable.cs
@@ -29,6 +29,9 @@ public struct VoxelRenderable : IRenderable
readonly PaletteReference shadowPalette;
readonly float scale;
+ // Generated at render-time
+ VoxelRenderProxy renderProxy;
+
public VoxelRenderable(IEnumerable<VoxelAnimation> voxels, WPos pos, int zOffset, WRot camera, float scale,
WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
PaletteReference color, PaletteReference normals, PaletteReference shadow)
@@ -44,6 +47,7 @@ public struct VoxelRenderable : IRenderable
this.palette = color;
this.normalsPalette = normals;
this.shadowPalette = shadow;
+ this.renderProxy = null;
}
public WPos Pos { get { return pos; } }
@@ -79,133 +83,72 @@ public IRenderable WithPos(WPos newPos)
palette, normalsPalette, shadowPalette);
}
- public void BeforeRender(WorldRenderer wr) {}
- public void Render(WorldRenderer wr)
+ // This will need generalizing once we support TS/RA2 terrain
+ static readonly float[] groundNormal = new float[] {0,0,1,1};
+ public void BeforeRender(WorldRenderer wr)
{
- // Depth and shadow buffers are cleared between actors so that
- // overlapping units and shadows behave like overlapping sprites.
- var vr = Game.Renderer.WorldVoxelRenderer;
- var draw = voxels.Where(v => v.DisableFunc == null || !v.DisableFunc());
-
- foreach (var v in draw)
- v.Voxel.PrepareForDraw(wr, pos + v.OffsetFunc(), v.RotationFunc(), camera,
- v.FrameFunc(), scale, lightSource);
-
- Game.Renderer.EnableDepthBuffer();
- Game.Renderer.EnableStencilBuffer();
- foreach (var v in draw)
- v.Voxel.DrawShadow(vr, shadowPalette.Index);
- Game.Renderer.DisableStencilBuffer();
- Game.Renderer.DisableDepthBuffer();
+ renderProxy = Game.Renderer.WorldVoxelRenderer.RenderAsync(
+ wr, voxels, camera, scale, groundNormal, lightSource,
+ lightAmbientColor, lightDiffuseColor,
+ palette, normalsPalette, shadowPalette);
+ }
- Game.Renderer.EnableDepthBuffer();
- foreach (var v in draw)
- v.Voxel.Draw(vr, lightAmbientColor, lightDiffuseColor, palette.Index, normalsPalette.Index);
- Game.Renderer.DisableDepthBuffer();
+ public void Render(WorldRenderer wr)
+ {
+ var pxOrigin = wr.ScreenPosition(pos);
+ var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0));
+ var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1));
+
+ var psb = renderProxy.ProjectedShadowBounds;
+ var sa = shadowOrigin + psb[0];
+ var sb = shadowOrigin + psb[2];
+ var sc = shadowOrigin + psb[1];
+ var sd = shadowOrigin + psb[3];
+ Game.Renderer.WorldRgbaSpriteRenderer.DrawSprite(renderProxy.ShadowSprite, sa, sb, sc, sd);
+ Game.Renderer.WorldRgbaSpriteRenderer.DrawSprite(renderProxy.Sprite, pxOrigin - 0.5f*renderProxy.Sprite.size);
}
public void RenderDebugGeometry(WorldRenderer wr)
{
- var draw = voxels.Where(v => v.DisableFunc == null || !v.DisableFunc());
- var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
var pxOrigin = wr.ScreenPosition(pos);
+ var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0));
+ var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1));
- // Correct for bogus light source definition
- var shadowTransform = Util.MakeFloatMatrix(new WRot(new WAngle(256) - lightSource.Pitch,
- WAngle.Zero, lightSource.Yaw + new WAngle(512)).AsMatrix());
-
- var invShadowTransform = Util.MatrixInverse(shadowTransform);
- var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
-
- // TODO: Generalize this once we support sloped terrain
- var groundNormal = new float[] {0,0,1,1};
- var groundPos = new float[] {0, 0, 0.5f*(wr.ScreenPosition(pos).Y - wr.ScreenZPosition(pos, 0)), 1};
- var shadowGroundNormal = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
- var shadowGroundPos = Util.MatrixVectorMultiply(shadowTransform, groundPos);
+ // Draw sprite rect
+ var offset = pxOrigin + renderProxy.Sprite.offset - 0.5f*renderProxy.Sprite.size;
+ Game.Renderer.WorldLineRenderer.DrawRect(offset, offset + renderProxy.Sprite.size, Color.Red);
- // Sprite rectangle
- var tl = new float2(float.MaxValue, float.MaxValue);
- var br = new float2(float.MinValue, float.MinValue);
+ // Draw transformed shadow sprite rect
+ var c = Color.Purple;
+ var psb = renderProxy.ProjectedShadowBounds;
+ Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[1], shadowOrigin + psb[3], c, c);
+ Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[3], shadowOrigin + psb[0], c, c);
+ Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[0], shadowOrigin + psb[2], c, c);
+ Game.Renderer.WorldLineRenderer.DrawLine(shadowOrigin + psb[2], shadowOrigin + psb[1], c, c);
- // Shadow sprite rectangle
- var stl = new float2(float.MaxValue, float.MaxValue);
- var sbr = new float2(float.MinValue, float.MinValue);
+ // Draw voxel bounding box
+ var draw = voxels.Where(v => v.DisableFunc == null || !v.DisableFunc());
+ var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
+ var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
foreach (var v in draw)
{
var bounds = v.Voxel.Bounds(v.FrameFunc());
var worldTransform = v.RotationFunc().Reverse().Aggregate(scaleTransform,
(x,y) => Util.MatrixMultiply(x, Util.MakeFloatMatrix(y.AsMatrix())));
- var worldBounds = Util.MatrixAABBMultiply(worldTransform, bounds);
- var screenBounds = Util.MatrixAABBMultiply(cameraTransform, worldBounds);
-
- // Aggregate bounds rect
var pxOffset = wr.ScreenVector(v.OffsetFunc());
var pxPos = pxOrigin + new float2(pxOffset[0], pxOffset[1]);
- tl = float2.Min(tl, pxPos + new float2(screenBounds[0], screenBounds[1]));
- br = float2.Max(br, pxPos + new float2(screenBounds[3], screenBounds[4]));
-
- // Box to render the shadow image from
- var shadowBounds = Util.MatrixAABBMultiply(shadowTransform, worldBounds);
- var shadowPxOffset = Util.MatrixVectorMultiply(shadowTransform, pxOffset);
-
- stl = float2.Min(stl, new float2(shadowPxOffset[0] + shadowBounds[0], shadowPxOffset[1] + shadowBounds[1]));
- sbr = float2.Max(sbr, new float2(shadowPxOffset[0] + shadowBounds[3], shadowPxOffset[1] + shadowBounds[4]));
-
- // Draw voxel bounding box
var screenTransform = Util.MatrixMultiply(cameraTransform, worldTransform);
DrawBoundsBox(pxPos, screenTransform, bounds, Color.Yellow);
}
-
- // Inflate rects by 1px each side to ensure rendering is within bounds
- var pad = new float2(1,1);
- tl -= pad;
- br += pad;
- stl -= pad;
- sbr += pad;
-
- // Corners of the shadow quad, in shadow-space
- var corners = new float[][]
- {
- new float[] {stl.X, stl.Y, 0, 1},
- new float[] {sbr.X, sbr.Y, 0, 1},
- new float[] {sbr.X, stl.Y, 0, 1},
- new float[] {stl.X, sbr.Y, 0, 1}
- };
-
- var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
- var screenCorners = new float2[4];
- for (var j = 0; j < 4; j++)
- {
- // Project to ground plane
- corners[j][2] -= (corners[j][2] - shadowGroundPos[2]) +
- (corners[j][1] - shadowGroundPos[1])*shadowGroundNormal[1]/shadowGroundNormal[2] +
- (corners[j][0] - shadowGroundPos[0])*shadowGroundNormal[0]/shadowGroundNormal[2];
-
- // Rotate to camera-space
- corners[j] = Util.MatrixVectorMultiply(shadowScreenTransform, corners[j]);
- screenCorners[j] = pxOrigin + new float2(corners[j][0], corners[j][1]);
- }
-
- // Draw transformed shadow sprite rect
- var c = Color.Purple;
- Game.Renderer.WorldLineRenderer.DrawLine(screenCorners[1], screenCorners[3], c, c);
- Game.Renderer.WorldLineRenderer.DrawLine(screenCorners[3], screenCorners[0], c, c);
- Game.Renderer.WorldLineRenderer.DrawLine(screenCorners[0], screenCorners[2], c, c);
- Game.Renderer.WorldLineRenderer.DrawLine(screenCorners[2], screenCorners[1], c, c);
-
- // Draw sprite rect
- Game.Renderer.WorldLineRenderer.DrawRect(tl, br, Color.Red);
}
+ static readonly uint[] ix = new uint[] {0,0,0,0,3,3,3,3};
+ static readonly uint[] iy = new uint[] {1,1,4,4,1,1,4,4};
+ static readonly uint[] iz = new uint[] {2,5,2,5,2,5,2,5};
static void DrawBoundsBox(float2 pxPos, float[] transform, float[] bounds, Color c)
{
- // Corner offsets
- var ix = new uint[] {0,0,0,0,3,3,3,3};
- var iy = new uint[] {1,1,4,4,1,1,4,4};
- var iz = new uint[] {2,5,2,5,2,5,2,5};
-
var corners = new float2[8];
for (var i = 0; i < 8; i++)
{
View
328 OpenRA.Game/Graphics/VoxelRenderer.cs
@@ -17,72 +17,326 @@
namespace OpenRA.Graphics
{
+ public class VoxelRenderProxy
+ {
+ public readonly Sprite Sprite;
+ public readonly Sprite ShadowSprite;
+ public readonly float ShadowDirection;
+ public readonly float2[] ProjectedShadowBounds;
+
+ public VoxelRenderProxy(Sprite sprite, Sprite shadowSprite, float2[] projectedShadowBounds, float shadowDirection)
+ {
+ Sprite = sprite;
+ ShadowSprite = shadowSprite;
+ ProjectedShadowBounds = projectedShadowBounds;
+ ShadowDirection = shadowDirection;
+ }
+ }
+
public class VoxelRenderer
{
Renderer renderer;
IShader shader;
- IShader shadowShader;
- public VoxelRenderer(Renderer renderer, IShader shader, IShader shadowShader)
+ SheetBuilder sheetBuilder;
+ Dictionary<Sheet, IFrameBuffer> mappedBuffers;
+ Stack<KeyValuePair<Sheet, IFrameBuffer>> unmappedBuffers;
+ List<Pair<Sheet, Action>> doRender;
+
+ // Static constants
+ static readonly float[] shadowDiffuse = new float[] {0,0,0};
+ static readonly float[] shadowAmbient = new float[] {1,1,1};
+ static readonly float2 spritePadding = new float2(2, 2);
+ static readonly float[] zeroVector = new float[] {0,0,0,1};
+ static readonly float[] zVector = new float[] {0,0,1,1};
+ static readonly float[] flipMtx = Util.ScaleMatrix(1, -1, 1);
+ static readonly float[] shadowScaleFlipMtx = Util.ScaleMatrix(2, -2, 2);
+
+ public VoxelRenderer(Renderer renderer, IShader shader)
{
this.renderer = renderer;
this.shader = shader;
- this.shadowShader = shadowShader;
+
+ mappedBuffers = new Dictionary<Sheet, IFrameBuffer>();
+ unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
+ doRender = new List<Pair<Sheet, Action>>();
+ }
+
+ public void SetPalette(ITexture palette)
+ {
+ shader.SetTexture("Palette", palette);
+ }
+
+ public void SetViewportParams(Size screen, float zoom, float2 scroll)
+ {
+ var a = 2f / Renderer.SheetSize;
+ var view = new float[]
+ {
+ a, 0, 0, 0,
+ 0, -a, 0, 0,
+ 0, 0, -2*a, 0,
+ -1, 1, 0, 1
+ };
+
+ shader.SetMatrix("View", view);
+ }
+
+ public VoxelRenderProxy RenderAsync(WorldRenderer wr, IEnumerable<VoxelAnimation> voxels, WRot camera, float scale,
+ float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
+ PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
+ {
+ // Correct for inverted y-axis
+ var scaleTransform = Util.ScaleMatrix(scale, scale, scale);
+
+ // Correct for bogus light source definition
+ var lightYaw = Util.MakeFloatMatrix(new WRot(WAngle.Zero, WAngle.Zero, -lightSource.Yaw).AsMatrix());
+ var lightPitch = Util.MakeFloatMatrix(new WRot(WAngle.Zero, -lightSource.Pitch, WAngle.Zero).AsMatrix());
+ var shadowTransform = Util.MatrixMultiply(lightPitch, lightYaw);
+
+ var invShadowTransform = Util.MatrixInverse(shadowTransform);
+ var cameraTransform = Util.MakeFloatMatrix(camera.AsMatrix());
+ var invCameraTransform = Util.MatrixInverse(cameraTransform);
+
+ // Sprite rectangle
+ var tl = new float2(float.MaxValue, float.MaxValue);
+ var br = new float2(float.MinValue, float.MinValue);
+
+ // Shadow sprite rectangle
+ var stl = new float2(float.MaxValue, float.MaxValue);
+ var sbr = new float2(float.MinValue, float.MinValue);
+
+ foreach (var v in voxels)
+ {
+ // Convert screen offset back to world coords
+ var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
+ var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
+
+ var worldTransform = v.RotationFunc().Aggregate(Util.IdentityMatrix(),
+ (x,y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
+ worldTransform = Util.MatrixMultiply(scaleTransform, worldTransform);
+ worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
+
+ var bounds = v.Voxel.Bounds(v.FrameFunc());
+ var worldBounds = Util.MatrixAABBMultiply(worldTransform, bounds);
+ var screenBounds = Util.MatrixAABBMultiply(cameraTransform, worldBounds);
+ var shadowBounds = Util.MatrixAABBMultiply(shadowTransform, worldBounds);
+
+ // Aggregate bounds rects
+ tl = float2.Min(tl, new float2(screenBounds[0], screenBounds[1]));
+ br = float2.Max(br, new float2(screenBounds[3], screenBounds[4]));
+ stl = float2.Min(stl, new float2(shadowBounds[0], shadowBounds[1]));
+ sbr = float2.Max(sbr, new float2(shadowBounds[3], shadowBounds[4]));
+ }
+
+ // Inflate rects to ensure rendering is within bounds
+ tl -= spritePadding;
+ br += spritePadding;
+ stl -= spritePadding;
+ sbr += spritePadding;
+
+ // Corners of the shadow quad, in shadow-space
+ var corners = new float[][]
+ {
+ new float[] {stl.X, stl.Y, 0, 1},
+ new float[] {sbr.X, sbr.Y, 0, 1},
+ new float[] {sbr.X, stl.Y, 0, 1},
+ new float[] {stl.X, sbr.Y, 0, 1}
+ };
+
+ var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
+ var shadowGroundNormal = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
+ var screenCorners = new float2[4];
+ for (var j = 0; j < 4; j++)
+ {
+ // Project to ground plane
+ corners[j][2] = -(corners[j][1]*shadowGroundNormal[1]/shadowGroundNormal[2] +
+ corners[j][0]*shadowGroundNormal[0]/shadowGroundNormal[2]);
+
+ // Rotate to camera-space
+ corners[j] = Util.MatrixVectorMultiply(shadowScreenTransform, corners[j]);
+ screenCorners[j] = new float2(corners[j][0], corners[j][1]);
+ }
+
+ // Shadows are rendered at twice the resolution to reduce artefacts
+ Size spriteSize, shadowSpriteSize;
+ int2 spriteOffset, shadowSpriteOffset;
+ CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
+ CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);
+
+ var sprite = sheetBuilder.Allocate(spriteSize, spriteOffset);
+ var shadowSprite = sheetBuilder.Allocate(shadowSpriteSize, shadowSpriteOffset);
+ var sb = sprite.bounds;
+ var ssb = shadowSprite.bounds;
+ var spriteCenter = new float2(sb.Left + sb.Width / 2, sb.Top + sb.Height / 2);
+ var shadowCenter = new float2(ssb.Left + ssb.Width / 2, ssb.Top + ssb.Height / 2);
+
+ var translateMtx = Util.TranslationMatrix(spriteCenter.X - spriteOffset.X, Renderer.SheetSize - (spriteCenter.Y - spriteOffset.Y), 0);
+ var shadowTranslateMtx = Util.TranslationMatrix(shadowCenter.X - shadowSpriteOffset.X, Renderer.SheetSize - (shadowCenter.Y - shadowSpriteOffset.Y), 0);
+ var correctionTransform = Util.MatrixMultiply(translateMtx, flipMtx);
+ var shadowCorrectionTransform = Util.MatrixMultiply(shadowTranslateMtx, shadowScaleFlipMtx);
+
+ doRender.Add(Pair.New<Sheet, Action>(sprite.sheet, () =>
+ {
+ foreach (var v in voxels)
+ {
+ // Convert screen offset to world offset
+ var offsetVec = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(v.OffsetFunc()));
+ var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);
+
+ var rotations = v.RotationFunc().Aggregate(Util.IdentityMatrix(),
+ (x,y) => Util.MatrixMultiply(Util.MakeFloatMatrix(y.AsMatrix()), x));
+ var worldTransform = Util.MatrixMultiply(scaleTransform, rotations);
+ worldTransform = Util.MatrixMultiply(offsetTransform, worldTransform);
+
+ var transform = Util.MatrixMultiply(cameraTransform, worldTransform);
+ transform = Util.MatrixMultiply(correctionTransform, transform);
+
+ var shadow = Util.MatrixMultiply(shadowTransform, worldTransform);
+ shadow = Util.MatrixMultiply(shadowCorrectionTransform, shadow);
+
+ var lightTransform = Util.MatrixMultiply(Util.MatrixInverse(rotations), invShadowTransform);
+
+ var frame = v.FrameFunc();
+ for (uint i = 0; i < v.Voxel.Limbs; i++)
+ {
+ var rd = v.Voxel.RenderData(i);
+ var t = v.Voxel.TransformationMatrix(i, frame);
+
+ // Transform light vector from shadow -> world -> limb coords
+ var lightDirection = ExtractRotationVector(Util.MatrixMultiply(Util.MatrixInverse(t), lightTransform));
+
+ Render(rd, Util.MatrixMultiply(transform, t), lightDirection,
+ lightAmbientColor, lightDiffuseColor, color.Index, normals.Index);
+
+ // Disable shadow normals by forcing zero diffuse and identity ambient light
+ Render(rd, Util.MatrixMultiply(shadow, t), lightDirection,
+ shadowAmbient, shadowDiffuse, shadowPalette.Index, normals.Index);
+ }
+ }
+ }));
+
+ var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, zVector);
+ screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
+ return new VoxelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2]/screenLightVector[1]);
}
- public void Render(VoxelLoader loader, VoxelRenderData renderData,
- float[] t, float[] lightDirection,
- float[] ambientLight, float[] diffuseLight,
- int colorPalette, int normalsPalette)
+ static void CalculateSpriteGeometry(float2 tl, float2 br, float scale, out Size size, out int2 offset)
+ {
+ var width = (int)(scale*(br.X - tl.X));
+ var height = (int)(scale*(br.Y - tl.Y));
+ offset = (0.5f*scale*(br + tl)).ToInt2();
+
+ // Width and height must be even to avoid rendering glitches
+ if ((width & 1) == 1)
+ width += 1;
+ if ((height & 1) == 1)
+ height += 1;
+
+ size = new Size(width, height);
+ }
+
+ static float[] ExtractRotationVector(float[] mtx)
+ {
+ var tVec = Util.MatrixVectorMultiply(mtx, zVector);
+ var tOrigin = Util.MatrixVectorMultiply(mtx, zeroVector);
+ tVec[0] -= tOrigin[0]*tVec[3]/tOrigin[3];
+ tVec[1] -= tOrigin[1]*tVec[3]/tOrigin[3];
+ tVec[2] -= tOrigin[2]*tVec[3]/tOrigin[3];
+
+ // Renormalize
+ var w = (float)Math.Sqrt(tVec[0]*tVec[0] + tVec[1]*tVec[1] + tVec[2]*tVec[2]);
+ tVec[0] /= w;
+ tVec[1] /= w;
+ tVec[2] /= w;
+ tVec[3] = 1f;
+
+ return tVec;
+ }
+
+ void Render(VoxelRenderData renderData,
+ float[] t, float[] lightDirection,
+ float[] ambientLight, float[] diffuseLight,
+ int colorPalette, int normalsPalette)
{
shader.SetTexture("DiffuseTexture", renderData.Sheet.Texture);
shader.SetVec("PaletteRows", (colorPalette + 0.5f) / HardwarePalette.MaxPalettes,
- (normalsPalette + 0.5f) / HardwarePalette.MaxPalettes);
+ (normalsPalette + 0.5f) / HardwarePalette.MaxPalettes);
shader.SetMatrix("TransformMatrix", t);
shader.SetVec("LightDirection", lightDirection, 4);
shader.SetVec("AmbientLight", ambientLight, 3);
shader.SetVec("DiffuseLight", diffuseLight, 3);
- shader.Render(() => renderer.DrawBatch(loader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.QuadList));
+
+ shader.Render(() => renderer.DrawBatch(Game.modData.VoxelLoader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.QuadList));
}
- public void RenderShadow(VoxelLoader loader, VoxelRenderData renderData,
- float[] t, float[] lightDirection, float[] groundNormal, float groundZ, int colorPalette)
+ public void BeginFrame()
{
- shadowShader.SetTexture("DiffuseTexture", renderData.Sheet.Texture);
- shadowShader.SetVec("PaletteRows", (colorPalette + 0.5f) / HardwarePalette.MaxPalettes, 0);
- shadowShader.SetMatrix("TransformMatrix", t);
- shadowShader.SetVec("LightDirection", lightDirection, 4);
- shadowShader.SetVec("GroundNormal", groundNormal, 3);
- shadowShader.SetVec("GroundZ", groundZ);
- shadowShader.Render(() => renderer.DrawBatch(loader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.QuadList));
+ foreach (var kv in mappedBuffers)
+ unmappedBuffers.Push(kv);
+ mappedBuffers.Clear();
+
+ sheetBuilder = new SheetBuilder(SheetType.BGRA, AllocateSheet);
+ doRender.Clear();
}
- public void SetPalette(ITexture palette)
+ IFrameBuffer EnableFrameBuffer(Sheet s)
{
- shader.SetTexture("Palette", palette);
- shadowShader.SetTexture("Palette", palette);
+ var fbo = mappedBuffers[s];
+ Game.Renderer.Flush();
+ fbo.Bind();
+
+ Game.Renderer.Device.EnableDepthBuffer();
+ return fbo;
}
- public void SetViewportParams(Size screen, float zoom, float2 scroll)
+ void DisableFrameBuffer(IFrameBuffer fbo)
{
- // Construct projection matrix
- // Clip planes are set at -height and +2*height
+ Game.Renderer.Flush();
+ Game.Renderer.Device.DisableDepthBuffer();
+ fbo.Unbind();
+ }
- var tiw = 2*zoom / screen.Width;
- var tih = 2*zoom / screen.Height;
- var view = new float[]
+ public void EndFrame()
+ {
+ if (doRender.Count == 0)
+ return;
+
+ Sheet currentSheet = null;
+ IFrameBuffer fbo = null;
+ foreach (var v in doRender)
{
- tiw, 0, 0, 0,
- 0, -tih, 0, 0,
- 0, 0, -tih/3, 0,
- -1 - tiw*scroll.X,
- 1 + tih*scroll.Y,
- 1 + tih*scroll.Y/3,
- 1
- };
+ // Change sheet
+ if (v.First != currentSheet)
+ {
+ if (fbo != null)
+ DisableFrameBuffer(fbo);
- shader.SetMatrix("View", view);
- shadowShader.SetMatrix("View", view);
+ currentSheet = v.First;
+ fbo = EnableFrameBuffer(currentSheet);
+ }
+
+ v.Second();
+ }
+
+ DisableFrameBuffer(fbo);
+ }
+
+ public Sheet AllocateSheet()
+ {
+ // Reuse cached fbo
+ if (unmappedBuffers.Count > 0)
+ {
+ var kv = unmappedBuffers.Pop();
+ mappedBuffers.Add(kv.Key, kv.Value);
+ return kv.Key;
+ }
+
+ var size = new Size(Renderer.SheetSize, Renderer.SheetSize);
+ var framebuffer = renderer.Device.CreateFrameBuffer(size);
+ var sheet = new Sheet(framebuffer.Texture);
+ mappedBuffers.Add(sheet, framebuffer);
+
+ return sheet;
}
}
}
View
3 OpenRA.Game/Graphics/WorldRenderer.cs
@@ -87,8 +87,11 @@ List<IRenderable> GenerateRenderables()
// Iterating via foreach() copies the structs, so enumerate by index
var renderables = worldRenderables.Concat(effectRenderables).ToList();
+
+ Game.Renderer.WorldVoxelRenderer.BeginFrame();
for (var i = 0; i < renderables.Count; i++)
renderables[i].BeforeRender(this);
+ Game.Renderer.WorldVoxelRenderer.EndFrame();
return renderables;
}
View
7 OpenRA.Mods.RA/Render/RenderVoxels.cs
@@ -32,11 +32,10 @@ public class RenderVoxelsInfo : ITraitInfo, Requires<IBodyOrientationInfo>
[Desc("Change the image size.")]
public readonly float Scale = 10;
- public readonly WAngle LightPitch = new WAngle(170); // 60 degrees
- public readonly WAngle LightYaw = new WAngle(739); // 260 degrees
+ public readonly WAngle LightPitch = WAngle.FromDegrees(50);
+ public readonly WAngle LightYaw = WAngle.FromDegrees(240);
public readonly float[] LightAmbientColor = new float[] {0.6f, 0.6f, 0.6f};
public readonly float[] LightDiffuseColor = new float[] {0.4f, 0.4f, 0.4f};
-
public virtual object Create(ActorInitializer init) { return new RenderVoxels(init.self, this); }
}
@@ -55,7 +54,7 @@ public RenderVoxels(Actor self, RenderVoxelsInfo info)
this.info = info;
body = self.Trait<IBodyOrientation>();
camera = new WRot(WAngle.Zero, body.CameraPitch - new WAngle(256), new WAngle(256));
- lightSource = new WRot(WAngle.Zero, info.LightPitch, info.LightYaw - new WAngle(256));
+ lightSource = new WRot(WAngle.Zero,new WAngle(256) - info.LightPitch, info.LightYaw);
}
bool initializePalettes = true;
View
90 cg/vxlshadow.fx
@@ -1,90 +0,0 @@
-mat4x4 View;
-mat4x4 TransformMatrix;
-float4 LightDirection;
-float GroundZ;
-float3 GroundNormal;
-
-float2 PaletteRows;
-float3 AmbientLight, DiffuseLight;
-
-sampler2D DiffuseTexture = sampler_state {
- MinFilter = Nearest;
- MagFilter = Nearest;
- WrapS = Repeat;
- WrapT = Repeat;
-};
-
-sampler2D Palette = sampler_state {
- MinFilter = Nearest;
- MagFilter = Nearest;
- WrapS = Repeat;
- WrapT = Repeat;
-};
-
-struct VertexIn {
- float4 Position: POSITION;
- float4 Tex0: TEXCOORD0;
-};
-
-struct VertexOut {
- float4 Position: POSITION;
- float2 Tex0: TEXCOORD0;
- float4 ColorChannel: TEXCOORD1;
- float4 NormalsChannel: TEXCOORD2;
-};
-
-float4 DecodeChannelMask(float x)
-{
- if (x > 0)
- return (x > 0.5f) ? float4(1,0,0,0) : float4(0,1,0,0);
- else
- return (x <-0.5f) ? float4(0,0,0,1) : float4(0,0,1,0);
-}
-
-VertexOut Simple_vp(VertexIn v) {
- // Distance between vertex and ground
- float d = dot(v.Position.xyz - float3(0.0,0.0,GroundZ), GroundNormal) / dot(LightDirection.xyz, GroundNormal);
- float3 shadow = v.Position.xyz - d*LightDirection.xyz;
-
- VertexOut o;
- o.Position = mul(mul(vec4(shadow, 1), TransformMatrix), View);
- o.Tex0 = v.Tex0.xy;
- o.ColorChannel = DecodeChannelMask(v.Tex0.z);
- o.NormalsChannel = DecodeChannelMask(v.Tex0.w);
- return o;
-}
-
-float4 Simple_fp(VertexOut f) : COLOR0 {
- float4 x = tex2D(DiffuseTexture, f.Tex0.xy);
- vec4 color = tex2D(Palette, float2(dot(x, f.ColorChannel), PaletteRows.x));
- if (color.a < 0.01)
- discard;
-
- return color;
-}
-
-technique high_quality {
- pass p0 {
- BlendEnable = true;
- DepthTestEnable = true;
- CullFaceEnable = false;
- VertexProgram = compile latest Simple_vp();
- FragmentProgram = compile latest Simple_fp();
-
- BlendEquation = FuncAdd;
- BlendFunc = int2(SrcAlpha, OneMinusSrcAlpha);
- }
-}
-
-technique high_quality_cg21 {
- pass p0 {
- BlendEnable = true;
- DepthTestEnable = true;
- CullFaceEnable = false;
- VertexProgram = compile arbvp1 Simple_vp();
- FragmentProgram = compile arbfp1 Simple_fp();
-
- BlendEquation = FuncAdd;
- BlendFunc = int2(SrcAlpha, OneMinusSrcAlpha);
- }
-}
View
12 glsl/vxlshadow.frag
@@ -1,12 +0,0 @@
-uniform sampler2D Palette, DiffuseTexture;
-uniform vec2 PaletteRows;
-
-void main()
-{
- vec4 x = texture2D(DiffuseTexture, gl_TexCoord[0].st);
- vec4 color = texture2D(Palette, vec2(dot(x, gl_TexCoord[1]), PaletteRows.x));
- if (color.a < 0.01)
- discard;
-
- gl_FragColor = color;
-}
View
26 glsl/vxlshadow.vert
@@ -1,26 +0,0 @@
-uniform mat4 View;
-uniform mat4 TransformMatrix;
-uniform vec4 LightDirection;
-uniform float GroundZ;
-uniform vec3 GroundNormal;
-
-vec4 DecodeChannelMask(float x)
-{
- if (x > 0.0)
- return (x > 0.5) ? vec4(1,0,0,0) : vec4(0,1,0,0);
- else
- return (x < -0.5) ? vec4(0,0,0,1) : vec4(0,0,1,0);
-}
-
-void main()
-{
- // Distance between vertex and ground
- float d = dot(gl_Vertex.xyz - vec3(0.0,0.0,GroundZ), GroundNormal) / dot(LightDirection.xyz, GroundNormal);
-
- // Project onto ground plane
- vec3 shadow = gl_Vertex.xyz - d*LightDirection.xyz;
- gl_Position = View*TransformMatrix*vec4(shadow, 1);
- gl_TexCoord[0] = gl_MultiTexCoord0;
- gl_TexCoord[1] = DecodeChannelMask(gl_MultiTexCoord0.z);
- gl_TexCoord[2] = DecodeChannelMask(gl_MultiTexCoord0.w);
-}

0 comments on commit c5337cd

Please sign in to comment.