Reimplement voxel rendering with a FBO.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user