protected override void Initialise()
		{
			camera = new Camera3D();
			camera.Projection.FarClip = 300;
			camera.Projection.NearClip = 10;
			camera.Projection.FieldOfView *= 0.55f;
			//create the draw target.
			drawToScreen = new DrawTargetScreen(camera);

			//no need to clear the colour buffer, as a special background will be drawn
			drawToScreen.ClearBuffer.ClearColourEnabled = false;

			//create the light collection first
			lights = new MaterialLightCollection();

			// In this example, the rendering order has been manually optimized to reduce the number of pixels drawn
			// 
			// In xen, rendering is usually explicit. This means, when a call to Draw() is made, the draw order is
			// respected, and internally the object will be drawn using the graphics API.
			// Objects added to the screen will have Draw() called in the order they were added.
			//
			// However, the draw order can also cause performance problems.
			// In general, it's best to draw front to back, this means draw the objects closest to the screen first.
			//
			// This way, the objects at the back will be drawing behind the objects already drawn.
			// Modern video cards can quickly discard pixels if they are 'behind' what is already drawn.
			// Without front-to-back, the objects at the front could be drawing *over* objects already drawn.
			//
			// This is known as overdraw, a case where an object is drawn, only to be 'overdrawn' later in the frame.
			// Reducing overdraw can help performance, especially when complex shaders are used.
			//
			// In this example, the sample is usually vertex-limited (that is, the bottleneck is vertex processing)
			// However, it can demonstrate how optimizing for overdraw can significantly reduce the number of pixels
			// that are shaded.
			//
			// In debug builds, the DrawStatisticsDisplay class will show the number of pixels drawn in the frame.
			// 
			// With overdraw optimized draw order, ~1,000,000 pixels are drawn per frame. Without, upto 2,100,000
			// pixels are drawn per frame (usually ~1,800,000). (A 1280x720 display has 921,600 pixels)
			// 
			// This means that without an overdraw optimized draw order, on average, each pixel is being drawn
			// twice. With an optimized draw order, this number is closer to 1.1, which is very close to the 
			// optimal value of 1.0 (where each pixel is only drawn once).
			//
			// Note that the number of pixels reported by the DrawStatisticsDisplay is for the entire frame, including
			// every render target. Some PCs may not support this value, and display -1.
			//
			// One last point....
			// This sample is an extreme test of a GPU's ability to push triangles onto the screen (vertex/triangle rate).
			// However, observation will show the number of triangles drawn is often over 3,300,000!
			// Assuming half the triangles are back-face culled (an accurate approximation), this still means
			// there are around 1,650,000 triangles that are visible at any time.
			// (But remember, the vertex shader still runs for back facing triangles!)
			//
			// Assuming approximatly half of these triangles are depth occluded (very approximate), still
			// results in a huge number of visible triangles.
			// This all means that the average triangle is drawing a *very* small number of pixels, in this case,
			// the average for the actors is probably *less than 1 pixel per triangle!*.
			//
			// Triangles averaging less than 1 pixel are known as subpixel triangles.
			// For a number of reasons, subpixel triangles are very inefficent.
			// For example, if a single pixel is drawn, due to the way a video card works, the pixel shader will always
			// run in multiples of 4 pixels, so a single pixel triangle will still run the pixel shader 4 times.
			//
			// As an approximate rule:
			// Typically drawing a 1 pixel triangle will be as no faster than drawing a 16 pixel triangle.
			//
			// This makes this sample a perfect candidate for level of detail optimization, where a lower resolution
			// model is used as an actor gets further away from the screen. (Eg, two modelInstances, sharing a controller)
			//
			// The vertex shader is also very expensive, and for each triangle, it will be run upto 3 times.
			// This means the vertex shader is running more often than the pixel shader!
			// This hypothesis can be confirmed; setting the lights to per-vertex, instead of per-pixel, results
			// in a significantly *lower* frame rate!
			//
			//
			//

			bool optimizeForOverdraw = true;

			//create a list of actors to added to the screen
			var actors = new List<Actor>(500);

			//create 500 actors!
			for (int i = 0; i < 500; i++)
			{
				Actor actor = new Actor(this.Content, this.UpdateManager, lights, diskRadius);
				actors.Add(actor);
			}


			//create the lights, similar to Tutorial 14
			lights.AmbientLightColour = new Vector3(0.35f, 0.35f, 0.45f);

			Vector3[] lightPositions =
			{ 
				new Vector3(0, 30, 12), 
				new Vector3(0, -30, 12) 
			};

			//setup the two lights in the scene
			IDraw lightGeometry = null;
			IDraw lightPoleGeometry = null;
			//create geometry to display the lights
			var lightSourceGeometry = new List<IDraw>();

			//setup the lights, and create the light globe geometry
			for (int i = 0; i < lightPositions.Length; i++)
			{
				var colour = new Vector3(2, 2, 2);

				var light = lights.CreatePointLight(lightPositions[i], 1, colour, colour);

				light.SourceRadius = 6;

				if (lightGeometry == null)
				{
					lightGeometry = new Xen.Ex.Geometry.Sphere(Vector3.One, 8, true, false, false);
					lightPoleGeometry = new Xen.Ex.Geometry.Cube(new Vector3(0.4f, 0.4f, lightPositions[i].Z * 0.5f));
				}

				//visually show the light
				//create the light sphere geometry from tutorial 14.
				var position = lightPositions[i];
				lightSourceGeometry.Add(new Tutorial_14.LightSourceDrawer(position, lightGeometry, Color.LightYellow));
				position.Z *= 0.5f;
				lightSourceGeometry.Add(new Tutorial_14.LightSourceDrawer(position, lightPoleGeometry, new Color(40,40,70)));
			}

			//create the ground plane, also from tutorial 14
			var ground = new Tutorial_14.GroundDisk(this.Content, lights, diskRadius);


			//this is a special background element,
			//it draws a gradient over the entire screen, fading from dark at the bottom to light at the top.
			Color darkBlue = new Color(40, 40, 50);
			Color lightBlue = new Color(100, 100, 110);
			var background = new BackgroundGradient(lightBlue, darkBlue);


			if (optimizeForOverdraw == false)
			{
				//add all the objects in a naive order

				//first add the background (fills the entire screen, draws to every pixel, but is very fast)
				drawToScreen.Add(background);

				//then add the ground plane (all the actors will appear on top of the ground plane, overdrawing it)
				drawToScreen.Add(ground);

				//then add the lights (which are on top of the ground, overdrawing it)
				foreach (IDraw geometry in lightSourceGeometry)
					drawToScreen.Add(geometry);

				//then finally add the actors, in the order they were created
				foreach (Actor actor in actors)
					drawToScreen.Add(actor);
			}
			else
			{
				//or, add the objects in a order optimized for overdraw

#if !XBOX360
				//first, add the actors. Because they are almost always closest to the screen
				//however, use a depth sorter so the actors are sorted into a front to back draw order,
				//this sorting is based on the centre point of the cull tests they perform.

				var sorter = new Xen.Ex.Scene.DepthDrawSorter(Xen.Ex.Scene.DepthSortMode.FrontToBack);

				//Remember, the objects placed in the sorter *must* perform a valid CullTest,
				//if the CullTest simply returns true/false, no sorting will occur.
				//(Note the Actor.CullTest method)

				//to ease the CPU load, have the sorter only sort the actors every few frames...
				sorter.SortDelayFrameCount = 5;

				foreach (Actor actor in actors)
					sorter.Add(actor); // add the actors to the sorter (not the screen)

				//the sorter itself must be added to the screen!
				drawToScreen.Add(sorter); // the sorter will then draw the actors in a sorted order
#else

				//In this case (on the Xbox), because the application is heavily vertex limited
				//and already heavily CPU stretched by the animation system, the cost of 
				//sorting the actors actually causes a larger performance hit on the CPU than 
				//the time saved on the GPU. This inballance causes a frame rate drop.
				//
				//However, the reason for this may be unexpected. 
				//The framerate drop is not caused by the overhead of sorting the actors.
				//
				//Any 3D API calls made are doubly expensive on the XBOX, so in order to 
				//maintain 20fps in this sample, the primary (rendering) thread must not 
				//block, or switch to task processing.
				//If it does so, valuable rendering time is lost.
				//
				//When using a sorter, the actors are drawn in an order that is constantly changing.
				//However, they always have Update() called in a consistent order.
				//
				//During Update() the actors animation controllers will spawn thread tasks to 
				//process their animation.
				//
				//These tasks are processed on the spare xbox hardware threads, they are
				//processed in the order they were added. 
				//Processing the animation usually completes before the rendering finishes.
				//(the rendering is not delayed waiting for the animation to finish).
				//
				//However, when sorting the actors get drawn in an unpredictable order, 
				//this means the last actor added could be the first actor to draw,
				//in such a case, the chances of it's animation processing having completed
				//is *very* low. When this happens, the rendering thread has to switch to
				//processing animations, delaying rendering.
				//
				//So, for the xbox, in this sample it's best just to draw in the update order.

				foreach (Actor actor in actors)
					drawToScreen.Add(actor);

#endif

				//add the light source geometry, as they are usually below the actors, but above the ground
				foreach (IDraw geometry in lightSourceGeometry)
					drawToScreen.Add(geometry);

				//then add the ground plane, which is usually below the actors and lights.
				drawToScreen.Add(ground);

				//finally, enable a special feature of ElementRect.
				//This makes the element draw at the maximum possible Z distance
				//(behind anything else that has been drawn)
				background.DrawAtMaxZDepth = true;
				
				//add it to the screen
				drawToScreen.Add(background);
			}

			//finally,
			//create the draw statistics display
			stats = new Xen.Ex.Graphics2D.Statistics.DrawStatisticsDisplay(this.UpdateManager);
			drawToScreen.Add(stats);
		}
