четверг, 30 июня 2011 г.

XNA Game Studio и движок Xen. Третий урок. Custom Shaders


Вернемся к нашим урокам.  Третий урок посвящен создания шейдера. В прошлой статье уже описывался метод генерации C# классов для шейдерных эффектов, который применяется в Xen, так что сейчас мы это опустим и перейдем к самому уроку.
В папке с уроком находятся несколько файлов, откроем сначала shader.fx (комментарии убраны)

float4x4 worldViewProj : WORLDVIEWPROJECTION; 
float4 colour : GLOBAL;
//finally, a constant without a semantic:
float scale = 1;

//--------------------------------------------------------------//
// vertex shader, scales the mesh by the scale attribute
//--------------------------------------------------------------//
void Tutorial03VS( 
                                                   float4 position                          : POSITION,
                                         out float4 positionOut                   : POSITION)
{
          position.xyz *= scale;
         
          positionOut = mul(position,worldViewProj);
}


//--------------------------------------------------------------//
// pixel shader, returns the global colour
//--------------------------------------------------------------//
float4 Tutorial03PS() : COLOR
{
          return colour;
}



//--------------------------------------------------------------//
// Technique that uses the shaders (a class will be generated)
//--------------------------------------------------------------//
technique Tutorial03Technique
{
   pass
   {
                    VertexShader = compile vs_2_0 Tutorial03VS();
                    PixelShader = compile ps_2_0 Tutorial03PS();
   }
}

 

Мы видим, что этот шейдер просто масштабирует модель, перед тем как пересчитать ее координаты. Пиксельный шейдер просто возвращает константу, в качестве цвета модели.
Взглянем теперь на сгенерированный класс, который хранится в файле shader.fx.cs (я удалил часть кода, которая содержала скомпилированный байт-код эффекта, чтобы статья была более читаемой).
// XenFX

// Assembly = Xen.Graphics.ShaderSystem.CustomTool, Version=7.0.1.1, Culture=neutral, PublicKeyToken=e706afd07878dfca

// SourceFile = shader.fx

// Namespace = Tutorials.Tutorial_03

 

namespace Tutorials.Tutorial_03.Shader

{

          

          /// <summary><para>Technique 'Tutorial03Technique' generated from file 'shader.fx'</para><para>Vertex Shader: approximately 6 instruction slots used, 5 registers</para><para>Pixel Shader: approximately 1 instruction slot used, 1 register</para></summary>

          [System.Diagnostics.DebuggerStepThroughAttribute()]

          [System.CodeDom.Compiler.GeneratedCodeAttribute("Xen.Graphics.ShaderSystem.CustomTool.dll""5040fe79-bab4-4d2f-9335-463d3794d366")]

          public sealed class Tutorial03Technique : Xen.Graphics.ShaderSystem.BaseShader

          {

                    /// <summary>Construct an instance of the 'Tutorial03Technique' shader</summary>

                    public Tutorial03Technique()

                    {

                               this.vreg[4] = new Microsoft.Xna.Framework.Vector4(1F, 0F, 0F, 0F);

                               this.sc0 = -1;

                               this.sc1 = -1;

                               this.sc2 = -1;

                               this.gc0 = -1;

                    }

                    /// <summary>Setup shader static values</summary><param name="state"/>

                    private void gdInit(Xen.Graphics.ShaderSystem.ShaderSystemBase state)

                    {

                               // set the graphics ID

                               Tutorial03Technique.gd = state.DeviceUniqueIndex;

                               this.GraphicsID = state.DeviceUniqueIndex;

                               Tutorial03Technique.cid0 = state.GetNameUniqueID("scale");

                               Tutorial03Technique.gid0 = state.GetGlobalUniqueID<Microsoft.Xna.Framework.Vector4>("colour");

                    }

                    /// <summary>Bind the shader, 'ic' indicates the shader instance has changed and 'ec' indicates the extension has changed.</summary><param name="state"/><param name="ic"/><param name="ec"/><param name="ext"/>

                    protected override void BeginImpl(Xen.Graphics.ShaderSystem.ShaderSystemBase state, bool ic, bool ec, Xen.Graphics.ShaderSystem.ShaderExtension ext)

