internal float SAHofPair(SSAABB boxa, SSAABB boxb) { SSAABB pairbox = boxa; pairbox.ExpandToFit(boxb); return(SAH(ref pairbox)); }
internal SSAABB AABBofPair(ssBVHNode <GO> nodea, ssBVHNode <GO> nodeb) { SSAABB box = nodea.box; box.ExpandToFit(nodeb.box); return(box); }
public void addObject(GO newOb) { SSAABB box = nAda.boundingBox(newOb); float boxSAH = ssBVHNode <GO> .SA(ref box); rootBVH.addObject(nAda, newOb, ref box, boxSAH); }
public void addObject(GO newOb) { SSAABB box = SSAABB.FromSphere(nAda.objectpos(newOb), nAda.radius(newOb)); float boxSAH = ssBVHNode <GO> .SA(ref box); rootBVH.addObject(nAda, newOb, ref box, boxSAH); }
internal float SAHofPair(ssBVHNode <GO> nodea, ssBVHNode <GO> nodeb) { SSAABB box = nodea.box; box.ExpandToFit(nodeb.box); return(SAH(ref box)); }
internal float SAH(ref SSAABB box) { float x_size = box.Max.X - box.Min.X; float y_size = box.Max.Y - box.Min.Y; float z_size = box.Max.Z - box.Min.Z; return(2.0f * ((x_size * y_size) + (x_size * z_size) + (y_size * z_size))); }
internal void computeVolume(SSBVHNodeAdaptor <GO> nAda) { box = nAda.boundingBox(gobjects[0]); for (int i = 1; i < gobjects.Count; i++) { expandVolume(nAda, nAda.boundingBox(gobjects[i])); } }
public SSSkeletalAnimation(int frameRate, SSSkeletalJoint[] jointInfo, SSSkeletalJointLocation[][] frames, SSAABB[] bounds = null) { _hierarchy = jointInfo; _frames = frames; _bounds = bounds; _frameRate = frameRate; }
private SSSkeletalAnimationMD5 readAnimation() { Match[] matches; // header seekEntry ("MD5Version", "10"); seekEntry ("commandline", SSMD5Parser._quotedStrRegex); matches = seekEntry ("numFrames", SSMD5Parser._uintRegex); var numFrames = Convert.ToInt32 (matches [1].Value); matches = seekEntry ("numJoints", SSMD5Parser._uintRegex); var numJoints = Convert.ToInt32 (matches [1].Value); matches = seekEntry ("frameRate", SSMD5Parser._uintRegex); var frameRate = Convert.ToInt32 (matches [1].Value); matches = seekEntry ("numAnimatedComponents", SSMD5Parser._uintRegex); int numAnimatedComponents = Convert.ToInt32 (matches [1].Value); var floatComponents = new float[numAnimatedComponents]; // hierarchy seekEntry ("hierarchy", "{"); var hierarchy = new SSSkeletalJoint[numJoints]; var flags = new byte[numJoints]; for (int j = 0; j < numJoints; ++j) { hierarchy [j] = readHierarchyEntry (out flags [j]); } seekEntry ("}"); // bounds seekEntry ("bounds", "{"); var bounds = new SSAABB[numFrames]; for (int f = 0; f < numFrames; ++f) { bounds [f] = readBounds (); } seekEntry ("}"); // base frame seekEntry ("baseframe", "{"); for (int j = 0; j < numJoints; ++j) { hierarchy[j].bindPoseLocation = readBaseFrame (); } seekEntry ("}"); // frames var frames = new SSSkeletalJointLocation[numFrames][]; for (int f = 0; f < numFrames; ++f) { matches = seekEntry ("frame", SSMD5Parser._uintRegex, "{"); int frameIdx = Convert.ToInt32 (matches [1].Value); frames [frameIdx] = readFrameJoints (flags, hierarchy, floatComponents); seekEntry ("}"); } return new SSSkeletalAnimationMD5 (frameRate, hierarchy, frames, bounds); }
internal SSAABB toAABB() { SSAABB aabb = new SSAABB(); aabb.Min.X = box.Min.X; aabb.Min.Y = box.Min.Y; aabb.Min.Z = box.Min.Z; aabb.Max.X = box.Max.X; aabb.Max.Y = box.Max.Y; aabb.Max.Z = box.Max.Z; return(aabb); }
private void expandVolume(SSBVHNodeAdaptor <GO> nAda, SSAABB targetBox) { var boxCopy = box; box.ExpandToFit(targetBox); var expanded = !boxCopy.Equals(box); if (expanded && parent != null) { parent.childExpanded(nAda, this); } }
public void renderCells(ssBVHNode <SSObject> n, ref SSAABB parentbox, int depth) { float nudge = 0f; if (parentbox.Equals(n.box)) { // attempt to nudge out of z-fighting nudge = 0.2f; } if (highlightNodes.Contains(n)) { if (n.gobjects == null) { GL.Color4(Color.FromArgb(255, 25, 25, 100)); } else { GL.Color4(Color.Green); } } else { if (n.gobjects == null) { GL.Color4(Color.FromArgb(255, 20, 20, 20)); } else { GL.Color4(Color.DarkRed); } } Vector3 nudgeVect = new Vector3(nudge); Vector3 scale = n.box.max - n.box.min - 2f * nudgeVect; Matrix4 mat = Matrix4.CreateScale(scale) * Matrix4.CreateTranslation(n.box.min + nudgeVect); GL.PushMatrix(); GL.MultMatrix(ref mat); ibo.DrawElements(PrimitiveType.Lines, false); GL.PopMatrix(); if (n.right != null) { renderCells(n.right, ref n.box, depth: depth + 1); } if (n.left != null) { renderCells(n.left, ref n.box, depth: depth + 1); } }
internal void findOverlappingLeaves(SSBVHNodeAdaptor <GO> nAda, SSAABB aabb, List <ssBVHNode <GO> > overlapList) { if (toAABB().IntersectsAABB(aabb)) { if (gobjects != null) { overlapList.Add(this); } else { left.findOverlappingLeaves(nAda, aabb, overlapList); right.findOverlappingLeaves(nAda, aabb, overlapList); } } }
internal static void childRefit(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, bool propagate = true) { do { SSAABB oldbox = curNode.box; ssBVHNode <GO> left = curNode.left; ssBVHNode <GO> right = curNode.right; // start with the left box SSAABB newBox = left.box; // expand any dimension bigger in the right node if (right.box.Min.X < newBox.Min.X) { newBox.Min.X = right.box.Min.X; } if (right.box.Min.Y < newBox.Min.Y) { newBox.Min.Y = right.box.Min.Y; } if (right.box.Min.Z < newBox.Min.Z) { newBox.Min.Z = right.box.Min.Z; } if (right.box.Max.X > newBox.Max.X) { newBox.Max.X = right.box.Max.X; } if (right.box.Max.Y > newBox.Max.Y) { newBox.Max.Y = right.box.Max.Y; } if (right.box.Max.Z > newBox.Max.Z) { newBox.Max.Z = right.box.Max.Z; } // now set our box to the newly created box curNode.box = newBox; // and walk up the tree curNode = curNode.parent; } while (propagate && curNode != null); }
/// <summary> /// Computes vertex positions and normals (based on the state of runtime joint hierarchy). /// Updates VBO with the result. /// </summary> /// <returns>AABB of the vertices.</returns> public SSAABB ComputeVertices() { SSAABB aabb = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); for (int v = 0; v < _runtimeMesh.numVertices; ++v) { // position Vector3 pos = _runtimeMesh.computeVertexPos(v); _vertices [v].Position = pos; aabb.UpdateMin(pos); aabb.UpdateMax(pos); // normal _vertices [v].Normal = _runtimeMesh.computeVertexNormal(v); } _vbo.UpdateBufferData(_vertices); _bvh = null; // invalidate bvh return(aabb); }
internal void removeLeaf(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> removeLeaf) { if (left == null || right == null) { throw new Exception("bad intermediate node"); } ssBVHNode <GO> keepLeaf; if (removeLeaf == left) { keepLeaf = right; } else if (removeLeaf == right) { keepLeaf = left; } else { throw new Exception("removeLeaf doesn't match any leaf!"); } // "become" the leaf we are keeping. box = keepLeaf.box; left = keepLeaf.left; right = keepLeaf.right; gobjects = keepLeaf.gobjects; // clear the leaf.. // keepLeaf.left = null; keepLeaf.right = null; keepLeaf.gobjects = null; keepLeaf.parent = null; if (gobjects == null) { left.parent = this; right.parent = this; // reassign child parents.. this.setDepth(this.depth); // this reassigns depth for our children } else { // map the objects we adopted to us... gobjects.ForEach(o => { nAda.mapObjectToBVHLeaf(o, this); }); } // propagate our new volume.. if (parent != null) { parent.childRefit(nAda); } }
// Ray to AABB (AxisAlignedBoundingBox) // http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms public static bool intersectRayAABox2(SSRay ray, SSAABB box, ref float tnear, ref float tfar) { Vector3d T_1 = new Vector3d(); Vector3d T_2 = new Vector3d(); // vectors to hold the T-values for every direction double t_near = double.MinValue; // maximums defined in float.h double t_far = double.MaxValue; for (int i = 0; i < 3; i++) { //we test slabs in every direction if (ray.dir[i] == 0) { // ray parallel to planes in this direction if ((ray.pos[i] < box.Min[i]) || (ray.pos[i] > box.Max[i])) { return(false); // parallel AND outside box : no intersection possible } } else { // ray not parallel to planes in this direction T_1[i] = (box.Min[i] - ray.pos[i]) / ray.dir[i]; T_2[i] = (box.Max[i] - ray.pos[i]) / ray.dir[i]; if (T_1[i] > T_2[i]) { // we want T_1 to hold values for intersection with near plane var swp = T_2; // swap T_1 = swp; T_2 = T_1; } if (T_1[i] > t_near) { t_near = T_1[i]; } if (T_2[i] < t_far) { t_far = T_2[i]; } if ((t_near > t_far) || (t_far < 0)) { return(false); } } } tnear = (float)t_near; tfar = (float)t_far; // put return values in place return(true); // if we made it here, there was an intersection - YAY }
public override void renderMesh(SSRenderConfig renderConfig) { // apply animation channels _hierarchy.applySkeletalControllers(_channelControllers); SSAABB totalAABB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); foreach (var sub in _renderSubMeshes) { SSAABB aabb = sub.ComputeVertices(); sub.renderMesh(renderConfig); totalAABB.ExpandBy(aabb); } // update the bounding sphere var sphere = totalAABB.ToSphere(); base.boundingSphereCenter = sphere.center; base.boundingSphereRadius = sphere.radius; NotifyMeshPositionOrSizeChanged(); }
internal void childRefit(SSBVHNodeAdaptor <GO> nAda, bool recurse = true) { SSAABB oldbox = box; box.Min.X = left.box.Min.X; box.Max.X = left.box.Max.X; box.Min.Y = left.box.Min.Y; box.Max.Y = left.box.Max.Y; box.Min.Z = left.box.Min.Z; box.Max.Z = left.box.Max.Z; if (right.box.Min.X < box.Min.X) { box.Min.X = right.box.Min.X; } if (right.box.Min.Y < box.Min.Y) { box.Min.Y = right.box.Min.Y; } if (right.box.Min.Z < box.Min.Z) { box.Min.Z = right.box.Min.Z; } if (right.box.Max.X > box.Max.X) { box.Max.X = right.box.Max.X; } if (right.box.Max.Y > box.Max.Y) { box.Max.Y = right.box.Max.Y; } if (right.box.Max.Z > box.Max.Z) { box.Max.Z = right.box.Max.Z; } if (recurse && parent != null) { parent.childRefit(nAda); } }
public static bool intersectRayAABox1(SSRay ray, SSAABB box, ref float tnear, ref float tfar) { // r.dir is unit direction vector of ray Vector3 dirfrac = new Vector3(); float t; dirfrac.X = 1.0f / ray.dir.X; dirfrac.Y = 1.0f / ray.dir.Y; dirfrac.Z = 1.0f / ray.dir.Z; // lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner // r.org is origin of ray float t1 = (box.Min.X - ray.pos.X) * dirfrac.X; float t2 = (box.Max.X - ray.pos.X) * dirfrac.X; float t3 = (box.Min.Y - ray.pos.Y) * dirfrac.Y; float t4 = (box.Max.Y - ray.pos.Y) * dirfrac.Y; float t5 = (box.Min.Z - ray.pos.Z) * dirfrac.Z; float t6 = (box.Max.Z - ray.pos.Z) * dirfrac.Z; float tmin = Math.Max(Math.Max(Math.Min(t1, t2), Math.Min(t3, t4)), Math.Min(t5, t6)); float tmax = Math.Min(Math.Min(Math.Max(t1, t2), Math.Max(t3, t4)), Math.Max(t5, t6)); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0) { t = tmax; return(false); } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax) { t = tmax; return(false); } t = tmin; return(true); }
public static SSAABB GetBoundingBox(this LoadResult obj) { var bbox = new SSAABB(); foreach (var g in obj.Groups) { foreach (var f in g.Faces) { if (f.Count != 3) { throw new NotImplementedException("Non triangular face found in obj"); } var v0 = obj.Vertices[f[0].VertexIndex - 1].ToVector3(); var v1 = obj.Vertices[f[1].VertexIndex - 1].ToVector3(); var v2 = obj.Vertices[f[2].VertexIndex - 1].ToVector3(); var t = new Triangle(v0, v1, v2, null); bbox.ExpandToFit(t.BoundingBox); } } return(bbox); }
internal bool refitVolume(SSBVHNodeAdaptor <GO> nAda) { if (gobjects.Count == 0) { throw new NotImplementedException(); } // TODO: fix this... we should never get called in this case... SSAABB oldbox = box; computeVolume(nAda); if (!box.Equals(oldbox)) { if (parent != null) { parent.childRefit(nAda); } return(true); } else { return(false); } }
public static bool intersectRayAABox1(raytracinginoneweekend.Ray ray, SSAABB box, ref float tnear, ref float tfar) { float tMin = tnear; float tMax = tfar; if (!intersectPlane(box.Min.X, box.Max.X, ray.Direction.X, ray.Origin.X, ref tMin, ref tMax)) { return(false); } if (!intersectPlane(box.Min.Y, box.Max.Y, ray.Direction.Y, ray.Origin.Y, ref tMin, ref tMax)) { return(false); } if (!intersectPlane(box.Min.Z, box.Max.Z, ray.Direction.Z, ray.Origin.Z, ref tMin, ref tMax)) { return(false); } if (tMax > tfar) { throw new Exception("tMax > tfar"); } return(true); }
// Ray to AABB (AxisAlignedBoundingBox) // http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms public static bool intersectRayAABox2(SSRay ray, SSAABB box, ref float tnear, ref float tfar) { Vector3d T_1 = new Vector3d(); Vector3d T_2 = new Vector3d(); // vectors to hold the T-values for every direction double t_near = double.MinValue; // maximums defined in float.h double t_far = double.MaxValue; for (int i = 0; i < 3; i++){ //we test slabs in every direction if (ray.dir[i] == 0){ // ray parallel to planes in this direction if ((ray.pos[i] < box.Min[i]) || (ray.pos[i] > box.Max[i])) { return false; // parallel AND outside box : no intersection possible } } else { // ray not parallel to planes in this direction T_1[i] = (box.Min[i] - ray.pos[i]) / ray.dir[i]; T_2[i] = (box.Max[i] - ray.pos[i]) / ray.dir[i]; if(T_1[i] > T_2[i]){ // we want T_1 to hold values for intersection with near plane var swp = T_2; // swap T_1 = swp; T_2 = T_1; } if (T_1[i] > t_near){ t_near = T_1[i]; } if (T_2[i] < t_far){ t_far = T_2[i]; } if( (t_near > t_far) || (t_far < 0) ){ return false; } } } tnear = (float)t_near; tfar = (float)t_far; // put return values in place return true; // if we made it here, there was an intersection - YAY }
public SSSkeletalAnimationMD5(int frameRate, SSSkeletalJoint[] jointInfo, SSSkeletalJointLocation[][] frames, SSAABB[] bounds) : base(frameRate, jointInfo, frames, bounds) { }
public List <ssBVHNode <GO> > traverse(SSAABB volume) { return(traverse(box => box.IntersectsAABB(volume))); }
public static bool intersectRayAABox1(SSRay ray, SSAABB box, ref float tnear, ref float tfar) { // r.dir is unit direction vector of ray Vector3 dirfrac = new Vector3(); float t; dirfrac.X = 1.0f / ray.dir.X; dirfrac.Y = 1.0f / ray.dir.Y; dirfrac.Z = 1.0f / ray.dir.Z; // lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner // r.org is origin of ray float t1 = (box.Min.X - ray.pos.X)*dirfrac.X; float t2 = (box.Max.X - ray.pos.X)*dirfrac.X; float t3 = (box.Min.Y - ray.pos.Y)*dirfrac.Y; float t4 = (box.Max.Y - ray.pos.Y)*dirfrac.Y; float t5 = (box.Min.Z - ray.pos.Z)*dirfrac.Z; float t6 = (box.Max.Z - ray.pos.Z)*dirfrac.Z; float tmin = Math.Max(Math.Max(Math.Min(t1, t2), Math.Min(t3, t4)), Math.Min(t5, t6)); float tmax = Math.Min(Math.Min(Math.Max(t1, t2), Math.Max(t3, t4)), Math.Max(t5, t6)); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0) { t = tmax; return false; } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax) { t = tmax; return false; } t = tmin; return true; }
internal static void addObject(SSBVHNodeAdaptor <GO> nAda, ssBVHNode <GO> curNode, GO newOb, ref SSAABB newObBox, float newObSAH) { // 1. first we traverse the node looking for the best leaf while (curNode.gobjects == null) { // find the best way to add this object.. 3 options.. // 1. send to left node (L+N,R) // 2. send to right node (L,R+N) // 3. merge and pushdown left-and-right node (L+R,N) var left = curNode.left; var right = curNode.right; float leftSAH = SA(left); float rightSAH = SA(right); float sendLeftSAH = rightSAH + SA(left.box.ExpandedBy(newObBox)); // (L+N,R) float sendRightSAH = leftSAH + SA(right.box.ExpandedBy(newObBox)); // (L,R+N) float mergedLeftAndRightSAH = SA(AABBofPair(left, right)) + newObSAH; // (L+R,N) // Doing a merge-and-pushdown can be expensive, so we only do it if it's notably better const float MERGE_DISCOUNT = 0.3f; if (mergedLeftAndRightSAH < (Math.Min(sendLeftSAH, sendRightSAH)) * MERGE_DISCOUNT) { addObject_Pushdown(nAda, curNode, newOb); return; } else { if (sendLeftSAH < sendRightSAH) { curNode = left; } else { curNode = right; } } } // 2. then we add the object and map it to our leaf curNode.gobjects.Add(newOb); nAda.mapObjectToBVHLeaf(newOb, curNode); curNode.refitVolume(nAda); // split if necessary... curNode.splitIfNecessary(nAda); }
internal void addObject(SSBVHNodeAdaptor <GO> nAda, GO newOb, ref SSAABB newObBox, float newObSAH) { addObject(nAda, this, newOb, ref newObBox, newObSAH); }
private SSSkeletalAnimationMD5 readAnimation() { Match[] matches; // header seekEntry("MD5Version", "10"); seekEntry("commandline", SSMD5Parser._quotedStrRegex); matches = seekEntry("numFrames", SSMD5Parser._uintRegex); var numFrames = Convert.ToInt32(matches [1].Value); matches = seekEntry("numJoints", SSMD5Parser._uintRegex); var numJoints = Convert.ToInt32(matches [1].Value); matches = seekEntry("frameRate", SSMD5Parser._uintRegex); var frameRate = Convert.ToInt32(matches [1].Value); matches = seekEntry("numAnimatedComponents", SSMD5Parser._uintRegex); int numAnimatedComponents = Convert.ToInt32(matches [1].Value); var floatComponents = new float[numAnimatedComponents]; // hierarchy seekEntry("hierarchy", "{"); var hierarchy = new SSSkeletalJoint[numJoints]; var flags = new byte[numJoints]; for (int j = 0; j < numJoints; ++j) { hierarchy [j] = readHierarchyEntry(out flags [j]); } seekEntry("}"); // bounds seekEntry("bounds", "{"); var bounds = new SSAABB[numFrames]; for (int f = 0; f < numFrames; ++f) { bounds [f] = readBounds(); } seekEntry("}"); // base frame seekEntry("baseframe", "{"); for (int j = 0; j < numJoints; ++j) { hierarchy[j].bindPoseLocation = readBaseFrame(); } seekEntry("}"); // frames var frames = new SSSkeletalJointLocation[numFrames][]; for (int f = 0; f < numFrames; ++f) { matches = seekEntry("frame", SSMD5Parser._uintRegex, "{"); int frameIdx = Convert.ToInt32(matches [1].Value); frames [frameIdx] = readFrameJoints(flags, hierarchy, floatComponents); seekEntry("}"); } return(new SSSkeletalAnimationMD5(frameRate, hierarchy, frames, bounds)); }
private void ComputeProjections(List<SSObject> objects, SSLightBase light, Matrix4 cameraView, Matrix4 cameraProj) { if (light.GetType() != typeof(SSDirectionalLight)) { throw new NotSupportedException(); } SSDirectionalLight dirLight = (SSDirectionalLight)light; // light-aligned unit vectors Vector3 lightZ = dirLight.Direction.Normalized(); Vector3 lightX, lightY; OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY); // transform matrix from regular space into light aligned space Matrix4 lightTransform = new Matrix4 ( lightX.X, lightX.Y, lightX.Z, 0f, lightY.X, lightY.Y, lightY.Z, 0f, lightZ.X, lightZ.Y, lightZ.Z, 0f, 0f, 0f, 0f, 0f ); // Find AABB of frustum corners in light coordinates Matrix4 cameraViewProj = cameraView * cameraProj; SSAABB frustumLightBB = SSAABB.FromFrustum(ref lightTransform, ref cameraViewProj); bool shrink = false; SSAABB objsLightBB = new SSAABB (float.PositiveInfinity, float.NegativeInfinity); #if true // (optional) scene dependent optimization // Trim the light-bounding box by the shadow receivers (only in light-space x,y,maxz) FrustumCuller cameraFrustum = new FrustumCuller (ref cameraViewProj); foreach (var obj in objects) { // pass through all shadow casters and receivers if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.receivesShadows) { continue; } else if (cameraFrustum.isSphereInsideFrustum(obj.worldBoundingSphere)) { // determine AABB in light coordinates of the objects so far shrink = true; Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; objsLightBB.UpdateMin(localMin); objsLightBB.UpdateMax(localMax); } } #endif // Optimize the light-frustum-projection bounding box by the object-bounding-box SSAABB resultLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); if (shrink) { // shrink the XY & far-Z coordinates.. resultLightBB.Min.Xy = Vector2.ComponentMax(frustumLightBB.Min.Xy, objsLightBB.Min.Xy); resultLightBB.Min.Z = objsLightBB.Min.Z; resultLightBB.Max = Vector3.ComponentMin(frustumLightBB.Max, objsLightBB.Max); } else { resultLightBB = frustumLightBB; } // View and projection matrices, used by the scene later viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY, out m_shadowViewMatrix, out m_shadowProjMatrix); // Now extend Z of the result AABB to cover shadow-casters closer to the light inside the // original box foreach (var obj in objects) { if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.castsShadow) { continue; } Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; if (localMin.Z < resultLightBB.Min.Z) { Vector3 localMax = lightAlignedPos + rad; if (OpenTKHelper.RectsOverlap(resultLightBB.Min.Xy, resultLightBB.Max.Xy, localMin.Xy, localMax.Xy)) { resultLightBB.Min.Z = localMin.Z; } } } // Generate frustum culler from the BB extended towards light to include shadow casters Matrix4 frustumView, frustumProj; viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY, out frustumView, out frustumProj); Matrix4 frustumMatrix = frustumView * frustumProj; FrustumCuller = new FrustumCuller (ref frustumMatrix); }
protected static void viewProjFromLightAlignedBB(ref SSAABB bb, ref Matrix4 lightTransform, ref Vector3 lightY, out Matrix4 viewMatrix, out Matrix4 projMatrix) { // Use center of AABB in regular coordinates to get the view matrix Vector3 targetLightSpace = bb.Center(); Vector3 eyeLightSpace = new Vector3 (targetLightSpace.X, targetLightSpace.Y, bb.Min.Z); Vector3 viewTarget = Vector3.Transform(targetLightSpace, lightTransform.Inverted()); Vector3 viewEye = Vector3.Transform(eyeLightSpace, lightTransform.Inverted()); Vector3 viewUp = lightY; viewMatrix = Matrix4.LookAt(viewEye, viewTarget, viewUp); // Finish the projection matrix Vector3 diff = bb.Diff(); float width, height, nearZ, farZ; width = diff.X; height = diff.Y; nearZ = 1f; farZ = 1f + diff.Z; projMatrix = Matrix4.CreateOrthographic(width, height, nearZ, farZ); }
protected void ComputeProjections( List<SSObject> objects, Matrix4 cameraView, Matrix4 cameraProj, float fov, float aspect, float cameraNearZ, float cameraFarZ) { if (m_light.GetType() != typeof(SSDirectionalLight)) { throw new NotSupportedException(); } SSDirectionalLight dirLight = (SSDirectionalLight)m_light; // light-aligned unit vectors Vector3 lightZ = dirLight.Direction.Normalized(); Vector3 lightX, lightY; OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY); // transform matrix from regular space into light aligned space Matrix4 lightTransform = new Matrix4 ( lightX.X, lightX.Y, lightX.Z, 0f, lightY.X, lightY.Y, lightY.Z, 0f, lightZ.X, lightZ.Y, lightZ.Z, 0f, 0f, 0f, 0f, 0f ); // Step 0: camera projection matrix (nearZ and farZ modified) for each frustum split float prevFarZ = cameraNearZ; for (int i = 0; i < c_numberOfSplits; ++i) { // generate frustum splits using Practical Split Scheme (GPU Gems 3, 10.2.1) float iRatio = (float)(i+1) / (float)c_numberOfSplits; float cLog = cameraNearZ * (float)Math.Pow(cameraFarZ / cameraNearZ, iRatio); float cUni = cameraNearZ + (cameraFarZ - cameraNearZ) * iRatio; float nextFarZ = LogVsLinearSplitFactor * cLog + (1f - LogVsLinearSplitFactor) * cUni; float nextNearZ = prevFarZ; // exported to the shader m_viewSplits [i] = nextFarZ; // create a view proj matrix with the nearZ, farZ values for the current split m_frustumViewProjMatrices[i] = cameraView * Matrix4.CreatePerspectiveFieldOfView(fov, aspect, nextNearZ, nextFarZ); // create light-aligned AABBs of frustums m_frustumLightBB [i] = SSAABB.FromFrustum(ref lightTransform, ref m_frustumViewProjMatrices [i]); prevFarZ = nextFarZ; } #if true // Optional scene-dependent optimization for (int i = 0; i < c_numberOfSplits; ++i) { m_objsLightBB[i] = new SSAABB(float.PositiveInfinity, float.NegativeInfinity); m_splitFrustums[i] = new SSFrustumCuller(ref m_frustumViewProjMatrices[i]); m_shrink[i] = false; } foreach (var obj in objects) { // pass through all shadow casters and receivers if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.receivesShadows) { continue; } else { for (int i = 0; i < c_numberOfSplits; ++i) { if (m_splitFrustums[i].isSphereInsideFrustum(obj.worldBoundingSphere)) { // determine AABB in light coordinates of the objects so far m_shrink[i] = true; Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; m_objsLightBB[i].UpdateMin(localMin); m_objsLightBB[i].UpdateMax(localMax); } } } } #endif for (int i = 0; i < c_numberOfSplits; ++i) { if (m_shrink [i]) { m_resultLightBB[i].Min = Vector3.ComponentMax(m_frustumLightBB [i].Min, m_objsLightBB [i].Min); m_resultLightBB [i].Max = Vector3.ComponentMin(m_frustumLightBB [i].Max, m_objsLightBB [i].Max); } else { m_resultLightBB [i] = m_frustumLightBB [i]; } } for (int i = 0; i < c_numberOfSplits; ++i) { // Obtain view + projection + crop matrix, need it later Matrix4 shadowView, shadowProj; viewProjFromLightAlignedBB(ref m_resultLightBB [i], ref lightTransform, ref lightY, out shadowView, out shadowProj); m_shadowViewProjMatrices[i] = shadowView * shadowProj * c_cropMatrices[i]; // obtain view + projection + clio + bias m_shadowViewProjBiasMatrices[i] = m_shadowViewProjMatrices[i] * c_biasMatrix; // There is, currently, no mathematically derived solution to how much Poisson spread scaling // you need for each split. Current improvisation combines 1) increasing spread for the near // splits; reducing spread for the far splits and 2) reducing spread for splits with larger // light-aligned areas; increasing spread for splits with smaller light-aligned areas m_poissonScaling [i] = m_resultLightBB [i].Diff().Xy / (100f * (float)Math.Pow(3.0, i - 1)); } // Combine all splits' BB into one and extend it to include shadow casters closer to light SSAABB castersLightBB = new SSAABB (float.PositiveInfinity, float.NegativeInfinity); for (int i = 0; i < c_numberOfSplits; ++i) { castersLightBB.Combine(ref m_resultLightBB [i]); } // extend Z of the AABB to cover shadow-casters closer to the light foreach (var obj in objects) { if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f || !obj.renderState.visible || !obj.renderState.castsShadow) { continue; } else { Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform); Vector3 rad = new Vector3(obj.worldBoundingSphereRadius); Vector3 localMin = lightAlignedPos - rad; Vector3 localMax = lightAlignedPos + rad; if (localMin.Z < castersLightBB.Min.Z) { if (OpenTKHelper.RectsOverlap(castersLightBB.Min.Xy, castersLightBB.Max.Xy, localMin.Xy, localMax.Xy)) { castersLightBB.Min.Z = localMin.Z; } } } } // Generate frustum culler from the BB extended towards light to include shadow casters Matrix4 frustumView, frustumProj; viewProjFromLightAlignedBB(ref castersLightBB, ref lightTransform, ref lightY, out frustumView, out frustumProj); Matrix4 frustumMatrix = frustumView * frustumProj; FrustumCuller = new SSFrustumCuller (ref frustumMatrix); }
internal void addObject(SSBVHNodeAdaptor <GO> nAda, GO newOb, ref SSAABB newObBox, float newObSAH) { if (gobjects != null) { // add the object and map it to our leaf gobjects.Add(newOb); nAda.mapObjectToBVHLeaf(newOb, this); refitVolume(nAda); // split if necessary... splitIfNecessary(nAda); } else { // find the best way to add this object.. 3 options.. // 1. send to left node (L+N,R) // 2. send to right node (L,R+N) // 3. merge and pushdown left-and-right node (L+R,N) float leftSAH = SAH(left); float rightSAH = SAH(right); float sendLeftSAH = rightSAH + SAH(left.box.ExpandedBy(newObBox)); // (L+N,R) float sendRightSAH = leftSAH + SAH(right.box.ExpandedBy(newObBox)); // (L,R+N) float mergedLeftAndRightSAH = SAH(AABBofPair(left, right)) + newObSAH; // (L+R,N) // Doing a merge-and-pushdown can be expensive, so we only do it if it's notably better const float MERGE_DISCOUNT = 0.3f; if (mergedLeftAndRightSAH < (Math.Min(sendLeftSAH, sendRightSAH)) * MERGE_DISCOUNT) { // merge and pushdown left and right as a new node.. var mSubnode = new ssBVHNode <GO>(nAda.BVH); mSubnode.left = left; mSubnode.right = right; mSubnode.parent = this; mSubnode.gobjects = null; // we need to be an interior node... so null out our object list.. left.parent = mSubnode; right.parent = mSubnode; mSubnode.childRefit(nAda, recurse: false); // make new subnode for obj var nSubnode = new ssBVHNode <GO>(nAda.BVH); nSubnode.parent = this; nSubnode.gobjects = new List <GO> { newOb }; nAda.mapObjectToBVHLeaf(newOb, nSubnode); nSubnode.computeVolume(nAda); // make assignments.. this.left = mSubnode; this.right = nSubnode; this.setDepth(this.depth); // propagate new depths to our children. this.childRefit(nAda); } else { if (sendLeftSAH < sendRightSAH) { // send left left.addObject(nAda, newOb, ref newObBox, newObSAH); } else { // send right right.addObject(nAda, newOb, ref newObBox, newObSAH); } } } }