Добавление фантастических световых эффектов в XNA-игры
В этом XNA-проекте демонстрируется, как автор добавляет фантастические эффекты освещения в игры. И конечно, вы получите немного кода для игр.
XNA Light Pre-Pass: каскадные теневые карты
В этом сообщении я расскажу о собственной реализации каскадных теневых карт – общей методике, применяемой для управления направленными светотенями. Описания этой техники можно найти здесь и здесь. Как и прежде – полный исходный код примера можно найти здесь. Его использование производится на собственный страх и риск!!
Основная идея состоит в разбиении конуса видимости на более мелкие фрагменты (обычно на меньшие конусы), и генерации карты теней для каждого из них. Таким образом можно получить лучшее распределение наших любимых текселей (пикселей текстуры) теневой карты в каждой области: элемент, более близкий к камере меньше тех, которые располагаются дальше, поэтому ближе к камере получается лучшее разрешение теней. Недостатком метода является то, что в этом случае приходится выполнять больше вызовов перерисовки/изменения состояния, так как приходится рендерить больше одной теневой карты. Можно создавать атлас теневых карт для оптимизации этих вещей, просто надо сгенерировать большую теневую карту, вмещающую все меньшие теневые карты и компенсировать выборки текселов на пиксельном шейдере.
Вот снимок решения:
Цикл перерисовки XNA, где совершается основная магия:
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
//clear our visible lights from previous frame
_visibleLights.Clear();
//perform a basic frustum test on our lights
//we could do the same for the meshes, but its enough for this tutorial
foreach (Light light in _lights)
{
if (light.LightType == Light.Type.Directional)
{
_visibleLights.Add(light);
}
else if (light.LightType == Light.Type.Spot)
{
if (_camera.Frustum.Intersects(light.Frustum))
_visibleLights.Add(light);
}
else if (light.LightType == Light.Type.Point)
{
if (_camera.Frustum.Intersects(light.BoundingSphere))
_visibleLights.Add(light);
}
}
RenderTarget2D output = _renderer.RenderScene(_camera, _visibleLights, _meshes);
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.BlendState = BlendState.Opaque;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
//output our final composition into the backbuffer. We could output it into a
//post-process fx, or to an object diffuse texture, or or or...well, its up to you =)
//Just remember that we are using a floating point buffer, so we should use Point Clamp here
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise);
_spriteBatch.Draw(output, new Rectangle(0, 0, GraphicsDevice.Viewport.Width, GraphicsDevice.Viewport.Height), Color.White);
if (_renderGBuffer)
{
int smallWidth = GraphicsDevice.Viewport.Width / 3;
int smallHeigth = GraphicsDevice.Viewport.Height / 3;
//draw the intermediate steps into screen
_spriteBatch.Draw(_renderer.NormalBuffer, new Rectangle(0, 0, smallWidth, smallHeigth),
Color.White);
_spriteBatch.Draw(_renderer.DepthBuffer, new Rectangle(smallWidth, 0, smallWidth, smallHeigth),
Color.White);
_spriteBatch.Draw(_renderer.LightBuffer, new Rectangle(smallWidth * 2, 0, smallWidth, smallHeigth),
Color.White);
/*if (_renderer.GetShadowMap(0) != null)
_spriteBatch.Draw(_renderer.GetShadowMap(0), new Rectangle(0, smallHeigth, smallWidth, smallHeigth),
Color.White);
if (_renderer.GetShadowMap(1) != null)
_spriteBatch.Draw(_renderer.GetShadowMap(1), new Rectangle(smallWidth, smallHeigth, smallWidth, smallHeigth),
Color.White);*/
}
_spriteBatch.End();
//legacy, left here just in case
if (_renderScreenSpaceLightQuads)
{
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise);
Rectangle rect = new Rectangle();
foreach (Light light in _visibleLights)
{
//redundant, but its ok for now
Vector2 topLeftCorner, bottomRightCorner, size;
//compute a screen-space quad that fits the light's bounding sphere
_camera.ProjectBoundingSphereOnScreen(light.BoundingSphere, out topLeftCorner, out size);
rect.X = (int)((topLeftCorner.X * 0.5f + 0.5f) * GraphicsDevice.Viewport.Width);
rect.Y = (int)((topLeftCorner.Y * 0.5f + 0.5f) * GraphicsDevice.Viewport.Height);
rect.Width = (int)((size.X * 0.5f) * GraphicsDevice.Viewport.Width);
rect.Height = (int)((size.Y * 0.5f) * GraphicsDevice.Viewport.Height);
Color color = light.Color;
color.A = 150;
_spriteBatch.Draw(_lightDebugTexture, rect, color);
}
_spriteBatch.End();
}
bool drawText = true;
if (drawText)
{
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp,
DepthStencilState.Default, RasterizerState.CullCounterClockwise);
_spriteBatch.DrawString(_spriteFont, "Press O / P to visualize the GBuffer", new Vector2(0, 0),
Color.White);
_spriteBatch.DrawString(_spriteFont, "Hold space to stop the lights", new Vector2(0, 20), Color.White);
_spriteBatch.DrawString(_spriteFont,
String.Format("Update:{0:00.0}, Draw:{1:00.0}, GPU:{2:00.0}", updateMs, drawMs,
gpuMs), new Vector2(0, 40), Color.White);
RenderStatistics renderStatistics = _renderer.RenderStatistics;
_spriteBatch.DrawString(_spriteFont, "Visible lights: " + renderStatistics.VisibleLights,
new Vector2(0, 60), Color.White);
_spriteBatch.DrawString(_spriteFont, "Draw calls: " + renderStatistics.DrawCalls, new Vector2(0, 80),
Color.White);
_spriteBatch.DrawString(_spriteFont, "Shadow Lights: " + renderStatistics.ShadowCasterLights,
new Vector2(0, 100), Color.White);
_spriteBatch.DrawString(_spriteFont, "Shadow Meshes: " + renderStatistics.ShadowCasterMeshes,
new Vector2(0, 120), Color.White);
_spriteBatch.End();
}
base.Draw(gameTime);
swDraw.Stop();
updateMs = swUpdate.Elapsed.TotalMilliseconds;
drawMs = swDraw.Elapsed.TotalMilliseconds;
swGPU.Reset();
swGPU.Start();
}
Если вы ищете как добавить классные эффекты освещения в XNA-игры, то на этот проект стоит обратить внимание. Также пока вы здесь, убедитесь, что посмотрели и другие аналогичные проекты. Вот несколько последних: