CodeWalker/Rendering/Utils/Shadowmap.cs

970 lines
43 KiB
C#
Raw Normal View History

2017-09-21 18:33:05 +08:00
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Device = SharpDX.Direct3D11.Device;
using Buffer = SharpDX.Direct3D11.Buffer;
using Resource = SharpDX.Direct3D11.Resource;
using MapFlags = SharpDX.Direct3D11.MapFlags;
using CodeWalker.World;
using CodeWalker.Properties;
namespace CodeWalker.Rendering
{
public class Shadowmap
{
Texture2D DepthTexture;
SamplerState DepthTextureSS;
ShaderResourceView DepthTextureSRV;
DepthStencilView DepthTextureDSV;
RasterizerState DepthRenderRS;
DepthStencilState DepthRenderDS;
ViewportF DepthRenderVP;
GpuVarsBuffer<ShadowmapVars> ShadowVars;
RenderTargetSwitch RTS = new RenderTargetSwitch();
public List<ShadowmapCascade> Cascades;
Matrix SceneCamView;
Matrix LightView;
Vector3 LightDirection;
int TextureSize;
public int CascadeCount;
int PCFSize;
float PCFOffset;
float BlurBetweenCascades;
Vector3 WorldMin;
Vector3 WorldMax;
public Vector3 SceneOrigin;
Vector3 SceneCamPos;
Vector3 SceneMin;
Vector3 SceneMax;
Vector3 SceneCenter;
Vector3 SceneExtent;
float[] fCascadeIntervals = { 7.0f, 20.0f, 65.0f, 160.0f, 600.0f, 3000.0f, 5000.0f, 10000.0f };
2019-12-08 15:40:09 +08:00
public float maxShadowDistance = 3000.0f;
2017-09-21 18:33:05 +08:00
long graphicsMemoryUsage = 0;
public long VramUsage
{
get
{
return graphicsMemoryUsage;
}
}
public Shadowmap(Device device)
{
TextureSize = 1024; //todo: make this a setting...
CascadeCount = Math.Min(Settings.Default.ShadowCascades, 8);// 6; //use setting
PCFSize = 3;
PCFOffset = 0.000125f; //0.002f
BlurBetweenCascades = 0.05f;
ShadowVars = new GpuVarsBuffer<ShadowmapVars>(device);
DepthTexture = DXUtility.CreateTexture2D(device, TextureSize* CascadeCount, TextureSize, 1, 1, Format.R32_Typeless, 1, 0, ResourceUsage.Default, BindFlags.DepthStencil | BindFlags.ShaderResource, 0, 0);
DepthTextureSS = DXUtility.CreateSamplerState(device, TextureAddressMode.Border, new Color4(0.0f), Comparison.Less, Filter.ComparisonMinMagLinearMipPoint, 0, 0.0f, 0.0f, 0.0f);
DepthTextureSRV = DXUtility.CreateShaderResourceView(device, DepthTexture, Format.R32_Float, ShaderResourceViewDimension.Texture2D, 1, 0, 0, 0);
DepthTextureDSV = DXUtility.CreateDepthStencilView(device, DepthTexture, Format.D32_Float, DepthStencilViewDimension.Texture2D);
Cascades = new List<ShadowmapCascade>(CascadeCount);
for (int i = 0; i < CascadeCount; i++)
{
ShadowmapCascade c = new ShadowmapCascade();
c.Owner = this;
c.Index = i;
c.ZNear = 0.0f;
c.ZFar = 1.0f;
c.IntervalNear = 0.0f;
c.IntervalFar = 1.0f;
c.DepthRenderVP = new ViewportF()
{
Height = (float)TextureSize,
Width = (float)TextureSize,
MaxDepth = 1.0f,
MinDepth = 0.0f,
X = (float)(TextureSize * i),
Y = 0,
};
Cascades.Add(c);
}
DepthRenderRS = DXUtility.CreateRasterizerState(device, FillMode.Solid, CullMode.None, true, false, true, 0, 0.0f, 1.0f);
DepthRenderDS = DXUtility.CreateDepthStencilState(device, true, DepthWriteMask.All);
DepthRenderVP = new ViewportF();
DepthRenderVP.Height = (float)TextureSize;
DepthRenderVP.Width = (float)TextureSize;
DepthRenderVP.MaxDepth = 1.0f;
DepthRenderVP.MinDepth = 0.0f;
DepthRenderVP.X = 0;
DepthRenderVP.Y = 0;
graphicsMemoryUsage = (long)(TextureSize * TextureSize * CascadeCount * 4);
}
public void Dispose()
{
graphicsMemoryUsage = 0;
if (DepthTexture != null)
{
DepthTexture.Dispose();
DepthTexture = null;
}
if (DepthTextureSS != null)
{
DepthTextureSS.Dispose();
DepthTextureSS = null;
}
if (DepthTextureSRV != null)
{
DepthTextureSRV.Dispose();
DepthTextureSRV = null;
}
if (DepthTextureDSV != null)
{
DepthTextureDSV.Dispose();
DepthTextureDSV = null;
}
if (DepthRenderRS != null)
{
DepthRenderRS.Dispose();
DepthRenderRS = null;
}
if (DepthRenderDS != null)
{
DepthRenderDS.Dispose();
DepthRenderDS = null;
}
if (ShadowVars != null)
{
ShadowVars.Dispose();
ShadowVars = null;
}
if (Cascades != null)
{
Cascades.Clear();
Cascades = null;
}
}
public void BeginUpdate(DeviceContext context, Camera cam, Vector3 lightDir, List<RenderableGeometryInst> items)
{
//items should be potential shadow casters.
RTS.Set(context);
var ppos = cam.Position;
var view = cam.ViewMatrix;
var proj = cam.ProjMatrix;
var viewproj = cam.ViewProjMatrix;
2017-09-21 18:33:05 +08:00
//need to compute a local scene space for the shadows. use a snapped version of the camera coords...
Vector3 pp = ppos;
float snapsize = 20.0f; //20m snap... //ideally should snap to texel size
2017-09-21 18:33:05 +08:00
SceneOrigin.X = pp.X - (pp.X % snapsize);
SceneOrigin.Y = pp.Y - (pp.Y % snapsize);
SceneOrigin.Z = pp.Z - (pp.Z % snapsize);
SceneCamPos = (pp - SceneOrigin);
2017-09-21 18:33:05 +08:00
//the items passed in here are visible items. need to compute the scene bounds from these.
Vector4 vFLTMAX = new Vector4(float.MaxValue);
Vector4 vFLTMIN = new Vector4(float.MinValue);
Vector3 vHMAX = new Vector3(float.MaxValue);
Vector3 vHMIN = new Vector3(float.MinValue);
2017-09-21 18:33:05 +08:00
WorldMin = vHMAX;
WorldMax = vHMIN;
for (int i = 0; i < items.Count; i++)
2017-09-21 18:33:05 +08:00
{
var inst = items[i].Inst;
2017-09-21 18:33:05 +08:00
Vector3 imin = inst.BBMin - 100.0f; //extra bias to make sure scene isn't too small in model view...
Vector3 imax = inst.BBMax + 100.0f;
2017-09-21 18:33:05 +08:00
WorldMin = Vector3.Min(WorldMin, imin);
WorldMax = Vector3.Max(WorldMax, imax);
}
SceneMin = (WorldMin - SceneOrigin);
SceneMax = (WorldMax - SceneOrigin);
2017-09-21 18:33:05 +08:00
SceneCenter = (SceneMax + SceneMin) * 0.5f;
SceneExtent = (SceneMax - SceneMin) * 0.5f;
Matrix sceneCamTrans = Matrix.Translation(-SceneCamPos);
SceneCamView = Matrix.Multiply(sceneCamTrans, view);
Matrix camViewInv = Matrix.Invert(SceneCamView);
Vector3 lightUp = new Vector3(0.0f, 1.0f, 0.0f); //BUG: should select this depending on light dir!?
LightView = Matrix.LookAtLH(lightDir, Vector3.Zero, lightUp); //BUG?: pos/lightdir wrong way around??
LightDirection = lightDir;
Vector4[] vSceneAABBPointsLightSpace = new Vector4[8];
// This function simply converts the center and extents of an AABB into 8 points
CreateAABBPoints(ref vSceneAABBPointsLightSpace, SceneCenter, SceneExtent);
// Transform the scene AABB to Light space.
for (int index = 0; index < 8; ++index)
{
vSceneAABBPointsLightSpace[index] = LightView.Multiply(vSceneAABBPointsLightSpace[index]);
}
float fFrustumIntervalBegin, fFrustumIntervalEnd;
Vector4 vLightCameraOrthographicMin; // light space frustrum aabb
Vector4 vLightCameraOrthographicMax;
//float[] fCascadeIntervals = { 7.5f, 20.0f, 60.0f, 150.0f, 500.0f, 1000.0f, 1500.0f, 2500.0f };
//float[] fCascadeIntervals = { 7.0f, 20.0f, 65.0f, 160.0f, 650.0f, 2000.0f, 5000.0f, 10000.0f };
2017-09-21 18:33:05 +08:00
Vector4 vWorldUnitsPerTexel = Vector4.Zero;
float fInvTexelCount = 1.0f / (float)TextureSize;
// We loop over the cascades to calculate the orthographic projection for each cascade.
for (int iCascadeIndex = 0; iCascadeIndex < CascadeCount; ++iCascadeIndex)
{
ShadowmapCascade cascade = Cascades[iCascadeIndex];
fFrustumIntervalBegin = 0.0f;
// Scale the intervals between 0 and 1. They are now percentages that we can scale with.
fFrustumIntervalEnd = fCascadeIntervals[iCascadeIndex];
//fFrustumIntervalBegin = fFrustumIntervalBegin * fCameraNearFarRange;
//fFrustumIntervalEnd = fFrustumIntervalEnd * fCameraNearFarRange;
2017-09-21 18:33:05 +08:00
Vector4[] vFrustumPoints = new Vector4[8];
// This function takes the began and end intervals along with the projection matrix and returns the 8
// points that repreresent the cascade Interval
CreateFrustumPointsFromCascadeInterval(fFrustumIntervalBegin, fFrustumIntervalEnd, proj, ref vFrustumPoints);
vLightCameraOrthographicMin = vFLTMAX;
vLightCameraOrthographicMax = vFLTMIN;
Vector4 vTempTranslatedCornerPoint;
// This next section of code calculates the min and max values for the orthographic projection.
for (int icpIndex = 0; icpIndex < 8; ++icpIndex)
{
// Transform the frustum from camera view space to world space.
vFrustumPoints[icpIndex] = camViewInv.Multiply(vFrustumPoints[icpIndex]);
// Transform the point from world space to Light Camera Space.
vTempTranslatedCornerPoint = LightView.Multiply(vFrustumPoints[icpIndex]);
// Find the closest point.
2017-09-21 18:33:05 +08:00
vLightCameraOrthographicMin = Vector4.Min(vTempTranslatedCornerPoint, vLightCameraOrthographicMin);
vLightCameraOrthographicMax = Vector4.Max(vTempTranslatedCornerPoint, vLightCameraOrthographicMax);
}
// This code removes the shimmering effect along the edges of shadows due to
// the light changing to fit the camera.
// Fit the ortho projection to the cascades far plane and a near plane of zero.
// Pad the projection to be the size of the diagonal of the Frustum partition.
//
// To do this, we pad the ortho transform so that it is always big enough to cover
// the entire camera view frustum.
Vector4 vDiagonal = (vFrustumPoints[0] - vFrustumPoints[6]);
// The bound is the length of the diagonal of the frustum interval.
float fCascadeBound = vDiagonal.XYZ().Length();
2017-09-21 18:33:05 +08:00
vDiagonal = new Vector4(fCascadeBound);
// The offset calculated will pad the ortho projection so that it is always the same size
// and big enough to cover the entire cascade interval.
Vector4 vBorderOffset = (vDiagonal - (vLightCameraOrthographicMax - vLightCameraOrthographicMin)) * 0.5f;
// Set the Z and W components to zero.
//vBoarderOffset *= g_vMultiplySetzwToZero;
vBorderOffset.Z = 0.0f;
vBorderOffset.W = 0.0f;
// Add the offsets to the projection.
vLightCameraOrthographicMax += vBorderOffset;
vLightCameraOrthographicMin -= vBorderOffset;
// The world units per texel are used to snap the shadow the orthographic projection
// to texel sized increments. This keeps the edges of the shadows from shimmering.
float fWorldUnitsPerTexel = fCascadeBound / (float)TextureSize;
vWorldUnitsPerTexel = new Vector4(fWorldUnitsPerTexel, fWorldUnitsPerTexel, 1.0f, 1.0f); //1.0 instead of 0.0 to remove divide by 0
// We snap the camera to 1 pixel increments so that moving the camera does not cause the shadows to jitter.
// This is a matter of integer dividing by the world space size of a texel
vLightCameraOrthographicMin = vLightCameraOrthographicMin / vWorldUnitsPerTexel;
vLightCameraOrthographicMin = vLightCameraOrthographicMin.Floor();
vLightCameraOrthographicMin = vLightCameraOrthographicMin * vWorldUnitsPerTexel;
vLightCameraOrthographicMax = vLightCameraOrthographicMax / vWorldUnitsPerTexel;
vLightCameraOrthographicMax = vLightCameraOrthographicMax.Floor();
vLightCameraOrthographicMax = vLightCameraOrthographicMax * vWorldUnitsPerTexel;
//These are the unconfigured near and far plane values. They are purposly awful to show
// how important calculating accurate near and far planes is.
float fNearPlane;
float fFarPlane;
2017-09-21 18:33:05 +08:00
// By intersecting the light frustum with the scene AABB we can get a tighter bound on the near and far plane.
ComputeNearAndFar(out fNearPlane, out fFarPlane, vLightCameraOrthographicMin, vLightCameraOrthographicMax, vSceneAABBPointsLightSpace);
// Create the orthographic projection for this cascade.
2017-09-21 18:33:05 +08:00
cascade.Ortho = Matrix.OrthoOffCenterLH(vLightCameraOrthographicMin.X, vLightCameraOrthographicMax.X, vLightCameraOrthographicMin.Y, vLightCameraOrthographicMax.Y, fNearPlane, fFarPlane);
cascade.ZNear = fNearPlane;
cascade.ZFar = fFarPlane;
cascade.IntervalNear = fFrustumIntervalBegin;
cascade.IntervalFar = fFrustumIntervalEnd;
cascade.Matrix = Matrix.Multiply(LightView, cascade.Ortho);
cascade.MatrixInv = Matrix.Invert(cascade.Matrix);
cascade.WorldUnitsPerTexel = fWorldUnitsPerTexel;
cascade.WorldUnitsToCascadeUnits = 2.0f / fCascadeBound;
2017-09-21 18:33:05 +08:00
}
context.ClearDepthStencilView(DepthTextureDSV, DepthStencilClearFlags.Depth, 1.0f, 0);
// Set a null render target so as not to render color.
context.OutputMerger.SetRenderTargets(DepthTextureDSV, (RenderTargetView)null);
context.OutputMerger.SetDepthStencilState(DepthRenderDS);
context.Rasterizer.State = DepthRenderRS;
}
public void BeginDepthRender(DeviceContext context, int cascadeIndex)
{
ShadowmapCascade cascade = Cascades[cascadeIndex];
context.Rasterizer.SetViewport(cascade.DepthRenderVP);
}
public void EndUpdate(DeviceContext context)
{
RTS.Reset(context);
}
public void SetFinalRenderResources(DeviceContext context)
{
ShadowVars.Vars.CamScenePos = new Vector4(SceneCamPos, 0.0f); //in shadow scene coords
ShadowVars.Vars.CamSceneView = Matrix.Transpose(SceneCamView);
ShadowVars.Vars.LightView = Matrix.Transpose(LightView);
ShadowVars.Vars.LightDir = new Vector4(LightDirection, 0.0f);
Matrix dxmatTextureScale = Matrix.Scaling(0.5f, -0.5f, 1.0f);
Matrix dxmatTextureTranslation = Matrix.Translation(0.5f, 0.5f, 0.0f);
Matrix dxmatTextureST = Matrix.Multiply(dxmatTextureScale, dxmatTextureTranslation);
for (int i = 0; i < CascadeCount; ++i)
{
ShadowmapCascade cascade = Cascades[i];
Matrix mShadowTexture = Matrix.Multiply(cascade.Ortho, dxmatTextureST);
ShadowVars.Vars.CascadeScales.Set(i, new Vector4(mShadowTexture.M11, mShadowTexture.M22, mShadowTexture.M33, 1.0f));
ShadowVars.Vars.CascadeOffsets.Set(i, new Vector4(mShadowTexture.M41, mShadowTexture.M42, mShadowTexture.M43, 0.0f));
ShadowVars.Vars.CascadeDepths.Set(i, new Vector4(cascade.IntervalFar, 0.0f, 0.0f, 0.0f));
}
ShadowVars.Vars.CascadeCount = CascadeCount;
ShadowVars.Vars.CascadeVisual = 0;
ShadowVars.Vars.PCFLoopStart = (PCFSize) / -2;
ShadowVars.Vars.PCFLoopEnd = (PCFSize) / 2 + 1;
// The border padding values keep the pixel shader from reading the borders during PCF filtering.
float txs = (float)TextureSize;
ShadowVars.Vars.BorderPaddingMax = (txs - 1.0f) / txs;
ShadowVars.Vars.BorderPaddingMin = 1.0f / txs;
ShadowVars.Vars.Bias = PCFOffset;
ShadowVars.Vars.BlurBetweenCascades = BlurBetweenCascades;
ShadowVars.Vars.CascadeCountInv = 1.0f / (float)CascadeCount;
ShadowVars.Vars.TexelSize = 1.0f / txs;
ShadowVars.Vars.TexelSizeX = ShadowVars.Vars.TexelSize / (float)CascadeCount;
ShadowVars.Vars.ShadowMaxDistance = maxShadowDistance;
2017-09-21 18:33:05 +08:00
ShadowVars.Update(context);
SetBuffers(context);
}
public void SetBuffers(DeviceContext context)
{
context.VertexShader.SetConstantBuffer(1, ShadowVars.Buffer); //todo: set resource slots
context.PixelShader.SetConstantBuffer(1, ShadowVars.Buffer);
context.PixelShader.SetShaderResource(1, DepthTextureSRV);
context.PixelShader.SetSampler(1, DepthTextureSS);
}
static readonly Vector3[] vExtentsMap =
{
new Vector3(1.0f, 1.0f, -1.0f),
new Vector3( -1.0f, 1.0f, -1.0f ),
new Vector3(1.0f, -1.0f, -1.0f ),
new Vector3( -1.0f, -1.0f, -1.0f ),
new Vector3( 1.0f, 1.0f, 1.0f ),
new Vector3( -1.0f, 1.0f, 1.0f ),
new Vector3( 1.0f, -1.0f, 1.0f ),
new Vector3( -1.0f, -1.0f, 1.0f )
};
void CreateAABBPoints(ref Vector4[] vAABBPoints, Vector3 vCenter, Vector3 vExtents)
{
//--------------------------------------------------------------------------------------
// This function converts the "center, extents" version of an AABB into 8 points.
//--------------------------------------------------------------------------------------
//This map enables us to use a for loop and do vector math.
for (int index = 0; index < 8; ++index)
{
vAABBPoints[index] = new Vector4((vExtentsMap[index] * vExtents) + vCenter, 1.0f);
}
}
static readonly Vector4[] HomogeneousPoints =
{
// Corners of the projection frustum in homogeneous space.
new Vector4( 1.0f, 0.0f, 1.0f, 1.0f ), // right (at far plane)
new Vector4( -1.0f, 0.0f, 1.0f, 1.0f ), // left
new Vector4( 0.0f, 1.0f, 1.0f, 1.0f ), // top
new Vector4( 0.0f, -1.0f, 1.0f, 1.0f ), // bottom
new Vector4( 0.0f, 0.0f, 0.0f, 1.0f ), // near
new Vector4( 0.0f, 0.0f, 1.0f, 1.0f ) // far
};
void ComputeFrustumFromProjection(out ShadowmapFrustum sf, Matrix pProjection)
{
//-----------------------------------------------------------------------------
// Build a frustum from a persepective projection matrix. The matrix may only
// contain a projection; any rotation, translation or scale will cause the
// constructed frustum to be incorrect.
//-----------------------------------------------------------------------------
Matrix matInverse;
Matrix.Invert(ref pProjection, out matInverse);
// Compute the frustum corners in world space.
Vector4[] Points = new Vector4[6];
for (int i = 0; i < 6; i++)
{
// Transform point.
Points[i] = matInverse.Multiply(HomogeneousPoints[i]);
}
sf = new ShadowmapFrustum();
sf.Origin = new Vector3(0.0f, 0.0f, 0.0f);
sf.Orientation = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
// Compute the slopes.
Points[0] = Points[0] * (1.0f / Points[0].Z);
Points[1] = Points[1] * (1.0f / Points[1].Z);
Points[2] = Points[2] * (1.0f / Points[2].Z);
Points[3] = Points[3] * (1.0f / Points[3].Z);
2017-09-21 18:33:05 +08:00
sf.RightSlope = Points[0].X;
sf.LeftSlope = Points[1].X;
sf.TopSlope = Points[2].Y;
sf.BottomSlope = Points[3].Y;
2017-09-21 18:33:05 +08:00
// Compute near and far.
Points[4] = Points[4] * (1.0f / Points[4].W);
Points[5] = Points[5] * (1.0f / Points[5].W);
2017-09-21 18:33:05 +08:00
sf.Near = Points[4].Z;
sf.Far = Points[5].Z;
2017-09-21 18:33:05 +08:00
}
void CreateFrustumPointsFromCascadeInterval(float fCascadeIntervalBegin, float fCascadeIntervalEnd, Matrix vProjection, ref Vector4[] pvCornerPointsWorld)
{
//--------------------------------------------------------------------------------------
// This function takes the camera's projection matrix and returns the 8
// points that make up a view frustum.
// The frustum is scaled to fit within the Begin and End interval paramaters.
//--------------------------------------------------------------------------------------
ShadowmapFrustum vViewFrust;
ComputeFrustumFromProjection(out vViewFrust, vProjection);
vViewFrust.Near = -fCascadeIntervalBegin; //negative due to negative aspect ratio projection matrix..
vViewFrust.Far = -fCascadeIntervalEnd;
Vector3 vGrabY = new Vector3(0.0f, 1.0f, 0.0f);
Vector3 vGrabX = new Vector3(1.0f, 0.0f, 0.0f);
2017-09-21 18:33:05 +08:00
Vector3 vRightTop = new Vector3(vViewFrust.RightSlope, vViewFrust.TopSlope, 1.0f);
Vector3 vLeftBottom = new Vector3(vViewFrust.LeftSlope, vViewFrust.BottomSlope, 1.0f);
Vector3 vNear = new Vector3(vViewFrust.Near, vViewFrust.Near, vViewFrust.Near);
Vector3 vFar = new Vector3(vViewFrust.Far, vViewFrust.Far, vViewFrust.Far);
Vector3 vRightTopNear = vRightTop * vNear;
Vector3 vRightTopFar = vRightTop * vFar;
Vector3 vLeftBottomNear = vLeftBottom * vNear;
Vector3 vLeftBottomFar = vLeftBottom * vFar;
2017-09-21 18:33:05 +08:00
pvCornerPointsWorld[0] = new Vector4(vRightTopNear, 1.0f);
pvCornerPointsWorld[1] = new Vector4(V3FSelect(vRightTopNear, vLeftBottomNear, vGrabX), 1.0f);
pvCornerPointsWorld[2] = new Vector4(vLeftBottomNear, 1.0f);
pvCornerPointsWorld[3] = new Vector4(V3FSelect(vRightTopNear, vLeftBottomNear, vGrabY), 1.0f);
pvCornerPointsWorld[4] = new Vector4(vRightTopFar, 1.0f);
pvCornerPointsWorld[5] = new Vector4(V3FSelect(vRightTopFar, vLeftBottomFar, vGrabX), 1.0f);
pvCornerPointsWorld[6] = new Vector4(vLeftBottomFar, 1.0f);
pvCornerPointsWorld[7] = new Vector4(V3FSelect(vRightTopFar, vLeftBottomFar, vGrabY), 1.0f);
}
Vector3 V3FSelect(Vector3 v1, Vector3 v2, Vector3 control)
{
Vector3 r;
r.X = (control.X == 0.0f) ? v1.X : v2.X;
r.Y = (control.Y == 0.0f) ? v1.Y : v2.Y;
r.Z = (control.Z == 0.0f) ? v1.Z : v2.Z;
return r;
}
static readonly int[] iAABBTriIndexes =
{
// These are the indices used to tesselate an AABB into a list of triangles.
0,1,2, 1,2,3,
4,5,6, 5,6,7,
0,2,4, 2,4,6,
1,3,5, 3,5,7,
0,1,4, 1,4,5,
2,3,6, 3,6,7
};
void ComputeNearAndFar(out float fNearPlane, out float fFarPlane, Vector4 vLightCameraOrthographicMin, Vector4 vLightCameraOrthographicMax, Vector4[] pvPointsInCameraView)
{
//--------------------------------------------------------------------------------------
// Computing an accurate near and flar plane will decrease surface acne and Peter-panning.
// Surface acne is the term for erroneous self shadowing. Peter-panning is the effect where
// shadows disappear near the base of an object.
// As offsets are generally used with PCF filtering due self shadowing issues, computing the
// correct near and far planes becomes even more important.
// This concept is not complicated, but the intersection code is.
//--------------------------------------------------------------------------------------
// Initialize the near and far planes
fNearPlane = float.MaxValue;
fFarPlane = float.MinValue;
2017-09-21 18:33:05 +08:00
ShadowmapTriangle[] triangleList = new ShadowmapTriangle[16];
int iTriangleCnt = 1;
triangleList[0].pt0 = pvPointsInCameraView[0];
triangleList[0].pt1 = pvPointsInCameraView[1];
triangleList[0].pt2 = pvPointsInCameraView[2];
triangleList[0].culled = false;
int[] iPointPassesCollision = new int[3];
// At a high level:
// 1. Iterate over all 12 triangles of the AABB.
// 2. Clip the triangles against each plane. Create new triangles as needed.
// 3. Find the min and max z values as the near and far plane.
//This is easier because the triangles are in camera spacing making the collisions tests simple comparisions.
float fLightCameraOrthographicMinX = vLightCameraOrthographicMin.X;
float fLightCameraOrthographicMaxX = vLightCameraOrthographicMax.X;
float fLightCameraOrthographicMinY = vLightCameraOrthographicMin.Y;
float fLightCameraOrthographicMaxY = vLightCameraOrthographicMax.Y;
for (int AABBTriIter = 0; AABBTriIter < 12; ++AABBTriIter)
{
triangleList[0].pt0 = pvPointsInCameraView[iAABBTriIndexes[AABBTriIter * 3 + 0]];
triangleList[0].pt1 = pvPointsInCameraView[iAABBTriIndexes[AABBTriIter * 3 + 1]];
triangleList[0].pt2 = pvPointsInCameraView[iAABBTriIndexes[AABBTriIter * 3 + 2]];
iTriangleCnt = 1;
triangleList[0].culled = false;
// Clip each invidual triangle against the 4 frustums. When ever a triangle is clipped into new triangles,
//add them to the list.
for (int frustumPlaneIter = 0; frustumPlaneIter < 4; ++frustumPlaneIter)
{
float fEdge;
int iComponent;
if (frustumPlaneIter == 0)
{
fEdge = fLightCameraOrthographicMinX; // todo make float temp
iComponent = 0;
}
else if (frustumPlaneIter == 1)
{
fEdge = fLightCameraOrthographicMaxX;
iComponent = 0;
}
else if (frustumPlaneIter == 2)
{
fEdge = fLightCameraOrthographicMinY;
iComponent = 1;
}
else
{
fEdge = fLightCameraOrthographicMaxY;
iComponent = 1;
}
for (int triIter = 0; triIter < iTriangleCnt; ++triIter)
{
// We don't delete triangles, so we skip those that have been culled.
if (!triangleList[triIter].culled)
{
int iInsideVertCount = 0;
Vector4 tempOrder;
// Test against the correct frustum plane.
// This could be written more compactly, but it would be harder to understand.
if (frustumPlaneIter == 0)
{
for (int triPtIter = 0; triPtIter < 3; ++triPtIter)
{
if (triangleList[triIter].pt(triPtIter).X > vLightCameraOrthographicMin.X)
{
iPointPassesCollision[triPtIter] = 1;
}
else
{
iPointPassesCollision[triPtIter] = 0;
}
iInsideVertCount += iPointPassesCollision[triPtIter];
}
}
else if (frustumPlaneIter == 1)
{
for (int triPtIter = 0; triPtIter < 3; ++triPtIter)
{
if (triangleList[triIter].pt(triPtIter).X < vLightCameraOrthographicMax.X)
{
iPointPassesCollision[triPtIter] = 1;
}
else
{
iPointPassesCollision[triPtIter] = 0;
}
iInsideVertCount += iPointPassesCollision[triPtIter];
}
}
else if (frustumPlaneIter == 2)
{
for (int triPtIter = 0; triPtIter < 3; ++triPtIter)
{
if (triangleList[triIter].pt(triPtIter).Y > vLightCameraOrthographicMin.Y)
{
iPointPassesCollision[triPtIter] = 1;
}
else
{
iPointPassesCollision[triPtIter] = 0;
}
iInsideVertCount += iPointPassesCollision[triPtIter];
}
}
else
{
for (int triPtIter = 0; triPtIter < 3; ++triPtIter)
{
if (triangleList[triIter].pt(triPtIter).Y < vLightCameraOrthographicMax.Y)
{
iPointPassesCollision[triPtIter] = 1;
}
else
{
iPointPassesCollision[triPtIter] = 0;
}
iInsideVertCount += iPointPassesCollision[triPtIter];
}
}
// Move the points that pass the frustum test to the begining of the array.
if ((iPointPassesCollision[1] != 0) && (iPointPassesCollision[0] == 0))
{
tempOrder = triangleList[triIter].pt0;
triangleList[triIter].pt0 = triangleList[triIter].pt1;
triangleList[triIter].pt1 = tempOrder;
iPointPassesCollision[0] = 1;
iPointPassesCollision[1] = 0;
}
if ((iPointPassesCollision[2] != 0) && (iPointPassesCollision[1] == 0))
{
tempOrder = triangleList[triIter].pt1;
triangleList[triIter].pt1 = triangleList[triIter].pt2;
triangleList[triIter].pt2 = tempOrder;
iPointPassesCollision[1] = 1;
iPointPassesCollision[2] = 0;
}
if ((iPointPassesCollision[1] != 0) && (iPointPassesCollision[0] == 0))
{
tempOrder = triangleList[triIter].pt0;
triangleList[triIter].pt0 = triangleList[triIter].pt1;
triangleList[triIter].pt1 = tempOrder;
iPointPassesCollision[0] = 1;
iPointPassesCollision[1] = 0;
}
if (iInsideVertCount == 0)
{ // All points failed. We're done,
triangleList[triIter].culled = true;
}
else if (iInsideVertCount == 1)
{// One point passed. Clip the triangle against the Frustum plane
triangleList[triIter].culled = false;
//
Vector4 vVert0ToVert1 = triangleList[triIter].pt1 - triangleList[triIter].pt0;
Vector4 vVert0ToVert2 = triangleList[triIter].pt2 - triangleList[triIter].pt0;
// Find the collision ratio.
float fHitPointTimeRatio = fEdge - triangleList[triIter].pt0[iComponent];
// Calculate the distance along the vector as ratio of the hit ratio to the component.
float fDistanceAlongVector01 = fHitPointTimeRatio / vVert0ToVert1[iComponent];
float fDistanceAlongVector02 = fHitPointTimeRatio / vVert0ToVert2[iComponent];
// Add the point plus a percentage of the vector.
vVert0ToVert1 = vVert0ToVert1 * fDistanceAlongVector01;
vVert0ToVert1 = vVert0ToVert1 + triangleList[triIter].pt0;
vVert0ToVert2 = vVert0ToVert2 * fDistanceAlongVector02;
vVert0ToVert2 = vVert0ToVert2 + triangleList[triIter].pt0;
triangleList[triIter].pt1 = vVert0ToVert2;
triangleList[triIter].pt2 = vVert0ToVert1;
}
else if (iInsideVertCount == 2)
{ // 2 in // tesselate into 2 triangles
// Copy the triangle\(if it exists) after the current triangle out of
// the way so we can override it with the new triangle we're inserting.
triangleList[iTriangleCnt] = triangleList[triIter + 1];
triangleList[triIter].culled = false;
triangleList[triIter + 1].culled = false;
// Get the vector from the outside point into the 2 inside points.
Vector4 vVert2ToVert0 = triangleList[triIter].pt0 - triangleList[triIter].pt2;
Vector4 vVert2ToVert1 = triangleList[triIter].pt1 - triangleList[triIter].pt2;
// Get the hit point ratio.
float fHitPointTime_2_0 = fEdge - triangleList[triIter].pt2[iComponent];
float fDistanceAlongVector_2_0 = fHitPointTime_2_0 / vVert2ToVert0[iComponent];
// Calcaulte the new vert by adding the percentage of the vector plus point 2.
vVert2ToVert0 = vVert2ToVert0 * fDistanceAlongVector_2_0;
vVert2ToVert0 = vVert2ToVert0 + triangleList[triIter].pt2;
// Add a new triangle.
triangleList[triIter + 1].pt0 = triangleList[triIter].pt0;
triangleList[triIter + 1].pt1 = triangleList[triIter].pt1;
triangleList[triIter + 1].pt2 = vVert2ToVert0;
//Get the hit point ratio.
float fHitPointTime_2_1 = fEdge - triangleList[triIter].pt2[iComponent];
float fDistanceAlongVector_2_1 = fHitPointTime_2_1 / vVert2ToVert1[iComponent];
vVert2ToVert1 = vVert2ToVert1 * fDistanceAlongVector_2_1;
vVert2ToVert1 = vVert2ToVert1 + triangleList[triIter].pt2;
triangleList[triIter].pt0 = triangleList[triIter + 1].pt1;
triangleList[triIter].pt1 = triangleList[triIter + 1].pt2;
triangleList[triIter].pt2 = vVert2ToVert1;
// Increment triangle count and skip the triangle we just inserted.
++iTriangleCnt;
++triIter;
}
else
{ // all in
triangleList[triIter].culled = false;
}
}// end if !culled loop
}
}
for (int index = 0; index < iTriangleCnt; ++index)
{
if (!triangleList[index].culled)
{
// Set the near and far plan and the min and max z values respectivly.
for (int vertind = 0; vertind < 3; ++vertind)
{
float fTriangleCoordZ = triangleList[index].pt(vertind).Z;
if (fNearPlane > fTriangleCoordZ)
{
fNearPlane = fTriangleCoordZ;
}
if (fFarPlane < fTriangleCoordZ)
{
fFarPlane = fTriangleCoordZ;
}
}
}
}
}
}
}
public struct ShadowmapVars
{
public Vector4 CamScenePos; //in shadow scene coords
public Matrix CamSceneView;
public Matrix LightView;
public Vector4 LightDir;
public ShadowmapVarsCascadeData CascadeOffsets;
public ShadowmapVarsCascadeData CascadeScales;
public ShadowmapVarsCascadeData CascadeDepths; //in scene eye space
public int CascadeCount;
public int CascadeVisual;
public int PCFLoopStart;
public int PCFLoopEnd;
public float BorderPaddingMin;
public float BorderPaddingMax;
public float Bias;
public float BlurBetweenCascades;
public float CascadeCountInv;
public float TexelSize;
public float TexelSizeX;
public float ShadowMaxDistance;
2017-09-21 18:33:05 +08:00
}
public struct ShadowmapVarsCascadeData
{
public Vector4 V00;
public Vector4 V01;
public Vector4 V02;
public Vector4 V03;
public Vector4 V04;
public Vector4 V05;
public Vector4 V06;
public Vector4 V07;
public Vector4 V08;
public Vector4 V09;
public Vector4 V10;
public Vector4 V11;
public Vector4 V12;
public Vector4 V13;
public Vector4 V14;
public Vector4 V15;
public void Set(int index, Vector4 v)
{
switch (index)
{
case 0: V00 = v; break;
case 1: V01 = v; break;
case 2: V02 = v; break;
case 3: V03 = v; break;
case 4: V04 = v; break;
case 5: V05 = v; break;
case 6: V06 = v; break;
case 7: V07 = v; break;
case 8: V08 = v; break;
case 9: V09 = v; break;
case 10: V10 = v; break;
case 11: V11 = v; break;
case 12: V12 = v; break;
case 13: V13 = v; break;
case 14: V14 = v; break;
case 15: V15 = v; break;
}
}
public Vector4 Get(int index)
{
switch (index)
{
case 0: return V00;
case 1: return V01;
case 2: return V02;
case 3: return V03;
case 4: return V04;
case 5: return V05;
case 6: return V06;
case 7: return V07;
case 8: return V08;
case 9: return V09;
case 10: return V10;
case 11: return V11;
case 12: return V12;
case 13: return V13;
case 14: return V14;
case 15: return V15;
}
return Vector4.Zero;
}
}
public class ShadowmapCascade
{
public Shadowmap Owner { get; set; }
public int Index { get; set; }
public float IntervalNear { get; set; }
public float IntervalFar { get; set; }
public float ZNear { get; set; }
public float ZFar { get; set; }
public Matrix Ortho { get; set; }
public Matrix Matrix { get; set; }
public Matrix MatrixInv { get; set; }
public ViewportF DepthRenderVP { get; set; }
public float WorldUnitsPerTexel { get; set; } //updated each frame for culling
public float WorldUnitsToCascadeUnits { get; set; }
}
public struct ShadowmapFrustum
{
public Vector3 Origin; // Origin of the frustum (and projection).
public Quaternion Orientation; // Unit quaternion representing rotation.
public float RightSlope; // Positive X slope (X/Z).
public float LeftSlope; // Negative X slope.
public float TopSlope; // Positive Y slope (Y/Z).
public float BottomSlope; // Negative Y slope.
public float Near, Far; // Z of the near plane and far plane.
}
public struct ShadowmapTriangle
{
//--------------------------------------------------------------------------------------
// Used to compute an intersection of the orthographic projection and the Scene AABB
//--------------------------------------------------------------------------------------
public Vector4 pt0;
public Vector4 pt1;
public Vector4 pt2;
public bool culled;
public Vector4 pt(int i)
{
switch (i)
{
default:
case 0: return pt0;
case 1: return pt1;
case 2: return pt2;
}
}
public void pt(int i, Vector4 v)
{
switch (i)
{
default:
case 0: pt0 = v; break;
case 1: pt1 = v; break;
case 2: pt2 = v; break;
}
}
}
}