Multiple Render Targets (MRT) в XNA Framework

Multiple Render Targets (MRT) в XNA Framework

В этот раз я немного опишу технологию использования множественных поверхностей рендеринга (MRT- multiple render targets) в XNA Framework.

Раньше мы использовали все наши шейдеры по очереди, что, по всей видимости, не является самым быстрым способом работы с видеокартой.

То есть раньше процесс выглядел примерно так:

  • Установить некоторый render target как основной
  • При помощи шейдера нарисовать изображение в установленный render target.
  • Установить следующий render target как основной
  • При помощи шейдера нарисовать изображение в установленный render target.
  • И т.д.
  • Сбрасить установку основной поверхности рендеринга
  • Нарисовать сцену.

При MRT подход несколько меняется: мы устанавливаем сразу несколько поверхностей рисования и, при помощи специального нового шейдера, рисуем сразу на все поверхности.

Ничего принципиально сложного тут нет, перейдем сразу к шейдеру:

Нас новый шейдер будет рисовать различные G-буферы: глубины, нормали, идентификаторы и интенсивность дуффузного освещения.

Нужно будет все старые шейдеры объединить в один, для этого введем новую структуру, которая будет содержать вывод пиксельного шейдера (раньше мы всегда возвращали только один цвет)

struct PixelShaderOutput

{


float4 Color0 : COLOR0;


float4 Color1 : COLOR1;


float4 Color2 : COLOR2;


float4 Color3 : COLOR3;

};

Собственно, на этом все интересное почти закончилось.

Вершинный шейдер будет также возвращать новую структуру:

struct VertexShaderOutput

{


float4 Position : POSITION0;


float3 WorldPosition : TEXCOORD0;


float3 Normal : TEXCOORD1;


float4 Color0 : COLOR0;


float4 Color1 : COLOR1;


float4 Color2 : COLOR2;

};

Вершинный шейдер будет возвращать на один цвет меньше чем нужно, поскольку мы будем вычислять этот цвет в пиксельном шейдере.

Вершинный шейдер будет содержать код из всех прошлых шейдеров:

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);


float4 projPosition = mul(viewPosition, Projection);

output.Position = projPosition;


float3 worldNormal = normalize(mul(input.Normal, World));

output.WorldPosition = worldPosition;

output.Normal = worldNormal;

// depth

output.Color0.rgb = length(Eye — worldPosition) / 10.0;

output.Color0.a = 1;


// id

output.Color1.rgb = ID.rgb;

output.Color1.a = 1;


// normal

output.Color2.rgb = (worldNormal + 1) / 2.0;

output.Color2.a = 1;


return output;

}

Пиксельный шейдер станет несколько более интересным, теперь он вызвращать структуру.

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)

{

PixelShaderOutput output;

output.Color0 = input.Color0;

output.Color1 = input.Color1;

output.Color2 = input.Color2;


float3 worldPosition = input.WorldPosition;


float3 worldNormal = normalize(input.Normal);


float3 lightDirection = normalize(LightPosition — worldPosition);


float4 Diffuse = max(0, dot(worldNormal, lightDirection));

output.Color3 = Diffuse;


return output;

}

Первые три цвета просто получим из вершинного шейдера, а последний вычислим, как мы делали это раньше для диффузного освещения.

В код Game1 добавится новый метод для рисования с использованием MRT.


private
void DrawMRT(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetDepth);

GraphicsDevice.SetRenderTarget(1, targetID);

GraphicsDevice.SetRenderTarget(2, targetNormals);

GraphicsDevice.SetRenderTarget(3, targetDiffuse);

GraphicsDevice.Clear(Color.Black);

effectMRT.Parameters[«View»].SetValue(view);

effectMRT.Parameters[«Projection»].SetValue(projection);

effectMRT.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

effectMRT.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(0, 1, 0));

teapot.Draw(effectMRT);

world = Matrix.CreateTranslation(1.5f, 0, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(0, 0, 1));

cube.Draw(effectMRT);

world = Matrix.CreateTranslation(0, 0, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(1, 0, 0));

cylinder.Draw(effectMRT);

}

Интересный код там в самом верху, где мы устанавливаем сразу четыре поверхности рисования.

Аналогичино нужно будет сбросить четыре поверхности в методе DrawScene:

private
void DrawScene(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, null);

GraphicsDevice.SetRenderTarget(1, null);

GraphicsDevice.SetRenderTarget(2, null);

GraphicsDevice.SetRenderTarget(3, null);

GraphicsDevice.Clear(Color.Black);

effectLight.Parameters[«View»].SetValue(view);

