忍者ブログ

Memeplexes

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

かんたんXNA HLSL編 その3 スカラーとベクトル型

このページは古いです
最新版はこちら

前回の終わりのほうでHLSLを直接いじると
おもしろいいことができるというようなことを書いた気がしますが、
それにはまず変数を操作できないといけないことに気がつきました・・・・・・。
(くそっ、何で気がつかなかったんだ!)

そこでスカラー型とベクトル型の変数に関する簡単な説明をしながら
そのおもしろい効果を見ていこうと思います。


スカラーとベクトル型

HLSLで使えるスカラー型(普通の数値を表す型)は
以下のようなものがあります:

HLSLのスカラー型
型名 説明
bool 真偽を表します。trueかfalseです。
int 32bitの符号付き整数です。
half 16bitの浮動小数点数です。
float 32bitの浮動小数点数です。
double 64bitの浮動小数点数です。


そして、さらにそれをまとめたベクトル型の変数を作ることができます。
HLSLの目的からして、ベクトルはよく使うでしょうからね。

[成分の型][ベクトルの成分の数] [変数名];

具体的にはこんな感じです。

float4 color;

float4は4つの成分を持つベクトルで、
位置や色を表すのに使われます。
(「今やっているのは3DCGなのに、どうして位置を表すのに4次元のベクトルを使うんだろう?」と思われるかもしれません。これはおそらく頂点データをマトリックスで変換するときの計算が関係しています。より柔軟なデータの変換をするためには、3次元のベクトルとして使うよりも4次元のベクトルとして扱ったほうが都合がいいのです。拡大なんかは3次元のままでもいいのですが、たとえば平行移動するときには3次元ではなく4次元ベクトルとして計算しなくてはなりません。つまり計算上の都合です。シェーダの引数として使われる空間ベクトルの4つ目の成分wには変換の計算を上手くやるため1が入っています。この値は計算に使うため変更してはいけません。たとえばwに2をセットすると、平行移動の距離も2倍になって、結果がなんだか変なことになってしまいます。)

この4つの成分にアクセスするためには、
x, y, z, w, (位置としてアクセス)または
r, g, b, a(色としてアクセス)を使います。

位置としても色としてもアクセスできるなんて
ちょっとカプセル化が破壊されているように思えるかもしれません。
でもまぁマーチン・ファウラーさんも
少しくらいカプセル化が壊れてても大丈夫
みたいなことを言ってたのでいいんじゃないでしょうか(たぶん)。


ちなみに初期化は次のように行います:

//new が無いことに注意!!C/C++風です!
float4 variable = float4(0, 1, 2, 3); 

あるいはこうです:

float4 variable = {0, 1, 2, 3};



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


public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    Effect effect;
    VertexPositionColor[] vertices = {
            new VertexPositionColor(new Vector3(0, 1, 0), Color.White),
            new VertexPositionColor(new Vector3(1, 0, 0), Color.Blue),
            new VertexPositionColor(new Vector3(-1, 0, 0), Color.Red)
        };


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        content = new ContentManager(Services);
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            effect = content.Load<Effect>("MyEffect");

            graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
                graphics.GraphicsDevice,
                VertexPositionColor.VertexElements
                );
        }
    }

    protected override void UnloadGraphicsContent(bool unloadAllContent)
    {
        if (unloadAllContent) { content.Unload(); }
    }


    protected override void Draw(GameTime gameTime)
    {
        graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

        effect.Begin();

        foreach (EffectPass pass in effect.CurrentTechnique.Passes)
        {
            pass.Begin();

            graphics.GraphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                vertices.Length / 3
                );

            pass.End();
        }

        effect.End();
    }
}


MyEffect.fx

struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.r = 0;
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

redMasked.jpg

ここではピクセルシェーダで各ピクセルの赤い成分を0にしています。
そのため三角形全体から赤っぽさが消えています。

※参考までに、赤っぽさを消す前はこんなかんじでした:
customEffectColor.jpg参考:赤っぽさを消す前

各ピクセルの赤い成分を消すために、ピクセルシェーダで
color.r = 0;
としています。
これは実は
color.x = 0;
としても同じです。変な感じはしますけどね。



複数の成分を扱う

ここまではC#的な観点からもまだ理解しやすいものだったと思います。
つまりメンバ変数やプロパティのような感じだからです。

しかしここから、見慣れない不穏な感じがでてきます。

こんな風に、複数の成分にアクセスできるのです。

color.rb = 0;

//color.r = 0; 
//color.b = 0; 
//と同じ


ふつうのC言語系の言語になれている人は絶句するのではないでしょうか!
これはrとbの両方を0にしているのですが、
書き方としてはまるでrbというメンバがあるかのようです。

もちろん実際はそんなことはなくて、書き方としてこういうのがあるだけです。
順番を逆、color.br = 0;としても同じことです。

そしてここでは2つの成分だけを同時に扱っていますが、
たとえば3つの成分でcolor.rgb = 0;なんてのもアリです。
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.rb = 0;
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}


rbMasked.jpg
赤と青の成分を消したため、緑の成分だけが残っています。


また、次のように書くことで成分を入れ替えることができます:

color.rb = color.br;
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	color.rb = color.br;
	return color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}

rbExchanged.jpg

一見何も変わっていないように見えますが注意ぶかく見てください。
赤と青が入れ替わっています。(左右反転したようにも見えますが、変わっているのは色です)
ここではrとbだけ入れ替えていますが、
r, g, bの3つでローテーションのように入れ替えることも可能です。



スカラーとベクトルの計算

スカラーとベクトルを一緒に計算することもできます。
その場合、ベクトルの各成分がスカラーと計算されます。

たとえば、

color += 0.3f;

は次と同じです:

color.r += 0.3f;
color.g += 0.3f;
color.b += 0.3f;
color.a += 0.3f;


struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	return 1 - color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}


colorReversed.jpg
ここでは色を反転しています。
colorの各成分が、1から引かれています。
{1, 1, 1, 1}が白なので、そこからもとの色を引くと
色がひっくり返るのです。


ベクトルとベクトルの計算

ベクトルとベクトルの計算は、お互いの各成分を計算します。
足し算は数学のベクトルの足し算と同じですが、
かけ算は内積や外積とは違うので注意してください。

足し算:
float4 position = float4(0, 1, 2, 3);
float4 offset = float4(1, 2, 3, 4);
//position + offset は {1, 3, 5, 7}
// {0 + 1, 1 + 2, 2 + 3, 3 + 4}


かけ算:
float4 materialColor = float4(0, 1, 2, 3);
float4 lightColor = float4(1, 2, 3, 4);
// materialColor * lightColor は {0, 2, 6, 12}
// {0 * 1, 1 * 2, 2 * 3, 3 * 4}


struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	float4 lightColor = float4(1, 0, 0.5f, 1);
	return lightColor * color;
}

technique MyTechnique
{
	pass MyPass
	{
		VertexShader = compile vs_2_0 MyVertexShader();
		PixelShader = compile ps_2_0 MyPixelShader();
	}
}


pinkLight.jpg

今度の三角形はピンクのライトが当たったようになっています。

ピクセルシェーダによって返される色は、

{1 * color.r, 0 * color.g, 0.5f * color.b, 1 * color.a}

です。

これは内積や外積とは違うことに注意してください。
詳しくは後で述べますが、内積はdot関数、外積はcross関数を使うのです。

拍手[1回]

PR