忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

XNAでエリア総和テーブル実験その2

エリア総和テーブル(Summed Area table)を作る実験をまたやってみました。

前回は横方向にだけ足しましたが、今回は縦方向にも足します。
驚いたことに、ちょっとした修正をするだけで縦方向にも足せるようになりますね。
これでようやくエリア総和テーブルとして使えるようになるはずです。(たぶん)



MyGame.cs
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

public class MyGame : Game
{
    GraphicsDeviceManager graphics;

    Texture2D texture;
    BasicEffect basicEffect;
    VertexDeclaration vertexDeclaration;

    Effect tableCreationEffect;
    RenderTarget2D renderTarget;
   

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

    protected override void LoadContent()
    {
        //指定したサイズのテクスチャを作ります。
        //サイズをいろいろ変えると、
        //いろいろなサイズのエリア総和テーブルが作れます。
        initTexture(8, 8);

        renderTarget = new RenderTarget2D(
            GraphicsDevice,
            texture.Width, texture.Height,
            1,
            SurfaceFormat.Vector4
            );

        tableCreationEffect = Content.Load<Effect>("SummedAreaTableCreator");
        
        basicEffect = new BasicEffect(GraphicsDevice, null);


        vertexDeclaration = new VertexDeclaration(
            GraphicsDevice,
            VertexPositionTexture.VertexElements
            );
        GraphicsDevice.VertexDeclaration = vertexDeclaration;
    }

    private void initTexture(int width, int height)
    {
        texture = new Texture2D(
            GraphicsDevice,
            width, height,
            1,
            TextureUsage.None,
            SurfaceFormat.Vector4
            );
        Vector4[] data = new Vector4[texture.Width * texture.Height];

        for (int i = 0; i < data.Length; i++)
            data[i] = Vector4.One / data.Length;

        texture.SetData<Vector4>(data);
    }

    protected override void UnloadContent()
    {
        texture.Dispose();
        renderTarget.Dispose();

        basicEffect.Dispose();
        vertexDeclaration.Dispose();
    }

    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.SetRenderTarget(0, renderTarget);
        GraphicsDevice.Clear(Color.CornflowerBlue);

        drawTexture(texture);

        createSummedAreaTable();

        GraphicsDevice.SetRenderTarget(0, null);
        GraphicsDevice.Clear(Color.CornflowerBlue);

        drawTexture(renderTarget.GetTexture());
    }

    //GraphicsDeviceにセットされているレンダーターゲットを、
    //自分自身のエリア総和テーブルに変えます。
    private void createSummedAreaTable()
    {
        tableCreationEffect.Parameters["Width"].SetValue(renderTarget.Width);
        tableCreationEffect.Parameters["Height"].SetValue(renderTarget.Height);


        tableCreationEffect.Begin();

        //横方向に足す
        for (int i = 0; i < System.Math.Log(renderTarget.Width, 2); i++)
        {
            tableCreationEffect.CurrentTechnique.Passes["SumXPass"].Begin();

            GraphicsDevice.SetRenderTarget(0, null);
            tableCreationEffect.Parameters["PreviousProduct"].SetValue(renderTarget.GetTexture());
            GraphicsDevice.SetRenderTarget(0, renderTarget);

            tableCreationEffect.Parameters["PassIndex"].SetValue(i);

            drawRect();

            tableCreationEffect.CurrentTechnique.Passes["SumXPass"].End();
        }


        //縦方向に足す
        for (int i = 0; i < System.Math.Log(renderTarget.Height, 2); i++)
        {
            tableCreationEffect.CurrentTechnique.Passes["SumYPass"].Begin();

            GraphicsDevice.SetRenderTarget(0, null);
            tableCreationEffect.Parameters["PreviousProduct"].SetValue(renderTarget.GetTexture());
            GraphicsDevice.SetRenderTarget(0, renderTarget);

            tableCreationEffect.Parameters["PassIndex"].SetValue(i);

            drawRect();

            tableCreationEffect.CurrentTechnique.Passes["SumYPass"].End();
        }

        tableCreationEffect.End();

    }

    private void drawTexture(Texture2D texture)
    {
        basicEffect.TextureEnabled = true;
        basicEffect.Texture = texture;
        basicEffect.Begin();
        basicEffect.CurrentTechnique.Passes[0].Begin();

        drawRect();

        basicEffect.CurrentTechnique.Passes[0].End();
        basicEffect.End();
    }

    private void drawRect()
    {
        //四角形(テクスチャつき)の頂点
        VertexPositionTexture[] vertices = {
            new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2()),
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1)),
            
            new VertexPositionTexture(new Vector3(1, 1, 0), new Vector2(1, 0)),
            new VertexPositionTexture(new Vector3(1, -1, 0), new Vector2(1, 1)),
            new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 1))
        };

        GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(
            PrimitiveType.TriangleList,
            vertices,
            0,
            2
            );
    }

    static void Main()
    {
        using (MyGame game = new MyGame())
        {
            game.Run();
        }
    }
}




