new mesh sprite and diffuse lit sprite system

instancing
cosmonaut 2020-12-06 18:52:14 -08:00
parent 60ebb19e24
commit ca6c91446e
6 changed files with 327 additions and 133 deletions

View File

@ -5,8 +5,6 @@ namespace Kav
{ {
public class DiffuseLitSpriteEffect : Effect public class DiffuseLitSpriteEffect : Effect
{ {
EffectParameter textureParam;
EffectParameter ambientColorParam; EffectParameter ambientColorParam;
EffectParameter directionalLightDirectionParam; EffectParameter directionalLightDirectionParam;
@ -16,7 +14,12 @@ namespace Kav
EffectParameter worldViewProjectionParam; EffectParameter worldViewProjectionParam;
EffectParameter worldInverseTransposeParam; EffectParameter worldInverseTransposeParam;
EffectParameter shaderIndexParam;
Texture2D texture; Texture2D texture;
Texture2D normal;
bool normalMapEnabled = false;
Vector3 ambientColor; Vector3 ambientColor;
@ -29,13 +32,16 @@ namespace Kav
EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All;
public Texture2D Texture public bool NormalMapEnabled
{ {
get { return texture; } get { return normalMapEnabled; }
set set
{ {
texture = value; if (normalMapEnabled != value)
textureParam.SetValue(texture); {
normalMapEnabled = value;
dirtyFlags |= EffectDirtyFlags.ShaderIndex;
}
} }
} }
@ -135,12 +141,22 @@ namespace Kav
dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; dirtyFlags &= ~EffectDirtyFlags.WorldViewProj;
} }
if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0)
{
int shaderIndex = 0;
if (normalMapEnabled)
{
shaderIndex = 1;
}
shaderIndexParam.SetValue(shaderIndex);
}
} }
private void CacheEffectParameters() private void CacheEffectParameters()
{ {
textureParam = Parameters["Texture"];
worldParam = Parameters["World"]; worldParam = Parameters["World"];
worldViewProjectionParam = Parameters["WorldViewProjection"]; worldViewProjectionParam = Parameters["WorldViewProjection"];
worldInverseTransposeParam = Parameters["WorldInverseTranspose"]; worldInverseTransposeParam = Parameters["WorldInverseTranspose"];
@ -149,6 +165,8 @@ namespace Kav
directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; directionalLightDirectionParam = Parameters["DirectionalLightDirection"];
directionalLightColorParam = Parameters["DirectionalLightColor"]; directionalLightColorParam = Parameters["DirectionalLightColor"];
shaderIndexParam = Parameters["ShaderIndex"];
} }
} }
} }

BIN
Effects/FXB/DiffuseLitSpriteEffect.fxb (Stored with Git LFS)

Binary file not shown.

View File

@ -1,90 +0,0 @@
#include "Macros.fxh" //from FNA
// Effect applies normalmapped lighting to a 2D sprite.
DECLARE_TEXTURE(Texture, 0);
DECLARE_TEXTURE(Normal, 1);
BEGIN_CONSTANTS
float AmbientColor _ps(c0) _cb(c0);
float3 PointLightPositions[8] _ps(c1) _cb(c1);
float3 PointLightColors[8] _ps(c9) _cb(c9);
float DirectionalLightDirection _ps(c17) _cb(c17);
float DirectionalLightColor _ps(c18) _cb(c18);
MATRIX_CONSTANTS
float4x4 WorldInverseTranspose _ps(c19) _cb(c19);
float4x4 World _vs(c0) _cb(c23);
float4x4 WorldViewProjection _vs(c4) _cb(c27);
END_CONSTANTS
struct VertexShaderInput
{
float4 Position : POSITION;
float2 TexCoord : TEXCOORD0;
};
struct PixelShaderInput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float3 PositionWS : TEXCOORD2;
};
PixelShaderInput main_vs(VertexShaderInput input)
{
PixelShaderInput output;
output.TexCoord = input.TexCoord;
output.PositionWS = mul(input.Position, World).xyz;
output.Position = mul(input.Position, WorldViewProjection);
return output;
}
float4 main_ps(PixelShaderInput input) : COLOR0
{
// Look up the texture and normalmap values.
float4 tex = tex2D(TextureSampler, input.TexCoord);
float3 normal = tex2D(NormalSampler, input.TexCoord).xyz;
float3 normalWS = mul(normal, (float3x3)WorldInverseTranspose).xyz;
float3 lightColor = float3(0.0, 0.0, 0.0);
// point lights
for (int i = 0; i < 8; i++)
{
float3 lightVec = PointLightPositions[i] - input.PositionWS;
float distance = length(lightVec);
float3 lightDir = normalize(lightVec);
float diffuse = max(dot(normalWS, lightDir), 0.0);
float3 attenuation = 1.0 / (distance * distance);
lightColor += diffuse * attenuation * PointLightColors[i];
}
// directional light
float directionalDiffuse = max(dot(normalWS, DirectionalLightDirection), 0.0);
lightColor += directionalDiffuse * DirectionalLightColor;
// ambient light
lightColor += AmbientColor;
// blend with sample
return tex * float4(lightColor, 1.0);
}
Technique DiffuseLitSprite
{
pass
{
VertexShader = compile vs_3_0 main_vs();
PixelShader = compile ps_3_0 main_ps();
}
}

View File

@ -0,0 +1,134 @@
#include "Macros.fxh" //from FNA
// Effect applies normalmapped lighting to a 2D sprite.
DECLARE_TEXTURE(Texture, 0);
DECLARE_TEXTURE(Normal, 1);
BEGIN_CONSTANTS
float3 AmbientColor _ps(c0) _cb(c0);
float3 PointLightPositions[8] _ps(c1) _cb(c1);
float3 PointLightColors[8] _ps(c9) _cb(c9);
float3 DirectionalLightDirection _ps(c17) _cb(c17);
float3 DirectionalLightColor _ps(c18) _cb(c18);
MATRIX_CONSTANTS
float4x4 WorldInverseTranspose _ps(c19) _cb(c19);
float4x4 World _vs(c0) _cb(c23);
float4x4 WorldViewProjection _vs(c4) _cb(c27);
END_CONSTANTS
struct VertexShaderInput
{
float4 Position : POSITION;
float3 Normal : NORMAL;
float2 TexCoord : TEXCOORD0;
};
struct PixelShaderInput
{
float4 Position : SV_Position;
float2 TexCoord : TEXCOORD0;
float3 NormalWS : TEXCOORD1;
float3 PositionWS : TEXCOORD2;
};
PixelShaderInput main_vs(VertexShaderInput input)
{
PixelShaderInput output;
output.Position = mul(input.Position, WorldViewProjection);
output.TexCoord = input.TexCoord;
output.NormalWS = mul(input.Normal, (float3x3)WorldInverseTranspose).xyz;
output.PositionWS = mul(input.Position, World).xyz;
return output;
}
// Easy trick to get tangent-normals to world-space to keep PBR code simplified.
float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal)
{
float3 tangentNormal = SAMPLE_TEXTURE(Normal, texCoords).xyz * 2.0 - 1.0;
float3 Q1 = ddx(worldPos);
float3 Q2 = ddy(worldPos);
float2 st1 = ddx(texCoords);
float2 st2 = ddy(texCoords);
float3 N = normalize(normal);
float3 T = normalize(Q1*st2.y - Q2*st1.y);
float3 B = -normalize(cross(N, T));
float3x3 TBN = float3x3(T, B, N);
return normalize(mul(tangentNormal, TBN));
}
float4 LightColor(float3 worldPosition, float3 worldNormal)
{
float3 lightColor = float3(0.0, 0.0, 0.0);
// point lights
for (int i = 0; i < 8; i++)
{
float3 lightVec = PointLightPositions[i] - worldPosition;
float distance = length(lightVec);
float3 lightDir = normalize(lightVec);
float diffuse = max(dot(worldNormal, lightDir), 0.0);
float3 attenuation = 1.0 / (distance * distance);
lightColor += diffuse * attenuation * PointLightColors[i];
}
// directional light
float directionalDiffuse = max(dot(worldNormal, DirectionalLightDirection), 0.0);
lightColor += directionalDiffuse * DirectionalLightColor;
// ambient light
lightColor += AmbientColor;
return float4(lightColor, 1.0);
}
float4 WithoutNormalMap(PixelShaderInput input) : COLOR0
{
float4 tex = SAMPLE_TEXTURE(Texture, input.TexCoord);
float3 normalWS = normalize(input.NormalWS);
return tex * LightColor(input.PositionWS, normalWS);
}
float4 WithNormalMap(PixelShaderInput input) : COLOR0
{
float4 tex = SAMPLE_TEXTURE(Texture, input.TexCoord);
float3 normalWS = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS);
return tex * LightColor(input.PositionWS, normalWS);
}
PixelShader PSArray[2] =
{
compile ps_3_0 WithoutNormalMap(),
compile ps_3_0 WithNormalMap()
};
int PSIndices[2] =
{
0, 1
};
int ShaderIndex = 0;
Technique DiffuseLitSprite
{
pass
{
VertexShader = compile vs_3_0 main_vs();
PixelShader = (PSArray[PSIndices[ShaderIndex]]);
}
}

105
Geometry/MeshSprite.cs Normal file
View File

@ -0,0 +1,105 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Kav
{
public class MeshSprite
{
private static readonly int PixelScale = 40;
public IndexBuffer IndexBuffer { get; }
public VertexBuffer VertexBuffer { get; }
public Texture2D Texture { get; }
public Texture2D Normal { get; }
public SpriteBillboardConstraint BillboardConstraint { get; }
public MeshSprite(
GraphicsDevice graphicsDevice,
Texture2D texture,
SpriteBillboardConstraint billboardConstraint
) {
Texture = texture;
Normal = null;
BillboardConstraint = billboardConstraint;
IndexBuffer = new IndexBuffer(
graphicsDevice,
IndexElementSize.SixteenBits,
6,
BufferUsage.WriteOnly
);
IndexBuffer.SetData(GenerateIndexArray());
VertexBuffer = new VertexBuffer(
graphicsDevice,
typeof(VertexPositionNormalTexture),
4,
BufferUsage.WriteOnly
);
VertexBuffer.SetData(GenerateVertexArray(Texture));
}
public MeshSprite(
GraphicsDevice graphicsDevice,
Texture2D texture,
Texture2D normal,
SpriteBillboardConstraint billboardConstraint
) {
Texture = texture;
Normal = normal;
BillboardConstraint = billboardConstraint;
IndexBuffer = new IndexBuffer(
graphicsDevice,
IndexElementSize.SixteenBits,
6,
BufferUsage.WriteOnly
);
IndexBuffer.SetData(GenerateIndexArray());
VertexBuffer = new VertexBuffer(
graphicsDevice,
typeof(VertexPositionNormalTexture),
4,
BufferUsage.WriteOnly
);
VertexBuffer.SetData(GenerateVertexArray(Texture));
}
private static short[] GenerateIndexArray()
{
short[] result = new short[6];
result[0] = 0;
result[1] = 1;
result[2] = 2;
result[3] = 1;
result[4] = 3;
result[5] = 2;
return result;
}
private static VertexPositionNormalTexture[] GenerateVertexArray(Texture2D texture)
{
VertexPositionNormalTexture[] result = new VertexPositionNormalTexture[4];
result[0].Position = new Vector3(-texture.Width / 2, texture.Height / 2, 0) / PixelScale;
result[0].Normal = new Vector3(0, 0, -1);
result[0].TextureCoordinate = new Vector2(0, 0);
result[1].Position = new Vector3(texture.Width / 2, texture.Height / 2, 0) / PixelScale;
result[1].Normal = new Vector3(0, 0, -1);
result[1].TextureCoordinate = new Vector2(1, 0);
result[2].Position = new Vector3(-texture.Width / 2, -texture.Height / 2, 0) / PixelScale;
result[2].Normal = new Vector3(0, 0, -1);
result[2].TextureCoordinate = new Vector2(0, 1);
result[3].Position = new Vector3(texture.Width / 2, -texture.Height / 2, 0) / PixelScale;
result[3].Normal = new Vector3(0, 0, -1);
result[3].TextureCoordinate = new Vector2(1, 1);
return result;
}
}
}

