Exemple #1
0
        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 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 pxOffset        = wr.ScreenVector(v.OffsetFunc());
                var pxPos           = pxOrigin + new float2(pxOffset[0], pxOffset[1]);
                var screenTransform = Util.MatrixMultiply(cameraTransform, worldTransform);
                DrawBoundsBox(pxPos, screenTransform, bounds, Color.Yellow);
            }
        }
        public ModelRenderProxy RenderAsync(
            WorldRenderer wr, IEnumerable <ModelAnimation> models, WRot camera, float scale,
            float[] groundNormal, WRot lightSource, float[] lightAmbientColor, float[] lightDiffuseColor,
            PaletteReference color, PaletteReference normals, PaletteReference shadowPalette)
        {
            if (!isInFrame)
            {
                throw new InvalidOperationException("BeginFrame has not been called. You cannot render until a frame has been started.");
            }

            // 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);

            if (invCameraTransform == null)
            {
                throw new InvalidOperationException("Failed to invert the cameraTransform matrix during RenderAsync.");
            }

            // 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 m in models)
            {
                // Convert screen offset back to world coords
                var offsetVec       = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
                var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);

                var worldTransform = m.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       = m.Model.Bounds(m.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[] { stl.X, stl.Y, 0, 1 },
                new[] { sbr.X, sbr.Y, 0, 1 },
                new[] { sbr.X, stl.Y, 0, 1 },
                new[] { stl.X, sbr.Y, 0, 1 }
            };

            var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
            var shadowGroundNormal    = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
            var screenCorners         = new float3[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 float3(corners[j][0], corners[j][1], 0);
            }

            // Shadows are rendered at twice the resolution to reduce artifacts
            Size spriteSize, shadowSpriteSize;
            int2 spriteOffset, shadowSpriteOffset;

            CalculateSpriteGeometry(tl, br, 1, out spriteSize, out spriteOffset);
            CalculateSpriteGeometry(stl, sbr, 2, out shadowSpriteSize, out shadowSpriteOffset);

            if (sheetBuilderForFrame == null)
            {
                sheetBuilderForFrame = new SheetBuilder(SheetType.BGRA, AllocateSheet);
            }

            var sprite       = sheetBuilderForFrame.Allocate(spriteSize, 0, spriteOffset);
            var shadowSprite = sheetBuilderForFrame.Allocate(shadowSpriteSize, 0, 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 m in models)
                {
                    // Convert screen offset to world offset
                    var offsetVec       = Util.MatrixVectorMultiply(invCameraTransform, wr.ScreenVector(m.OffsetFunc()));
                    var offsetTransform = Util.TranslationMatrix(offsetVec[0], offsetVec[1], offsetVec[2]);

                    var rotations = m.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 = m.FrameFunc();
                    for (uint i = 0; i < m.Model.Sections; i++)
                    {
                        var rd = m.Model.RenderData(i);
                        var t  = m.Model.TransformationMatrix(i, frame);
                        var it = Util.MatrixInverse(t);
                        if (it == null)
                        {
                            throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));
                        }

                        // Transform light vector from shadow -> world -> limb coords
                        var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));

                        Render(rd, wr.World.ModelCache, Util.MatrixMultiply(transform, t), lightDirection,
                               lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);

                        // Disable shadow normals by forcing zero diffuse and identity ambient light
                        if (m.ShowShadow)
                        {
                            Render(rd, wr.World.ModelCache, Util.MatrixMultiply(shadow, t), lightDirection,
                                   ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
                        }
                    }
                }
            }));

            var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);

            screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
            return(new ModelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2] / screenLightVector[1]));
        }
Exemple #3
0
        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]));
        }
Exemple #4
0
        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);
            if (invCameraTransform == null)
                throw new InvalidOperationException("Failed to invert the cameraTransform matrix during RenderAsync.");

            // 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[] { stl.X, stl.Y, 0, 1 },
                new[] { sbr.X, sbr.Y, 0, 1 },
                new[] { sbr.X, stl.Y, 0, 1 },
                new[] { stl.X, sbr.Y, 0, 1 }
            };

            var shadowScreenTransform = Util.MatrixMultiply(cameraTransform, invShadowTransform);
            var shadowGroundNormal = Util.MatrixVectorMultiply(shadowTransform, groundNormal);
            var screenCorners = new float3[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 float3(corners[j][0], corners[j][1], 0);
            }

            // Shadows are rendered at twice the resolution to reduce artifacts
            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, 0, spriteOffset);
            var shadowSprite = sheetBuilder.Allocate(shadowSpriteSize, 0, 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);
                        var it = Util.MatrixInverse(t);
                        if (it == null)
                            throw new InvalidOperationException("Failed to invert the transformed matrix of frame {0} during RenderAsync.".F(i));

                        // Transform light vector from shadow -> world -> limb coords
                        var lightDirection = ExtractRotationVector(Util.MatrixMultiply(it, lightTransform));

                        Render(rd, Util.MatrixMultiply(transform, t), lightDirection,
                            lightAmbientColor, lightDiffuseColor, color.TextureMidIndex, normals.TextureMidIndex);

                        // Disable shadow normals by forcing zero diffuse and identity ambient light
                        if (v.ShowShadow)
                            Render(rd, Util.MatrixMultiply(shadow, t), lightDirection,
                                ShadowAmbient, ShadowDiffuse, shadowPalette.TextureMidIndex, normals.TextureMidIndex);
                    }
                }
            }));

            var screenLightVector = Util.MatrixVectorMultiply(invShadowTransform, ZVector);
            screenLightVector = Util.MatrixVectorMultiply(cameraTransform, screenLightVector);
            return new VoxelRenderProxy(sprite, shadowSprite, screenCorners, -screenLightVector[2] / screenLightVector[1]);
        }