protected override void OnUnload() { _cameraObject = null; // Remove terrain from scene. TerrainNode.Parent.Children.Remove(TerrainNode); TerrainNode.Dispose(false); TerrainNode = null; // We have to dispose the textures which were not loaded via the content manager. _terrainTile.HeightTexture.SafeDispose(); _terrainTile.HeightTexture = null; _terrainTile.NormalTexture.SafeDispose(); _terrainTile.NormalTexture = null; _rigidBody.Simulation.RigidBodies.Remove(_rigidBody); _terrainTile = null; _rigidBody = null; }
//-------------------------------------------------------------- #region Methods //-------------------------------------------------------------- protected override void OnLoad() { // Get common services and game objects. _graphicsService = _services.GetInstance<IGraphicsService>(); var content = _services.GetInstance<ContentManager>(); var scene = _services.GetInstance<IScene>(); _simulation = _services.GetInstance<Simulation>(); var gameObjectService = _services.GetInstance<IGameObjectService>(); _cameraObject = gameObjectService.Objects.OfType<CameraObject>().First(); _previousCameraFar = _cameraObject.CameraNode.Camera.Projection.Far; // Create a new terrain. var terrain = new Terrain(); _terrainTile = new TerrainTile(_graphicsService) { CellSize = 2, }; terrain.Tiles.Add(_terrainTile); var shadowMapEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap"); var gBufferEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer"); var materialEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainMaterial"); var material = new Material { { "ShadowMap", new EffectBinding(_graphicsService, shadowMapEffect, null, EffectParameterHint.Material) }, { "GBuffer", new EffectBinding(_graphicsService, gBufferEffect, null, EffectParameterHint.Material) }, { "Material", new EffectBinding(_graphicsService, materialEffect, null, EffectParameterHint.Material) } }; TerrainNode = new TerrainNode(terrain, material) { BaseClipmap = { CellsPerLevel = 128, NumberOfLevels = 6, }, DetailClipmap = { CellsPerLevel = 1364, NumberOfLevels = 9, }, }; scene.Children.Add(TerrainNode); // Create a rigid body with a height field for collision detection. var heightField = new HeightField { Depth = 1, UseFastCollisionApproximation = false, }; _rigidBody = new RigidBody(heightField, new MassFrame(), null) { MotionType = MotionType.Static, UserData = _terrainTile, }; _simulation.RigidBodies.Add(_rigidBody); InitializeHeightsAndNormals(); InitializeClipmapCellSizes(); InitializeTerrainLayers(content); // Enable mipmaps when using anisotropic filtering on AMD graphics cards: //TerrainNode.DetailClipmap.EnableMipMap = true; CreateGuiControls(); }
//-------------------------------------------------------------- #region Methods //-------------------------------------------------------------- protected override void OnLoad() { // Get common services and game objects. _graphicsService = _services.GetInstance<IGraphicsService>(); _graphicsScreen = _graphicsService.Screens.OfType<DeferredGraphicsScreen>().First(); var content = _services.GetInstance<ContentManager>(); var scene = _services.GetInstance<IScene>(); _simulation = _services.GetInstance<Simulation>(); var gameObjectService = _services.GetInstance<IGameObjectService>(); _cameraObject = gameObjectService.Objects.OfType<CameraObject>().First(); _previousCameraFar = _cameraObject.CameraNode.Camera.Projection.Far; // Create a new terrain. var terrain = new Terrain(); // The terrain is made up of terrain tiles which can be loaded independently. // Each terrain tile consists of height and normal textures which define the terrain // geometry and terrain layers which define the material (detail textures). // In this sample we create 2x2 tiles. _tiles = new Tile[2, 2]; for (int row = 0; row < 2; row++) { for (int column = 0; column < 2; column++) { // Create a tile and add it to the terrain. // (The tile content is loaded later.) var terrainTile = new TerrainTile(_graphicsService) { CellSize = 1, // The terrain has a resolution of 1 height sample per world space unit. }; terrain.Tiles.Add(terrainTile); // Create a rigid body with a height field for collision detection and add // it to the simulation. (The height data is loaded later.) var heightField = new HeightField { Depth = 1, UseFastCollisionApproximation = false, }; var rigidBody = new RigidBody(heightField, new MassFrame(), null) { MotionType = MotionType.Static, UserData = terrainTile, }; _simulation.RigidBodies.Add(rigidBody); // Store the tile for use later in this sample. _tiles[row, column] = new Tile { TerrainTile = terrainTile, RigidBody = rigidBody, }; } } // Create a terrain node which represents the terrain in the scene graph. // The terrain node is rendered by the TerrainRenderer (see DeferredGraphicsScreen). // The material used to render the terrain is customizable. The material must specify // the effects for the different render passes which we use in the DeferredGraphicsScreen // ("ShadowMap", "GBuffer", "Material"). // The prebuilt DigitalRune content contains standard terrain effects. However, you could // change the effects to change how the material is rendered. // We can create the material by loading a .drmat file. Or we can create the material in // code like this: var shadowMapEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap"); var gBufferEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer"); var materialEffect = content.Load<Effect>("DigitalRune/Terrain/TerrainMaterial"); var material = new Material { { "ShadowMap", new EffectBinding(_graphicsService, shadowMapEffect, null, EffectParameterHint.Material) }, { "GBuffer", new EffectBinding(_graphicsService, gBufferEffect, null, EffectParameterHint.Material) }, { "Material", new EffectBinding(_graphicsService, materialEffect, null, EffectParameterHint.Material) } }; TerrainNode = new TerrainNode(terrain, material) { // The terrain rendering uses clipmaps. // The clipmaps are updated by the TerrainClipmapRenderer (see DeferredGraphicsScreen) // when the camera moves. // The base clipmap contains the basic geometry info (height, normals, hole info). // It also determines the terrain mesh resolution. BaseClipmap = { CellsPerLevel = 128, NumberOfLevels = 6 }, // The detail clipmap contains the splatted detail textures (e.g. grass, rock, ...). // (The max texture size in XNA is 4096x4096. That means we can fit 9 clipmap levels // into a single texture.) DetailClipmap = { CellsPerLevel = 1365, NumberOfLevels = 9, }, }; scene.Children.Add(TerrainNode); // Load the height and normal maps which define the terrain geometry. InitializeHeightsAndNormals(); // Set the clipmap cell sizes. InitializeClipmapCellSizes(); // Create the terrain layers which define the detail textures (e.g. grass, rock, ...) InitializeTerrainLayers(content); // Special note for AMD GPUs: // If we want anisotropic filtering for the terrain, then we need to enable mipmaps for // AMD GPUs. NVIDIA and Intel can do anisotropic filtering without mipmaps. //TerrainNode.DetailClipmap.EnableMipMap = true; CreateGuiControls(); }
private void ProcessClipmap(TerrainNode node, TerrainClipmap clipmap, RenderContext context) { var graphicsDevice = context.GraphicsService.GraphicsDevice; var lodCameraNode = context.LodCameraNode ?? context.CameraNode; bool isBaseClipmap = (node.BaseClipmap == clipmap); // Update the clipmap render targets if necessary. InitializeClipmapTextures(graphicsDevice, clipmap); // Update other clipmap data (origins, offsets, ...). No rendering. // (Data is stored in TerrainClipmap class.) UpdateClipmapData(node, clipmap, lodCameraNode, isBaseClipmap); // Compute which rectangular regions need to be updated. // (Data is stored in TerrainClipmap class.) ComputeInvalidRegions(node, clipmap, isBaseClipmap); // Abort if there are no invalid regions. int numberOfInvalidRegions = 0; for (int level = 0; level < clipmap.NumberOfLevels; level++) numberOfInvalidRegions += clipmap.InvalidRegions[level].Count; Debug.Assert(numberOfInvalidRegions > 0 || clipmap.UseIncrementalUpdate, "If the clipmap update is not incremental, there must be at least one invalid region."); if (numberOfInvalidRegions == 0) return; // Set render target binding to render into all clipmap textures at once. int numberOfTextures = clipmap.Textures.Length; if (_renderTargetBindings[numberOfTextures] == null) _renderTargetBindings[numberOfTextures] = new RenderTargetBinding[numberOfTextures]; for (int i = 0; i < numberOfTextures; i++) _renderTargetBindings[numberOfTextures][i] = new RenderTargetBinding((RenderTarget2D)clipmap.Textures[i]); switch (numberOfTextures) { case 1: context.Technique = "RenderTargets1"; break; case 2: context.Technique = "RenderTargets2"; break; case 3: context.Technique = "RenderTargets3"; break; case 4: context.Technique = "RenderTargets4"; break; default: context.Technique = null; break; } graphicsDevice.SetRenderTargets(_renderTargetBindings[numberOfTextures]); // The viewport covers the whole texture atlas. var viewport = graphicsDevice.Viewport; context.Viewport = viewport; Debug.Assert(_previousMaterialBinding == null); // Loop over all layers. Render each layer into all levels (if there is an invalid region). Aabb tileAabb = new Aabb(new Vector3F(-Terrain.TerrainLimit), new Vector3F(Terrain.TerrainLimit)); ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, _clearLayer, tileAabb); foreach (var tile in node.Terrain.Tiles) { tileAabb = tile.Aabb; context.Object = tile; ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, tile, tileAabb); foreach (var layer in tile.Layers) ProcessLayer(graphicsDevice, context, clipmap, isBaseClipmap, layer, tileAabb); context.Object = null; } _previousMaterialBinding = null; ClearFlags(_clearLayer, context); foreach (var tile in node.Terrain.Tiles) { ClearFlags(tile, context); foreach (var layer in tile.Layers) ClearFlags(layer, context); } // All invalid regions handled. for (int i = 0; i < clipmap.NumberOfLevels; i++) clipmap.InvalidRegions[i].Clear(); // The next time we can update incrementally. clipmap.UseIncrementalUpdate = true; }
private void ComputeInvalidRegions(TerrainNode node, TerrainClipmap clipmap, bool isBaseClipmap) { // Note: // This method computes the AABBs of terrain parts that need to be updated. // Important: AABBs must not overlap because the materials can use alpha blending and they // must not blend twice into the same area. var terrain = node.Terrain; var invalidRegions = isBaseClipmap ? terrain.InvalidBaseRegions : terrain.InvalidDetailRegions; var areInvalidRegionsClipped = isBaseClipmap ? terrain.AreInvalidBaseRegionsClipped : terrain.AreInvalidDetailRegionsClipped; float border = isBaseClipmap ? 0 : Border; for (int level = 0; level < clipmap.NumberOfLevels; level++) { if (clipmap.InvalidRegions[level] == null) clipmap.InvalidRegions[level] = new List<Aabb>(); // Compute AABB of whole level. float borderWorld = border * clipmap.ActualCellSizes[level]; Vector2F newOrigin = clipmap.Origins[level]; var aabb = new Aabb { Minimum = { X = newOrigin.X - borderWorld, Y = float.MinValue, Z = newOrigin.Y - borderWorld }, Maximum = { X = newOrigin.X + clipmap.LevelSizes[level] + borderWorld, Y = float.MaxValue, Z = newOrigin.Y + clipmap.LevelSizes[level] + borderWorld } }; // Store AABB of whole clipmap. if (level == clipmap.NumberOfLevels - 1) clipmap.Aabb = aabb; // For debugging: //var oldOrigin = clipmap.OldOrigins[level]; //if (oldOrigin != newOrigin) // clipmap.InvalidRegions[level].Add(aabb); if (!clipmap.UseIncrementalUpdate) { // Incremental update not possible. --> The whole clipmap is the invalid region. clipmap.InvalidRegions[level].Add(aabb); } else { // The clipmap contains info from the last frame and the layers where not changed. // We can use a toroidal update. Vector2F oldOrigin = clipmap.OldOrigins[level]; float levelSize = clipmap.LevelSizes[level]; if (oldOrigin != newOrigin) { // The camera or the clipmap level origin has moved. if (oldOrigin.X >= newOrigin.X + levelSize || oldOrigin.Y >= newOrigin.Y + levelSize || newOrigin.X >= oldOrigin.X + levelSize || newOrigin.Y >= oldOrigin.Y + levelSize || isBaseClipmap) // Base clipmap does not (yet) use toroidal wrap. { // We moved a lot and we cannot reuse anything from the last frame. clipmap.InvalidRegions[level].Clear(); clipmap.InvalidRegions[level].Add(aabb); clipmap.Offsets[level] = new Vector2F(0); } else { // The origin has moved. An L shape has to be updated. --> We need two AABBs. // To avoid an unnecessary overlap: The horizontal AABB covers the whole width. // The vertical AABB is shorter to avoid overlap with the horizontal AABB. Aabb horizontalAabb = new Aabb(); horizontalAabb.Minimum.X = newOrigin.X; horizontalAabb.Maximum.X = newOrigin.X + levelSize; horizontalAabb.Minimum.Y = float.MinValue; horizontalAabb.Maximum.Y = float.MaxValue; if (oldOrigin.Y <= newOrigin.Y) { // Origin moved down. horizontalAabb.Minimum.Z = oldOrigin.Y + levelSize; horizontalAabb.Maximum.Z = newOrigin.Y + levelSize; } else { // Origin moved up. horizontalAabb.Minimum.Z = newOrigin.Y; horizontalAabb.Maximum.Z = oldOrigin.Y; } Aabb verticalAabb = new Aabb(); verticalAabb.Minimum.Y = float.MinValue; verticalAabb.Maximum.Y = float.MaxValue; verticalAabb.Minimum.Z = newOrigin.Y; verticalAabb.Maximum.Z = newOrigin.Y + levelSize; if (oldOrigin.X <= newOrigin.X) { // Origin moved right. verticalAabb.Minimum.X = oldOrigin.X + levelSize; verticalAabb.Maximum.X = newOrigin.X + levelSize; } else { // Origin moved left. verticalAabb.Minimum.X = newOrigin.X; verticalAabb.Maximum.X = oldOrigin.X; } Debug.Assert(horizontalAabb.Minimum <= horizontalAabb.Maximum); Debug.Assert(verticalAabb.Minimum <= verticalAabb.Maximum); Debug.Assert(Numeric.AreEqual(horizontalAabb.Extent.X, levelSize)); // (Assertions need larger epsilon.) //Debug.Assert(Numeric.AreEqual(horizontalAabb.Extent.Z, Math.Abs(oldOrigin.Y - newOrigin.Y))); //Debug.Assert(Numeric.AreEqual(verticalAabb.Extent.X, Math.Abs(oldOrigin.X - newOrigin.X))); // Add the border for texture filtering. horizontalAabb.Minimum.X -= borderWorld; horizontalAabb.Minimum.Z -= borderWorld; horizontalAabb.Maximum.X += borderWorld; horizontalAabb.Maximum.Z += borderWorld; verticalAabb.Minimum.X -= borderWorld; verticalAabb.Minimum.Z -= borderWorld; verticalAabb.Maximum.X += borderWorld; verticalAabb.Maximum.Z += borderWorld; // AABBs must not overlap. if (oldOrigin.Y <= newOrigin.Y) verticalAabb.Maximum.Z = horizontalAabb.Minimum.Z; else verticalAabb.Minimum.Z = horizontalAabb.Maximum.Z; Debug.Assert(horizontalAabb.Minimum.X >= verticalAabb.Maximum.X || horizontalAabb.Maximum.X <= verticalAabb.Minimum.X || horizontalAabb.Minimum.Y >= verticalAabb.Maximum.Y || horizontalAabb.Maximum.Y <= verticalAabb.Minimum.Y || horizontalAabb.Minimum.Z >= verticalAabb.Maximum.Z || horizontalAabb.Maximum.Z <= verticalAabb.Minimum.Z, "Invalid region AABBs must not overlap."); if (clipmap.InvalidRegions[level].Count == 0) { clipmap.InvalidRegions[level].Add(horizontalAabb); clipmap.InvalidRegions[level].Add(verticalAabb); } else { AddAabbWithClipping(clipmap.InvalidRegions[level], ref horizontalAabb); AddAabbWithClipping(clipmap.InvalidRegions[level], ref verticalAabb); } } } if (!areInvalidRegionsClipped && invalidRegions.Count > 1) { // Clip the invalid regions stored in the Terrain once. _aabbList.Clear(); for (int i = 0; i < invalidRegions.Count; i++) { var invalidAabb = invalidRegions[i]; AddAabbWithClipping(_aabbList, ref invalidAabb); } invalidRegions.Clear(); invalidRegions.AddRange(_aabbList); // Update flag (also in terrain in case other TerrainNodes use the same Terrain). areInvalidRegionsClipped = true; if (isBaseClipmap) terrain.AreInvalidBaseRegionsClipped = true; else terrain.AreInvalidDetailRegionsClipped = true; } if (invalidRegions.Count > 0) { // Add all invalid regions - without overlap. for (int i = 0; i < invalidRegions.Count; i++) { var invalidAabb = invalidRegions[i]; AddAabbWithClipping(clipmap.InvalidRegions[level], ref invalidAabb); } } } #if DEBUG // Assert: Invalid regions do not overlap. for (int i = 0; i < clipmap.InvalidRegions[level].Count; i++) { var a = clipmap.InvalidRegions[level][i]; for (int j = i + 1; j < clipmap.InvalidRegions[level].Count; j++) { var b = clipmap.InvalidRegions[level][j]; Debug.Assert(!HaveContactXZ(ref a, ref b)); } } #endif // Compute the union of all invalid regions. We can use this to early out if a layer // does not overlap this combined region. var numberOfInvalidRegions = clipmap.InvalidRegions[level].Count; if (numberOfInvalidRegions == 0) { clipmap.CombinedInvalidRegionsAabbs[level] = new Aabb(new Vector3F(float.NaN), new Vector3F(float.NaN)); } else { clipmap.CombinedInvalidRegionsAabbs[level] = clipmap.InvalidRegions[level][0]; for (int i = 1; i < numberOfInvalidRegions; i++) clipmap.CombinedInvalidRegionsAabbs[level].Grow(clipmap.InvalidRegions[level][i]); } // For debugging: //clipmap.CombinedInvalidRegionsAabbs[level] = new Aabb(new Vector3F(float.MinValue), new Vector3F(float.MaxValue)); } }
private static void UpdateClipmapData(TerrainNode node, TerrainClipmap clipmap, CameraNode lodCameraNode, bool isBaseClipmap) { int border = isBaseClipmap ? 0 : Border; var terrain = node.Terrain; var terrainAabb = terrain.Aabb; Vector2F terrainAabbMin = new Vector2F(terrainAabb.Minimum.X, terrainAabb.Minimum.Z); Vector2F terrainAabbMax = new Vector2F(terrainAabb.Maximum.X, terrainAabb.Maximum.Z); for (int level = clipmap.NumberOfLevels - 1; level >= 0; level--) { // Compute new origins. int texelsPerLevel = clipmap.CellsPerLevel - 2 * border; Vector3F referencePosition3D = lodCameraNode.PoseWorld.Position; Vector2F referencePosition2D = new Vector2F(referencePosition3D.X, referencePosition3D.Z); clipmap.LevelSizes[level] = clipmap.ActualCellSizes[level] * texelsPerLevel; Vector2F levelOrigin = new Vector2F( referencePosition2D.X - clipmap.LevelSizes[level] / 2, referencePosition2D.Y - clipmap.LevelSizes[level] / 2); // Do not move detail clipmap outside the terrain. This would only waste resources. if (!isBaseClipmap) { // The fade range is hardcoded to 15% of the radius in Terrain.fxh. float fadeRange = clipmap.LevelSizes[level] * node.DetailFadeRange / 2; // / 2 because its relative to the radius. // The last level does not need a fade range. if (level == clipmap.NumberOfLevels - 1) fadeRange = 0; levelOrigin.X = Math.Min(levelOrigin.X, terrainAabbMax.X - clipmap.LevelSizes[level] + fadeRange); levelOrigin.Y = Math.Min(levelOrigin.Y, terrainAabbMax.Y - clipmap.LevelSizes[level] + fadeRange); levelOrigin.X = Math.Max(levelOrigin.X, terrainAabbMin.X - fadeRange); levelOrigin.Y = Math.Max(levelOrigin.Y, terrainAabbMin.Y - fadeRange); } // Snap to grid. levelOrigin = RoundToGrid(levelOrigin, clipmap.ActualCellSizes[level]); if (isBaseClipmap) { // To align the base clipmap with the terrain tile cell raster, we have to move it by // half a cell size. E.g. if the tile origin is (0, 0), then the first mesh vertex // is over (0, 0). The vertices should sample the clipmap texel centers. levelOrigin.X -= clipmap.ActualCellSizes[0] / 2; levelOrigin.Y -= clipmap.ActualCellSizes[0] / 2; } // Simple movement threshold for debugging: Only update clipmaps if origin // has moved more than 10 units. //if ((levelOrigin - clipmap.OldOrigins[level]).Length < 10) // continue; if (levelOrigin == clipmap.OldOrigins[level] && clipmap.UseIncrementalUpdate) continue; if (level < clipmap.MinLevel) { // We do not update this level. // Note: As long as the level is within the region of the next level we could still // draw it - but we ignore this here: // Set to invalid origin, to make the shader ignore this level. (The value is still used // in the shader, so we must not set it to a totally extreme value.) clipmap.Origins[level] = referencePosition2D - new Vector2F(10000); clipmap.OldOrigins[level] = clipmap.Origins[level]; continue; } clipmap.OldOrigins[level] = clipmap.Origins[level]; clipmap.OldOffsets[level] = clipmap.Offsets[level]; // Compute new toroidal wrap offset. if (!clipmap.UseIncrementalUpdate || isBaseClipmap) { // Full clipmap update needed or it is a base clipmap. clipmap.Origins[level] = levelOrigin; // Base clipmaps are usually super small (< 256²) and are updated infrequently. // Therefore we do not make a toroidal update for the base clipmap. clipmap.Offsets[level] = new Vector2F(0, 0); } else { clipmap.Origins[level] = levelOrigin; Vector2F levelSize = new Vector2F(clipmap.LevelSizes[level]); Vector2F newOffset = clipmap.OldOffsets[level] + (levelOrigin - clipmap.OldOrigins[level]) / levelSize; float cellsPerLevelWithoutBorder = clipmap.CellsPerLevel - 2 * border; //Debug.Assert( // Numeric.AreEqual(newOffset.X * cellsPerLevelWithoutBorder, // (float)Math.Floor(newOffset.X * cellsPerLevelWithoutBorder + 0.5f), 0.01f), // "New clipmap offset is not snapped to texel grid."); //Debug.Assert( // Numeric.AreEqual(newOffset.Y * cellsPerLevelWithoutBorder, // (float)Math.Floor(newOffset.Y * cellsPerLevelWithoutBorder + 0.5f), 0.01f), // "New clipmap offset is not snapped to texel grid."); // The offset should always correspond to clipmap texels, but if we compute the new offset // from the old offset then we accumulate errors. --> Snap to texels to remove error. newOffset.X = (float)Math.Floor(newOffset.X * cellsPerLevelWithoutBorder + 0.5f) / cellsPerLevelWithoutBorder; newOffset.Y = (float)Math.Floor(newOffset.Y * cellsPerLevelWithoutBorder + 0.5f) / cellsPerLevelWithoutBorder; // Use "positive modulo" to wrap to [0, 1]. newOffset.X = ((newOffset.X % 1) + 1) % 1; newOffset.Y = ((newOffset.Y % 1) + 1) % 1; Debug.Assert(Numeric.IsGreaterOrEqual(newOffset.X, 0), "New offset is not in [0,1]"); Debug.Assert(Numeric.IsLessOrEqual(newOffset.X, 1), "New offset is not in [0,1]"); Debug.Assert(Numeric.IsGreaterOrEqual(newOffset.Y, 0), "New offset is not in [0,1]"); Debug.Assert(Numeric.IsLessOrEqual(newOffset.Y, 1), "New offset is not in [0,1]"); // Note: clipmap.Offsets stores the offsets as if border is 0. clipmap.Offsets[level].X = newOffset.X; clipmap.Offsets[level].Y = newOffset.Y; } } }
public TerrainTextureSample(Microsoft.Xna.Framework.Game game) : base(game) { SampleFramework.IsMouseVisible = false; _graphicsScreen = new DeferredGraphicsScreen(Services); _graphicsScreen.DrawReticle = true; GraphicsService.Screens.Insert(0, _graphicsScreen); GameObjectService.Objects.Add(new DeferredGraphicsOptionsObject(Services)); Services.Register(typeof(DebugRenderer), null, _graphicsScreen.DebugRenderer); var scene = _graphicsScreen.Scene; Services.Register(typeof(IScene), null, scene); // Add gravity and damping to the physics simulation. Simulation.ForceEffects.Add(new Gravity()); Simulation.ForceEffects.Add(new Damping()); // Add a custom game object which controls the camera. var cameraGameObject = new CameraObject(Services, 60); cameraGameObject.ResetPose(new Vector3F(0, 1.8f, 0), 0, 0); GameObjectService.Objects.Add(cameraGameObject); _graphicsScreen.ActiveCameraNode = cameraGameObject.CameraNode; for (int i = 0; i < 10; i++) GameObjectService.Objects.Add(new DynamicObject(Services, 1)); GameObjectService.Objects.Add(new DynamicSkyObject(Services, true, false, true)); // Create a simple flat terrain. var terrain = new Terrain(); _terrainTile = new TerrainTile(GraphicsService) { OriginX = -100, OriginZ = -100, CellSize = 1, }; terrain.Tiles.Add(_terrainTile); // Create a flat dummy height texture. float[] heights = new float[200 * 200]; Texture2D heightTexture = null; TerrainHelper.CreateHeightTexture( GraphicsService.GraphicsDevice, heights, 200, 200, false, ref heightTexture); _terrainTile.HeightTexture = heightTexture; var shadowMapEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainShadowMap"); var gBufferEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainGBuffer"); var materialEffect = ContentManager.Load<Effect>("DigitalRune/Terrain/TerrainMaterial"); var material = new Material { { "ShadowMap", new EffectBinding(GraphicsService, shadowMapEffect, null, EffectParameterHint.Material) }, { "GBuffer", new EffectBinding(GraphicsService, gBufferEffect, null, EffectParameterHint.Material) }, { "Material", new EffectBinding(GraphicsService, materialEffect, null, EffectParameterHint.Material) } }; var terrainNode = new TerrainNode(terrain, material) { DetailClipmap = { CellsPerLevel = 1364, NumberOfLevels = 9, EnableMipMap = true, }, }; scene.Children.Add(terrainNode); // Add 3 detail textures layers: gravel, rock, snow. float detailCellSize = terrainNode.DetailClipmap.CellSizes[0]; var materialGravel = new TerrainMaterialLayer(GraphicsService) { DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Diffuse"), NormalTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Normal"), SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Gravel-Specular"), TileSize = detailCellSize * 512, BlendRange = 0.1f, }; _terrainTile.Layers.Add(materialGravel); var noiseTexture = NoiseHelper.GetNoiseTexture(GraphicsService, 128, 60); var materialRock = new TerrainMaterialLayer(GraphicsService) { DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Diffuse"), NormalTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Normal"), SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Specular"), HeightTexture = ContentManager.Load<Texture2D>("Terrain/Rock-02-Height"), TileSize = detailCellSize * 1024, DiffuseColor = new Vector3F(1 / 0.702f), BlendTexture = noiseTexture, BlendTextureChannel = 0, BlendRange = 0.1f, TerrainHeightBlendRange = 0.1f, }; _terrainTile.Layers.Add(materialRock); var materialSnow = new TerrainMaterialLayer(GraphicsService) { DiffuseTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Diffuse"), NormalTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Normal"), SpecularTexture = ContentManager.Load<Texture2D>("Terrain/Snow-Specular"), TileSize = detailCellSize * 512, BlendTexture = noiseTexture, BlendTextureChannel = 1, BlendRange = 0.1f, }; _terrainTile.Layers.Add(materialSnow); // Create a flat plane for collision detection. var rigidBody = new RigidBody(new PlaneShape(), new MassFrame(), null) { MotionType = MotionType.Static, }; Simulation.RigidBodies.Add(rigidBody); CreateGuiControls(); }