Reimplement voxel rendering with a FBO.

This commit is contained in:
Paul Chote
2013-06-12 16:28:01 +12:00
parent 2215f74959
commit c5337cdcf3
10 changed files with 377 additions and 378 deletions

View File

@@ -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))

View File

@@ -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"));

View File

@@ -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<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 @@ 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

View File

@@ -29,6 +29,9 @@ namespace OpenRA.Graphics
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 @@ 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++)
{

View File

@@ -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<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;
}
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<Sheet, IFrameBuffer>();
unmappedBuffers = new Stack<KeyValuePair<Sheet, IFrameBuffer>>();
doRender = new List<Pair<Sheet, Action>>();
}
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<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]);
}
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;
}
}
}

View File

@@ -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;
}

View File

@@ -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<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 File

@@ -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 File

@@ -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 File

@@ -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);
}