SummedAreaTableCreator.fx (Contentフォルダ内)
texture PreviousProduct;

sampler TextureSampler = sampler_state
{
	Texture = (PreviousProduct);
};

float Width;
float Height;
int PassIndex;


struct VertexPositionTexture
{
    float4 Position : POSITION;
    float2 TextureCoordinate : TEXCOORD;
};

VertexPositionTexture VertexShaderFunction(VertexPositionTexture input)
{
    return input;
}

float4 getColor(float2 texCoord)
{
    if(texCoord.x < 0 || texCoord.y < 0) return 0;
    else return tex2D(TextureSampler, texCoord + float2(0.5/Width, 0.5/Height));
}


float4 SumX(float2 textureCoordinate : TEXCOORD):COLOR
{
    return getColor(textureCoordinate + float2((-1/ Width) * exp2(PassIndex), 0))
        + getColor(textureCoordinate);
}

float4 SumY(float2 textureCoordinate : TEXCOORD):COLOR
{
    return getColor(textureCoordinate + float2(0, (-1/ Height) * exp2(PassIndex)))
        + getColor(textureCoordinate);
}

technique Technique1
{
    pass SumXPass
    {
		VertexShader = compile vs_1_1 VertexShaderFunction();
		PixelShader = compile ps_2_0 SumX();
    }
    
    pass SumYPass
    {
		VertexShader = compile vs_1_1 VertexShaderFunction();
		PixelShader = compile ps_2_0 SumY();
    }
}



initTextureの引数をいろいろ変えてやってみました。
結果は・・・良くも悪くも予想どおりといった感じです。
パッと見問題はないようにも見えますが、
横方向のときにあった問題をそのまま受け継いでいます(4x4のときが顕著ですね)。

2x2:
satFrom2x2.jpg

4x4:(こりゃないよ・・・)
satFrom4x4.jpg

8x8:
satFrom8x8.jpg

16x16:
satFrom16x16.jpg

32x32:
satFrom32x32.jpg

64x64:
satFrom64x64.jpg

128x128:
satFrom128x128.jpg

あいかわらず「完全な白にはならない」問題と、「(左上の領域が)2倍の太さになる」問題は残っていますが、いちおうエリア総和テーブルらしくなってきました。


このうち、「完全な白にはならない」問題は、パスのBeginとEndを呼ぶタイミングで解決できるようです。
つまり、パスのBeginとEndをforループの外にくくりだし、drawRectの直前でEffect.CommitChanges()を呼び出すのです。

    private void createSummedAreaTable()
    {
        tableCreationEffect.Parameters["Width"].SetValue(renderTarget.Width);
        tableCreationEffect.Parameters["Height"].SetValue(renderTarget.Height);


        tableCreationEffect.Begin();

        //横方向に足す
        tableCreationEffect.CurrentTechnique.Passes["SumXPass"].Begin();
        for (int i = 0; i < System.Math.Log(renderTarget.Width, 2); i++)
        {
            GraphicsDevice.SetRenderTarget(0, null);
            tableCreationEffect.Parameters["PreviousProduct"].SetValue(renderTarget.GetTexture());
            GraphicsDevice.SetRenderTarget(0, renderTarget);

            tableCreationEffect.Parameters["PassIndex"].SetValue(i);
            tableCreationEffect.CommitChanges();

            drawRect();
        }
        tableCreationEffect.CurrentTechnique.Passes["SumXPass"].End();


        //縦方向に足す
        tableCreationEffect.CurrentTechnique.Passes["SumYPass"].Begin();
        for (int i = 0; i < System.Math.Log(renderTarget.Height, 2); i++)
        {
            GraphicsDevice.SetRenderTarget(0, null);
            tableCreationEffect.Parameters["PreviousProduct"].SetValue(renderTarget.GetTexture());
            GraphicsDevice.SetRenderTarget(0, renderTarget);

            tableCreationEffect.Parameters["PassIndex"].SetValue(i);
            tableCreationEffect.CommitChanges();

            drawRect();
        }
        tableCreationEffect.CurrentTechnique.Passes["SumYPass"].End();

        tableCreationEffect.End();

    }


これをやると一応右下は白くなります。
4x4:
satFrom4x4WithCommitChangesInvocation.jpg

が、トレードオフもあって、「太さが2倍になる」症候群が悪化します。

8x8:
satFrom8x8WithCommitChangesInvocation.jpg

16x16:
satFrom16x16WithCommitChangesInvocation.jpg

もう踏んだり蹴ったりですね。
いちおうこれでも平均の色を求めるのにはたいして支障はないはずですが、それでも気持ち悪いです。
もう少し実験を重ねたほうがいいかもしれません。












 

拍手[0回]

PR