Hi there,
I am trying to implement normal maps for my 2d pixel art game. I got it to work using a single light, but for the life of me can’t figure out how to have multiple lights. I want to pass an array with a few ‘Light’ structs (which contain a position, color and intensity) via a buffer to the shader. The trouble is, when i use StructuredBuffer in C#, it doesn’t get recognized. I tried using a VertexBuffer, but then the line:
myShader.Parameters["lights"].SetValue(lightBuffer);
gets the error: connot convert from ‘Microsoft.Xna.Framework.Graphics.VertexBuffer’ to ‘bool’.
In the shader i have tried these two lines of code, neither of which fixed it:
struct Light
{
float2 pos;
float3 lightColor;
float lightStrength;
};
//only one of these i had actually in the code at a time
StructuredBuffer<Light> lights : register(t0); //first try
ByteAddressBuffer lights : register(t0); //second try
Does anyone know what is wrong or know about another way to tackle this problem?
For completeness’ sake: here is the complete shader and the main C# script:
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0_level_9_1
#define PS_SHADERMODEL ps_4_0_level_9_1
#endif
Texture2D SpriteTexture;
sampler s0;
Texture2D NormalTexture;
sampler s1;
float3 lightPos;
float3 topLeft;
float2 size;
float3 ambientColor;
float3 lightColor;
struct Light
{
float2 pos;
float3 lightColor;
float lightStrength;
};
StructuredBuffer<Light> lights : register(t0);
sampler2D SpriteTextureSampler = sampler_state
{
Texture = <SpriteTexture>;
};
struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
float2 TextureCoordinates : TEXCOORD0;
};
float4 MainPS(VertexShaderOutput input) : COLOR
{
float3 normal = NormalTexture.Sample(s0, input.TextureCoordinates).rgb;
normal = normal * 2 - float3(1,1,1);
//float3 normal = float3(0, 0, 1);
float4 color = tex2D(s0, input.TextureCoordinates);
float3 pos = topLeft+float3(size.x * input.TextureCoordinates.x, (1 - input.TextureCoordinates.y) * size.y, 0);
float3 dir = pos - lightPos;
dir.y = -dir.y;
float dirLen = length(dir);
dir = dir / dirLen;
color.rgb *= ambientColor + lightColor * saturate(dot(normal, -dir) * 1000.0f / (4 * 3.1415f * dirLen * sqrt(dirLen))); //kwadratenwet / (4 * 3.1415f * length * length)
return color;
//return tex2D(SpriteTextureSampler,input.TextureCoordinates) * input.Color;
}
technique SpriteDrawing
{
pass P0
{
PixelShader = compile PS_SHADERMODEL MainPS();
}
};
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace physics_engine
{
public class AWalkInTheAbyss : Game
{
private GraphicsDeviceManager graphics;
private SpriteBatch spriteBatch;
Texture2D crateTexture;
Texture2D playerTexture;
Texture2D playerNormals;
Player player;
Camera camera;
Effect spriteRendering;
public Random random;
Map map;
Dictionary<string,SpriteContainer> spriteContainers;
Light[] lights;
VertexBuffer lightBuffer;
struct Light
{
public Vector2 pos;
public Vector3 lightColor;
public float lightStrength;
};
Light InitLight(Vector2 pos, Vector3 lightColor, float lightStrength)
{
Light light = new Light();
light.pos = pos;
light.lightColor = lightColor;
light.lightStrength = lightStrength;
return light;
}
public AWalkInTheAbyss()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = false;
/*graphics.SynchronizeWithVerticalRetrace = false;
IsFixedTimeStep = false;*/
}
protected override void Initialize()
{
graphics.PreferredBackBufferWidth = GraphicsDevice.Adapter.CurrentDisplayMode.Width;
graphics.PreferredBackBufferHeight = GraphicsDevice.Adapter.CurrentDisplayMode.Height;
graphics.ToggleFullScreen();
graphics.ApplyChanges();
// TODO: Add your initialization logic here
spriteContainers = new Dictionary<string, SpriteContainer>();
map = new Map(rows: LevelData.rows, columns: LevelData.columns, gridSizeX: 10, gridSizeY: 10, tileWidth: 16, tileHeight: 16);
random = new Random();
camera = new Camera(new Vector3(240, 135, 50), 480, 270,GraphicsDevice);
lights = new Light[3] {InitLight(new Vector2(100,100), new Vector3(1,1,1),1000.0f), InitLight(new Vector2(50, 50), new Vector3(1, 1, 1), 1000.0f), InitLight(new Vector2(200, 50), new Vector3(1, 1, 1), 1000.0f) };
lightBuffer = new VertexBuffer(GraphicsDevice, typeof(Light), 3, BufferUsage.None);
lightBuffer.SetData(lights);
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
playerTexture = Content.Load<Texture2D>("NewPlayer");
playerNormals = Content.Load<Texture2D>("playerNormalMap2");
crateTexture = Content.Load<Texture2D>("crate");
//spriteContainers["tile"] = new SpriteContainer(Content.Load<Texture2D>("tileSprites"),16,16);
spriteContainers["tile"] = new SpriteContainer(Content.Load<Texture2D>("DirtBlocks2"), 16, 16);
spriteRendering = Content.Load<Effect>("spriteRendering");
player = new Player(new Vector2(120, 100), playerTexture);
SetShaderParameters(GetLightBuffer());
}
private VertexBuffer GetLightBuffer()
{
return lightBuffer;
}
void SetShaderParameters(VertexBuffer lightBuffer)
{
spriteRendering.Parameters["lights"].SetValue(lightBuffer);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
float deltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
player.Update(deltaTime, map);
camera.Update(player, map, deltaTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
if (random.Next(0,1000) == 0)
{
Debug.WriteLine((1 / gameTime.ElapsedGameTime.TotalSeconds));
}
// TODO: Add your drawing code here
GraphicsDevice.SetRenderTarget(camera.canvas);
//GraphicsDevice.Clear(new Color(10, 88, 111));
GraphicsDevice.Clear(new Color(2, 16, 22));
//BasicEffect basicEffect = new BasicEffect(GraphicsDevice);
//basicEffect.Texture = playerTexture; // Set your texture
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, samplerState: SamplerState.PointClamp, effect: spriteRendering);
/*var valueIG = spriteRendering.Parameters["SpriteTexture"];
if (valueIG != null)
{
valueIG.SetValue(3);
}
else
{
// Handle the error, print a message, or throw an exception
Debug.WriteLine("Parameter 'light' not found in the shader.");
}
Debug.WriteLine("WTF");*/
//spriteRendering.Parameters["lightDir"].SetValue(Circle((float)gameTime.TotalGameTime.TotalSeconds/10f));
spriteRendering.Parameters["lightPos"].SetValue(new Vector3(295,90, 5));
spriteRendering.Parameters["topLeft"].SetValue(new Vector3(player.rect.topLeft,0));
spriteRendering.Parameters["size"].SetValue(new Vector2(player.rect.width, player.rect.height));
spriteRendering.Parameters["NormalTexture"].SetValue(playerNormals);
spriteRendering.Parameters["ambientColor"].SetValue(new Vector3(0.2f,0.2f,0.2f));
spriteRendering.Parameters["lightColor"].SetValue(new Vector3(249/255f,193 / 255f, 150 / 255f));
spriteRendering.CurrentTechnique.Passes[0].Apply();
player.Draw(spriteBatch, camera);
spriteBatch.End();
spriteBatch.Begin(samplerState: SamplerState.PointClamp);
map.Draw(spriteBatch, spriteContainers["tile"], camera, player,false);
spriteBatch.End();
spriteBatch.Begin(samplerState: SamplerState.PointClamp,blendState: BlendState.AlphaBlend);
GraphicsDevice.SetRenderTarget(null);
spriteBatch.Draw(
texture: camera.canvas,
position: new Vector2(0,0),
sourceRectangle: null,
color: Color.White,
rotation: 0,
origin: new Vector2(0.5f, 0.5f),
scale: new Vector2(4,4),
effects: SpriteEffects.None,
layerDepth: 0f);
spriteBatch.End();
base.Draw(gameTime);
}
}
}