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


    昨天,各大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/





  • 相关阅读:
    mssql分页原理及效率分析
    [ActiveX]使用VS2010创建MFC ActiveX工程项目
    Study notes for Discrete Probability Distribution
    oracle存储过程异常捕获
    (Python学习9)Python虚拟机中的一般表达式
    用Arduino做一个可视化网络威胁级别指示器!
    iOS上线项目源码分享
    android实习程序6——拨号通话
    评价等级使用的五星选择,包含半星的选择
    AJAX实现无刷新验证用户名
  • 原文地址:https://www.cnblogs.com/kenkao/p/2164626.html
Copyright © 2020-2023  润新知