Well, something else got in the way and pushed UI further down the pipe, but thats OK. That thing that got in the way is my new Deferred rendering system. I poked around for a while looking to figure out how one makes one of these, and found some good examples at www.catalinzima.com and www.soolstyle.com. I looked at how they went about it, and figured I could incorporate their ideas. Now I have a nice shiny deferred renderer in place for my game. I chose Deferred rendering over multi-pass because I can expect lots of use of light-based effects later on and the modularized/componet nautre of my ECS Framework favors deferred over multi-pass for flexibility.
Demo in action:
The scene composed of a generated cave and 100 randomly colored evenly spaced lights and one reticle light.
My deferred renderer works with 3 basic maps: Color, Normals, and Shading. These are held by a special component called the GeometryMap. Color and Normals are defined in appropriate texutres and are drawn to my respective sprite drawing systems. Shading is a bit different and is calculated through a ShadingSystem which utilizes the drawn Color and Normal maps. They are then all combined in the DeferredSystem.
The DeferredSystem only processes the GeometryMap, and is the last of the shading "draw" systems to be called (later when i finally get around to UIs, they will actually be the last "draw" system).
It is layed out as such:
the shader program is very simple so it doesnt really diverge at all from the source material:
But that is the simpe part. The more complex parts come into play with the ShadingSystem. The ShadingSystem crunches on light components. All lights are considered components in my ECS Framework, so they can be attached to anything. In this demo above, i have 100 stand-alone lihgts and I attached one to my reticle which i can toggle on and off. At present I only support simple point lights, but i'm working towards directional lights and shadowing (but thats on the tail end of my to-do list as its just a nice-to-have). It looks like such:
And all that sets up the following shader program:
Thats all there is to it. As of now, it only adds about 1-2ms of overhead, so i'm happy. Also, if you look carefully during the video, I show an interesting transparency artifact of this technique with the reticle. I'm not overly worried about it, since i'll be rendering the reticle in the UI system when i get it finalized so this will OBE.
Demo in action:
The scene composed of a generated cave and 100 randomly colored evenly spaced lights and one reticle light.
My deferred renderer works with 3 basic maps: Color, Normals, and Shading. These are held by a special component called the GeometryMap. Color and Normals are defined in appropriate texutres and are drawn to my respective sprite drawing systems. Shading is a bit different and is calculated through a ShadingSystem which utilizes the drawn Color and Normal maps. They are then all combined in the DeferredSystem.
The DeferredSystem only processes the GeometryMap, and is the last of the shading "draw" systems to be called (later when i finally get around to UIs, they will actually be the last "draw" system).
It is layed out as such:
class DeferredSystem : EntityProcessingSystem
{
private GameContainer d_Container;
private SpriteBatch d_SpriteBatch;
private Effect d_CombinedEffect;
private ComponentMapper d_GeometryMapper;
public DeferredSystem(GameContainer container)
{
d_Container = container;
d_SpriteBatch = d_Container.SpriteBatch;
}
public override void initialize()
{
d_GeometryMapper = new ComponentMapper(new GeometryMap(), e_ECSInstance);
}
protected override void preLoadContent(ECSFramework.Utils.Bag<Entity> entities)
{
d_CombinedEffect = d_Container.ContentManager.Load<Effect>("effects\\DiferredCombine");
}
protected override void process(Entity entity)
{
GeometryMap geometry = (GeometryMap)d_GeometryMapper.get(entity);
//setup effect parameters and techniques
d_CombinedEffect.CurrentTechnique = d_CombinedEffect.Techniques["Combine"];
d_CombinedEffect.Parameters["ambient"].SetValue(1f);
d_CombinedEffect.Parameters["lightAmbient"].SetValue(4);
d_CombinedEffect.Parameters["ambientColor"].SetValue(geometry.AmbientColor);
d_CombinedEffect.Parameters["ColorMap"].SetValue(geometry.ColorMap);
d_CombinedEffect.Parameters["ShadingMap"].SetValue(geometry.ShadingMap);
d_CombinedEffect.Parameters["NormalMap"].SetValue(geometry.NormalMap);
d_CombinedEffect.CurrentTechnique.Passes[0].Apply();
d_SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, d_CombinedEffect);
d_SpriteBatch.Draw(geometry.ColorMap, Vector2.Zero, Color.White);
d_SpriteBatch.End();
}
}
the shader program is very simple so it doesnt really diverge at all from the source material:
float ambient;
float4 ambientColor;
float lightAmbient;
Texture ColorMap;
sampler ColorMapSampler = sampler_state {
texture = <ColorMap>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
Texture ShadingMap;
sampler ShadingMapSampler = sampler_state {
texture = <ShadingMap>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
Texture NormalMap;
sampler NormalMapSampler = sampler_state {
texture = <NormalMap>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
float4 CombinedPixelShader(float4 color : COLOR0, float2 texCoords : TEXCOORD0) : COLOR0
{
float4 color2 = tex2D(ColorMapSampler, texCoords);
float4 shading = tex2D(ShadingMapSampler, texCoords);
float4 finalColor = color2 * ambientColor * ambient;
finalColor += (shading * color2) * lightAmbient;
return finalColor;
}
technique Combine
{
pass Pass1
{
PixelShader = compile ps_2_0 CombinedPixelShader();
}
}
But that is the simpe part. The more complex parts come into play with the ShadingSystem. The ShadingSystem crunches on light components. All lights are considered components in my ECS Framework, so they can be attached to anything. In this demo above, i have 100 stand-alone lihgts and I attached one to my reticle which i can toggle on and off. At present I only support simple point lights, but i'm working towards directional lights and shadowing (but thats on the tail end of my to-do list as its just a nice-to-have). It looks like such:
class ShadingSystem : EntityProcessingSystem
{
private GameContainer s_Container;
private Effect s_ShadingEffect;
private GraphicsDevice s_GraphicsDevice;
private ComponentMapper s_GeometryMapper;
private ComponentMapper s_LightMapper;
private ComponentMapper s_PositionMapper;
private ComponentMapper s_ViewPortMapper;
private Entity s_GeometryMap;
private Entity s_Camera;
private GeometryMap s_Geometry;
private BlendState s_BlendState;
private VertexPositionColorTexture[] s_Vertices;
private VertexBuffer s_VertexBuffer;
public ShadingSystem(GameContainer container)
{
s_Container = container;
s_GraphicsDevice = s_Container.GraphicsDevice;
}
public override void initialize()
{
//s_ShadingEffect.CurrentTechnique = s_ShadingEffect.Techniques["PointLight"];
s_GeometryMapper = new ComponentMapper(new GeometryMap(), e_ECSInstance);
s_LightMapper = new ComponentMapper(new Light(), e_ECSInstance);
s_PositionMapper = new ComponentMapper(new Position(), e_ECSInstance);
s_ViewPortMapper = new ComponentMapper(new ViewPort(), e_ECSInstance);
//setup vertex buffer
s_Vertices = new VertexPositionColorTexture[4];
s_Vertices[0] = new VertexPositionColorTexture(new Vector3(-1, 1, 0), Color.White, new Vector2(0, 0));
s_Vertices[1] = new VertexPositionColorTexture(new Vector3(1, 1, 0), Color.White, new Vector2(1, 0));
s_Vertices[2] = new VertexPositionColorTexture(new Vector3(-1, -1, 0), Color.White, new Vector2(0, 1));
s_Vertices[3] = new VertexPositionColorTexture(new Vector3(1, -1, 0), Color.White, new Vector2(1, 1));
s_VertexBuffer = new VertexBuffer(s_GraphicsDevice, typeof(VertexPositionColorTexture), s_Vertices.Length, BufferUsage.None);
s_VertexBuffer.SetData(s_Vertices);
//setup blending
s_BlendState = new BlendState();
s_BlendState.ColorBlendFunction = BlendFunction.Add;
s_BlendState.ColorSourceBlend = Blend.One;
s_BlendState.ColorDestinationBlend = Blend.One;
s_BlendState.AlphaBlendFunction = BlendFunction.Add;
s_BlendState.AlphaSourceBlend = Blend.SourceAlpha;
s_BlendState.AlphaDestinationBlend = Blend.One;
}
protected override void preLoadContent(ECSFramework.Utils.Bag<Entity> entities)
{
s_ShadingEffect = s_Container.ContentManager.Load<Effect>("effects\\Shading");
s_GeometryMap = e_ECSInstance.TagManager.getEntityByTag("GEOMETRY");
s_Camera = e_ECSInstance.TagManager.getEntityByTag("CAMERA");
}
protected override void begin()
{
s_Geometry = (GeometryMap) s_GeometryMapper.get(s_GeometryMap);
}
protected override void process(Entity entity)
{
Light light = (Light)s_LightMapper.get(entity);
if (!light.IsEnabled)
return;
//setup locals
ViewPort viewport = (ViewPort)s_ViewPortMapper.get(s_Camera);
Position lightPos = (Position)s_PositionMapper.get(entity);
Vector2 position = lightPos.getPosition()+lightPos.getOffset();
Vector2 origin = viewport.getOrigin();
Vector2 center = viewport.getDimensions() / 2;
int radius = light.LightRadius;
//calculate the wide-view rectangle
Rectangle view = new Rectangle((int)(origin.X - center.X - radius),
(int)(origin.Y - center.Y - radius),
(int)(viewport.getDimensions().X + 2*radius),
(int)(viewport.getDimensions().Y + 2*radius));
//if light is not on the screen, dont process it
if (!view.Contains((int)(position.X), (int)(position.Y)))
return;
//setup some parameter variables
Vector3 center3 = new Vector3(center, 0);
Vector3 origin3 = new Vector3(origin, 0);
Vector3 position3 = new Vector3(position, light.Position.Z);
//set vertex buffer
s_GraphicsDevice.SetVertexBuffer(s_VertexBuffer);
//get light source parameters
s_ShadingEffect.Parameters["lightStrength"].SetValue(light.ActualPower);
s_ShadingEffect.Parameters["lightPosition"].SetValue(position3);
s_ShadingEffect.Parameters["viewCenter"].SetValue(center3);
s_ShadingEffect.Parameters["viewOrigin"].SetValue(origin3);
s_ShadingEffect.Parameters["lightColor"].SetValue(light.Color);
s_ShadingEffect.Parameters["lightRadius"].SetValue(light.LightRadius);
s_ShadingEffect.Parameters["specularStrength"].SetValue(.1f);
s_ShadingEffect.Parameters["specularColor"].SetValue(new Vector4(1,1,1,1));//light.Color);
s_ShadingEffect.Parameters["screenWidth"].SetValue(s_GraphicsDevice.Viewport.Width);
s_ShadingEffect.Parameters["screenHeight"].SetValue(s_GraphicsDevice.Viewport.Height);
s_ShadingEffect.Parameters["NormalMap"].SetValue(s_Geometry.NormalMap);
s_ShadingEffect.Parameters["ColorMap"].SetValue(s_Geometry.ColorMap);
s_ShadingEffect.CurrentTechnique = s_ShadingEffect.Techniques["PointLight"];
s_ShadingEffect.CurrentTechnique.Passes[0].Apply();
s_GraphicsDevice.BlendState = s_BlendState;
s_GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleStrip, s_Vertices, 0, 2);
}
}
And all that sets up the following shader program:
float screenWidth;
float screenHeight;
float lightStrength;
float lightRadius;
float3 lightPosition;
float4 lightColor;
float specularStrength;
float3 viewCenter;
float3 viewOrigin;
float4 specularColor;
Texture NormalMap;
sampler NormalMapSampler = sampler_state {
texture = <NormalMap>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
Texture ColorMap;
sampler ColorMapSampler = sampler_state {
texture = <ColorMap>;
magfilter = LINEAR;
minfilter = LINEAR;
mipfilter = LINEAR;
AddressU = mirror;
AddressV = mirror;
};
struct VertexToPixel
{
float4 Position : POSITION;
float2 TexCoord : TEXCOORD0;
float4 Color : COLOR0;
};
struct PixelToFrame
{
float4 Color : COLOR0;
};
VertexToPixel MyVertexShader(float4 inPos: POSITION0, float2 texCoord: TEXCOORD0, float4 color: COLOR0)
{
VertexToPixel Output = (VertexToPixel)0;
Output.Position = inPos;
Output.TexCoord = texCoord;
Output.Color = color;
return Output;
}
PixelToFrame PointLightShader(VertexToPixel PSIn) : COLOR0
{
PixelToFrame Output = (PixelToFrame)0;
float4 colorMap = tex2D(ColorMapSampler, PSIn.TexCoord);
float3 normal = (2.0f * (tex2D(NormalMapSampler, PSIn.TexCoord))) - 1.0f;
float3 pixelPosition;
pixelPosition.x = screenWidth * PSIn.TexCoord.x;
pixelPosition.y = screenHeight * PSIn.TexCoord.y;
pixelPosition.z = 0;
float3 view = viewOrigin - viewCenter;
float3 realLightPos = lightPosition - view;
float3 lightDir = realLightPos - pixelPosition;
float3 lightDirNorm = normalize(lightDir);
float3 eye = float3(viewCenter.x, viewCenter.y,1000) - pixelPosition;
float3 eyeNorm = normalize(eye);
float amount = max(dot(normal, lightDirNorm), 0);
float coneAttenuation = saturate(1.0f - length(lightDir) / (lightRadius));
float3 ref = normalize(reflect(-lightDirNorm,normal));
float specular = min(pow(saturate(dot(ref,eyeNorm)),10),amount) * specularStrength;
Output.Color = colorMap * coneAttenuation * lightColor * lightStrength + (specular * coneAttenuation) * specularColor;
return Output;
}
technique PointLight
{
pass P0
{
VertexShader = compile vs_2_0 MyVertexShader();
PixelShader = compile ps_2_0 PointLightShader();
}
}
(NOTE: I'm fairly sure the rendering equation needs some tweaks, so don't expect it to be 100% accurate)Thats all there is to it. As of now, it only adds about 1-2ms of overhead, so i'm happy. Also, if you look carefully during the video, I show an interesting transparency artifact of this technique with the reticle. I'm not overly worried about it, since i'll be rendering the reticle in the UI system when i get it finalized so this will OBE.






Create a custom theme