effectLight.Parameters[«Projection»].SetValue(projection);

effectLight.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

effectLight.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(1, 0, 0, 1));

teapot.Draw(effectLight);

world = Matrix.CreateTranslation(1.5f, 0, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 1, 0, 1));

cube.Draw(effectLight);

world = Matrix.CreateTranslation(0, 0, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 0, 1, 1));

cylinder.Draw(effectLight);

}

Весь код, как всегда, в конце.

Получится такая картинка:

Исходный код Game1.cs:

using System;

using System.Collections.Generic;

using System.Linq;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Audio;

using Microsoft.Xna.Framework.Content;

using Microsoft.Xna.Framework.GamerServices;

using Microsoft.Xna.Framework.Graphics;

using Microsoft.Xna.Framework.Input;

using Microsoft.Xna.Framework.Media;

using Microsoft.Xna.Framework.Net;

using Microsoft.Xna.Framework.Storage;

using Primitives3D;

namespace XNA_MRT

{


///
<summary>


/// This is the main type for your game


///
</summary>


public
class
Game1 : Microsoft.Xna.Framework.Game

{


GraphicsDeviceManager graphics;


SpriteBatch spriteBatch;


CubePrimitive cube;


TeapotPrimitive teapot;


CylinderPrimitive cylinder;


Effect effectLight;


Effect effectMRT;


RenderTarget2D targetDiffuse;


RenderTarget2D targetDepth;


RenderTarget2D targetID;


RenderTarget2D targetNormals;


SpriteComponent spriteComp;


public Game1()

{

graphics = new
GraphicsDeviceManager(this);

Content.RootDirectory = «Content»;

graphics.PreferredBackBufferHeight = 600;

graphics.PreferredBackBufferWidth = 800;

}


///
<summary>


/// Allows the game to perform any initialization it needs to before starting to run.


/// This is where it can query for any required services and load any non-graphic


/// related content. Calling base.Initialize will enumerate through any components


/// and initialize them as well.


///
</summary>


protected
override
void Initialize()

{


// TODO: Add your initialization logic here

cube = new
CubePrimitive(GraphicsDevice);

teapot = new
TeapotPrimitive(GraphicsDevice);

cylinder = new
CylinderPrimitive(GraphicsDevice);

spriteComp = new
SpriteComponent(this);


this.Components.Add(spriteComp);


base.Initialize();

}


///
<summary>


/// LoadContent will be called once per game and is the place to load


/// all of your content.


///
</summary>


protected
override
void LoadContent()

{


// Create a new SpriteBatch, which can be used to draw textures.

spriteBatch = new
SpriteBatch(GraphicsDevice);


// TODO: use this.Content to load your game content here

effectMRT = Content.Load<Effect>(«MRT»);

effectLight = Content.Load<Effect>(«Light»);

targetDepth = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetDiffuse = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetID = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

targetNormals = new
RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, 1, SurfaceFormat.Color);

}


///
<summary>


/// UnloadContent will be called once per game and is the place to unload


/// all content.


///
</summary>


protected
override
void UnloadContent()

{


// TODO: Unload any non ContentManager content here

}


///
<summary>


/// Allows the game to run logic such as updating the world,


/// checking for collisions, gathering input, and playing audio.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Update(GameTime gameTime)

{


// Allows the game to exit


if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)


this.Exit();


// TODO: Add your update logic here


base.Update(gameTime);

}


///
<summary>


/// This is called when the game should draw itself.


///
</summary>


///
<param name=»gameTime»>Provides a snapshot of timing values.</param>


protected
override
void Draw(GameTime gameTime)

{

GraphicsDevice.RenderState.AlphaBlendEnable = false;

GraphicsDevice.RenderState.DepthBufferEnable = true;


Matrix world = Matrix.Identity;


Matrix view = Matrix.CreateLookAt(new
Vector3(-3, 0, 2), Vector3.Zero, Vector3.Up);


Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);

DrawMRT(ref world, ref view, ref projection);

DrawScene(ref world, ref view, ref projection);

spriteComp.Clear();

spriteComp.Add(targetDepth.GetTexture());

spriteComp.Add(targetNormals.GetTexture());

spriteComp.Add(targetID.GetTexture());

spriteComp.Add(targetDiffuse.GetTexture());


base.Draw(gameTime);

}


private
void DrawMRT(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, targetDepth);

GraphicsDevice.SetRenderTarget(1, targetID);

GraphicsDevice.SetRenderTarget(2, targetNormals);

GraphicsDevice.SetRenderTarget(3, targetDiffuse);

GraphicsDevice.Clear(Color.Black);

