Создание стереоизображений (анаглифов) при помощи XNA Game Studio. Часть 2

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

Насколько я пока понимаю, создание цветного стереоизображение требует достаточно сложных фильтров, так что пока попробуем сделать анаглиф из черно-белого изображения.

Давайте сначала напишем шейдер, который будет получать черно-белое изображение для каждой картинки. Можно сделать это на основе среднего арифметического значения по всем цветовым каналам, я же лучше буду использовать яркость цвета. Такой шейдер уже много раз встречался в моем блоге, так что тут проблем никаких нет.

 

Texture leftTexture;
Texture rightTexture;

sampler LeftSampler = sampler_state
{
Texture = <leftTexture>;
};

sampler RightSampler = sampler_state
{
Texture = <rightTexture>;
};

float4 PSStereoImageLeft( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 left = tex2D(LeftSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float leftIntensity = dot(left, luminance);

          float4 leftColor = float4(1,1,1,1) * leftIntensity; 

          return leftColor;
}

float4 PSStereoImageRight( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 right = tex2D(RightSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float rightIntensity = dot(right, luminance);

          float4 rightColor = float4(1,1,1,1) * rightIntensity; 

          return rightColor;
}

technique StereoLeft { pass Pass1 { PixelShader = compile ps_2_0 PSStereoImageLeft(); } } technique StereoRight { pass Pass1 { PixelShader = compile ps_2_0 PSStereoImageRight(); } }


technique StereoRight
{
pass Pass1
{
PixelShader = compile ps_2_0 PSStereoImageRight();
}
}

Теперь загрузим шейдер, установим все нужные параметры и нарисуем две картинки.

 
Effect stereoEffect;

        /// <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

            primitives.Add(new GameObject(new TeapotPrimitive(GraphicsDevice, 1, 30), new Vector3(0,-0.4f,-0.3f), Color.Blue));
            primitives.Add(new GameObject(new TeapotPrimitive(GraphicsDevice, 1, 30), new Vector3(0.7f, +0.8f, -1.7f), Color.Yellow));
            primitives.Add(new GameObject(new CubePrimitive(GraphicsDevice, 1), new Vector3(0, +0.4f, -2f), Color.Tomato));
            primitives.Add(new GameObject(new CylinderPrimitive(GraphicsDevice, 1, 1, 35), new Vector3(-0.6f, +0.5f, -1.3f), Color.Teal));

            stereoEffect = Content.Load<Effect>("stereo");
        }

        /// <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)
        {

            // TODO: Add your drawing code here
            Matrix world = Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds);

            Vector3 cameraPosition = new Vector3(0,0,2);

            Matrix view = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);

            GraphicsDevice.SetRenderTarget(targetLeft);
            GraphicsDevice.Clear(Color.White);

            view = Matrix.CreateLookAt(cameraPosition + Vector3.Left*0.05f, Vector3.Zero, Vector3.Up);

            foreach (var item in primitives)
            {
                item.Draw(world, view, projection);
            }

            GraphicsDevice.SetRenderTarget(targetRight);
            GraphicsDevice.Clear(Color.White);

            view = Matrix.CreateLookAt(cameraPosition + Vector3.Right * 0.05f, Vector3.Zero, Vector3.Up);

            foreach (var item in primitives)
            {
                item.Draw(world, view, projection);
            }

            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);

            stereoEffect.Parameters["leftTexture"].SetValue(targetLeft);             stereoEffect.Parameters["rightTexture"].SetValue(targetRight);             stereoEffect.CurrentTechnique = stereoEffect.Techniques["StereoLeft"];                              spriteBatch.Begin(0, null, null, null, null, stereoEffect);             spriteBatch.Draw(targetLeft, new Rectangle(0, 0, 640, 480), Color.White);             spriteBatch.End();

            RestoreGraphics();

            base.Draw(gameTime);
        }

Правое изображение получается аналогичным образом.

Сейчас у нас установлен совсем небольшой сдвиг для камеры, так что разница в изображениях практически не заметна. А разница в углах поворота чайников связана лишь с тем, что сейчас все объекты крутятся постоянно.

Далее нам нужно наложить красный и бирюзовый фильтры на наши изображения. Для этого нам нужно будет просто помножить яркость каждого пикселя на «цвет фильтра».

 
float4 PSStereoImageLeft( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 left = tex2D(LeftSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float leftIntensity = dot(left, luminance);

          float4 red = float4(1,0,0,1);             float4 leftColor = red * leftIntensity;           
          return leftColor;
}

float4 PSStereoImageRight( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 right = tex2D(RightSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float rightIntensity = dot(right, luminance);

          float4 cyan = float4(0,1,1,1);             float4 rightColor = cyan * rightIntensity; 

          return rightColor;
}

technique StereoLeft
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImageLeft();
    }
}

technique StereoRight
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImageRight();
    }
}

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

 
float4 PSStereoImage( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 left = tex2D(LeftSampler, TexCoords);
          float4 right = tex2D(RightSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float leftIntensity = dot(left, luminance);
          float rightIntensity = dot(right, luminance);

          float4 cyan = float4(0,1,1,1);
          float4 red = float4(1,0,0,1);

          float4 leftColor = red * leftIntensity;
          float4 rightColor = cyan * rightIntensity; 

          return (leftColor + rightColor) / 2;
}

technique Stereo
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImage();
    }
}