View File

@ -183,7 +183,7 @@ namespace Kav
IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable<(Model, Matrix)> modelTransforms,
AmbientLight ambientLight, AmbientLight ambientLight,
IEnumerable<PointLight> pointLights, IEnumerable<PointLight> pointLights,
DirectionalLight directionalLight DirectionalLight? directionalLight
) { ) {
GBufferRender(camera, modelTransforms); GBufferRender(camera, modelTransforms);
@ -199,7 +199,10 @@ namespace Kav
PointLightRender(camera, modelTransforms, pointLight); PointLightRender(camera, modelTransforms, pointLight);
} }
DirectionalLightRender(camera, modelTransforms, directionalLight); if (directionalLight.HasValue)
{
DirectionalLightRender(camera, modelTransforms, directionalLight.Value);
}
GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.SetRenderTarget(renderTarget);
SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect);
@ -213,7 +216,7 @@ namespace Kav
IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable<(Model, Matrix)> modelTransforms,
AmbientLight ambientLight, AmbientLight ambientLight,
IEnumerable<PointLight> pointLights, IEnumerable<PointLight> pointLights,
DirectionalLight directionalLight, DirectionalLight? directionalLight,
TextureCube skybox TextureCube skybox
) { ) {
GBufferRender(camera, modelTransforms); GBufferRender(camera, modelTransforms);
@ -230,7 +233,12 @@ namespace Kav
{ {
PointLightRender(camera, modelTransforms, pointLight); PointLightRender(camera, modelTransforms, pointLight);
} }
DirectionalLightToonRender(camera, modelTransforms, directionalLight);
if (directionalLight.HasValue)
{
DirectionalLightToonRender(camera, modelTransforms, directionalLight.Value);
}
SkyboxRender(camera, skybox); SkyboxRender(camera, skybox);
GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.SetRenderTarget(renderTarget);
@ -239,16 +247,16 @@ namespace Kav
SpriteBatch.End(); SpriteBatch.End();
} }
// billboards sprites into the scene // TODO: we could make this a lot more efficient probably
// FIXME: we can frustum cull the sprites probably // draws mesh sprites with a forward rendered diffuse lighting technique
public void BillboardSpriteRender( public void MeshSpriteRender(
RenderTarget2D renderTarget, RenderTarget2D renderTarget,
PerspectiveCamera camera, PerspectiveCamera camera,
IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable<(Model, Matrix)> modelTransforms,
IEnumerable<Sprite> sprites, IEnumerable<(MeshSprite, Matrix)> meshSpriteTransforms,
AmbientLight ambientLight, AmbientLight ambientLight,
IEnumerable<PointLight> pointLights, IEnumerable<PointLight> pointLights,
DirectionalLight directionalLight DirectionalLight? directionalLight
) { ) {
GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.SetRenderTarget(ColorRenderTarget);
GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);
@ -257,16 +265,29 @@ namespace Kav
DepthRender(camera, modelTransforms); DepthRender(camera, modelTransforms);
GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0); GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0);
GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
GraphicsDevice.RasterizerState = RasterizerState.CullNone;
GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp;
GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;
GraphicsDevice.BlendState = BlendState.AlphaBlend;
DiffuseLitSpriteEffect.View = camera.View; DiffuseLitSpriteEffect.View = camera.View;
DiffuseLitSpriteEffect.Projection = camera.Projection; DiffuseLitSpriteEffect.Projection = camera.Projection;
DiffuseLitSpriteEffect.AmbientColor = DiffuseLitSpriteEffect.AmbientColor =
ambientLight.Color.ToVector3(); ambientLight.Color.ToVector3();
if (directionalLight.HasValue)
{
DiffuseLitSpriteEffect.DirectionalLightDirection = DiffuseLitSpriteEffect.DirectionalLightDirection =
directionalLight.Direction; directionalLight.Value.Direction;
DiffuseLitSpriteEffect.DirectionalLightColor = DiffuseLitSpriteEffect.DirectionalLightColor =
directionalLight.Color.ToVector3() * directionalLight.Intensity; directionalLight.Value.Color.ToVector3() * directionalLight.Value.Intensity;
}
else
{
DiffuseLitSpriteEffect.DirectionalLightColor = Vector3.Zero;
}
var i = 0; var i = 0;
foreach (var pointLight in pointLights) foreach (var pointLight in pointLights)
@ -276,26 +297,28 @@ namespace Kav
i += 1; i += 1;
} }
foreach (var sprite in sprites) foreach (var (sprite, transform) in meshSpriteTransforms)
{ {
DiffuseLitSpriteEffect.NormalMapEnabled = sprite.Normal != null;
if (sprite.BillboardConstraint == SpriteBillboardConstraint.None) if (sprite.BillboardConstraint == SpriteBillboardConstraint.None)
{ {
DiffuseLitSpriteEffect.World = sprite.TransformMatrix; DiffuseLitSpriteEffect.World = transform;
} }
else if (sprite.BillboardConstraint == SpriteBillboardConstraint.Horizontal) else if (sprite.BillboardConstraint == SpriteBillboardConstraint.Horizontal)
{ {
DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard( DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard(
sprite.Position, transform.Translation,
camera.Position, camera.Position,
Vector3.Up, Vector3.Up,
camera.Forward, camera.Forward,
camera.Position - sprite.Position camera.Position - transform.Translation
); );
} }
else else
{ {
DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard( DiffuseLitSpriteEffect.World = Matrix.CreateConstrainedBillboard(
sprite.Position, transform.Translation,
camera.Position, camera.Position,
Vector3.Up, Vector3.Up,
null, null,
@ -303,23 +326,26 @@ namespace Kav
); );
} }
GraphicsDevice.Textures[1] = sprite.Texture; GraphicsDevice.Textures[0] = sprite.Texture;
GraphicsDevice.Textures[1] = sprite.Normal;
SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, DiffuseLitSpriteEffect); GraphicsDevice.SetVertexBuffer(sprite.VertexBuffer);
SpriteBatch.Draw( GraphicsDevice.Indices = sprite.IndexBuffer;
sprite.Texture,
Vector2.Zero, foreach (var pass in DiffuseLitSpriteEffect.CurrentTechnique.Passes)
null, {
Color.White, pass.Apply();
sprite.Rotation,
sprite.Origin, GraphicsDevice.DrawIndexedPrimitives(
sprite.Scale / new Vector2(sprite.Texture.Width, -sprite.Texture.Height), PrimitiveType.TriangleList,
0, 0,
0 0,
sprite.VertexBuffer.VertexCount,
0,
2
); );
SpriteBatch.End();
} }
}
GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.SetRenderTarget(renderTarget);
GraphicsDevice.Clear(new Color(0, 0, 0, 0)); GraphicsDevice.Clear(new Color(0, 0, 0, 0));
@ -558,6 +584,7 @@ namespace Kav
Deferred_ToonEffect.DitheredShadows = false; Deferred_ToonEffect.DitheredShadows = false;
Deferred_ToonEffect.EyePosition = camera.Position; Deferred_ToonEffect.EyePosition = camera.Position;
Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction;
Deferred_ToonEffect.DirectionalLightColor = Deferred_ToonEffect.DirectionalLightColor =
directionalLight.Color.ToVector3() * directionalLight.Intensity; directionalLight.Color.ToVector3() * directionalLight.Intensity;