diff --git a/Geometry/Sprite.cs b/Geometry/Sprite.cs new file mode 100644 index 0000000..80b2641 --- /dev/null +++ b/Geometry/Sprite.cs @@ -0,0 +1,39 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public struct Sprite + { + public Texture2D Texture { get; } + public Vector3 Position { get; } + public Vector2 Origin { get; } + public float Rotation { get; } + public Vector2 Scale { get; } + + public Sprite( + Texture2D texture, + Vector3 position, + Vector2 origin, + float rotation, + Vector2 scale + ) { + Texture = texture; + Position = position; + Origin = origin; + Rotation = rotation; + Scale = scale; + } + + public Sprite( + Texture2D texture, + Vector3 position + ) { + Texture = texture; + Position = position; + Origin = Vector2.Zero; + Rotation = 0f; + Scale = Vector2.One; + } + } +} diff --git a/Renderer.cs b/Renderer.cs index 2ad0ceb..ae54bad 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -33,6 +33,7 @@ namespace Kav private LinearDepthEffect LinearDepthEffect { get; } private Effect ToneMapEffect { get; } private SkyboxEffect SkyboxEffect { get; } + private BasicEffect BasicEffect { get; } private RenderTarget2D gPosition { get; } private RenderTarget2D gNormal { get; } @@ -159,6 +160,7 @@ namespace Kav ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect); Deferred_ToonEffect = new Deferred_ToonEffect(GraphicsDevice); SkyboxEffect = new SkyboxEffect(GraphicsDevice); + BasicEffect = new BasicEffect(GraphicsDevice); FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); FullscreenTriangle.SetData(new VertexPositionTexture[3] { @@ -206,13 +208,14 @@ namespace Kav } public void DeferredToonRender( + RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, AmbientLight ambientLight, IEnumerable pointLights, DirectionalLight directionalLight, TextureCube skybox - ) { + ) { GBufferRender(camera, modelTransforms); GraphicsDevice.SetRenderTarget(ColorRenderTarget); @@ -230,12 +233,64 @@ namespace Kav DirectionalLightToonRender(camera, modelTransforms, directionalLight); SkyboxRender(camera, skybox); - GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.SetRenderTarget(renderTarget); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, null); SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } + // billboards sprites into the scene + // FIXME: we can frustum cull the sprites probably + public void BillboardSpriteRender( + RenderTarget2D renderTarget, + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + IEnumerable sprites + ) { + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); + GraphicsDevice.DepthStencilState = DepthStencilState.Default; + + DepthRender(camera, modelTransforms); + GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0); + + Matrix invertY = Matrix.CreateScale(1, -1, 1); + + BasicEffect.World = invertY; + BasicEffect.View = Matrix.Identity; + BasicEffect.Projection = camera.Projection; + BasicEffect.TextureEnabled = true; + BasicEffect.VertexColorEnabled = true; + + SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, BasicEffect); + + foreach (var sprite in sprites) + { + // transform view space on CPU so we don't have to break the batch + Vector3 viewSpacePosition = Vector3.Transform(sprite.Position, camera.View * Matrix.CreateRotationX(sprite.Rotation) * invertY); + + SpriteBatch.Draw( + sprite.Texture, + new Vector2(viewSpacePosition.X, viewSpacePosition.Y), + null, + Color.White, + 0, + sprite.Origin, + sprite.Scale / new Vector2(sprite.Texture.Width, sprite.Texture.Height), + 0, + viewSpacePosition.Z + ); + } + + SpriteBatch.End(); + + GraphicsDevice.SetRenderTarget(renderTarget); + GraphicsDevice.Clear(new Color(0, 0, 0, 0)); + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null); + SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); + SpriteBatch.End(); + } + private void DepthRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms @@ -386,11 +441,11 @@ namespace Kav DeferredPointLightEffect.GPosition = gPosition; DeferredPointLightEffect.GAlbedo = gAlbedo; DeferredPointLightEffect.GNormal = gNormal; - DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; + DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; DeferredPointLightEffect.ShadowMap = PointShadowCubeMap; DeferredPointLightEffect.PointLightPosition = pointLight.Position; - DeferredPointLightEffect.PointLightColor = + DeferredPointLightEffect.PointLightColor = pointLight.Color.ToVector3() * pointLight.Intensity; DeferredPointLightEffect.FarPlane = 25f; // FIXME: magic value @@ -428,11 +483,11 @@ namespace Kav { DeferredDirectionalLightEffect.ShadowMapFour = ShadowRenderTargets[3]; } - + DeferredDirectionalLightEffect.DirectionalLightDirection = directionalLight.Direction; - DeferredDirectionalLightEffect.DirectionalLightColor = + DeferredDirectionalLightEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; - + DeferredDirectionalLightEffect.ViewMatrix = camera.View; DeferredDirectionalLightEffect.EyePosition = camera.Position; @@ -467,7 +522,7 @@ namespace Kav Deferred_ToonEffect.EyePosition = camera.Position; Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; - Deferred_ToonEffect.DirectionalLightColor = + Deferred_ToonEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; Deferred_ToonEffect.ShadowMapOne = ShadowRenderTargets[0]; @@ -483,7 +538,7 @@ namespace Kav { Deferred_ToonEffect.ShadowMapFour = ShadowRenderTargets[3]; } - + Deferred_ToonEffect.ViewMatrix = camera.View; foreach (EffectPass pass in Deferred_ToonEffect.CurrentTechnique.Passes) @@ -506,7 +561,7 @@ namespace Kav { var farPlane = camera.FarPlane / (MathHelper.Max((NumShadowCascades - i - 1) * 2f, 1f)); - // divide the view frustum + // divide the view frustum var shadowCamera = new PerspectiveCamera( camera.Position, camera.Forward, @@ -516,7 +571,7 @@ namespace Kav previousFarPlane, farPlane ); - + // TODO: This is tightly coupled to the effect and it sucks RenderDirectionalShadowMap(shadowCamera, modelTransforms, directionalLight, effect, i); @@ -526,8 +581,8 @@ namespace Kav } private void RenderDirectionalShadowMap( - PerspectiveCamera camera, - IEnumerable<(Model, Matrix)> modelTransforms, + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight, ShadowCascadeEffect effect, int shadowCascadeIndex @@ -536,7 +591,7 @@ namespace Kav GraphicsDevice.Clear(Color.White); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; - + var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection); Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners(); @@ -555,7 +610,7 @@ namespace Kav } BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners); - + SimpleDepthEffect.View = lightView; SimpleDepthEffect.Projection = Matrix.CreateOrthographicOffCenter( lightBox.Min.X, @@ -586,7 +641,7 @@ namespace Kav } var boundingFrustum = new BoundingFrustum(lightSpaceMatrix); - + foreach (var (model, transform) in FrustumCull(boundingFrustum, modelTransforms)) { foreach (var modelMesh in model.Meshes)