Также нужно поменять технику в Draw()

 

            stereoEffect.Parameters["leftTexture"].SetValue(targetLeft);
            stereoEffect.Parameters["rightTexture"].SetValue(targetRight);
            stereoEffect.CurrentTechnique = stereoEffect.Techniques["Stereo"];

            spriteBatch.Begin(0, null, null, null, null, stereoEffect);
            spriteBatch.Draw(targetLeft, new Rectangle(0, 0, 640, 480), Color.White);
            spriteBatch.End();

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

Как я уже писал, я не совсем уверен, что именно такой алгоритм нужно использовать и в дальнейшем я еще позанимаюсь этой проблемой и, пожалуй, напишу еще что-нибудь интересное на эту тему.

Исходный код:

Stereo.fx

Texture leftTexture;
Texture rightTexture;

sampler LeftSampler = sampler_state
{
Texture = <leftTexture>;
};

sampler RightSampler = sampler_state
{
Texture = <rightTexture>;
};

float4 PSStereoImage( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 left = tex2D(LeftSampler, TexCoords);
          float4 right = tex2D(RightSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float leftIntensity = dot(left, luminance);
          float rightIntensity = dot(right, luminance);

          float4 cyan = float4(0,1,1,1);
          float4 red = float4(1,0,0,1);

          float4 leftColor = red * leftIntensity;
          float4 rightColor = cyan * rightIntensity; 

          return (leftColor + rightColor) / 2;
}

float4 PSStereoImageLeft( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 left = tex2D(LeftSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float leftIntensity = dot(left, luminance);

          float4 red = float4(1,0,0,1);

          float4 leftColor = red * leftIntensity; 

          return leftColor;
}

float4 PSStereoImageRight( float2 TexCoords : TEXCOORD0 ) : COLOR0 
{                  
          float4 right = tex2D(RightSampler, TexCoords);

          float3 luminance = float3(0.299, 0.587, 0.114);
          float rightIntensity = dot(right, luminance);

          float4 cyan = float4(0,1,1,1);

          float4 rightColor = cyan * rightIntensity; 

          return rightColor;
}

technique Stereo
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImage();
    }
}

technique StereoLeft
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImageLeft();
    }
}

technique StereoRight
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PSStereoImageRight();
    }
}

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 Primitives3D;

namespace StereoGame
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        List<GameObject> primitives = new List<GameObject>();

        RenderTarget2D targetLeft;
        RenderTarget2D targetRight;

        Effect stereoEffect;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 480;
        }

        /// <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
            targetLeft = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth16);
            targetRight = new RenderTarget2D(GraphicsDevice, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth16);

            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

            primitives.Add(new GameObject(new TeapotPrimitive(GraphicsDevice, 1, 30), new Vector3(0,-0.4f,-0.3f), Color.Blue));
            primitives.Add(new GameObject(new TeapotPrimitive(GraphicsDevice, 1, 30), new Vector3(0.7f, +0.8f, -1.7f), Color.Yellow));
            primitives.Add(new GameObject(new CubePrimitive(GraphicsDevice, 1), new Vector3(0, +0.4f, -2f), Color.Tomato));
            primitives.Add(new GameObject(new CylinderPrimitive(GraphicsDevice, 1, 1, 35), new Vector3(-0.6f, +0.5f, -1.3f), Color.Teal));

            stereoEffect = Content.Load<Effect>("stereo");
        }

        /// <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)
        {
            // TODO: Add your drawing code here
            Matrix world = Matrix.CreateRotationY((float)gameTime.TotalGameTime.TotalSeconds);

            Vector3 cameraPosition = new Vector3(0,0,2);

            Matrix view = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Up);
            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10f);

            GraphicsDevice.SetRenderTarget(targetLeft);
            GraphicsDevice.Clear(Color.White);

            view = Matrix.CreateLookAt(cameraPosition + Vector3.Left*0.05f, Vector3.Zero, Vector3.Up);

            foreach (var item in primitives)
            {
                item.Draw(world, view, projection);
            }

            GraphicsDevice.SetRenderTarget(targetRight);
            GraphicsDevice.Clear(Color.White);

            view = Matrix.CreateLookAt(cameraPosition + Vector3.Right * 0.05f, Vector3.Zero, Vector3.Up);

            foreach (var item in primitives)
            {
                item.Draw(world, view, projection);
            }

            GraphicsDevice.SetRenderTarget(null);
            GraphicsDevice.Clear(Color.Black);

            stereoEffect.Parameters["leftTexture"].SetValue(targetLeft);
            stereoEffect.Parameters["rightTexture"].SetValue(targetRight);
            stereoEffect.CurrentTechnique = stereoEffect.Techniques["Stereo"];

            spriteBatch.Begin(0, null, null, null, null, stereoEffect);
            spriteBatch.Draw(targetLeft, new Rectangle(0, 0, 640, 480), Color.White);
            spriteBatch.End();

            RestoreGraphics();

            base.Draw(gameTime);
        }

        private void RestoreGraphics()
        {
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
        }
    }
}

GameObject.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Primitives3D;
using Microsoft.Xna.Framework;

namespace StereoGame
{
    class GameObject
    {
        GeometricPrimitive primitive;
        Vector3 position;
        Color color;

        public GameObject(GeometricPrimitive primitive, Vector3 position, Color color)
        {
            this.primitive = primitive;
            this.position = position;
            this.color = color;
        }

        public void Draw(Matrix world, Matrix view, Matrix projection)
        {
            world *= Matrix.CreateTranslation(position);
            primitive.Draw(world, view, projection, color);
        }

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Google+ photo

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

Connecting to %s