                    {

                               // if the device changed, call Warm()

                               if ((state.DeviceUniqueIndex != Tutorial03Technique.gd))

                               {

                                         this.WarmShader(state);

                                         ic = true;

                               }

                               // Force updating if the instance has changed

                               this.vreg_change = (this.vreg_change | ic);

                               this.preg_change = (this.preg_change | ic);

                               this.vbreg_change = (this.vbreg_change | ic);

                               this.vireg_change = (this.vireg_change | ic);

                               // Set the value for attribute 'worldViewProj'

                              this.vreg_change = (this.vreg_change | state.SetWorldViewProjectionMatrix(ref this.vreg[0], ref this.vreg[1], ref this.vreg[2], ref this.vreg[3], ref this.sc0));

                               // Set the value for global 'colour'

                              this.preg_change = (this.preg_change | state.SetGlobalVector4(ref this.preg[0], Tutorial03Technique.gid0, ref this.gc0));

                               if ((this.vreg_change == true))

                               {

                                         Tutorial03Technique.fx.vs_c.SetValue(this.vreg);

                                         this.vreg_change = false;

                                         ic = true;

                               }

                               if ((this.preg_change == true))

                               {

                                         Tutorial03Technique.fx.ps_c.SetValue(this.preg);

                                         this.preg_change = false;

                                         ic = true;

                               }

                               if ((ext == Xen.Graphics.ShaderSystem.ShaderExtension.Blending))

                               {

                                         ic = (ic | state.SetBlendMatricesDirect(Tutorial03Technique.fx.vsb_c, ref this.sc1));

                               }

                               if ((ext == Xen.Graphics.ShaderSystem.ShaderExtension.Instancing))

                               {

                                         this.vireg_change = (this.vireg_change | state.SetViewProjectionMatrix(ref this.vireg[0], ref this.vireg[1], ref this.vireg[2], ref this.vireg[3], ref this.sc2));

                                         if ((this.vireg_change == true))

                                         {

                                                   Tutorial03Technique.fx.vsi_c.SetValue(this.vireg);

                                                   this.vireg_change = false;

                                                   ic = true;

                                         }

                               }

                               // Finally, bind the effect

                               if ((ic | ec))

                               {

                                         state.SetEffect(thisref Tutorial03Technique.fx, ext);

                               }

                    }

                    /// <summary>Warm (Preload) the shader</summary><param name="state"/>

                    protected override void WarmShader(Xen.Graphics.ShaderSystem.ShaderSystemBase state)

                    {

                               // Shader is already warmed

                               if ((Tutorial03Technique.gd == state.DeviceUniqueIndex))

                               {

                                         return;

                               }

                               // Setup the shader

                               if ((Tutorial03Technique.gd != state.DeviceUniqueIndex))

                               {

                                        this.gdInit(state);

                               }

                               Tutorial03Technique.fx.Dispose();

                               // Create the effect instance

                               state.CreateEffect(out Tutorial03Technique.fx, Tutorial03Technique.fxb, 7, 1);

                    }

                    /// <summary>True if a shader constant has changed since the last Bind()</summary>

                    protected override bool Changed()

                    {

                               return (this.vreg_change | this.preg_change);

                    }

                    /// <summary>Returns the number of vertex inputs used by this shader</summary>

                    protected override int GetVertexInputCountImpl()

                    {

                               return 1;

                    }

                    /// <summary>Returns a vertex input used by this shader</summary><param name="i"/><param name="usage"/><param name="index"/>

                    protected override void GetVertexInputImpl(int i, out Microsoft.Xna.Framework.Graphics.VertexElementUsage usage, out int index)

                    {

                               usage = ((Microsoft.Xna.Framework.Graphics.VertexElementUsage)(Tutorial03Technique.vin[i]));

                               index = Tutorial03Technique.vin[(i + 1)];

                    }

                    /// <summary>Static graphics ID</summary>

                    private static int gd;

                    /// <summary>Static effect container instance</summary>

                    private static Xen.Graphics.ShaderSystem.ShaderEffect fx;

                    /// <summary/>

                    private bool vreg_change;

                    /// <summary/>

                    private bool preg_change;

                    /// <summary/>

                    private bool vbreg_change;

                    /// <summary/>

                    private bool vireg_change;

                    /// <summary>Return the supported modes for this shader</summary><param name="blendingSupport"/><param name="instancingSupport"/>

                    protected override void GetExtensionSupportImpl(out bool blendingSupport, out bool instancingSupport)