Пример #2
0
        protected override void Initialise()
        {
            Resource.EnableResourceTracking();

            camera = new Camera3D();
            camera.Projection.FarClip      = 300;
            camera.Projection.NearClip     = 10;
            camera.Projection.FieldOfView *= 0.55f;
            //create the draw target.
            drawToScreen = new DrawTargetScreen(this, camera);

            //no need to clear the colour buffer, as a special background will be drawn
            drawToScreen.ClearBuffer.ClearColourEnabled = false;

            //create the light collection first
            lights = new MaterialLightCollection();

            // In this example, the rendering order has been manually optimized to reduce the number of pixels drawn
            //
            // In xen, rendering is usually explicit. This means, when a call to Draw() is made, the draw order is
            // respected, and internally the object will be drawn using the graphics API.
            // Objects added to the screen will have Draw() called in the order they were added.
            //
            // However, the draw order can also cause performance problems.
            // In general, it's best to draw front to back, this means draw the objects closest to the screen first.
            //
            // This way, the objects at the back will be drawing behind the objects already drawn.
            // Modern video cards can quickly discard pixels if they are 'behind' what is already drawn.
            // Without front-to-back, the objects at the front could be drawing *over* objects already drawn.
            //
            // This is known as overdraw, a case where an object is drawn, only to be 'overdrawn' later in the frame.
            // Reducing overdraw can help performance, especially when complex shaders are used.
            //
            // In this example, the sample is usually vertex-limited (that is, the bottleneck is vertex processing)
            // However, it can demonstrate how optimizing for overdraw can significantly reduce the number of pixels
            // that are shaded.
            //
            // In debug builds, the DrawStatisticsDisplay class will show the number of pixels drawn in the frame.
            //
            // With overdraw optimized draw order, ~1,000,000 pixels are drawn per frame. Without, upto 2,100,000
            // pixels are drawn per frame (usually ~1,800,000). (A 1280x720 display has 921,600 pixels)
            //
            // This means that without an overdraw optimized draw order, on average, each pixel is being drawn
            // twice. With an optimized draw order, this number is closer to 1.1, which is very close to the
            // optimal value of 1.0 (where each pixel is only drawn once).
            //
            // Note that the number of pixels reported by the DrawStatisticsDisplay is for the entire frame, including
            // every render target. Some PCs may not support this value, and display -1.
            //
            // One last point....
            // This sample is an extreme test of a GPU's ability to push triangles onto the screen (vertex/triangle rate).
            // However, observation will show the number of triangles drawn is often over 3,300,000!
            // Assuming half the triangles are back-face culled (an accurate approximation), this still means
            // there are around 1,650,000 triangles that are visible at any time.
            // (But remember, the vertex shader still runs for back facing triangles!)
            //
            // Assuming approximatly half of these triangles are depth occluded (very approximate), still
            // results in a huge number of visible triangles.
            // This all means that the average triangle is drawing a *very* small number of pixels, in this case,
            // the average for the actors is probably *less than 1 pixel per triangle!*.
            //
            // Triangles averaging less than 1 pixel are known as subpixel triangles.
            // For a number of reasons, subpixel triangles are very inefficent.
            // For example, if a single pixel is drawn, due to the way a video card works, the pixel shader will always
            // run in multiples of 4 pixels, so a single pixel triangle will still run the pixel shader 4 times.
            //
            // This makes this sample a perfect candidate for level of detail optimization, where a lower resolution
            // model is used as an actor gets further away from the screen.
            //
            // The vertex shader is also very expensive, and for each triangle, it will be run upto 3 times.
            // This means the vertex shader is quite possibly running more often than the pixel shader.
            // This hypothesis can be confirmed; setting the lights to per-vertex, instead of per-pixel, results
            // in a significantly *lower* frame rate!
            //
            //
            //

            bool optimizeForOverdraw = true;

            //create a list of actors to added to the screen
            List <Actor> actors = new List <Actor>(500);

            //create 500 actors!
            for (int i = 0; i < 500; i++)
            {
                Actor actor = new Actor(this.Content, this.UpdateManager, lights, diskRadius);
                actors.Add(actor);
            }


            //create the lights, similar to Tutorial 14
            lights.AmbientLightColour = new Vector3(0.45f, 0.45f, 0.5f);

            Vector3[] lightPositions = new Vector3[]
            {
                new Vector3(0, 30, 12),
                new Vector3(0, -30, 12)
            };

            //setup the two lights in the scene
            IDraw lightGeometry     = null;
            IDraw lightPoleGeometry = null;
            //create geometry to display the lights
            List <IDraw> lightSourceGeometry = new List <IDraw>();

            //setup the lights, and create the light globe geometry
            for (int i = 0; i < lightPositions.Length; i++)
            {
                Vector3 colour = new Vector3(5, 5, 5);

                IMaterialPointLight light = lights.AddPointLight(i < 2, lightPositions[i], 8, colour, colour);

                light.ConstantAttenuation  = 0.25f;                // make the ligh falloff curve a lot sharper (brighter at the centre)
                light.LinearAttenuation    = 0;
                light.QuadraticAttenuation = 0.075f;               //approximate inverse distance in which the brightness of the light will halve

                if (lightGeometry == null)
                {
                    lightGeometry     = new Xen.Ex.Geometry.Sphere(Vector3.One, 8, true, false, false);
                    lightPoleGeometry = new Xen.Ex.Geometry.Cube(new Vector3(0.4f, 0.4f, lightPositions[i].Z * 0.5f));
                }

                //visually show the light
                //create the light sphere geometry from tutorial 14.
                Vector3 position = lightPositions[i];
                lightSourceGeometry.Add(new Tutorial_14.LightSourceDrawer(position, lightGeometry, Color.LightYellow));
                position.Z *= 0.5f;
                lightSourceGeometry.Add(new Tutorial_14.LightSourceDrawer(position, lightPoleGeometry, new Color(40, 40, 70)));
            }

            //create the ground plane, also from tutorial 14
            Tutorial_14.GroundDisk ground = new Tutorial_14.GroundDisk(this.Content, lights, diskRadius);


            //this is a special background element,
            //it draws a gradient over the entire screen, fading from dark at the bottom to light at the top.
            Color darkBlue  = new Color(40, 40, 50);
            Color lightBlue = new Color(100, 100, 110);
            BackgroundGradient background = new BackgroundGradient(lightBlue, darkBlue);


            if (optimizeForOverdraw == false)
            {
                //add all the objects in a naive order

                //first add the background (fills the entire screen, draws to every pixel, but is very fast)
                drawToScreen.Add(background);

                //then add the ground plane (all the actors will appear on top of the ground plane, overdrawing it)
                drawToScreen.Add(ground);

                //then add the lights (which are on top of the ground, overdrawing it)
                foreach (IDraw geometry in lightSourceGeometry)
                {
                    drawToScreen.Add(geometry);
                }

                //then finally add the actors, in the order they were created
                foreach (Actor actor in actors)
                {
                    drawToScreen.Add(actor);
                }
            }
            else
            {
                //or, add the objects in a order optimized for overdraw

#if !XBOX360
                //first, add the actors. Because they are almost always closest to the screen
                //however, use a depth sorter so the actors are sorted into a front to back draw order,
                //this sorting is based on the centre point of the cull tests they perform.

                Xen.Ex.Scene.DepthDrawSorter sorter = new Xen.Ex.Scene.DepthDrawSorter(Xen.Ex.Scene.DepthSortMode.FrontToBack);

                //Remember, the objects placed in the sorter *must* perform a valid CullTest,
                //if the CullTest simply returns true/false, no sorting will occur.
                //(Note the Actor.CullTest method)

                //to ease the CPU load, have the sorter only sort the actors every few frames...
                sorter.SortDelayFrameCount = 5;

                foreach (Actor actor in actors)
                {
                    sorter.Add(actor);                     // add the actors to the sorter (not the screen)
                }
                //the sorter itself must be added to the screen!
                drawToScreen.Add(sorter);                 // the sorter will then draw the actors in a sorted order
#else
                //In this case (on the Xbox), because the application is heavily vertex limited
                //and already heavily CPU stretched by the animation system, the cost of
                //sorting the actors actually causes a larger performance hit on the CPU than
                //the time saved on the GPU. This inballance causes a frame rate drop.
                //
                //However, the reason for this may be unexpected.
                //The framerate drop is not caused by the overhead of sorting the actors.
                //
                //Any 3D API calls made are doubly expensive on the XBOX, so in order to
                //maintain 20fps in this sample, the primary (rendering) thread must not
                //block, or switch to task processing.
                //If it does so, valuable rendering time is lost.
                //
                //When using a sorter, the actors are drawn in an order that is constantly changing.
                //However, they always have Update() called in a consistent order.
                //
                //During Update() the actors animation controllers will spawn thread tasks to
                //process their animation.
                //
                //These tasks are processed on the three spare xbox hardware threads, they are
                //processed in the order they were added.
                //Processing the animation usually completes before the rendering finishes.
                //(the rendering is not delayed waiting for the animation to finish).
                //
                //However, when sorting the actors get drawn in an unpredictable order,
                //this means the last actor added could be the first actor to draw,
                //in such a case, the chances of it's animation processing having completed
                //is *very* low. When this happens, the rendering thread has to switch to
                //processing animations, delaying rendering.
                //
                //So, for the xbox, it's best just to draw in the update order.

                foreach (Actor actor in actors)
                {
                    drawToScreen.Add(actor);
                }
#endif

                //add the light source geometry, as they are usually below the actors, but above the ground
                foreach (IDraw geometry in lightSourceGeometry)
                {
                    drawToScreen.Add(geometry);
                }

                //then add the ground plane, which is usually below the actors and lights.
                drawToScreen.Add(ground);

                //finally, enable a special feature of ElementRect.
                //This makes the element draw at the maximum possible Z distance
                //(behind anything else that has been drawn)
                background.DrawAtMaxZDepth = true;

                //add it to the screen
                drawToScreen.Add(background);
            }

            //finally,
            //create the draw statistics display
            stats = new Xen.Ex.Graphics2D.Statistics.DrawStatisticsDisplay(this.UpdateManager);
            drawToScreen.Add(stats);
        }