Graphics.render(image) or image.render()?

Started by
6 comments, last by Teknofreek 18 years, 3 months ago
I've often wondered which style is better for drawing renderable objects - having the graphics system call its render function and accept a renderable object as a parameter, or having the renderable object call its render() member function. With the first choice, the graphics class would need access to the renderable object's raw data. (for API calls) Using the second option, the renderable object would probably need access to some global device that's contained within the graphics class. Which do you guys prefer, and why? (Oh and of course, this applies to any resource system, i.e. sounds.)
Advertisement
Object::Render() calls Renderer::Render().

That way the renderer doesn't depend on the objects you want to render. For example, if you want to render a bicycle using Renderer::Render(), you would have to extend the Renderer to be able to render Bicycle objects. Bicycle::Render() doesn't have that problem.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Quote:
Object::Render() calls Renderer::Render()


In that case, wouldn't Object::Render() need access to whatever system device pointer? Then you would either need to have a public accessor for the device, or have the Renderer give friendship access to each type of object. (Which would kind of defeat the purpose of not having to modify the Renderer, I guess.)
your renderer should be abstracted to a common interface which any function can use.

Let's say you have a rendering instance:

Renderer world;

perhapse pass a pointer of that into your object that you want to be able to draw.

Wheel::Render(Renderer* rend){  for(int i = 0;i < bikeSpokes;i++){     rend->drawCylinder((Wheel::BikeSpoke).x, (Wheel::BikeSpoke).y, (Wheel::BikeSpoke).angle, (Wheel::BikeSpoke).length, 2/*diameter*/);  }  rend->drawToroid(Wheel::BikeSpoke).x, Wheel::BikeSpoke).y, (Wheel::BikeSpoke).toroidDiameter, (Wheel::BikeSpoke).ringDiameter);}


Just a thought.
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk
Here is a C#+MDX example of how my code works:

class Core{	public static GraphicsSystem Graphics;		public static void Init()	{		Graphics = new GraphicsSystem(...)	}}class GraphicsSystem{	private D3D.Device _device;	private D3D.Sprite _spriteRenderer;		internal D3D.Device Device { get { return _device; } }	internal D3D.Sprite SpriteRenderer { get { return _spriteRenderer; } }}class Sprite{	private D3D.Texture _texture;	private Point _rotationCenter;	private Color _color;		/* properties */		public Sprite(string textureFilename)	{		_texture = D3D.TextureLoader.FromFile(Core.Graphics.Device, textureFilename);				// set rotation center to center of texture		_rotationCenter = new Point(_texture.GetLevelDescription(0).Width / 2,			_texture.GetLevelDescription(0).Height / 2);						_color = Color.White;	}		public void Render(Point screenPosition, float rotation)	{		Core.Graphics.SpriteRenderer.Begin(...);		Core.Graphics.SpriteRenderer.Draw2D(_texture, _rotationCenter, rotation, position, _color);		Core.Graphics.SpriteRenderer.End();	}}class Bicycle{	private Sprite _sprite;	private Point _screenPosition;	private float _rotation = 0f;		public Bicycle(Point screenPosition)	{		_sprite = new Sprite("bicycle.png", Color.White);		_screenPosition = screenPosition;	}		public void Render()	{		_sprite.Render(_screenPosition, _rotation);	}}


As you can see, Bicycle objects don't need to communicate with the graphics system at all. Intermediate objects, like the Sprite class in the example, abstract away the API-specific graphics code so that game objects (like Bicycle) don't need to worry about it.

You could later decide to draw the character's name above his bicycle. To do this, you could implement a Font class that wraps Direct3D's Font class and simple add '_font.Write("Billy");' to Bicycle.Render().

I hope that all made sense. Let me know if you have any questions, or if anyone else wants to butcher my design ;).

p.s. I do realize the irony in giving examples of non-API-specific code in C# w/ Managed DirectX. Hopefully the overall design will shine through :)

- Mike
Normally I have three classes:

  1. Base Renderable class - Consists of one main method: Render (). This acts as an interface, it doesn't specify how an object will be rendered.
  2. Derived class: A class that derives from Renderable. This is the place where specific per-class rendering routine goes.
  3. RenderEngine: It's main purpose is to keep a good list of pointers to registered Renderable objects. In it's update routine, it loops through the pointers and call Render () method. How the object will be rendered depends on it's implementation.

RenderEngine has Register () and Unregister () methods for updating the list. Since the class knows nothing about the object, I can bring my class from projects to projects without having changed it.
--> The great thing about Object Oriented code is that it can make small, simple problems look like large, complex ones <--
An image (usually something like a sprite or more-or-less 2D bitmap/texture) is a type of data. An int doesn't know how to add and subtract, put itself in a std::vector, or write itself to the registry.

Personally, I'd favor the design that best represents this: an image is a kind of data, and there are other objects (i.e. the Renderer) that consumes and operates upon this data. Data, in many cases, should know how to load itself from a file and move itself around in memory/storage; aside from that, data should be "dumb" and rely on the operators to do meaningful work with the data.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Quote:Original post by ApochPiQ
An image (usually something like a sprite or more-or-less 2D bitmap/texture) is a type of data. An int doesn't know how to add and subtract, put itself in a std::vector, or write itself to the registry.


I agree with Apoch. I personally find it rather awkward that the defacto OOP example uses a Shape, with derived Circles and Boxes and a draw method. The problem is that a Circle, or Box, or Bicycle aren't a renderer. Given that, why should they be rendering anything? I don't think they should.

Furthermore, having every class under the sun know how to render itself can become problematic in real world scenarios, where you want to do more than simply iterate through a list of "things" and draw each one. For example, you may want to do things like do a rough z-sort on all the objects, render all the opaque objects before any transparent objects, sort objects by shader, sort objects by common render-state and so on. These things will become difficult to do if you simply iterate through the objects and have each one draw itself.

With that being said, I think it's fine to create one, or more, base renderable types and have the renderer deal with these abstract types. For example, the renderer could deal with images, splines, and meshes. Each derived mesh could convey how many vertices it has, the vertex type, the number of faces, its bounds, and so on. The renderer should be able to do it's job just fine without being told what each concrete renderable class is.

-John
- John

This topic is closed to new replies.

Advertisement