                    {

                               blendingSupport = true;

                               instancingSupport = true;

                    }

                    /// <summary>Name ID for 'scale'</summary>

                    private static int cid0;

                    /// <summary>Assign the shader value 'float scale'</summary>

                    public float Scale

                    {

                               set

                               {

                                         this.vreg[4] = new Microsoft.Xna.Framework.Vector4(value, 0F, 0F, 0F);

                                         this.vreg_change = true;

                               }

                    }

                    /// <summary>Change ID for Semantic bound attribute 'worldViewProj'</summary>

                    private int sc0;

                    /// <summary>Change ID for Semantic bound attribute '__BLENDMATRICES__GENMATRIX'</summary>

                    private int sc1;

                    /// <summary>Change ID for Semantic bound attribute '__VIEWPROJECTION__GENMATRIX'</summary>

                    private int sc2;

                    /// <summary>TypeID for global attribute 'float4 colour'</summary>

                    private static int gid0;

                    /// <summary>Change ID for global attribute 'float4 colour'</summary>

                    private int gc0;

                    /// <summary>array storing vertex usages, and element indices</summary>

readonly 

                    private static int[] vin = new int[] {0,0};

                    /// <summary>Vertex shader register storage</summary>

readonly 

                    private Microsoft.Xna.Framework.Vector4[] vreg = new Microsoft.Xna.Framework.Vector4[5];

                    /// <summary>Pixel shader register storage</summary>

readonly 

                    private Microsoft.Xna.Framework.Vector4[] preg = new Microsoft.Xna.Framework.Vector4[1];

                    /// <summary>Instancing shader register storage</summary>

readonly 

                    private Microsoft.Xna.Framework.Vector4[] vireg = new Microsoft.Xna.Framework.Vector4[4];



                    /// <summary>Set a shader attribute of type 'Single' by global unique ID, see <see cref="Xen.Graphics.ShaderSystem.ShaderSystemBase.GetNameUniqueID"/> for details.</summary><param name="state"/><param name="id"/><param name="value"/>

                    protected override bool SetAttributeImpl(Xen.Graphics.ShaderSystem.ShaderSystemBase state, int id, float value)

                    {

                               if ((Tutorial03Technique.gd != state.DeviceUniqueIndex))

                               {

                                         this.WarmShader(state);

                               }

                               if ((id == Tutorial03Technique.cid0))

                               {

                                         this.Scale = value;

                                         return true;

                               }

                               return false;

                    }

          }

}

Отметим, что название класса совпадает с названием единственной техники в шейдере. А название пространства имен совпадает с названием самого файла с шейдером.
Также обратим внимание на то, что наш новый класс имеет лишь одно публичное свойство. Дело в том, что остальные параметры заданы со специальной семантикой.
Например, семантика WORLDVIEWPROJECTION говорит Xen о том, что нужно автоматически передавать произведение мировой матрицы, матрицы вида и матрицы проекции в качестве этого параметра.
Семантика GLOBAL сообщает о том, что значение переменной будет задаваться специальными способом, при котором значение будет принадлежать не только данному шейдеру, а будет доступно для всех шейдоров.
Посмотрим, что происходит в коде самого урока. Рассмотрим новый код из класса SphereDrawer:
private Shader.Tutorial03Technique shader;
public void Draw(DrawState state)

{

    using (state.WorldMatrix.PushMultiply(ref worldMatrix))

    {



        if (sphereGeometry.CullTest(state))

        {
            float scaleValue = (float)Math.Sin(state.TotalTimeSeconds) * 0.5f + 1.0f;            shader.Scale = scaleValue;

 

            using (state.Shader.Push(shader))

            {
                sphereGeometry.Draw(state);

            }

        }

 

    }

}
 
Тут мы просто задаем параметр Scale для эффекта.
В коде основного класса Tutorial изменился только метод Frame:
//main application draw method

protected override void Frame(FrameState state)

{

    //NEW CODE

    //set the global float4 attribute named 'colour'  to 1,0,0,1, which is bright red with an alpha of 1.

    state.ShaderGlobals.SetShaderGlobal("colour"new Vector4(1, 0, 0, 1));

 

    //draw to the screen.

    drawToScreen.Draw(state);

}
В этом месте устанавливается значение глобальной переменной colour.

Исходный код урока:
using System;

using System.Collections.Generic;

using System.Text;

 

 

 

using Xen;

