i'm following the tutorials on 3D Graphics with XNA Game Studio 4.0 and I came up with an horrible effect when I tried to implement the Light Map
http://i.stack.imgur.com/BUWvU.jpg
this effect shows up when I look towards the center of the house (and it moves with me).
it has this shape because I'm using a sphere to represent light; using other light shapes gives different results.
I'm using a class PreLightingRenderer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Dhpoware;
using Microsoft.Xna.Framework.Content;
namespace XNAFirstPersonCamera
{
public class PrelightingRenderer
{
// Normal, depth, and light map render targets
RenderTarget2D depthTarg;
RenderTarget2D normalTarg;
RenderTarget2D lightTarg;
// Depth/normal effect and light mapping effect
Effect depthNormalEffect;
Effect lightingEffect;
// Point light (sphere) mesh
Model lightMesh;
// List of models, lights, and the camera
public List<CModel> Models { get; set; }
public List<PPPointLight> Lights { get; set; }
public FirstPersonCamera Camera { get; set; }
GraphicsDevice graphicsDevice;
int viewWidth = 0, viewHeight = 0;
public PrelightingRenderer(GraphicsDevice GraphicsDevice, ContentManager Content)
{
viewWidth = GraphicsDevice.Viewport.Width;
viewHeight = GraphicsDevice.Viewport.Height;
// Create the three render targets
depthTarg = new RenderTarget2D(GraphicsDevice, viewWidth, viewHeight, false, SurfaceFormat.Single, DepthFormat.Depth24);
normalTarg = new RenderTarget2D(GraphicsDevice, viewWidth, viewHeight, false, SurfaceFormat.Color, DepthFormat.Depth24);
lightTarg = new RenderTarget2D(GraphicsDevice, viewWidth, viewHeight, false, SurfaceFormat.Color, DepthFormat.Depth24);
// Load effects
depthNormalEffect = Content.Load<Effect>(@"Effects\PPDepthNormal");
lightingEffect = Content.Load<Effect>(@"Effects\PPLight");
// Set effect parameters to light mapping effect
lightingEffect.Parameters["viewportWidth"].SetValue(viewWidth);
lightingEffect.Parameters["viewportHeight"].SetValue(viewHeight);
// Load point light mesh and set light mapping effect to it
lightMesh = Content.Load<Model>(@"Models\PPLightMesh");
lightMesh.Meshes[0].MeshParts[0].Effect = lightingEffect;
this.graphicsDevice = GraphicsDevice;
}
public void Draw()
{
drawDepthNormalMap();
drawLightMap();
prepareMainPass();
}
void drawDepthNormalMap()
{
// Set the render targets to 'slots' 1 and 2
graphicsDevice.SetRenderTargets(normalTarg, depthTarg);
// Clear the render target to 1 (infinite depth)
graphicsDevice.Clear(Color.White);
// Draw each model with the PPDepthNormal effect
foreach (CModel model in Models)
{
model.CacheEffects();
model.SetModelEffect(depthNormalEffect, false);
model.Draw(Camera.ViewMatrix, Camera.ProjectionMatrix, Camera.Position);
model.RestoreEffects();
}
// Un-set the render targets
graphicsDevice.SetRenderTargets(null);
}
void drawLightMap()
{
// Set the depth and normal map info to the effect
lightingEffect.Parameters["DepthTexture"].SetValue(depthTarg);
lightingEffect.Parameters["NormalTexture"].SetValue(normalTarg);
// Calculate the view * projection matrix
Matrix viewProjection = Camera.ViewMatrix * Camera.ProjectionMatrix;
// Set the inverse of the view * projection matrix to the effect
Matrix invViewProjection = Matrix.Invert(viewProjection);
lightingEffect.Parameters["InvViewProjection"].SetValue(invViewProjection);
// Set the render target to the graphics device
graphicsDevice.SetRenderTarget(lightTarg);
// Clear the render target to black (no light)
graphicsDevice.Clear(Color.Black);
// Set render states to additive (lights will add their influences)
graphicsDevice.BlendState = BlendState.Additive;
graphicsDevice.DepthStencilState = DepthStencilState.None;
foreach (PPPointLight light in Lights)
{
// Set the light's parameters to the effect
light.SetEffectParameters(lightingEffect);
// Calculate the world * view * projection matrix and set it to
// the effect
Matrix wvp = (Matrix.CreateScale(light.Attenuation) * Matrix.CreateTranslation(light.Position)) * viewProjection;
lightingEffect.Parameters["WorldViewProjection"].SetValue(wvp);
// Determine the distance between the light and camera
float dist = Vector3.Distance(Camera.Position, light.Position);
// If the camera is inside the light-sphere, invert the cull mode
// to draw the inside of the sphere instead of the outside
if (dist < light.Attenuation)
graphicsDevice.RasterizerState = RasterizerState.CullClockwise;
// Draw the point-light-sphere
lightMesh.Meshes[0].Draw();
// Revert the cull mode
graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
}
// Revert the blending and depth render states
graphicsDevice.BlendState = BlendState.Opaque;
graphicsDevice.DepthStencilState = DepthStencilState.Default;
// Un-set the render target
graphicsDevice.SetRenderTarget(null);
}
void prepareMainPass()
{
foreach (CModel model in Models)
foreach (ModelMesh mesh in model.Model.Meshes)
foreach (ModelMeshPart part in mesh.MeshParts)
{
// Set the light map and viewport parameters to each model's effect
if (part.Effect.Parameters["LightTexture"] != null)
part.Effect.Parameters["LightTexture"].SetValue(lightTarg);
if (part.Effect.Parameters["viewportWidth"] != null)
part.Effect.Parameters["viewportWidth"].SetValue(viewWidth);
if (part.Effect.Parameters["viewportHeight"] != null)
part.Effect.Parameters["viewportHeight"].SetValue(viewHeight);
}
}
}
}
that uses three effect:
PPDepthNormal.fx
float4x4 World;
float4x4 View;
float4x4 Projection;
struct VertexShaderInput
{
float4 Position : POSITION0;
float3 Normal : NORMAL0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 Depth : TEXCOORD0;
float3 Normal : TEXCOORD1;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4x4 viewProjection = mul(View, Projection);
float4x4 worldViewProjection = mul(World, viewProjection);
output.Position = mul(input.Position, worldViewProjection);
output.Normal = mul(input.Normal, World);
// Position's z and w components correspond to the distance
// from camera and distance of the far plane respectively
output.Depth.xy = output.Position.zw;
return output;
}
// We render to two targets simultaneously, so we can't
// simply return a float4 from the pixel shader
struct PixelShaderOutput
{
float4 Normal : COLOR0;
float4 Depth : COLOR1;
};
PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)
{
PixelShaderOutput output;
// Depth is stored as distance from camera / far plane distance
// to get value between 0 and 1
output.Depth = input.Depth.x / input.Depth.y;
// Normal map simply stores X, Y and Z components of normal
// shifted from (-1 to 1) range to (0 to 1) range
output.Normal.xyz = (normalize(input.Normal).xyz / 2) + .5;
// Other components must be initialized to compile
output.Depth.a = 1;
output.Normal.a = 1;
return output;
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
PPLight.fx
float4x4 WorldViewProjection;
float4x4 InvViewProjection;
texture2D DepthTexture;
texture2D NormalTexture;
sampler2D depthSampler = sampler_state {
texture = ;
minfilter = point;
magfilter = point;
mipfilter = point;
};
sampler2D normalSampler = sampler_state {
texture = ;
minfilter = point;
magfilter = point;
mipfilter = point;
};
float3 LightColor;
float3 LightPosition;
float LightAttenuation;
// Include shared functions
#include "PPShared.vsi"
struct VertexShaderInput
{
float4 Position : POSITION0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float4 LightPosition : TEXCOORD0;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
output.Position = mul(input.Position, WorldViewProjection);
output.LightPosition = output.Position;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// Find the pixel coordinates of the input position in the depth
// and normal textures
float2 texCoord = postProjToScreen(input.LightPosition) + halfPixel();
// Extract the depth for this pixel from the depth map
float4 depth = tex2D(depthSampler, texCoord);
// Recreate the position with the UV coordinates and depth value
float4 position;
position.x = texCoord.x * 2 - 1;
position.y = (1 - texCoord.y) * 2 - 1;
position.z = depth.r;
position.w = 1.0f;
// Transform position from screen space to world space
position = mul(position, InvViewProjection);
position.xyz /= position.w;
// Extract the normal from the normal map and move from
// 0 to 1 range to -1 to 1 range
float4 normal = (tex2D(normalSampler, texCoord) - .5) * 2;
// Perform the lighting calculations for a point light
float3 lightDirection = normalize(LightPosition - position);
float lighting = clamp(dot(normal, lightDirection), 0, 1);
// Attenuate the light to simulate a point light
float d = distance(LightPosition, position);
float att = 1 - pow(d / LightAttenuation, 6);
return float4(LightColor * lighting * att, 1);
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
PPShared.vsi has some common functions:
float viewportWidth;
float viewportHeight;
// Calculate the 2D screen position of a 3D position
float2 postProjToScreen(float4 position)
{
float2 screenPos = position.xy / position.w;
return 0.5f * (float2(screenPos.x, -screenPos.y) + 1);
}
// Calculate the size of one half of a pixel, to convert
// between texels and pixels
float2 halfPixel()
{
return 0.5f / float2(viewportWidth, viewportHeight);
}
and finally from the Game class I set up in LoadContent with:
effect = Content.Load(@"Effects\PPModel");
models[0] = new CModel(Content.Load(@"Models\teapot"), new Vector3(-50, 80, 0), new Vector3(0, 0, 0),
1f, Content.Load(@"Textures\prova_texture_autocad"), GraphicsDevice);
house = new CModel(Content.Load(@"Models\house"), new Vector3(0, 0, 0), new Vector3((float)-Math.PI / 2, 0, 0), 35.0f, Content.Load(@"Textures\prova_texture_autocad"), GraphicsDevice);
models[0].SetModelEffect(effect, true);
house.SetModelEffect(effect, true);
renderer = new PrelightingRenderer(GraphicsDevice, Content);
renderer.Models = new List();
renderer.Models.Add(house);
renderer.Models.Add(models[0]);
renderer.Lights = new List()
{
new PPPointLight(new Vector3(0, 120, 0), Color.White * .85f, 2000)
};
where PPModel.fx is:
float4x4 World;
float4x4 View;
float4x4 Projection;
texture2D BasicTexture;
sampler2D basicTextureSampler = sampler_state
{
texture = ;
addressU = wrap;
addressV = wrap;
minfilter = anisotropic;
magfilter = anisotropic;
mipfilter = linear;
};
bool TextureEnabled = true;
texture2D LightTexture;
sampler2D lightSampler = sampler_state
{
texture = ;
minfilter = point;
magfilter = point;
mipfilter = point;
};
float3 AmbientColor = float3(0.15, 0.15, 0.15);
float3 DiffuseColor;
#include "PPShared.vsi"
struct VertexShaderInput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : POSITION0;
float2 UV : TEXCOORD0;
float4 PositionCopy : TEXCOORD1;
};
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4x4 worldViewProjection = mul(World, mul(View, Projection));
output.Position = mul(input.Position, worldViewProjection);
output.PositionCopy = output.Position;
output.UV = input.UV;
return output;
}
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
// Sample model's texture
float3 basicTexture = tex2D(basicTextureSampler, input.UV);
if (!TextureEnabled)
basicTexture = float4(1, 1, 1, 1);
// Extract lighting value from light map
float2 texCoord = postProjToScreen(input.PositionCopy) +
halfPixel();
float3 light = tex2D(lightSampler, texCoord);
light += AmbientColor;
return float4(basicTexture * DiffuseColor * light, 1);
}
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}
I don't have any idea on what's wrong... googling the web I found that this tutorial may have some bug but I don't know if it's the LightModel fault (the sphere) or in a shader or in the class PrelightingRenderer.
Any help is very appreciated, thank you for reading!