// TODO: Find a way to replug this /// <summary> /// Adds a default frustum culling for rendering only meshes that are only inside the frustum/ /// </summary> /// <param name="modelRenderer">The model renderer.</param> /// <returns>ModelRenderer.</returns> //public static ModelComponentRenderer AddDefaultFrustumCulling(this ModelComponentRenderer modelRenderer) //{ // modelRenderer.UpdateMeshes = FrustumCulling; // return modelRenderer; //} private static void FrustumCulling(RenderContext context, FastList<RenderMesh> meshes) { Matrix viewProjection, mat1, mat2; // Compute view * projection context.Parameters.Get(TransformationKeys.View, out mat1); context.Parameters.Get(TransformationKeys.Projection, out mat2); Matrix.Multiply(ref mat1, ref mat2, out viewProjection); var frustum = new BoundingFrustum(ref viewProjection); for (var i = 0; i < meshes.Count; ++i) { var renderMesh = meshes[i]; // Fast AABB transform: http://zeuxcg.org/2010/10/17/aabb-from-obb-with-component-wise-abs/ // Get world matrix renderMesh.Mesh.Parameters.Get(TransformationKeys.World, out mat1); // Compute transformed AABB (by world) var boundingBoxExt = new BoundingBoxExt(renderMesh.Mesh.BoundingBox); boundingBoxExt.Transform(mat1); // Perform frustum culling if (!frustum.Contains(ref boundingBoxExt)) { meshes.SwapRemoveAt(i--); } } }
// TODO GRAPHICS REFACTOR not thread-safe public void Collect(RenderView view) { ReevaluateActiveRenderStages(); // Collect objects, and perform frustum culling // TODO GRAPHICS REFACTOR Create "VisibilityObject" (could contain multiple RenderNode) and separate frustum culling from RenderSystem // TODO GRAPHICS REFACTOR optimization: maybe we could process all views at once (swap loop between per object and per view) // View bounds calculation view.MinimumDistance = float.PositiveInfinity; view.MaximumDistance = float.NegativeInfinity; Matrix viewInverse = view.View; viewInverse.Invert(); var plane = new Plane(viewInverse.Forward, Vector3.Dot(viewInverse.TranslationVector, viewInverse.Forward)); // TODO: Point-normal-constructor seems wrong. Check. // TODO: This should be configured by the creator of the view. E.g. near clipping can be enabled for spot light shadows. var ignoreDepthPlanes = view is ShadowMapRenderView; // Prepare culling mask foreach (var renderViewStage in view.RenderStages) { var renderStageIndex = renderViewStage.RenderStage.Index; viewRenderStageMask[renderStageIndex / RenderStageMaskSizePerEntry] |= 1U << (renderStageIndex % RenderStageMaskSizePerEntry); } // Create the bounding frustum locally on the stack, so that frustum.Contains is performed with boundingBox that is also on the stack // TODO GRAPHICS REFACTOR frustum culling is currently hardcoded (cf previous TODO, we should make this more modular and move it out of here) var frustum = new BoundingFrustum(ref view.ViewProjection); var cullingMode = view.CullingMode; // TODO GRAPHICS REFACTOR we currently forward SceneCameraRenderer.CullingMask // Note sure this is really a good mechanism long term (it forces to recreate multiple time the same view, instead of using RenderStage + selectors or a similar mechanism) // This is still supported so that existing gizmo code kept working with new graphics refactor. Might be reconsidered at some point. var cullingMask = view.CullingMask; // Process objects foreach (var renderObject in RenderObjects) { // Skip not enabled objects if (!renderObject.Enabled || ((EntityGroupMask)(1U << (int)renderObject.RenderGroup) & cullingMask) == 0) continue; // Custom per-view filtering bool skip = false; lock (ViewObjectFilters) { // TODO HACK First filter with global static filters foreach (var filter in ViewObjectFilters) { if (!filter(view, renderObject)) { skip = true; break; } } if (skip) continue; } // TODO HACK Then filter with RenderSystem filters foreach (var filter in RenderSystem.ViewObjectFilters) { if (!filter(view, renderObject)) { skip = true; break; } } if (skip) continue; var renderStageMask = RenderData.GetData(RenderStageMaskKey); var renderStageMaskNode = renderObject.VisibilityObjectNode * stageMaskMultiplier; // Determine if this render object belongs to this view bool renderStageMatch = false; unsafe { fixed (uint* viewRenderStageMaskStart = viewRenderStageMask) fixed (uint* objectRenderStageMaskStart = renderStageMask.Data) { var viewRenderStageMaskPtr = viewRenderStageMaskStart; var objectRenderStageMaskPtr = objectRenderStageMaskStart + renderStageMaskNode.Index; for (int i = 0; i < viewRenderStageMask.Length; ++i) { if ((*viewRenderStageMaskPtr++ & *objectRenderStageMaskPtr++) != 0) { renderStageMatch = true; break; } } } } // Object not part of this view because no render stages in this objects are visible in this view if (!renderStageMatch) continue; // Fast AABB transform: http://zeuxcg.org/2010/10/17/aabb-from-obb-with-component-wise-abs/ // Compute transformed AABB (by world) if (cullingMode == CameraCullingMode.Frustum && renderObject.BoundingBox.Extent != Vector3.Zero && !FrustumContainsBox(ref frustum, ref renderObject.BoundingBox, ignoreDepthPlanes)) { continue; } // Add object to list of visible objects // TODO GRAPHICS REFACTOR we should be able to push multiple elements with future VisibilityObject // TODO GRAPHICS REFACTOR not thread-safe view.RenderObjects.Add(renderObject); // Calculate bounding box of all render objects in the view if (renderObject.BoundingBox.Extent != Vector3.Zero) CalculateMinMaxDistance(view, ref plane, ref renderObject.BoundingBox); } }
private static bool FrustumContainsBox(ref BoundingFrustum frustum, ref BoundingBoxExt boundingBoxExt, bool ignoreDepthPlanes) { unsafe { fixed (Plane* planeStart = &frustum.LeftPlane) { var plane = planeStart; for (int i = 0; i < 6; ++i) { if (ignoreDepthPlanes && i > 3) continue; // Previous code: if (Vector3.Dot(boundingBoxExt.Center, plane->Normal) + boundingBoxExt.Extent.X * Math.Abs(plane->Normal.X) + boundingBoxExt.Extent.Y * Math.Abs(plane->Normal.Y) + boundingBoxExt.Extent.Z * Math.Abs(plane->Normal.Z) <= -plane->D) return false; plane++; } } return true; } }
/// <summary> /// Determines whether a <see cref="BoundingFrustum" /> intersects or contains an AABB determined by its center and extent. /// Faster variant specific for frustum culling. /// </summary> /// <param name="frustum">The frustum.</param> /// <param name="boundingBoxExt">The bounding box ext.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public static bool FrustumContainsBox(ref BoundingFrustum frustum, ref BoundingBoxExt boundingBoxExt) { unsafe { fixed (Plane* planeStart = &frustum.LeftPlane) { var plane = planeStart; for (int i = 0; i < 6; ++i) { // Previous code: if (Vector3.Dot(boundingBoxExt.Center, plane->Normal) + boundingBoxExt.Extent.X * Math.Abs(plane->Normal.X) + boundingBoxExt.Extent.Y * Math.Abs(plane->Normal.Y) + boundingBoxExt.Extent.Z * Math.Abs(plane->Normal.Z) <= -plane->D) return false; plane++; } } return true; } /* unsafe { fixed (Plane* planeStart = &frustum.LeftPlane) fixed (Vector3* pExtent = &boundingBoxExt.Extent) { var plane = planeStart; for (int i = 0; i < 6; ++i) { // Previous code: //if (Vector3.Dot(boundingBoxExt.Center, plane->Normal) // + boundingBoxExt.Extent.X * Math.Abs(plane->Normal.X) // + boundingBoxExt.Extent.Y * Math.Abs(plane->Normal.Y) // + boundingBoxExt.Extent.Z * Math.Abs(plane->Normal.Z) // <= -plane->D) // Optimized version (only 1 dot and cheaper Math.Abs) // https://fgiesen.wordpress.com/2010/10/17/view-frustum-culling/ // return dot3(center, plane) + dot3(extent, absPlane) <= -plane.w; // or // vector4 signFlip = componentwise_and(plane, 0x80000000); // vector3 centerOffset = xor(extent, signFlip) // dot3(center + centerOffset, plane) <= -plane.w; uint val = (((uint*)&plane->Normal)[0] & 0x80000000) ^ ((uint*)pExtent)[0]; var dist = plane->Normal.X * ((*(float*)(&val)) + boundingBoxExt.Center.X); val = (((uint*)&plane->Normal)[1] & 0x80000000) ^ ((uint*)pExtent)[1]; dist += plane->Normal.Y * ((*(float*)(&val)) + boundingBoxExt.Center.Y); val = (((uint*)&plane->Normal)[2] & 0x80000000) ^ ((uint*)pExtent)[2]; dist += plane->Normal.Z * ((*(float*)(&val)) + boundingBoxExt.Center.Z); if (dist <= -plane->D) return false; plane++; } } return true; } */ }
/// <summary> /// Calculates the projection matrix and view matrix. /// </summary> /// <param name="screenAspectRatio">The current screen aspect ratio. If null, use the <see cref="AspectRatio"/> even if <see cref="UseCustomAspectRatio"/> is false.</param> public void Update(float? screenAspectRatio) { // Calculates the View if (!UseCustomViewMatrix) { var worldMatrix = EnsureEntity.Transform.WorldMatrix; Matrix.Invert(ref worldMatrix, out ViewMatrix); } // Calculates the projection // TODO: Should we throw an error if Projection is not set? if (!UseCustomProjectionMatrix) { // Calculates the aspect ratio var aspectRatio = AspectRatio; if (!UseCustomAspectRatio && screenAspectRatio.HasValue) { aspectRatio = screenAspectRatio.Value; } ProjectionMatrix = Projection == CameraProjectionMode.Perspective ? Matrix.PerspectiveFovRH(MathUtil.DegreesToRadians(VerticalFieldOfView), aspectRatio, NearClipPlane, FarClipPlane) : Matrix.OrthoRH(aspectRatio * OrthographicSize, OrthographicSize, NearClipPlane, FarClipPlane); } // Update ViewProjectionMatrix Matrix.Multiply(ref ViewMatrix, ref ProjectionMatrix, out ViewProjectionMatrix); // Update the frustum. Frustum = new BoundingFrustum(ref ViewProjectionMatrix); }
private void PrepareRenderMeshes(RenderModel renderModel, List<Mesh> meshes, ref FastListStruct<RenderMesh> renderMeshes, RenderItemCollection opaqueList, RenderItemCollection transparentList) { // Add new render meshes for (int i = renderMeshes.Count; i < meshes.Count; i++) { var renderMesh = new RenderMesh(renderModel, meshes[i]); renderMeshes.Add(renderMesh); } // Create the bounding frustum locally on the stack, so that frustum.Contains is performed with boundingBox that is also on the stack var frustum = new BoundingFrustum(ref ViewProjectionMatrix); for (int i = 0; i < renderMeshes.Count; i++) { var renderMesh = renderMeshes[i]; // Update the model hierarchy var modelViewHierarchy = renderModel.ModelComponent.ModelViewHierarchy; modelViewHierarchy.UpdateRenderMesh(renderMesh); if (!renderMesh.Enabled) { continue; } // Upload skinning blend matrices BoundingBoxExt boundingBox; skinningUpdater.Update(modelViewHierarchy, renderMesh, out boundingBox); // Fast AABB transform: http://zeuxcg.org/2010/10/17/aabb-from-obb-with-component-wise-abs/ // Compute transformed AABB (by world) // TODO: CullingMode should be pluggable // TODO: This should not be necessary. Add proper bounding boxes to gizmos etc. if (CullingMode == CullingMode.Frustum && boundingBox.Extent != Vector3.Zero && !frustum.Contains(ref boundingBox)) { continue; } // Project the position // TODO: This could be done in a SIMD batch, but we need to figure-out how to plugin in with RenderMesh object var worldPosition = new Vector4(renderMesh.WorldMatrix.TranslationVector, 1.0f); Vector4 projectedPosition; Vector4.Transform(ref worldPosition, ref ViewProjectionMatrix, out projectedPosition); var projectedZ = projectedPosition.Z / projectedPosition.W; renderMesh.RasterizerState = renderMesh.IsGeometryInverted ? RasterizerStateForInvertedGeometry : RasterizerState; renderMesh.UpdateMaterial(); var list = renderMesh.HasTransparency ? transparentList : opaqueList; list.Add(new RenderItem(this, renderMesh, projectedZ)); } }
/// <summary> /// Calculates the projection matrix and view matrix. /// </summary> /// <param name="screenAspectRatio">The current screen aspect ratio. If null, use the <see cref="AspectRatio"/> even if <see cref="UseCustomAspectRatio"/> is false.</param> public void Update(float? screenAspectRatio) { // Calculates the View if (!UseCustomViewMatrix) { var worldMatrix = EnsureEntity.Transform.WorldMatrix; Vector3 scale, translation; worldMatrix.Decompose(out scale, out ViewMatrix, out translation); // Transpose ViewMatrix (rotation only, so equivalent to inversing it) ViewMatrix.Transpose(); // Rotate our translation so that we can inject it in the view matrix directly Vector3.TransformCoordinate(ref translation, ref ViewMatrix, out translation); // Apply inverse of translation (equivalent to opposite) ViewMatrix.TranslationVector = -translation; } // Calculates the projection // TODO: Should we throw an error if Projection is not set? if (!UseCustomProjectionMatrix) { // Calculates the aspect ratio var aspectRatio = AspectRatio; if (screenAspectRatio.HasValue && !UseCustomAspectRatio) { aspectRatio = screenAspectRatio.Value; } ProjectionMatrix = Projection == CameraProjectionMode.Perspective ? Matrix.PerspectiveFovRH(MathUtil.DegreesToRadians(VerticalFieldOfView), aspectRatio, NearClipPlane, FarClipPlane) : Matrix.OrthoRH(aspectRatio * OrthographicSize, OrthographicSize, NearClipPlane, FarClipPlane); } // Update ViewProjectionMatrix Matrix.Multiply(ref ViewMatrix, ref ProjectionMatrix, out ViewProjectionMatrix); // Update the frustum. Frustum = new BoundingFrustum(ref ViewProjectionMatrix); }