using Xen.Camera;

using Xen.Graphics;

using Xen.Ex.Geometry;

using Xen.Ex.Material;

 

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Graphics;

 

 

/*

 * This sample modifies Tutorial_02 (Draw Sphere)

 * This sample demonstrates:

 * 

 * creating and using a custom shader

 * 

 * ---------------------------------------------------------------

 * Before reading further, please read the comments in 'shader.fx'

 * ---------------------------------------------------------------

 * 

 * 

 * see the 'NEW CODE' comments for code that has changed in this tutorial

 * 

 */

namespace Tutorials.Tutorial_03

{

    //this class is mostly the same as the Draw Sphere tutorial,

    //except the shader is hard coded to the custom shader

    class SphereDrawer : IDraw

    {

        private Xen.Ex.Geometry.Sphere sphereGeometry;

        private Matrix worldMatrix;

 

        //NEW CODE

 

        //the shader class has been generated in the namespace 'Shader', because the filename is 'shader.fx'.

        //The only technique in the file is named 'Tutorial03Technique'.

        //The class that was generated is Shader.Tutorial03Technique:

        private Shader.Tutorial03Technique shader;

 

        public SphereDrawer(Vector3 position)

        {

            //setup the sphere

            Vector3 size = new Vector3(1, 1, 1);

            this.sphereGeometry = new Sphere(size, 32);

 

            //setup the world matrix

            this.worldMatrix = Matrix.CreateTranslation(position);

 

            //NEW CODE

            //create an instance of the shader:

            this.shader = new Shader.Tutorial03Technique();

 

            //Note: All shaders implement the 'IShader' interface

        }

 

        public void Draw(DrawState state)

        {

            using (state.WorldMatrix.PushMultiply(ref worldMatrix))

            {

 

                //cull test the sphere (note, the cull test uses the current world matrix)

                if (sphereGeometry.CullTest(state))

                {

                    //NEW CODE

                    //In this sample, the shader instance is defined in this class, however

                    //the draw state can be used to get a shared static instance of a shader. 

                    //Getting shader instances in this way can reduce memory usage. Eg:

                    //

                    // var shader = state.GetShader<Shader.Tutorial03Technique>();

                    //

 

                    //compute a scale value that follows a sin wave

                    float scaleValue = (float)Math.Sin(state.TotalTimeSeconds) * 0.5f + 1.0f;

 

                    //Set the scale value (scale is declared in the shader source)

                    shader.Scale = scaleValue;

 

                    //Bind the custom shader instance

                    using (state.Shader.Push(shader))

                    {

                        //draw the sphere geometry

                        sphereGeometry.Draw(state);

                    }

                }

 

                //xen will reset the world matrix when the using statement finishes:

            }

        }

 

        //always draw.. don't cull yet

        public bool CullTest(ICuller culler)

        {

            return true;

        }

    }

 

    //an application that draws a sphere in the middle of the screen

    [DisplayName(Name = "Tutorial 03: Custom Shader")]

    public class Tutorial : Application

    {

        //a DrawTargetScreen is a draw target that draws items directly to the screen.

        //in this case it will only draw a SphereDrawer

        private DrawTargetScreen drawToScreen;

 

        protected override void Initialise()

        {

            Camera3D camera = new Camera3D();

            camera.LookAt(Vector3.Zero, new Vector3(0, 0, 4), Vector3.UnitY);

 

            //create the draw target.

            drawToScreen = new DrawTargetScreen(camera);

            drawToScreen.ClearBuffer.ClearColour = Color.CornflowerBlue;

 

            //create the sphere

            SphereDrawer sphere = new SphereDrawer(Vector3.Zero);

 

            //add it to be drawn to the screen

            drawToScreen.Add(sphere);

        }

 

        //main application draw method

        protected override void Frame(FrameState state)

        {

            //NEW CODE

            //set the global float4 attribute named 'colour'  to 1,0,0,1, which is bright red with an alpha of 1.

            state.ShaderGlobals.SetShaderGlobal("colour"new Vector4(1, 0, 0, 1));

 

            //draw to the screen.

            drawToScreen.Draw(state);

        }

 

        protected override void Update(UpdateState state)

        {

            if (state.PlayerInput[PlayerIndex.One].InputState.Buttons.Back.OnPressed)

                this.Shutdown();

        }

    }

}

Комментариев нет:

Отправить комментарий