From c5337cdcf33f20d36c14af7c0537d8e924bc1900 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 12 Jun 2013 16:28:01 +1200 Subject: [PATCH] Reimplement voxel rendering with a FBO. --- OpenRA.FileFormats/Graphics/HvaReader.cs | 15 +- OpenRA.Game/Graphics/Renderer.cs | 2 +- OpenRA.Game/Graphics/Voxel.cs | 115 ++------ OpenRA.Game/Graphics/VoxelRenderable.cs | 145 +++------- OpenRA.Game/Graphics/VoxelRenderer.cs | 340 ++++++++++++++++++++--- OpenRA.Game/Graphics/WorldRenderer.cs | 3 + OpenRA.Mods.RA/Render/RenderVoxels.cs | 7 +- cg/vxlshadow.fx | 90 ------ glsl/vxlshadow.frag | 12 - glsl/vxlshadow.vert | 26 -- 10 files changed, 377 insertions(+), 378 deletions(-) delete mode 100644 cg/vxlshadow.fx delete mode 100644 glsl/vxlshadow.frag delete mode 100644 glsl/vxlshadow.vert diff --git a/OpenRA.FileFormats/Graphics/HvaReader.cs b/OpenRA.FileFormats/Graphics/HvaReader.cs index 662be7d8a9..177d6e4b6d 100644 --- a/OpenRA.FileFormats/Graphics/HvaReader.cs +++ b/OpenRA.FileFormats/Graphics/HvaReader.cs @@ -19,7 +19,7 @@ namespace OpenRA.FileFormats { public readonly uint FrameCount; public readonly uint LimbCount; - float[] Transforms; + public readonly float[] Transforms; public HvaReader(Stream s) { @@ -48,19 +48,6 @@ namespace OpenRA.FileFormats } } - 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)) diff --git a/OpenRA.Game/Graphics/Renderer.cs b/OpenRA.Game/Graphics/Renderer.cs index bb803babe8..865d296b93 100644 --- a/OpenRA.Game/Graphics/Renderer.cs +++ b/OpenRA.Game/Graphics/Renderer.cs @@ -49,7 +49,7 @@ namespace OpenRA.Graphics 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")); diff --git a/OpenRA.Game/Graphics/Voxel.cs b/OpenRA.Game/Graphics/Voxel.cs index 9d9efd58cd..98d6f72544 100644 --- a/OpenRA.Game/Graphics/Voxel.cs +++ b/OpenRA.Game/Graphics/Voxel.cs @@ -27,19 +27,22 @@ namespace OpenRA.Graphics 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 @@ namespace OpenRA.Graphics 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) + public float[] TransformationMatrix(uint limb, uint frame) { - 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]; + 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)); - // 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) - { - for (var i = 0; i < limbs.Length; i++) - r.RenderShadow(loader, limbs[i].RenderData, transform[i], lightDirection[i], - groundNormal[i], groundZ[i], shadowPalette); - } - - 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 @@ namespace OpenRA.Graphics 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 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 @@ namespace OpenRA.Graphics 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 diff --git a/OpenRA.Game/Graphics/VoxelRenderable.cs b/OpenRA.Game/Graphics/VoxelRenderable.cs index 36b4eab252..525b7161c3 100644 --- a/OpenRA.Game/Graphics/VoxelRenderable.cs +++ b/OpenRA.Game/Graphics/VoxelRenderable.cs @@ -29,6 +29,9 @@ namespace OpenRA.Graphics readonly PaletteReference shadowPalette; readonly float scale; + // Generated at render-time + VoxelRenderProxy renderProxy; + public VoxelRenderable(IEnumerable 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 @@ namespace OpenRA.Graphics this.palette = color; this.normalsPalette = normals; this.shadowPalette = shadow; + this.renderProxy = null; } public WPos Pos { get { return pos; } } @@ -79,133 +83,72 @@ namespace OpenRA.Graphics palette, normalsPalette, shadowPalette); } - public void BeforeRender(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) + { + renderProxy = Game.Renderer.WorldVoxelRenderer.RenderAsync( + wr, voxels, camera, scale, groundNormal, lightSource, + lightAmbientColor, lightDiffuseColor, + palette, normalsPalette, shadowPalette); + } + public void Render(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()); + var pxOrigin = wr.ScreenPosition(pos); + var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0)); + var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1)); - 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(); - - Game.Renderer.EnableDepthBuffer(); - foreach (var v in draw) - v.Voxel.Draw(vr, lightAmbientColor, lightDiffuseColor, palette.Index, normalsPalette.Index); - Game.Renderer.DisableDepthBuffer(); + 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 pxOrigin = wr.ScreenPosition(pos); + var groundZ = 0.5f*(pxOrigin.Y - wr.ScreenZPosition(pos, 0)); + var shadowOrigin = pxOrigin - groundZ*(new float2(renderProxy.ShadowDirection, 1)); + + // 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); + + // 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); + + // Draw voxel bounding box var draw = voxels.Where(v => v.DisableFunc == null || !v.DisableFunc()); var scaleTransform = Util.ScaleMatrix(scale, scale, scale); - var pxOrigin = wr.ScreenPosition(pos); - - // 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); - - // 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 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++) { diff --git a/OpenRA.Game/Graphics/VoxelRenderer.cs b/OpenRA.Game/Graphics/VoxelRenderer.cs index 2961f13322..a587aab82d 100644 --- a/OpenRA.Game/Graphics/VoxelRenderer.cs +++ b/OpenRA.Game/Graphics/VoxelRenderer.cs @@ -17,72 +17,326 @@ using OpenRA.FileFormats.Graphics; 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 mappedBuffers; + Stack> unmappedBuffers; + List> 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; - } - public void Render(VoxelLoader loader, 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); - 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)); - } - - public void RenderShadow(VoxelLoader loader, VoxelRenderData renderData, - float[] t, float[] lightDirection, float[] groundNormal, float groundZ, int colorPalette) - { - 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)); + mappedBuffers = new Dictionary(); + unmappedBuffers = new Stack>(); + doRender = new List>(); } public void SetPalette(ITexture palette) { shader.SetTexture("Palette", palette); - shadowShader.SetTexture("Palette", palette); } public void SetViewportParams(Size screen, float zoom, float2 scroll) { - // Construct projection matrix - // Clip planes are set at -height and +2*height - - var tiw = 2*zoom / screen.Width; - var tih = 2*zoom / screen.Height; + var a = 2f / Renderer.SheetSize; var view = new float[] { - 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 + a, 0, 0, 0, + 0, -a, 0, 0, + 0, 0, -2*a, 0, + -1, 1, 0, 1 }; shader.SetMatrix("View", view); - shadowShader.SetMatrix("View", view); + } + + public VoxelRenderProxy RenderAsync(WorldRenderer wr, IEnumerable 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(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]); + } + + 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); + shader.SetMatrix("TransformMatrix", t); + shader.SetVec("LightDirection", lightDirection, 4); + shader.SetVec("AmbientLight", ambientLight, 3); + shader.SetVec("DiffuseLight", diffuseLight, 3); + + shader.Render(() => renderer.DrawBatch(Game.modData.VoxelLoader.VertexBuffer, renderData.Start, renderData.Count, PrimitiveType.QuadList)); + } + + public void BeginFrame() + { + foreach (var kv in mappedBuffers) + unmappedBuffers.Push(kv); + mappedBuffers.Clear(); + + sheetBuilder = new SheetBuilder(SheetType.BGRA, AllocateSheet); + doRender.Clear(); + } + + IFrameBuffer EnableFrameBuffer(Sheet s) + { + var fbo = mappedBuffers[s]; + Game.Renderer.Flush(); + fbo.Bind(); + + Game.Renderer.Device.EnableDepthBuffer(); + return fbo; + } + + void DisableFrameBuffer(IFrameBuffer fbo) + { + Game.Renderer.Flush(); + Game.Renderer.Device.DisableDepthBuffer(); + fbo.Unbind(); + } + + public void EndFrame() + { + if (doRender.Count == 0) + return; + + Sheet currentSheet = null; + IFrameBuffer fbo = null; + foreach (var v in doRender) + { + // Change sheet + if (v.First != currentSheet) + { + if (fbo != null) + DisableFrameBuffer(fbo); + + 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; } } } diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index b7c0228611..5d46f60a08 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -87,8 +87,11 @@ namespace OpenRA.Graphics // 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; } diff --git a/OpenRA.Mods.RA/Render/RenderVoxels.cs b/OpenRA.Mods.RA/Render/RenderVoxels.cs index ecf9e20960..e18e91966a 100755 --- a/OpenRA.Mods.RA/Render/RenderVoxels.cs +++ b/OpenRA.Mods.RA/Render/RenderVoxels.cs @@ -32,11 +32,10 @@ namespace OpenRA.Mods.RA.Render [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 @@ namespace OpenRA.Mods.RA.Render this.info = info; body = self.Trait(); 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; diff --git a/cg/vxlshadow.fx b/cg/vxlshadow.fx deleted file mode 100644 index 43a41ec8a3..0000000000 --- a/cg/vxlshadow.fx +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/glsl/vxlshadow.frag b/glsl/vxlshadow.frag deleted file mode 100644 index e75b8403ae..0000000000 --- a/glsl/vxlshadow.frag +++ /dev/null @@ -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; -} diff --git a/glsl/vxlshadow.vert b/glsl/vxlshadow.vert deleted file mode 100644 index 89b8671de1..0000000000 --- a/glsl/vxlshadow.vert +++ /dev/null @@ -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); -}