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