raycrasher / Fell-Sky

2d space combat simulator like EV Nova and Transcendence.
MIT License
2 stars 1 forks source link

Create a sprite border shader (HLSL) #16

Open raycrasher opened 8 years ago

raycrasher commented 8 years ago

Adds a border around a sprite, similar in effect to the "Border" Photoshop layer effect, by drawing pixels in the area where zero and non-zero alpha texels meet. Color and thickness should be parameterized.

Possible implementation is: SpriteBorderComponent - the component, specifying a color and thickness. This component should be added to an Entity containing a SpriteComponent and a Transform.

SpriteBorderSystem - An entity system taking in a SpriteComponent, Transform, and SpriteBorderComponent. Optionally, SceneGraphComponent should also be taken into account for the sake of getting world transforms.

This could be expanded in the future to include drawing a border over an entire scene graph node.

markgarcia commented 8 years ago

I'll try working on this. Gotta sharpen up my shader skillz.

raycrasher commented 8 years ago

Thanks! For starters, try just subclassing StandardShipRenderer and load the border code in spritebatch.Begin().

You will need MonoGame and the Pipeline tool to compile HLSL .fx files. Resource project is in Data\Source\FellSkyContent.mgcb. Fx files are in Data\Source\Fx. Building the project in the Pipeline will copy the XNB files to their proper folders (Data\Fx). Load the shader using Content.Load("Fx/your_file"), with no .xnb extension.

markgarcia commented 8 years ago

I want to integrate rendering into the scene graph but I'm having difficulty in doing proper rendering order. Currently, scene graph renderers draw entities into batches of T (of a type) and so because we're primarily doing simple painter's algorithm rendering (drawing stuff on top of another, in the correct presumed order) it's quite difficult to somehow insert a border below a sprite. I'll try to find an approach where SceneGraphRendererSystem<T> could perhaps "detect" for another renderer and in some way call that other renderer along with the T/default/primary one.

raycrasher commented 8 years ago

Actually, I think you can just do the following:

  1. Create a SpriteBorderRenderer, inherit from ISceneGraphRenderer.
  2. Create a SpriteBorderComponent, and inherit from ISceneGraphRenderableComponent
  3. Everytime you want to add a border, just add a SpriteBorderComponent to the component.
  4. Draw orders between systems are determined by the depth parameter (see ShipRefitState, the lines with World.SystemManager.SetSystem(new ...). So adding a new system with a depth less than that of SceneGraphRendererSystem will draw it behind the ship.

But yeah, the borders will then be drawn behind the ship itself, so its utility is reduced. I think you can finagle it by drawing it in front of the ship layer (StandardShipRenderer), and just drawing the border itself (the sprite itself is masked out with RGBA(0,0,0,0)).

For the "detect" part, you can just use entity.HasComponent() or even GetComponent and check for null/non-null.

For the "call other renderer" part, you can do that by entityWorld.SystemManager.GetSystem< SceneGraphRendererSystem >. Don't forget to pass in the world object to your renderer first.

Edit: Currently, because of the batched drawing of ships, a SceneGraphRenderer will always fully render after it ends drawing all the sprites (on SpriteBatch.End). Also, you cannot really "insert" a new effect in the middle of the batch without ending it and starting a new one. I think you can get away with that, but you would need some sort of push/pop state to be really effective.