effectMRT.Parameters[«View»].SetValue(view);

effectMRT.Parameters[«Projection»].SetValue(projection);

effectMRT.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

effectMRT.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(0, 1, 0));

teapot.Draw(effectMRT);

world = Matrix.CreateTranslation(1.5f, 0, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(0, 0, 1));

cube.Draw(effectMRT);

world = Matrix.CreateTranslation(0, 0, 0);

effectMRT.Parameters[«World»].SetValue(world);

effectMRT.Parameters[«ID»].SetValue(new
Vector3(1, 0, 0));

cylinder.Draw(effectMRT);

}


private
void DrawScene(ref
Matrix world, ref
Matrix view, ref
Matrix projection)

{

GraphicsDevice.SetRenderTarget(0, null);

GraphicsDevice.SetRenderTarget(1, null);

GraphicsDevice.SetRenderTarget(2, null);

GraphicsDevice.SetRenderTarget(3, null);

GraphicsDevice.Clear(Color.Black);

effectLight.Parameters[«View»].SetValue(view);

effectLight.Parameters[«Projection»].SetValue(projection);

effectLight.Parameters[«Eye»].SetValue(new
Vector3(-3, 0, 2));

effectLight.Parameters[«LightPosition»].SetValue(new
Vector3(0, 0.5f, 1));

world = Matrix.CreateRotationY(MathHelper.ToRadians(60)) * Matrix.CreateTranslation(1.5f, 0.8f, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(1, 0, 0, 1));

teapot.Draw(effectLight);

world = Matrix.CreateTranslation(1.5f, 0, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 1, 0, 1));

cube.Draw(effectLight);

world = Matrix.CreateTranslation(0, 0, 0);

effectLight.Parameters[«World»].SetValue(world);

effectLight.Parameters[«DiffuseColor»].SetValue(new
Vector4(0, 0, 1, 1));

cylinder.Draw(effectLight);

}

}

}

Исходный код шейдера:

float4x4 World;

float4x4 View;

float4x4 Projection;

float3 Eye;

float3 ID;

float3 LightPosition = float3(0, 0.5, 1);

// TODO: add effect parameters here.

struct VertexShaderInput

{


float4 Position : POSITION0;


float3 Normal : NORMAL;


// TODO: add input channels such as texture


// coordinates and vertex colors here.

};

struct VertexShaderOutput

{


float4 Position : POSITION0;


float3 WorldPosition : TEXCOORD0;


float3 Normal : TEXCOORD1;


float4 Color0 : COLOR0;


float4 Color1 : COLOR1;


float4 Color2 : COLOR2;


// TODO: add vertex shader outputs such as colors and texture


// coordinates here. These values will automatically be interpolated


// over the triangle, and provided as input to your pixel shader.

};

struct PixelShaderOutput

{


float4 Color0 : COLOR0;


float4 Color1 : COLOR1;


float4 Color2 : COLOR2;


float4 Color3 : COLOR3;


// TODO: add vertex shader outputs such as colors and texture


// coordinates here. These values will automatically be interpolated


// over the triangle, and provided as input to your pixel shader.

};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

{

VertexShaderOutput output;


float4 worldPosition = mul(input.Position, World);


float4 viewPosition = mul(worldPosition, View);


float4 projPosition = mul(viewPosition, Projection);

output.Position = projPosition;


float3 worldNormal = normalize(mul(input.Normal, World));

output.WorldPosition = worldPosition;

output.Normal = worldNormal;

// depth

output.Color0.rgb = length(Eye — worldPosition) / 10.0;

output.Color0.a = 1;


// id

output.Color1.rgb = ID.rgb;

output.Color1.a = 1;


// normal

output.Color2.rgb = (worldNormal + 1) / 2.0;

output.Color2.a = 1;


return output;

}

PixelShaderOutput PixelShaderFunction(VertexShaderOutput input)

{

PixelShaderOutput output;

output.Color0 = input.Color0;

output.Color1 = input.Color1;

output.Color2 = input.Color2;


float3 worldPosition = input.WorldPosition;


float3 worldNormal = normalize(input.Normal);


float3 lightDirection = normalize(LightPosition — worldPosition);


float4 Diffuse = max(0, dot(worldNormal, lightDirection));

output.Color3 = Diffuse;


return output;

}

technique Technique1

{


pass Pass1

{


// TODO: set renderstates here.


VertexShader = compile
vs_3_0 VertexShaderFunction();


PixelShader = compile
ps_3_0 PixelShaderFunction();

}

}

Реклама
Запись опубликована в рубрике Компьютерная графика с метками , , , , , , . Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s