private static void ClearFlags(IInternalTerrainLayer layer, RenderContext context) { EffectBinding materialBinding; if (layer.Material.TryGet(context.RenderPass, out materialBinding)) { materialBinding.EffectEx.Id = 0; materialBinding.Id = 0; } }
private void ProcessLayer(GraphicsDevice graphicsDevice, RenderContext context, TerrainClipmap clipmap, bool isBaseClipmap, IInternalTerrainLayer layer, Aabb tileAabb) { // The material needs to have a "Base" or "Detail" render pass. if (!layer.Material.Contains(context.RenderPass)) { return; } var layerAabb = layer.Aabb ?? tileAabb; //if (!HaveContactXZ(ref layerAabb, ref clipmap.Aabb)) // continue; bool isInInvalidRegion = false; for (int level = clipmap.NumberOfLevels - 1; level >= 0; level--) { for (int i = 0; i < clipmap.InvalidRegions[level].Count; i++) { var aabb = clipmap.InvalidRegions[level][i]; if (HaveContactXZ(ref layerAabb, ref aabb)) { isInInvalidRegion = true; break; } } if (isInInvalidRegion) { break; } } if (!isInInvalidRegion) { return; } // Reset render state for each layer because layers are allowed to change render state // without restoring it in a restore pass. // The base clipmap uses no blending. The detail clipmap uses alpha-blending (but only in // RGB, A is not changed, A is used e.g. for hole info). graphicsDevice.BlendState = isBaseClipmap ? BlendState.Opaque : BlendStateAlphaBlendRgb; graphicsDevice.RasterizerState = RasterizerStateCullNoneWithScissorTest; graphicsDevice.DepthStencilState = DepthStencilState.None; // Get the EffectBindings and the Effect for the current render pass. EffectBinding materialInstanceBinding = layer.MaterialInstance[context.RenderPass]; EffectBinding materialBinding = layer.Material[context.RenderPass]; EffectEx effectEx = materialBinding.EffectEx; Effect effect = materialBinding.Effect; context.MaterialInstanceBinding = materialInstanceBinding; context.MaterialBinding = materialBinding; if (effectEx.Id == 0) { effectEx.Id = 1; // Update and apply global effect parameter bindings - these bindings set the // effect parameter values for "global" parameters. For example, if an effect uses // a "ViewProjection" parameter, then a binding will compute this matrix from the // current CameraInstance in the render context and update the effect parameter. foreach (var binding in effect.GetParameterBindings()) { if (binding.Description.Hint == EffectParameterHint.Global) { binding.Update(context); binding.Apply(context); } } } // Update and apply material bindings. // If this material is the same as in the last ProcessLayer() call, then we can skip this. if (_previousMaterialBinding != materialBinding) { _previousMaterialBinding = materialBinding; if (materialBinding.Id == 0) { materialBinding.Id = 1; foreach (var binding in materialBinding.ParameterBindings) { binding.Update(context); binding.Apply(context); } } else { // The material has already been updated in this frame. foreach (var binding in materialBinding.ParameterBindings) { binding.Apply(context); } } } // Update and apply local, per-instance, and per-pass bindings - these are bindings // for parameters, like the "World" matrix or lighting parameters. foreach (var binding in materialInstanceBinding.ParameterBindings) { if (binding.Description.Hint != EffectParameterHint.PerPass) { binding.Update(context); binding.Apply(context); } } // Select and apply technique. var techniqueBinding = materialInstanceBinding.TechniqueBinding; techniqueBinding.Update(context); var technique = techniqueBinding.GetTechnique(effect, context); effect.CurrentTechnique = technique; int border = isBaseClipmap ? 0 : Border; // Region covered by the layer. Vector2F layerStart = new Vector2F(layerAabb.Minimum.X, layerAabb.Minimum.Z); Vector2F layerEnd = new Vector2F(layerAabb.Maximum.X, layerAabb.Maximum.Z); // Clamp to tile AABB layerStart.X = Math.Max(layerStart.X, tileAabb.Minimum.X); layerStart.Y = Math.Max(layerStart.Y, tileAabb.Minimum.Z); layerEnd.X = Math.Min(layerEnd.X, tileAabb.Maximum.X); layerEnd.Y = Math.Min(layerEnd.Y, tileAabb.Maximum.Z); // Loop over clipmap levels. for (int level = 0; level < clipmap.NumberOfLevels; level++) { // If there are no invalid regions, the combined AABB is NaN. if (Numeric.IsNaN(clipmap.CombinedInvalidRegionsAabbs[level].Minimum.X)) { continue; } if (layer.FadeInStart > level || layer.FadeOutEnd <= level) { continue; } if (!HaveContactXZ(ref layerAabb, ref clipmap.CombinedInvalidRegionsAabbs[level])) { continue; } Vector2F levelOrigin = clipmap.Origins[level]; Vector2F levelSize = new Vector2F(clipmap.LevelSizes[level]); // without border! Vector2F levelEnd = levelOrigin + levelSize; float cellsPerLevelWithoutBorder = clipmap.CellsPerLevel - 2 * border; float cellSize = clipmap.ActualCellSizes[level]; float texelsPerUnit = 1 / cellSize; // Following effect parameters change per clipmap level: UpdateAndApplyParameter(materialBinding, "TerrainClipmapLevel", (float)level, context); UpdateAndApplyParameter(materialBinding, "TerrainClipmapCellSize", cellSize, context); // The rectangle of the whole clipmap level (including border). var levelRect = GetScreenSpaceRectangle(clipmap, level); // The pixel position of the offset. int offsetX = levelRect.X + border + (int)(cellsPerLevelWithoutBorder * clipmap.Offsets[level].X + 0.5f); int offsetY = levelRect.Y + border + (int)(cellsPerLevelWithoutBorder * clipmap.Offsets[level].Y + 0.5f); // Handle the 4 rectangles of the toroidally wrapped clipmap. bool applyPass = true; for (int i = 0; i < 4; i++) { Rectangle quadrantRect; Vector2F offsetPosition; switch (i) { case 0: // Top left rectangle. quadrantRect = new Rectangle(levelRect.X, levelRect.Y, offsetX - levelRect.X, offsetY - levelRect.Y); offsetPosition = levelEnd; break; case 1: // Top right rectangle. quadrantRect = new Rectangle(offsetX, levelRect.Y, levelRect.Right - offsetX, offsetY - levelRect.Y); offsetPosition.X = levelOrigin.X; offsetPosition.Y = levelEnd.Y; break; case 2: // Bottom left rectangle. quadrantRect = new Rectangle(levelRect.X, offsetY, offsetX - levelRect.X, levelRect.Bottom - offsetY); offsetPosition.X = levelEnd.X; offsetPosition.Y = levelOrigin.Y; break; default: // Bottom right rectangle. quadrantRect = new Rectangle(offsetX, offsetY, levelRect.Right - offsetX, levelRect.Bottom - offsetY); offsetPosition = levelOrigin; break; } if (quadrantRect.Width == 0 || quadrantRect.Height == 0) { continue; } applyPass |= UpdateAndApplyParameter(materialBinding, "TerrainClipmapOffsetWorld", (Vector2)offsetPosition, context); applyPass |= UpdateAndApplyParameter(materialBinding, "TerrainClipmapOffsetScreen", new Vector2(offsetX, offsetY), context); var passBinding = techniqueBinding.GetPassBinding(technique, context); foreach (var pass in passBinding) { // Update and apply per-pass bindings. foreach (var binding in materialInstanceBinding.ParameterBindings) { if (binding.Description.Hint == EffectParameterHint.PerPass) { binding.Update(context); binding.Apply(context); applyPass = true; } } if (applyPass) { pass.Apply(); applyPass = false; } foreach (var aabb in clipmap.InvalidRegions[level]) { // Intersect layer AABB with invalid region AABB. Vector2F clippedLayerStart, clippedLayerEnd; clippedLayerStart.X = Math.Max(layerStart.X, aabb.Minimum.X); clippedLayerStart.Y = Math.Max(layerStart.Y, aabb.Minimum.Z); clippedLayerEnd.X = Math.Min(layerEnd.X, aabb.Maximum.X); clippedLayerEnd.Y = Math.Min(layerEnd.Y, aabb.Maximum.Z); // Nothing to do if layer AABB does not intersect invalid region. if (clippedLayerStart.X >= clippedLayerEnd.X || clippedLayerStart.Y >= clippedLayerEnd.Y) { continue; } // Compute screen space rectangle of intersection (relative to toroidal offset). var invalidRect = GetScissorRectangle( clippedLayerStart - offsetPosition, clippedLayerEnd - clippedLayerStart, texelsPerUnit); // Add toroidal offset screen position. invalidRect.X += offsetX; invalidRect.Y += offsetY; // Set a scissor rectangle to avoid drawing outside the current toroidal wrap // part and outside the invalid region. var scissorRect = Rectangle.Intersect(quadrantRect, invalidRect); if (scissorRect.Width <= 0 || scissorRect.Height <= 0) { continue; } graphicsDevice.ScissorRectangle = scissorRect; // Compute world space position of scissor rectangle corners. Vector2F start, end; start.X = offsetPosition.X + (scissorRect.X - offsetX) * cellSize; start.Y = offsetPosition.Y + (scissorRect.Y - offsetY) * cellSize; end.X = offsetPosition.X + (scissorRect.Right - offsetX) * cellSize; end.Y = offsetPosition.Y + (scissorRect.Bottom - offsetY) * cellSize; Debug.Assert(Numeric.IsLessOrEqual(start.X, end.X)); Debug.Assert(Numeric.IsLessOrEqual(start.Y, end.Y)); layer.OnDraw(graphicsDevice, scissorRect, start, end); } } } } }
private void ProcessLayer(GraphicsDevice graphicsDevice, RenderContext context, TerrainClipmap clipmap, bool isBaseClipmap, IInternalTerrainLayer layer, Aabb tileAabb) { // The material needs to have a "Base" or "Detail" render pass. if (!layer.Material.Contains(context.RenderPass)) return; var layerAabb = layer.Aabb ?? tileAabb; //if (!HaveContactXZ(ref layerAabb, ref clipmap.Aabb)) // continue; bool isInInvalidRegion = false; for (int level = clipmap.NumberOfLevels - 1; level >= 0; level--) { for (int i = 0; i < clipmap.InvalidRegions[level].Count; i++) { var aabb = clipmap.InvalidRegions[level][i]; if (HaveContactXZ(ref layerAabb, ref aabb)) { isInInvalidRegion = true; break; } } if (isInInvalidRegion) break; } if (!isInInvalidRegion) return; // Reset render state for each layer because layers are allowed to change render state // without restoring it in a restore pass. // The base clipmap uses no blending. The detail clipmap uses alpha-blending (but only in // RGB, A is not changed, A is used e.g. for hole info). graphicsDevice.BlendState = isBaseClipmap ? BlendState.Opaque : BlendStateAlphaBlendRgb; graphicsDevice.RasterizerState = RasterizerStateCullNoneWithScissorTest; graphicsDevice.DepthStencilState = DepthStencilState.None; #region ----- Effect binding updates ----- // Get the EffectBindings and the Effect for the current render pass. EffectBinding materialInstanceBinding = layer.MaterialInstance[context.RenderPass]; EffectBinding materialBinding = layer.Material[context.RenderPass]; EffectEx effectEx = materialBinding.EffectEx; Effect effect = materialBinding.Effect; context.MaterialInstanceBinding = materialInstanceBinding; context.MaterialBinding = materialBinding; if (effectEx.Id == 0) { effectEx.Id = 1; // Update and apply global effect parameter bindings - these bindings set the // effect parameter values for "global" parameters. For example, if an effect uses // a "ViewProjection" parameter, then a binding will compute this matrix from the // current CameraInstance in the render context and update the effect parameter. foreach (var binding in effect.GetParameterBindings()) { if (binding.Description.Hint == EffectParameterHint.Global) { binding.Update(context); binding.Apply(context); } } } // Update and apply material bindings. // If this material is the same as in the last ProcessLayer() call, then we can skip this. if (_previousMaterialBinding != materialBinding) { _previousMaterialBinding = materialBinding; if (materialBinding.Id == 0) { materialBinding.Id = 1; foreach (var binding in materialBinding.ParameterBindings) { binding.Update(context); binding.Apply(context); } } else { // The material has already been updated in this frame. foreach (var binding in materialBinding.ParameterBindings) binding.Apply(context); } } // Update and apply local, per-instance, and per-pass bindings - these are bindings // for parameters, like the "World" matrix or lighting parameters. foreach (var binding in materialInstanceBinding.ParameterBindings) { if (binding.Description.Hint != EffectParameterHint.PerPass) { binding.Update(context); binding.Apply(context); } } // Select and apply technique. var techniqueBinding = materialInstanceBinding.TechniqueBinding; techniqueBinding.Update(context); var technique = techniqueBinding.GetTechnique(effect, context); effect.CurrentTechnique = technique; #endregion int border = isBaseClipmap ? 0 : Border; // Region covered by the layer. Vector2F layerStart = new Vector2F(layerAabb.Minimum.X, layerAabb.Minimum.Z); Vector2F layerEnd = new Vector2F(layerAabb.Maximum.X, layerAabb.Maximum.Z); // Clamp to tile AABB layerStart.X = Math.Max(layerStart.X, tileAabb.Minimum.X); layerStart.Y = Math.Max(layerStart.Y, tileAabb.Minimum.Z); layerEnd.X = Math.Min(layerEnd.X, tileAabb.Maximum.X); layerEnd.Y = Math.Min(layerEnd.Y, tileAabb.Maximum.Z); // Loop over clipmap levels. for (int level = 0; level < clipmap.NumberOfLevels; level++) { // If there are no invalid regions, the combined AABB is NaN. if (Numeric.IsNaN(clipmap.CombinedInvalidRegionsAabbs[level].Minimum.X)) continue; if (layer.FadeInStart > level || layer.FadeOutEnd <= level) continue; if (!HaveContactXZ(ref layerAabb, ref clipmap.CombinedInvalidRegionsAabbs[level])) continue; Vector2F levelOrigin = clipmap.Origins[level]; Vector2F levelSize = new Vector2F(clipmap.LevelSizes[level]); // without border! Vector2F levelEnd = levelOrigin + levelSize; float cellsPerLevelWithoutBorder = clipmap.CellsPerLevel - 2 * border; float cellSize = clipmap.ActualCellSizes[level]; float texelsPerUnit = 1 / cellSize; // Following effect parameters change per clipmap level: UpdateAndApplyParameter(materialBinding, "TerrainClipmapLevel", (float)level, context); UpdateAndApplyParameter(materialBinding, "TerrainClipmapCellSize", cellSize, context); // The rectangle of the whole clipmap level (including border). var levelRect = GetScreenSpaceRectangle(clipmap, level); // The pixel position of the offset. int offsetX = levelRect.X + border + (int)(cellsPerLevelWithoutBorder * clipmap.Offsets[level].X + 0.5f); int offsetY = levelRect.Y + border + (int)(cellsPerLevelWithoutBorder * clipmap.Offsets[level].Y + 0.5f); // Handle the 4 rectangles of the toroidally wrapped clipmap. bool applyPass = true; for (int i = 0; i < 4; i++) { Rectangle quadrantRect; Vector2F offsetPosition; switch (i) { case 0: // Top left rectangle. quadrantRect = new Rectangle(levelRect.X, levelRect.Y, offsetX - levelRect.X, offsetY - levelRect.Y); offsetPosition = levelEnd; break; case 1: // Top right rectangle. quadrantRect = new Rectangle(offsetX, levelRect.Y, levelRect.Right - offsetX, offsetY - levelRect.Y); offsetPosition.X = levelOrigin.X; offsetPosition.Y = levelEnd.Y; break; case 2: // Bottom left rectangle. quadrantRect = new Rectangle(levelRect.X, offsetY, offsetX - levelRect.X, levelRect.Bottom - offsetY); offsetPosition.X = levelEnd.X; offsetPosition.Y = levelOrigin.Y; break; default: // Bottom right rectangle. quadrantRect = new Rectangle(offsetX, offsetY, levelRect.Right - offsetX, levelRect.Bottom - offsetY); offsetPosition = levelOrigin; break; } if (quadrantRect.Width == 0 || quadrantRect.Height == 0) continue; applyPass |= UpdateAndApplyParameter(materialBinding, "TerrainClipmapOffsetWorld", (Vector2)offsetPosition, context); applyPass |= UpdateAndApplyParameter(materialBinding, "TerrainClipmapOffsetScreen", new Vector2(offsetX, offsetY), context); var passBinding = techniqueBinding.GetPassBinding(technique, context); foreach (var pass in passBinding) { // Update and apply per-pass bindings. foreach (var binding in materialInstanceBinding.ParameterBindings) { if (binding.Description.Hint == EffectParameterHint.PerPass) { binding.Update(context); binding.Apply(context); applyPass = true; } } if (applyPass) { pass.Apply(); applyPass = false; } foreach (var aabb in clipmap.InvalidRegions[level]) { // Intersect layer AABB with invalid region AABB. Vector2F clippedLayerStart, clippedLayerEnd; clippedLayerStart.X = Math.Max(layerStart.X, aabb.Minimum.X); clippedLayerStart.Y = Math.Max(layerStart.Y, aabb.Minimum.Z); clippedLayerEnd.X = Math.Min(layerEnd.X, aabb.Maximum.X); clippedLayerEnd.Y = Math.Min(layerEnd.Y, aabb.Maximum.Z); // Nothing to do if layer AABB does not intersect invalid region. if (clippedLayerStart.X >= clippedLayerEnd.X || clippedLayerStart.Y >= clippedLayerEnd.Y) continue; // Compute screen space rectangle of intersection (relative to toroidal offset). var invalidRect = GetScissorRectangle( clippedLayerStart - offsetPosition, clippedLayerEnd - clippedLayerStart, texelsPerUnit); // Add toroidal offset screen position. invalidRect.X += offsetX; invalidRect.Y += offsetY; // Set a scissor rectangle to avoid drawing outside the current toroidal wrap // part and outside the invalid region. var scissorRect = Rectangle.Intersect(quadrantRect, invalidRect); if (scissorRect.Width <= 0 || scissorRect.Height <= 0) continue; graphicsDevice.ScissorRectangle = scissorRect; // Compute world space position of scissor rectangle corners. Vector2F start, end; start.X = offsetPosition.X + (scissorRect.X - offsetX) * cellSize; start.Y = offsetPosition.Y + (scissorRect.Y - offsetY) * cellSize; end.X = offsetPosition.X + (scissorRect.Right - offsetX) * cellSize; end.Y = offsetPosition.Y + (scissorRect.Bottom - offsetY) * cellSize; Debug.Assert(Numeric.IsLessOrEqual(start.X, end.X)); Debug.Assert(Numeric.IsLessOrEqual(start.Y, end.Y)); layer.OnDraw(graphicsDevice, scissorRect, start, end); } } } } }