public PostProcessingGraphicsScreen(IServiceLocator services) : base(services.GetInstance<IGraphicsService>()) { _sampleFramework = services.GetInstance<SampleFramework>(); _spriteBatch = new SpriteBatch(GraphicsService.GraphicsDevice); _clearGBufferRenderer = new ClearGBufferRenderer(GraphicsService); _rebuildZBufferRenderer = new RebuildZBufferRenderer(GraphicsService); _meshRenderer = new MeshRenderer(); _skyRenderer = new SkyRenderer(GraphicsService); _billboardRenderer = new BillboardRenderer(GraphicsService, 2048); Scene = new Scene(); PostProcessors = new PostProcessorChain(GraphicsService); // Use 2D texture for reticle. var contentManager = services.GetInstance<ContentManager>(); _reticle = contentManager.Load<Texture2D>("Reticle"); // Use the sprite font of the GUI. var uiContentManager = services.GetInstance<ContentManager>("UIContent"); var spriteFont = uiContentManager.Load<SpriteFont>("UI Themes/BlendBlue/Default"); DebugRenderer = new DebugRenderer(GraphicsService, spriteFont) { DefaultColor = new Color(0, 0, 0), DefaultTextPosition = new Vector2F(10), }; }
//-------------------------------------------------------------- #region Creation & Cleanup //-------------------------------------------------------------- public DeferredGraphicsScreen(IServiceLocator services) : base(services.GetInstance<IGraphicsService>()) { _sampleFramework = services.GetInstance<SampleFramework>(); var contentManager = services.GetInstance<ContentManager>(); SpriteBatch = GraphicsService.GetSpriteBatch(); // Let's create the necessary scene node renderers: #if !XBOX360 TerrainRenderer = new TerrainRenderer(GraphicsService); #endif MeshRenderer = new MeshRenderer(); // The _opaqueMeshSceneRenderer combines all renderers for opaque // (= not alpha blended) meshes. _opaqueMeshSceneRenderer = new SceneRenderer(); #if !XBOX360 _opaqueMeshSceneRenderer.Renderers.Add(TerrainRenderer); #endif _opaqueMeshSceneRenderer.Renderers.Add(MeshRenderer); _decalRenderer = new DecalRenderer(GraphicsService); _billboardRenderer = new BillboardRenderer(GraphicsService, 2048) { EnableSoftParticles = true, // If you have an extreme amount of particles that cover the entire screen, // you can turn on offscreen rendering to improve performance. //EnableOffscreenRendering = true, }; // The AlphaBlendSceneRenderer combines all renderers for transparent // (= alpha blended) objects. AlphaBlendSceneRenderer = new SceneRenderer(); AlphaBlendSceneRenderer.Renderers.Add(MeshRenderer); AlphaBlendSceneRenderer.Renderers.Add(_billboardRenderer); AlphaBlendSceneRenderer.Renderers.Add(new WaterRenderer(GraphicsService)); AlphaBlendSceneRenderer.Renderers.Add(new FogSphereRenderer(GraphicsService)); AlphaBlendSceneRenderer.Renderers.Add(new VolumetricLightRenderer(GraphicsService)); #if !XBOX360 // Update terrain clipmaps. (Only necessary if TerrainNodes are used.) _terrainClipmapRenderer = new TerrainClipmapRenderer(GraphicsService); #endif // Renderer for cloud maps. (Only necessary if LayeredCloudMaps are used.) _cloudMapRenderer = new CloudMapRenderer(GraphicsService); // Renderer for SceneCaptureNodes. See also SceneCapture2DSample. // In the constructor we specify a method which is called in SceneCaptureRenderer.Render() // when the scene must be rendered for the SceneCaptureNodes. SceneCaptureRenderer = new SceneCaptureRenderer(context => { // Get scene nodes which are visible by the current camera. CustomSceneQuery sceneQuery = Scene.Query<CustomSceneQuery>(context.CameraNode, context); // Render scene (with post-processing, with lens flares, no debug rendering, no reticle). RenderScene(sceneQuery, context, true, true, false, false); }); // Renderer for PlanarReflectionNodes. See also PlanarReflectionSample. // In the constructor we specify a method which is called in PlanarReflectionRenderer.Render() // to create the reflection images. _planarReflectionRenderer = new PlanarReflectionRenderer(context => { // Get scene nodes which are visible by the current camera. CustomSceneQuery sceneQuery = Scene.Query<CustomSceneQuery>(context.CameraNode, context); var planarReflectionNode = (PlanarReflectionNode)context.ReferenceNode; // Planar reflections are often for WaterNodes. These nodes should not be rendered // into their own reflection map because when the water surface is displaced by waves, // some waves could be visible in the reflection. // --> Remove the water node from the renderable nodes. (In our samples, the water // node is the parent of the reflection node.) if (planarReflectionNode.Parent is WaterNode) { var index = sceneQuery.RenderableNodes.IndexOf(planarReflectionNode.Parent); if (index >= 0) sceneQuery.RenderableNodes[index] = null; } // Render scene (no post-processing, no lens flares, no debug rendering, no reticle). RenderScene(sceneQuery, context, false, false, false, false); }); _waterWavesRenderer = new WaterWavesRenderer(GraphicsService); // The shadow map renderer renders a depth image from the viewpoint of the light and // stores it in LightNode.Shadow.ShadowMap. ShadowMapRenderer = new ShadowMapRenderer(context => { var query = context.Scene.Query<ShadowCasterQuery>(context.CameraNode, context); if (query.ShadowCasters.Count == 0) return false; _opaqueMeshSceneRenderer.Render(query.ShadowCasters, context); return true; }); // The shadow mask renderer evaluates the shadow maps, does shadow filtering // and stores the resulting shadow factor in a screen space image //(see LightNode.Shadow.ShadowMask/ShadowMaskChannel). ShadowMaskRenderer = new ShadowMaskRenderer(GraphicsService, 2); // Optionally, we can blur the shadow mask to make the shadows smoother. var blur = new Blur(GraphicsService) { IsAnisotropic = false, IsBilateral = true, EdgeSoftness = 0.05f, Scale = 1f, Enabled = false, // Disable blur by default. }; blur.InitializeGaussianBlur(11, 3, true); ShadowMaskRenderer.Filter = blur; // Renderers which create the intermediate render targets: // Those 2 renderers are implemented in this sample. Those functions could // be implemented directly in this class but we have created separate classes // to make the code more readable. _gBufferRenderer = new GBufferRenderer(GraphicsService, _opaqueMeshSceneRenderer, _decalRenderer); LightBufferRenderer = new LightBufferRenderer(GraphicsService); // Other specialized renderers: _lensFlareRenderer = new LensFlareRenderer(GraphicsService); _skyRenderer = new SkyRenderer(GraphicsService); _fogRenderer = new FogRenderer(GraphicsService); _internalDebugRenderer = new DebugRenderer(GraphicsService, null); _rebuildZBufferRenderer = new RebuildZBufferRenderer(GraphicsService); Scene = new Scene(); // This screen needs a HDR filter to map high dynamic range values back to // low dynamic range (LDR). PostProcessors = new PostProcessorChain(GraphicsService); PostProcessors.Add(new HdrFilter(GraphicsService) { EnableBlueShift = true, BlueShiftCenter = 0.0004f, BlueShiftRange = 0.5f, //BlueShiftColor = new Vector3F(1.05f / 4f, 0.97f / 4f, 1.27f / 4f), // Default physically-based blue-shift BlueShiftColor = new Vector3F(0.25f, 0.25f, 0.7f), // More dramatic blue-shift MinExposure = 0, MaxExposure = 10, BloomIntensity = 1, BloomThreshold = 0.6f, }); _underwaterPostProcessor = new UnderwaterPostProcessor(GraphicsService, contentManager); PostProcessors.Add(_underwaterPostProcessor); // Use 2D texture for reticle. _reticle = contentManager.Load<Texture2D>("Reticle"); // Use the sprite font of the GUI. var uiContentManager = services.GetInstance<ContentManager>("UIContent"); var spriteFont = uiContentManager.Load<SpriteFont>("UI Themes/BlendBlue/Default"); DebugRenderer = new DebugRenderer(GraphicsService, spriteFont) { DefaultColor = new Color(0, 0, 0), DefaultTextPosition = new Vector2F(10), }; EnableLod = true; }
public override void Render(IList<SceneNode> nodes, RenderContext context, RenderOrder order) { ThrowIfDisposed(); if (nodes == null) throw new ArgumentNullException("nodes"); if (context == null) throw new ArgumentNullException("context"); int numberOfNodes = nodes.Count; if (numberOfNodes == 0) return; context.Validate(_effect); context.ThrowIfCameraMissing(); float deltaTime = (float)context.DeltaTime.TotalSeconds; var graphicsService = context.GraphicsService; var graphicsDevice = graphicsService.GraphicsDevice; var renderTargetPool = graphicsService.RenderTargetPool; var cameraNode = context.CameraNode; Projection projection = cameraNode.Camera.Projection; Pose view = cameraNode.PoseWorld.Inverse; // Around the camera we push the waves down to avoid that the camera cuts the near plane. // Get largest vector from camera to near plane corners. float nearPlaneRadius = new Vector3F(Math.Max(Math.Abs(projection.Right), Math.Abs(projection.Left)), Math.Max(Math.Abs(projection.Top), Math.Abs(projection.Bottom)), projection.Near ).Length; var originalSourceTexture = context.SourceTexture; // Update SceneNode.LastFrame for all visible nodes. int frame = context.Frame; cameraNode.LastFrame = frame; var savedRenderState = new RenderStateSnapshot(graphicsDevice); // Water surface is opaque. graphicsDevice.BlendState = BlendState.Opaque; #region ----- Common Effect Parameters ----- _parameterView.SetValue(view); _parameterProjection.SetValue(projection); _parameterCameraParameters.SetValue(new Vector4( (Vector3)cameraNode.PoseWorld.Position, cameraNode.Camera.Projection.Far)); var viewport = graphicsDevice.Viewport; _parameterViewportSize.SetValue(new Vector2(viewport.Width, viewport.Height)); _parameterTime.SetValue((float)context.Time.TotalSeconds); // Query ambient and directional lights. var lightQuery = context.Scene.Query<GlobalLightQuery>(cameraNode, context); Vector3F ambientLight = Vector3F.Zero; if (lightQuery.AmbientLights.Count > 0) { var light = (AmbientLight)lightQuery.AmbientLights[0].Light; ambientLight = light.Color * light.Intensity * light.HdrScale; } _parameterAmbientLight.SetValue((Vector3)ambientLight); Vector3F directionalLightDirection = new Vector3F(0, -1, 0); Vector3F directionalLightIntensity = Vector3F.Zero; if (lightQuery.DirectionalLights.Count > 0) { var lightNode = lightQuery.DirectionalLights[0]; var light = (DirectionalLight)lightNode.Light; directionalLightDirection = -lightNode.PoseWorld.Orientation.GetColumn(2); directionalLightIntensity = light.Color * light.SpecularIntensity * light.HdrScale; } _parameterDirectionalLightDirection.SetValue((Vector3)directionalLightDirection); _parameterDirectionalLightIntensity.SetValue((Vector3)directionalLightIntensity); _parameterGBuffer0.SetValue(context.GBuffer0); if (_parameterNoiseMap != null) _parameterNoiseMap.SetValue(_noiseMap); #endregion #region ----- Fog Parameters ----- var fogNodes = context.Scene.Query<FogQuery>(cameraNode, context).FogNodes; SetFogParameters(fogNodes, cameraNode, directionalLightDirection); #endregion _parameterProjectedGridParameters.SetValue(new Vector3( ProjectedGridParameters.EdgeAttenuation, ProjectedGridParameters.DistanceAttenuationStart, ProjectedGridParameters.DistanceAttenuationEnd)); for (int i = 0; i < numberOfNodes; i++) { var node = nodes[i] as WaterNode; if (node == null) continue; // Node is visible in current frame. node.LastFrame = frame; var data = node.RenderData as WaterRenderData; if (data == null) { data = new WaterRenderData(); node.RenderData = data; } var water = node.Water; bool isCameraUnderwater = node.EnableUnderwaterEffect && node.IsUnderwater(cameraNode.PoseWorld.Position); #region ----- Wave bending ----- // Waves should not cut the near plane. --> Bend waves up or down if necessary. // Limits float upperLimit; // Waves must not move above this value. float lowerLimit; // Waves must not move below this value. // Bending fades in over interval [bendStart, bendEnd]: // distance ≤ bendStart ............. Wave is bent up or down. // bendStart < distance < bendEnd ... Lerp between normal wave and bent wave. // distance ≥ bendEnd ............... Normal wave. float bendStart = 1 * nearPlaneRadius; float bendEnd = 10 * nearPlaneRadius; if (!isCameraUnderwater) { // Bend waves down below the camera. upperLimit = cameraNode.PoseWorld.Position.Y - nearPlaneRadius; lowerLimit = -1e20f; if (node.EnableUnderwaterEffect) { if (node.Waves == null || node.Waves.DisplacementMap == null) { // No displacement. The wave bending stuff does not work because the surface // is usually not tessellated. We have to render the underwater geometry when // camera near plane might cut the water surface. if (node.Volume == null) { // Test water plane. isCameraUnderwater = (cameraNode.PoseWorld.Position.Y - nearPlaneRadius) < node.PoseWorld.Position.Y; } else { // Test water AABB. var aabb = node.Aabb; aabb.Minimum -= new Vector3F(nearPlaneRadius); aabb.Maximum += new Vector3F(nearPlaneRadius); isCameraUnderwater = GeometryHelper.HaveContact(aabb, cameraNode.PoseWorld.Position); } } } } else { // Camera is underwater, bend triangles up above camera. upperLimit = 1e20f; lowerLimit = cameraNode.PoseWorld.Position.Y + nearPlaneRadius; } _parameterCameraMisc.SetValue(new Vector4(upperLimit, lowerLimit, bendStart, bendEnd)); #endregion // Update the submesh for the given water volume. data.UpdateSubmesh(graphicsService, node); #region ----- Scroll Normal Maps ----- // We update the normal map offsets once(!) per frame. // Note: We could skip the offsets and compute all in the shader using absolute // time instead of deltaTime, but then the user cannot change the NormalMapVelocity // smoothly. if (data.LastNormalUpdateFrame != frame) { data.LastNormalUpdateFrame = frame; var baseVelocity = (node.Flow != null) ? node.Flow.BaseVelocity : Vector3F.Zero; // Increase offset. // (Note: We have to subtract value and divide by scale because if the normal // should scroll to the right, we have to move the texcoords in the other direction.) data.NormalMapOffset0.X -= (water.NormalMap0Velocity.X + baseVelocity.X) * deltaTime / water.NormalMap0Scale; data.NormalMapOffset0.Y -= (water.NormalMap0Velocity.Z + baseVelocity.Y) * deltaTime / water.NormalMap0Scale; data.NormalMapOffset1.X -= (water.NormalMap1Velocity.X + baseVelocity.X) * deltaTime / water.NormalMap1Scale; data.NormalMapOffset1.Y -= (water.NormalMap1Velocity.Z + baseVelocity.Y) * deltaTime / water.NormalMap1Scale; // Keep only the fractional part to avoid overflow. data.NormalMapOffset0.X = MathHelper.Frac(data.NormalMapOffset0.X); data.NormalMapOffset0.Y = MathHelper.Frac(data.NormalMapOffset0.Y); data.NormalMapOffset1.X = MathHelper.Frac(data.NormalMapOffset1.X); data.NormalMapOffset1.Y = MathHelper.Frac(data.NormalMapOffset1.Y); } #endregion _parameterSurfaceLevel.SetValue(node.PoseWorld.Position.Y); #region ----- Reflection Parameters ----- if (node.PlanarReflection != null && node.PlanarReflection.ActualIsEnabled && node.PlanarReflection.RenderToTexture.Texture is Texture2D) { // Planar reflection. var renderToTexture = node.PlanarReflection.RenderToTexture; var texture = (Texture2D)renderToTexture.Texture; _parameterReflectionTypeParameters.SetValue(new Vector2(0, 1)); _parameterReflectionMatrix.SetValue((Matrix)renderToTexture.TextureMatrix); _parameterReflectionTextureSize.SetValue(new Vector2(texture.Width, texture.Height)); if (_parameterPlanarReflectionMap != null) _parameterPlanarReflectionMap.SetValue(texture); _parameterReflectionParameters.SetValue(new Vector4( (Vector3)water.ReflectionColor, water.ReflectionDistortion)); } else if (node.SkyboxReflection != null) { // Cube map reflection. var rgbmEncoding = node.SkyboxReflection.Encoding as RgbmEncoding; float rgbmMax = 1; if (rgbmEncoding != null) rgbmMax = GraphicsHelper.ToGamma(rgbmEncoding.Max); else if (!(node.SkyboxReflection.Encoding is SRgbEncoding)) throw new NotImplementedException("The reflected skybox must be encoded using sRGB or RGBM."); _parameterReflectionTypeParameters.SetValue(new Vector2(1, rgbmMax)); // Cube maps are left handed --> Sample with inverted z. (Otherwise, the // cube map and objects or texts in it are mirrored.) var mirrorZ = Matrix44F.CreateScale(1, 1, -1); Matrix33F orientation = node.SkyboxReflection.PoseWorld.Orientation; _parameterReflectionMatrix.SetValue((Matrix)(new Matrix44F(orientation, Vector3F.Zero) * mirrorZ)); if (_parameterCubeReflectionMap != null) _parameterCubeReflectionMap.SetValue(node.SkyboxReflection.Texture); _parameterReflectionParameters.SetValue(new Vector4( (Vector3)(water.ReflectionColor * node.SkyboxReflection.Color), water.ReflectionDistortion)); } else { // No reflection texture. The reflection shows only the ReflectionColor. _parameterReflectionTypeParameters.SetValue(new Vector2(-1, 1)); _parameterReflectionParameters.SetValue(new Vector4( (Vector3)water.ReflectionColor, water.ReflectionDistortion)); } #endregion #region ----- Refraction Parameters ----- // If we do not have a source texture, resolve the current render target // and immediately rebuilt it. if (context.SourceTexture == null && context.RenderTarget != null) { // Get RebuildZBufferRenderer from RenderContext. RebuildZBufferRenderer rebuildZBufferRenderer = null; object obj; if (context.Data.TryGetValue(RenderContextKeys.RebuildZBufferRenderer, out obj)) rebuildZBufferRenderer = obj as RebuildZBufferRenderer; // If we didn't find the renderer in the context, use a default instance. if (rebuildZBufferRenderer == null) { if (_defaultRebuildZBufferRenderer == null) _defaultRebuildZBufferRenderer = new RebuildZBufferRenderer(graphicsService); rebuildZBufferRenderer = _defaultRebuildZBufferRenderer; } context.SourceTexture = context.RenderTarget; context.RenderTarget = renderTargetPool.Obtain2D(new RenderTargetFormat(context.RenderTarget)); graphicsDevice.SetRenderTarget(context.RenderTarget); graphicsDevice.Viewport = context.Viewport; rebuildZBufferRenderer.Render(context, context.SourceTexture); } _parameterRefractionTexture.SetValue(context.SourceTexture); _parameterRefractionParameters.SetValue(new Vector4( ((Vector3)water.RefractionColor), water.RefractionDistortion)); #endregion #region ----- Other Water Effect Parameters ----- if (water.NormalMap0 != null) { if (_parameterNormalMap0 != null) _parameterNormalMap0.SetValue(water.NormalMap0); _parameterNormalMap0Parameters.SetValue(new Vector4( 1 / water.NormalMap0Scale, data.NormalMapOffset0.X, data.NormalMapOffset0.Y, water.NormalMap0Strength)); } else { if (_parameterNormalMap0 != null) _parameterNormalMap0.SetValue(_graphicsService.GetDefaultNormalTexture()); _parameterNormalMap0Parameters.SetValue(new Vector4(1, 0, 0, 0)); } if (water.NormalMap1 != null) { if (_parameterNormalMap1 != null) _parameterNormalMap1.SetValue(water.NormalMap1); _parameterNormalMap1Parameters.SetValue(new Vector4( 1 / water.NormalMap1Scale, data.NormalMapOffset1.X, data.NormalMapOffset1.Y, water.NormalMap1Strength)); } else { if (_parameterNormalMap1 != null) _parameterNormalMap1.SetValue(_graphicsService.GetDefaultNormalTexture()); _parameterNormalMap1Parameters.SetValue(new Vector4(1, 0, 0, 0)); } _parameterSpecularParameters.SetValue(new Vector4((Vector3)water.SpecularColor, water.SpecularPower)); _parameterUnderwaterFogParameters.SetValue((Vector3)water.UnderwaterFogDensity); _parameterFresnelParameters.SetValue(new Vector3(water.FresnelBias, water.FresnelScale, water.FresnelPower)); _parameterIntersectionSoftness.SetValue(water.IntersectionSoftness); // We apply some arbitrary scale factors to the water and scatter colors to // move the values into a similar range from the user's perspective. _parameterWaterColor.SetValue((Vector3)water.WaterColor / 10); _parameterScatterColor.SetValue((Vector3)water.ScatterColor); if (_parameterFoamMap != null) { _parameterFoamMap.SetValue(water.FoamMap); _parameterFoamParameters0.SetValue(new Vector4( (Vector3)water.FoamColor, 1 / water.FoamMapScale)); _parameterFoamParameters1.SetValue(new Vector4( water.FoamDistortion, water.FoamShoreIntersection, // Enable crest foam only if we have waves. node.Waves != null ? water.FoamCrestMin : float.MaxValue, water.FoamCrestMax)); } _parameterCausticsSampleCount.SetValue(water.CausticsSampleCount); _parameterCausticsParameters.SetValue(new Vector4( water.CausticsSampleOffset, water.CausticsDistortion, water.CausticsPower, water.CausticsIntensity)); #endregion #region ----- Wave Map ----- var waves = node.Waves; // The displacement map can be null but the normal map must not be null. if (waves != null && waves.NormalMap != null) { // Type: 0 = Tiling, 1 = Clamp. float waveType; if (waves.IsTiling) waveType = 0; else waveType = 1; _parameterWaveMapParameters.SetValue(new Vector4( 1.0f / waves.TileSize, // Scale 0.5f - waves.TileCenter.X / waves.TileSize, // Offset X 0.5f - waves.TileCenter.Z / waves.TileSize, // Offset Y waveType)); if (_parameterDisplacementTexture != null) { if (waves.DisplacementMap != null) _parameterDisplacementTexture.SetValue(waves.DisplacementMap); else _parameterDisplacementTexture.SetValue(graphicsService.GetDefaultTexture2DBlack4F()); } _parameterWaveMapSize.SetValue(new Vector2( waves.NormalMap.Width, waves.NormalMap.Height)); if (_parameterWaveNormalMap != null) _parameterWaveNormalMap.SetValue(waves.NormalMap); } else { _parameterWaveMapParameters.SetValue(new Vector4(0, 0, 0, 0)); } #endregion #region ----- Flow ----- if (node.Flow != null) { var flow = node.Flow; float flowMapSpeed = (flow.FlowMap != null) ? flow.FlowMapSpeed : 0; _parameterFlowParameters0.SetValue(new Vector4(flow.SurfaceSlopeSpeed, flowMapSpeed, flow.CycleDuration, flow.MaxSpeed)); _parameterFlowParameters1.SetValue(new Vector3(flow.MinStrength, 1 / flow.NoiseMapScale, flow.NoiseMapStrength)); if (_parameterFlowMap != null) _parameterFlowMap.SetValue(flow.FlowMap); // Get world space (x, z) to texture space matrix. Aabb aabb = node.Shape.GetAabb(); Vector3F extent = aabb.Extent; Matrix44F m = Matrix44F.CreateScale(1 / extent.X, 1, 1 / extent.Z) * Matrix44F.CreateTranslation(-aabb.Minimum.X, 0, -aabb.Minimum.Z) * Matrix44F.CreateScale(1 / node.ScaleLocal.X, 1, 1 / node.ScaleLocal.Z) * node.PoseWorld.Inverse; // We use a 3x3 2d scale/rotation/translation matrix, ignoring the y component. _parameterFlowMapTextureMatrix.SetValue(new Matrix(m.M00, m.M20, 0, 0, m.M02, m.M22, 0, 0, m.M03, m.M23, 1, 0, 0, 0, 0, 0)); // Get local flow direction to world flow direction matrix. // We use a 2x2 2d rotation matrix, ignoring the y component. var r = node.PoseWorld.Orientation; _parameterFlowMapWorldMatrix.SetValue(new Matrix(r.M00, r.M20, 0, 0, r.M02, r.M22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)); } else { _parameterFlowParameters0.SetValue(new Vector4(0, 0, 0, 0)); _parameterFlowParameters1.SetValue(new Vector3(0, 0, 0)); } #endregion if (isCameraUnderwater) RenderUnderwaterGeometry(node, cameraNode); RenderSurface(node, cameraNode, isCameraUnderwater); } // Reset texture effect parameters. _parameterGBuffer0.SetValue((Texture2D)null); _parameterRefractionTexture.SetValue((Texture2D)null); if (_parameterPlanarReflectionMap != null) _parameterPlanarReflectionMap.SetValue((Texture2D)null); if (_parameterCubeReflectionMap != null) _parameterCubeReflectionMap.SetValue((TextureCube)null); if (_parameterNormalMap0 != null) _parameterNormalMap0.SetValue((Texture2D)null); if (_parameterNormalMap1 != null) _parameterNormalMap1.SetValue((Texture2D)null); if (_parameterDisplacementTexture != null) _parameterDisplacementTexture.SetValue((Texture2D)null); if (_parameterNoiseMap != null) _parameterNoiseMap.SetValue((Texture2D)null); if (_parameterWaveNormalMap != null) _parameterWaveNormalMap.SetValue((Texture2D)null); if (_parameterFlowMap != null) _parameterFlowMap.SetValue((Texture2D)null); // This seems to be necessary because the Displacement Texture (vertex texture!) // is not automatically removed from the texture stage, and the WaterWavesRenderer // cannot write into it. XNA Bug!? _passProjectedGrid.Apply(); savedRenderState.Restore(); // Restore original render context. if (originalSourceTexture == null) { // Current render target has been resolved and used as source texture. // A new render target (from pool) has been set. (See region "Refraction Parameters".) // --> Previous render target needs to be recycled. renderTargetPool.Recycle(context.SourceTexture); } context.SourceTexture = originalSourceTexture; }