代码改变世界

SilverXna初体验:SpriteBatch和基本的内容管道

2011-09-03 19:39  独孤残云  阅读(2817)  评论(14编辑  收藏  举报

昨天,各大IT网站纷纷刊登了Silverlight5 RC发布的消息。于是,第一时间到官网载了安装包,更新了本地的Silverlight5 Beta版本~

Silverlight5 RC的发布无疑是具有里程碑意义的,Xna3D API也在原有Beta版本基础上作了进一步扩展。

新增了BasicEffect、RenderTarget等Shader常用功能,上一节提到的Xna 3D数学库也被划入到Sliverlight原生资源当中,无需再从外部引用。

不过,目前SilverXna中的一系列绘制函数依然停留在顶点级,缺乏诸如SpriteBatch、ContentManager、Model等必要高级机制的支持。

曾一度感觉无奈,直到看过trcj兄编写的ElGameEngine之后才恍然大悟:所谓3D,只要显卡能画三角形(硬件加速),支持矩阵运算(顶点着色),支持纹理采样(像素着色),再给几个API其实就够用了。

本节,我们来自行实现SpriteBatch的相关功能,以供SilverXna中简单的2D图形绘制之用~

熟知3DGraphy机制的人应该都知道,3D领域中的2D,其实只是3D图元绘制的一种特例:两个三角形对接构成一个矩形表面,而后由目标纹理采样得到各点颜色。至于顶点运算的部分,世界矩阵及摄影矩阵固定使用单位矩阵,投影矩阵采用正交投影替代原有的透视投影即可。

大家还记得Direct3D轮回《为D3D量身定做SpriteBatch》一文吗?它其实就是对2D图形原理的一个很好的说明~

下面,我们就把Direct3D中的代码搬到Silverlight里,得到SilverXna专用的SpriteBatch对象~

由于Xna已经彻底舍弃了固定功能流水线(完全硬件加速),而SpriteBatch的绘制并不需要用到BasicEffect中的诸多功能,因此我们不妨自己来编写Shader。

Silverlight5 RC初步支持了效果框架,BasicEffect使用过程中对于EffectTechniquehe和Pass的解析均是标准而到位的。

不过,由Effect的定义来推断,我们似乎还不能随心所欲的引入外部的.fx到Silverlight。以下依然沿用Beta版的做法~

编写顶点着色器:

/*-------------------------------------

代码清单:SpriteBatch.vs.hlsl
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

// 世界·摄影·投影变换矩阵
float4x4 WorldViewProj : register(c0);

// 顶点着色器输入结构
struct VertexData
{
  float3 Position : POSITION;  
// 位置
  float4 Color : COLOR;        // 颜色
  float2 UV : TEXCOORD;        // 纹理坐标
};

// 顶点着色器输出结构
struct VertexShaderOutput
{
  float4 Position : POSITION;
  float4 Color : COLOR0;
  float2 UV : TEXCOORD0;
};

VertexShaderOutput main(VertexData vertex)
{
  VertexShaderOutput output;
  output.Position 
= mul(float4(vertex.Position,1), WorldViewProj); // 顶点位置变换
  output.Color = vertex.Color; // 传递颜色
  output.UV = vertex.UV;       // 传递纹理坐标
  return output;
}

编写像素着色器:

/*-------------------------------------

代码清单:SpriteBatch.ps.hlsl
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

// 目标纹理及采样器
texture cubeTexture : register(t0);
sampler cubeSampler 
= sampler_state
{
    texture 
= <cubeTexture>;    
};

// 像素着色器输入结构
struct VertexShaderOutput
{
  float4 Color : COLOR0;
  float2 UV : TEXCOORD0;
};

float4 main(VertexShaderOutput vertex) : COLOR
{
  
return vertex.Color *= tex2D(cubeSampler, vertex.UV).rgba; // 返回颜色
}

接下来,我们要使用DirectX命令行工具对其进行编译,得到顶点着色器及像素着色器可用的二进制文件~

微软Silverlight5官方实例中为我们封装了两个批处理文件,可用于x64和x86机型顶点着色器及像素着色器的编译生成~

>> 点击下载:

CompileShaders.bat.rar

执行相应的批处理文件:

则我们将得到3个新文件:

SpriteBatch.vs(顶点着色器)

SpriteBatch.ps(像素着色器)

hlslcomplog.txt(编译日志)

我们将得到的SpriteBatch.vs和SpriteBatch.ps引入工程,而后将其属性设置为Resource即可~

完成Shader之后就可以开始着手编写SpriteBatch主体代码了~

首先是顶点结构定义:

    public struct VertexPositionColorTexture
    {
        
public Vector3 _Position;  // 位置
        
public Color _Color;       // 颜色
        
public Vector2 _UV;        // 纹理坐标
     // 构造函数
        
public VertexPositionColorTexture(Vector3 position, Color color, Vector2 uv)
        {
            _Position 
= position;
            _Color 
= color;
            _UV 
= uv;
        }
     // 顶点声明
        
public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration(
            
new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
            
new VertexElement(12, VertexElementFormat.Color, VertexElementUsage.Color, 0),
            
new VertexElement(16, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0)
            );
    }

接下来是SpriteBatch的编写:

SpriteBatch.cs
/*-------------------------------------

代码清单:SpriteBatch.cs
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

using System;
using System.Net;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media.Animation;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;

namespace Microsoft.Xna.Framework.Graphics
{
    
public class SpriteBatch
    {
        
struct SpriteNode
        {
            
public Rectangle _DesRect;        // 目标区域
            public Rectangle _SurRect;        // 纹理区域
            public float _layerDepth;         // 深度(Z坐标)
            public Color _Color;              // 色相

            
public SpriteNode(Rectangle DesRect, Rectangle SurRect, float layerDepth, Color Color)
            {
                _DesRect 
= DesRect;
                _SurRect 
= SurRect;
                _layerDepth 
= layerDepth;
                _Color 
= Color;
            }
        }

        Texture2D _ActiveTexture;                       
// 活动纹理
        VertexShader _VertexShader;                     // 顶点着色器
        PixelShader _PixelShader;                       // 像素着色器

        Matrix _ViewMatrix;                             
// 摄影矩阵
        Matrix _ProjMatrix;                             // 投影矩阵

        List
<SpriteNode> _SpriteNodeList;               // 精灵节点列表

        SilverGame _SilverGame;                         
// SilverGame实体
        public SilverGame SilverGame
        { 
get { return _SilverGame; } }

        
/// <summary>
        
/// 构造方法
        
/// </summary>
        
/// <param name="game">SilverGame实体对象</param>
        public SpriteBatch(SilverGame game)
        {
            _SilverGame 
= game;
            _SpriteNodeList 
= new List<SpriteNode>();
            
// 加载SpriteBatch顶点着色器及像素着色器
            Stream shaderStream = Application.GetResourceStream(new Uri(@"SilverXna.Game.Library;component/SpriteBatch/SpriteBatch.vs", UriKind.Relative)).Stream;
            _VertexShader 
= VertexShader.FromStream(_SilverGame.GraphicsDevice, shaderStream);
            shaderStream 
= Application.GetResourceStream(new Uri(@"SilverXna.Game.Library;component/SpriteBatch/SpriteBatch.ps", UriKind.Relative)).Stream;
            _PixelShader 
= PixelShader.FromStream(_SilverGame.GraphicsDevice, shaderStream);
        }

        
/// <summary>
        
/// 开始绘制
        
/// </summary>
        
/// <param name="SpriteBlendMode">Blend模式</param>
        public void Begin(BlendState SpriteBlendMode)
        {
            Begin(SpriteBlendMode, _PixelShader);
        }

        
/// <summary>
        
/// 开始绘制
        
/// </summary>
        
/// <param name="SpriteBlendMode">Blend模式</param>
        
/// <param name="pixelShader">像素着色器</param>
        public void Begin(BlendState SpriteBlendMode, PixelShader pixelShader)
        {
            
// 得到摄影坐标
            _ViewMatrix = Matrix.Identity;
            
// 得到投影坐标
            _ProjMatrix = Matrix.CreateOrthographicOffCenter(0, _SilverGame.ActualSize.X, _SilverGame.ActualSize.Y, 001);
            Matrix viewprojMatrix 
= _ViewMatrix * _ProjMatrix;
            
// 设置Blend模式
            _SilverGame.GraphicsDevice.BlendState = SpriteBlendMode;
            
// 设置顶点着色器
            _SilverGame.GraphicsDevice.SetVertexShader(_VertexShader);
            
// 传入世界·摄影·投影矩阵参数(世界矩阵默认为单位矩阵)
            _SilverGame.GraphicsDevice.SetVertexShaderConstantFloat4(0ref viewprojMatrix);
            
// 设置像素着色器
            _SilverGame.GraphicsDevice.SetPixelShader(pixelShader);
        }

        
/// <summary>
        
/// 结束绘制
        
/// </summary>
        public void End()
        {
            
// 结束之前Flush一次全部精灵节点
            Flush();
        }

        
/// <summary>
        
/// 单帧投递
        
/// </summary>
        
/// <param name="DesRect">目标区域</param>
        
/// <param name="SurRect">纹理区域</param>
        
/// <param name="layerDepth">深度坐标</param>
        
/// <param name="Color">颜色值</param>
        private void PostFrame(Rectangle DesRect, Rectangle SurRect, float layerDepth, Color Color)
        {
            
// 新增精灵节点
            _SpriteNodeList.Add(new SpriteNode(DesRect, SurRect, layerDepth, Color));
        }

        
/// <summary>
        
/// 合并当前全部精灵节点的顶点缓冲及索引缓冲,一次性完成绘制
        
/// </summary>
        private void Flush()
        {
            
// 异常判别
            if (_SpriteNodeList == null || _ActiveTexture == null || _SpriteNodeList.Count == 0)
            {
                
return;
            }
            
// 生成顶点缓冲数组
            var vb = new VertexPositionColorTexture[_SpriteNodeList.Count * 4];
            
// 生成索引缓冲数组
            var ib = new UInt16[_SpriteNodeList.Count * 6];
            
int i = 0;
            
foreach (SpriteNode node in _SpriteNodeList)
            {
                
// 将纹理区域折合成uv坐标
                float Txcrd_LU_u = node._SurRect.Left / _ActiveTexture.Width;
                
float Txcrd_LU_v = node._SurRect.Top / _ActiveTexture.Height;

                
float Txcrd_RU_u = node._SurRect.Right / _ActiveTexture.Width;
                
float Txcrd_RU_v = node._SurRect.Top / _ActiveTexture.Height;

                
float Txcrd_RD_u = node._SurRect.Right / _ActiveTexture.Width;
                
float Txcrd_RD_v = node._SurRect.Bottom / _ActiveTexture.Height;

                
float Txcrd_LD_u = node._SurRect.Left / _ActiveTexture.Width;
                
float Txcrd_LD_v = node._SurRect.Bottom / _ActiveTexture.Height;

                
// 填充顶点缓冲区数据
                vb[i * 4= new VertexPositionColorTexture(new Vector3(node._DesRect.Left, node._DesRect.Top, node._layerDepth), node._Color, new Vector2(Txcrd_LU_u, Txcrd_LU_v));
                vb[i 
* 4 + 1= new VertexPositionColorTexture(new Vector3(node._DesRect.Right, node._DesRect.Top, node._layerDepth), node._Color, new Vector2(Txcrd_RU_u, Txcrd_RU_v));
                vb[i 
* 4 + 2= new VertexPositionColorTexture(new Vector3(node._DesRect.Right, node._DesRect.Bottom, node._layerDepth), node._Color, new Vector2(Txcrd_RD_u, Txcrd_RD_v));
                vb[i 
* 4 + 3= new VertexPositionColorTexture(new Vector3(node._DesRect.Left, node._DesRect.Bottom, node._layerDepth), node._Color, new Vector2(Txcrd_LD_u, Txcrd_LD_v));

                
// 填充索引缓冲区数据
                ib[i * 6= (UInt16)(i * 4);
                ib[i 
* 6 + 1= (UInt16)(i * 4 + 1);
                ib[i 
* 6 + 2= (UInt16)(i * 4 + 2);
                ib[i 
* 6 + 3= (UInt16)(i * 4);
                ib[i 
* 6 + 4= (UInt16)(i * 4 + 2);
                ib[i 
* 6 + 5= (UInt16)(i * 4 + 3);

                i
++;
            }

            
// 顶点缓冲、索引缓冲赋值
            VertexBuffer _VertexBuffer = new VertexBuffer(_SilverGame.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, vb.Length, BufferUsage.WriteOnly);
            _VertexBuffer.SetData(
0, vb, 0, vb.Length, 0);
            IndexBuffer _IndexBuffer 
= new IndexBuffer(_SilverGame.GraphicsDevice, IndexElementSize.SixteenBits, ib.Length, BufferUsage.WriteOnly);
            _IndexBuffer.SetData(
0, ib, 0, ib.Length);

            
// 设置活动纹理
            _SilverGame.GraphicsDevice.Textures[0= _ActiveTexture;
            
// 设置顶点缓冲
            _SilverGame.GraphicsDevice.SetVertexBuffer(_VertexBuffer);
            
// 设置索引缓冲
            _SilverGame.GraphicsDevice.Indices = _IndexBuffer;
            
// 三角形绘制
            _SilverGame.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 00, vb.Length, 0, ib.Length / 3);
            
// 精灵节点清空
            _SpriteNodeList.Clear();
        }

        
public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle sourceRectangle, float layerDepth, Color color)
        {
            
// 异常判断
            if (texture == null)
                
return;
            
// _ActiveTexture第一次赋值
            if (_ActiveTexture == null)
                _ActiveTexture 
= texture;
            
// 如果当前纹理与活动纹理不同
            if (_ActiveTexture != texture)
            {
                
// 则Flush一次先前的全部节点
                Flush();
                
// 更新活动纹理
                _ActiveTexture = texture;
            }
            
// 投递本帧
            PostFrame(destinationRectangle, sourceRectangle, layerDepth, color);
        }

        
// 一系列重载的Draw函数

        
public void Draw(Texture2D texture, Rectangle destinationRectangle, Rectangle sourceRectangle, Color color)
        {
            Draw(texture, destinationRectangle, sourceRectangle, 
0, color);
        }

        
public void Draw(Texture2D texture, Rectangle destinationRectangle, Color color)
        {
            Draw(texture, destinationRectangle, 
new Rectangle(00, texture.Width, texture.Height), color);
        }

        
public void Draw(Texture2D texture, Vector2 position, Color color)
        {
            Draw(texture, 
new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height), new Rectangle(00, texture.Width, texture.Height), color);
        }
    }
}

 

因为是纯粹的3D硬件加速,所以跟传统Silverlight应用层面的Image相比,其优势是不言而喻的。这一点 园友 黯淡的橘子 已在其博文中给出了相关证明,大家可以参看他的文章~

我们简单的封装一下Silverlight资源加载的相关方法,构成一个内容管道的雏形,以便于后续功能扩展之用~

ContentManager.cs
/*-------------------------------------

代码清单:ContentManager.cs
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;

namespace Microsoft.Xna.Framework.Content
{
    
public class ContentManager
    {
        
// SilverGame主体对象
        SilverGame _SilverGame;
        
public SilverGame SilverGame
        {
            
get 
            {
                
return _SilverGame;
            }
        }

        
/// <summary>
        
/// 构造方法
        
/// </summary>
        
/// <param name="game"></param>
        public ContentManager(SilverGame game)
        {
            _SilverGame 
= game;
        }

        
/// <summary>
        
/// 打开资源流
        
/// </summary>
        
/// <param name="uri">相对Uri</param>
        
/// <returns>资源流</returns>
        public Stream OpenResourceStream(Uri uri)
        {
            
return Application.GetResourceStream(uri).Stream;
        }

        
/// <summary>
        
/// 打开资源流
        
/// </summary>
        
/// <param name="ProjectName">工程名</param>
        
/// <param name="uri">相对Uri(字符串形式)</param>
        
/// <returns>资源流</returns>
        public Stream OpenResourceStream(string ProjectName, string uri)
        {
            
return Application.GetResourceStream(new Uri(ProjectName + @";component/" + uri, UriKind.Relative)).Stream;
        }

        
/// <summary>
        
/// 加载Texture2D
        
/// </summary>
        
/// <param name="stream">资源流</param>
        
/// <returns>所得Texture2D</returns>
        public Texture2D LoadTexture2D(Stream stream)
        {
            Texture2D texture;
            var image 
= new BitmapImage();
            image.SetSource(stream);
            texture 
= new Texture2D(_SilverGame.GraphicsDevice, image.PixelWidth, image.PixelHeight, false, SurfaceFormat.Color);
            image.CopyTo(texture);
            
return texture;
        }

        
/// <summary>
        
/// 加载顶点着色器
        
/// </summary>
        
/// <param name="stream">资源流</param>
        
/// <returns>所得顶点着色器</returns>
        public VertexShader LoadVertexShader(Stream stream)
        {
            VertexShader vertexShader;
            vertexShader 
= VertexShader.FromStream(_SilverGame.GraphicsDevice, stream);
            
return vertexShader;
        }

        
/// <summary>
        
/// 加载像素着色器
        
/// </summary>
        
/// <param name="stream">资源流</param>
        
/// <returns>所得像素着色器</returns>
        public PixelShader LoadPixelShader(Stream stream)
        {
            PixelShader pixelShader;
            pixelShader 
= PixelShader.FromStream(_SilverGame.GraphicsDevice, stream);
            
return pixelShader;
        }
    }
}

这里的ContentManager只是一个雏形,肯定是没办法跟Xna原生态的ContentManager相提并论的 ^ ^

我们在SilverGame基类中声明一个ContentManager对象,以便令全部的子类持有这个对象:

SilverGame.cs
 
        ContentManager _Content;
        
public ContentManager Content
        { 
get { return _Content; } }
        
/// <summary>
        
/// 构造方法
        
/// </summary>
        
/// <param name="GameSurface">所关联的渲染表面</param>
        public SilverGame(DrawingSurface GameSurface)
        {
            _GameSurface 
= GameSurface;
            _Content 
= new ContentManager(this);
            _ActualSize 
= new Vector2((float)_GameSurface.ActualWidth, (float)_GameSurface.ActualHeight);
            
// 自动为渲染表面关联必要的事件
            _GameSurface.Loaded += new RoutedEventHandler(_GameSurface_Loaded);
            _GameSurface.Unloaded 
+= new RoutedEventHandler(_GameSurface_Unloaded);
            _GameSurface.Draw 
+= new EventHandler<DrawEventArgs>(_GameSurface_Draw);
            _GameSurface.SizeChanged 
+= new SizeChangedEventHandler(_GameSurface_SizeChanged);
            
// 虚函数调用——初始化
            this.Initialize();
        }

然后是主体代码:

Game.cs
/*-------------------------------------

代码清单:Game.cs
来自:
http://www.cnblogs.com/kenkao

-------------------------------------
*/

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.IO;

namespace SilverXna
{
    
public class Game : SilverGame
    {
        SpriteBatch _SpriteBatch;
        Texture2D texture;

        
public Game(DrawingSurface GameSurface)
            : 
base(GameSurface)
        { }

        
public override void Initialize()
        {
            
base.Initialize();
        }

        
public override void LoadContent()
        {
            _SpriteBatch 
= new SpriteBatch(this);

            Stream imageStream 
= Content.OpenResourceStream("SilverXna","Content/SLXNA.png");
            texture 
= Content.LoadTexture2D(imageStream);
            
base.LoadContent();
        }

        
public override void UnloadContent()
        {
            
base.UnloadContent();
        }

        
public override void Update(TimeSpan DeltaTime, TimeSpan TotalTime)
        {
            
base.Update(DeltaTime, TotalTime);
        }

        
public override void Draw(TimeSpan DeltaTime, TimeSpan TotalTime)
        {
            GraphicsDevice.Clear(ClearOptions.Target 
| ClearOptions.DepthBuffer, new Color(100149237255), 1.0f0);

            _SpriteBatch.Begin(BlendState.AlphaBlend);
            _SpriteBatch.Draw(texture, new Vector2(100, 100), 
new Color(255255255255));
            _SpriteBatch.End();

            
base.Draw(DeltaTime, TotalTime);
        }
    }
}

SpriteBatch的用法跟原生态的Xna环境下的用法是一模一样的,相关绘制方法可以在现有基础上随意重载扩展,并且兼容Shazzam全部的ps特效,你只需借助Content对象Load得到相应的PixelShader,而后传入SpriteBatch重载的Begin函数中即可 ^ ^

最后是效果图:

虽然目前Silverlight5 RC版本下的Xna框架还不够尽善尽美,但我们看到的是光明的前景,未来值得期待 ^ ^

以上,谢谢~

 

=============================================

>> Silverlight5 RC资源下载及工具包语言版本冲突问题的解决方法:

http://space.cnblogs.com/group/topic/49727/