忍者ブログ

Memeplexes

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

[PR]

×

[PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。


かんたんXNA4.0 HLSL編 その6 マトリックス


ここまではHLSLで面白い効果を出すと言っておきながら、
見た目は3Dではなく2Dでした。
頂点シェーダに流し込む頂点データが変換されずにそのまま返されていたからです。
この点で、Projectionプロパティでモデルを立体的に見せることのできるBasicEffectより劣っています。
この差を埋めることにしましょう。

頂点の座標データを変換するにはマトリックスを使います。
ちょうどBasicEffectのViewプロパティやProjectionプロパティのようにです。
グローバル変数としてマトリックスを宣言し、それをC#側から操作すればいいのです。
HLSL側でマトリックスを宣言するには、次のようにします:

[型][変換前のベクトルの次元数]x[変換後のベクトルの次元数] [変数名];

普通は4次元ベクトル→4次元ベクトルの変換なのでfloat4x4です。
(3DCGの話をしているのになぜ4次元が出てくるのか不思議に思えるかもしれませんが、これはマトリックスによる変換の柔軟性を大きくするためです。3次元ベクトルではなく4次元ベクトルの変換ということにしたほうが、よりいろんな種類の変換が出来るのです。そこで、位置ベクトルなんかは3次元でなくて4次元ベクトルとして扱います。その4次元ベクトルの一番最後の成分には位置の情報ではなく1が入っていますが(ジョークではありませんよ)、これは数学的な計算のつじつまを合わせるためで、特に意味はありません。かといって一番最後の成分に全く意味がないと言うわけでもなく、この値が1でないとマトリックスで変換したときの結果が変になってしまいます。)

float4x4 transform;

ただし、『プログラミングDirectX9グラフィックスパイプライン』なんかをみると、
4次元ベクトル→3次元ベクトルの変換を表すfloat4x3や
3次元ベクトル→3次元ベクトルの変換を表すfloat3x3も使われるようです。
きっとこれはそれほど柔軟性が必要ないからでしょう。(多分)

HLSL側でグローバル変数として宣言したマトリックスはたいていC#側で値がセットされるので
初期化についてはあんまり気にする必要はないでしょう。

このグローバル変数を使って頂点シェーダに引数として入ってくる位置ベクトルを変換していくわけですが、
それにはmul関数を使います。

mul ( vector, matrix )

vectorは変換前のベクトル、matrixは変換を表すマトリックスです。
戻り値は変換後のベクトルです。

mulという名前から想像できるように、これは掛け算を意味する関数です。
(※マトリックスによるベクトルデータの変換は掛け算として表されるのです。)
そのため、mul関数の引数は実はスカラーでもベクトルでも何でもかまいません

mul(2, 3);    // == 6
mul(2, float3(1, 0, 0));    // == float3(2, 0, 0)

もちろん、マトリックス同士のかけ算をすることも出来ます。
その場合は、2つのマトリックスの変換を同時に行うマトリックスを返します。

mul(view, projection);    // viewとprojectionの変換をいっぺんにやってしまうマトリックス。



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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
        Matrix view = Matrix.CreateLookAt(
            new Vector3(2, 0, 3),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(90),
            GraphicsDevice.Viewport.AspectRatio,
            0.1f,
            100
            );

        effect.Parameters["Transform"].SetValue(view * projection);
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx
float4x4 Transform;

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	VertexPositionColor output = input;
	output.Position = mul(input.Position, Transform);
	return output;
}

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

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

hlslTransformed.jpg
斜めから見た図です。
きちんと三角形が遠近法の効果でゆがんでいるのがわかると思います。
(いやちょっと微妙? カメラの位置をいろいろ変えてみてください)
これでようやくBasicEffectに一歩近づきました。

BasicEffectと違ってViewやProjectionを分けていません。
この2つのマトリックスを一緒にしてTransformという名前のグローバル変数にしています。
C#側では別々に作ってはいますが、結局掛け合せてエフェクトにセットしています。
こういうのもアリと言うことです。

もちろん、ViewとProjectionを分けることも出来ます。

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
        Matrix view = Matrix.CreateLookAt(
            new Vector3(2, 0, 3),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(90),
            GraphicsDevice.Viewport.AspectRatio,
            0.1f,
            100
            );

        effect.Parameters["View"].SetValue(view);
        effect.Parameters["Projection"].SetValue(projection);
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx
float4x4 View;
float4x4 Projection;

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	VertexPositionColor output = input;
	output.Position = mul(input.Position, mul(View, Projection));
	return output;
}

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

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

実行結果はさっきと同じです。
さっきと違うのは、マトリックスを2つに分けていることです。
このようにマトリックスはいくらでも増やすことが出来ます。

が、あんまり増やしすぎるのも現実的ではないでしょう。
デフォルトで作成されるエフェクトファイルの中には
WorldとViewとProjectionの3つのマトリックスがあります。
そのくらいが適当なのではないでしょうか

拍手[1回]

PR

かんたんXNA4.0 HLSL編 その5 グローバル変数


今回のお題はグローバル変数です。
HLSLのグローバル変数はちょうどC#で言うプロパティのようなノリで使います
つまり、BasicEffectのViewやProjectionプロパティみたいな感じです。
実際、BasicEffectのプロパティの多くがHLSLのグローバル変数をラップしたもののようです。

(※エフェクトのグローバル変数はこんなコードで確かめることが出来ます。
         BasicEffect effect = new BasicEffect(graphics.GraphicsDevice);

            foreach (EffectParameter parameter in effect.Parameters)
            {
                System.Console.WriteLine(parameter.Name);
            }

結果はこうなりました:

Texture
DiffuseColor
EmissiveColor
SpecularColor
SpecularPower
DirLight0Direction
DirLight0DiffuseColor
DirLight0SpecularColor
DirLight1Direction
DirLight1DiffuseColor
DirLight1SpecularColor
DirLight2Direction
DirLight2DiffuseColor
DirLight2SpecularColor
EyePosition
FogColor
FogVector
World
WorldInverseTranspose
WorldViewProj
ShaderIndex

これはBasicEffectのエフェクトで使われているグローバル変数ですが、
ほとんどBasicEffectのプロパティと対応します。(全部ではありませんけどね))


概念としてはC#のプロパティやメンバ変数と同じと言うことです。
このグローバル変数を使うと、C#側からいろいろ操作できるので
エフェクトをより柔軟なものに出来るでしょう。




宣言の仕方はこうです:

スコープ データ型 変数名 : セマンティクス = 初期化する値;

このうち必須なのはデータ型と変数名だけです。
そのため、一番シンプルな例だとこうなります:

float4 DiffuseColor;

これだけでも外側から(C#のコードから)アクセスできます。
スコープを指定しなくてもデフォルトで外側からアクセスできるようになっているのです。
(これがC#とは違うところです。C#のメンバはpublicをつけないと外側からアクセスできませんからね)

C#側からアクセスするには、Microsoft.Xna.Framework.Graphics.EffectParameterクラスを使います。

public sealed class EffectParameter

このクラスはエフェクトのグローバル変数を表しており、
これ使って、グローバル変数をsetしたりgetしたりします。
※なお、msdnによると、getもsetも遅いのであんまり頻繁に使うのは避けたほうがいいそうです。ちょっぴりショックです・・・

読み込むにはEffectParameter.GetValue*****という名前のメソッドを使います。
色々あるのはC#からだとグローバル変数の型がわからないからでしょうね。

public bool GetValueBoolean ()
public bool[] GetValueBooleanArray ( int count )       //count は配列の要素数

public int GetValueInt32 ()
public int[] GetValueInt32Array ( int count )        //countは配列の要素数

public Matrix GetValueMatrix ()
public Matrix[] GetValueMatrixArray ( int count )    //countは配列の要素数

public Matrix GetValueMatrixTranspose ()    //転置行列(行と列が入れ替わったマトリックス)
public Matrix[] GetValueMatrixTransposeArray ( int count )    //countは(以下略

public Quaternion GetValueQuaternion ()
public Quaternion[] GetValueQuaternionArray ( int count ) 

public float GetValueSingle ()
public float[] GetValueSingleArray ( int count )

public string GetValueString ()

public Texture2D GetValueTexture2D ()

public Texture3D GetValueTexture3D ()

public TextureCube GetValueTextureCube ()

public Vector2 GetValueVector2 ()
public Vector2[] GetValueVector2Array ( int count )

public Vector3 GetValueVector3 ()
public Vector3[] GetValueVector3Array ( int count ) 

public Vector4 GetValueVector4 ()
public Vector4[] GetValueVector4Array ( int count )



書き込みにはほとんどの場合、EffectParameter.SetValueメソッドを使います。
GetValueの場合と違って、引数の型によってオーバーロードしてるので
名前が違うなんてことはありません。

public void SetValue ( bool value )
public void SetValue ( bool[] value )

public void SetValue ( int value )
public void SetValue ( int[] value )

public void SetValue ( Matrix value )
public void SetValue ( Matrix[] value )

public void SetValue ( Quaternion value )
public void SetValue ( Quaternion[] value )

public void SetValue ( float value )
public void SetValue ( float[] value )

public void SetValue ( string value )

public void SetValue ( Texture value )

public void SetValue ( Vector2 value )
public void SetValue ( Vector2[] value )

public void SetValue ( Vector3 value )
public void SetValue ( Vector3[] value )

public void SetValue ( Vector4 value )
public void SetValue ( Vector4[] value )



さきほど「ほとんどの場合」といいましたが、それは例外があるからで、
転置行列をセットする場合です。
普通のマトリックスをセットする場合と引数の型が同じなので
オーバーロードできなくなり、別の名前にしたのでしょう。
(まぁあんまり使いそうにないのでこの解説はスルーしてもよかったのですが)

public void SetValueTranspose ( Matrix value )
public void SetValueTranspose ( Matrix[] value )



さて、肝心のEffectParameterクラスのインスタンスを得る方法ですが、
それにはEffect.Parametersプロパティを使います。

public EffectParameterCollection Parameters { get; }

戻り値はHLSLで書いたエフェクトファイルのグローバル変数の一覧を表す、
Microsoft.Xna.Framework.Graphics.EffectParameterCollectionクラスの
インスタンスです。
こいつにEffectParameterのインスタンスが入っています。

public sealed class EffectParameterCollection : IEnumerable<EffectParameter>

目的のグローバル変数を表すEffectParameterを得るには、インデクサを使います。

public EffectParameter this [ string name ] { get; }

nameはもちろんグローバル変数の名前です。


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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

    protected override void Update(GameTime gameTime)
    {
        double angle = gameTime.TotalGameTime.TotalSeconds;
        effect.Parameters["offset"].SetValue(
            new Vector3(
                (float)System.Math.Cos(angle),
                (float)System.Math.Sin(angle),
                0)
            );
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx
float3 offset;

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

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	input.Position.xyz += offset;
	return input;
}

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

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


このサンプルでは三角形をぐるぐる回しています。

rotationByEffectParameter1.jpg
rotationByEffectParameter2.jpg

rotationByEffectParameter3.jpg
rotationByEffectParameter4.jpg
rotationByEffectParameter5.jpg

回転の中心はウィンドウの中心です。
エフェクトファイルで三角形のオフセットを表すグローバル変数を宣言して、
それをC#側から連続して変更していき、回転させているのです。


スコープ

HLSLのグローバル変数の前には記憶域クラス修飾子であるスコープをつけることが出来ます。
C#で言うpublicとかprivateのことです。
ただし、HLSLではC言語風の名前になっていて少し戸惑うかもしれません。


static

C#でいうprivateです。
これを付けられたグローバル変数はエフェクトの外から「見えなく」なります。
アプリケーションからEffectParameterを使ってgetしたりsetしたり出来ません。

C#でstaticと言えば全てのインスタンスでその変数が共有されることを意味しますが、
HLSLでこれに対応するのは"shared"です。

(※とはいえまったく関係ないということはなく、
これを関数などのローカルスコープ内の変数につけた場合、
C#のstaticと似た意味を持つようになります。
つまり、全ての呼び出しでその変数が共有されます。

float incrementA()
{
        static float a;
        a++;
        return a;
}

float4 MyPixelShader() : COLOR
{
        incrementA();    //この時点でaは1。この行をコメントアウトすると色は灰色になります。
        return incrementA() / 2;    //この時aは2。結局白を返します。
}

まぁピクセルシェーダ間で共有することは出来ないようですが。



extern

C#でいうpublicです。
これを付けられたグローバル変数はエフェクトの外からgetしたりsetしたり出来ます。
これはスコープのデフォルトで、何もスコープが付けられていないグローバル変数はexternになります。


shared

C#でいうstaticです。
これを付けられたグローバル変数はエフェクト間で共有されます。


拍手[1回]


かんたんXNA4.0 HLSL編 その4 フロー制御文


HLSLにも他の多くのC言語ベースの言語と同じように、
フロー制御文があります。

つまりifやfor, while, doとかのことです。

まず、msdnを見ると、
if, for, while, switch, do, break, continueと、
一通りそろっているようです。(以下msdnより)

[Attribute] if ( Conditional )
{
    Statement Block
}

[Attribute] for ( Initializer; Conditional; Iterator )
{
    Statement Block;
}

[Attribute] while ( Conditional )
{
    Statement Block;
}

[Attribute] switch ( Selector
{
    case 0 :
        { Statement Block; }
    break;
    case 1 :
        { Statement Block; }
    break;
    case n :
        { Statement Block; }
    break;
    default :
        { Statement Block; }
    break;
}

do 
{
    Statement Block;
} while( Conditional )

break;

continue;


おなじみの文です。
・・・・・・と思ったら[Attribute]とかいう変なのがありますね・・・。
どうやらこれはコンパイルの方法を指定する、C#の一部の属性のようなものらしいです。
でも省略可能なので普通のC言語系のフロー制御と同じように使うことができます。


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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

MyEffect.fx
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

float4 MyPixelShader(float4 color : COLOR) : COLOR
{
	if(color.r % 0.1f < 0.05f)
		return float4(1,1,1,1);
	else
		return color;
}

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


stripeTriangle.jpg
ほどよくしましまになっているのがわかると思います。
ピクセルシェーダに与えられる色の赤いチャンネルをもとにして
条件分岐を行ったのです。
こんなことをしても実際には何の役にも立たないかもしれませんが、
HLSLの威力がよくわかる例ではないでしょうか。
少なくともこれはBasicEffectでは出来ませんからね。







拍手[1回]


かんたんXNA4.0 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;

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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

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回]


かんたんXNA4.0 HLSL編 その2 構造体


前回表示したポリゴンに色はついていませんでした。
使った頂点データには色がついていたにもかかわらず、
実際に描画された三角形のすべてのピクセルの色が白だったのです。

これは前回のエフェクトファイルが色を扱っていないことが原因です。
頂点シェーダでは、位置情報だけではなく色の情報も扱わなければなりませんし、
ピクセルシェーダだって引数として色をとらなければなりません。

位置だけでなく色の情報も扱うためには、その二つをまとめるための
構造体を作る必要があります。
(複数の戻り値を返すことはできませんからね)

構造体のつくり方はC/C++のものとほとんど同じです。
もっともセマンティクス(用途)をつける必要はありますが・・・。

struct 構造体名
{
        型 変数名1 : セマンティクス;
        型 変数名2 : セマンティクス;
...
};


具体的に書くとしたらこんな感じでしょうか。

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

C#やJavaになれているひとは最後のセミコロン(;)を忘れないでください。
また、デフォルトでpublicのようにアクセスできることも意外かもしれません。


MyEffect.fx
struct VertexPositionColor
{
	float4 Position : POSITION;
	float4 Color : COLOR;
};

VertexPositionColor MyVertexShader(VertexPositionColor input)
{
	return input;
}

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

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

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;

    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);
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("Content/MyEffect");
    }

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

        foreach (var pass in effect.CurrentTechnique.Passes)
        {
            pass.Apply();

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


 customEffectColor.jpg

今度は色がついています!

頂点シェーダで色を返すようになったので、
ピクセルシェーダの引数として色をとり、
それを描画に反映できるようになったのです。

でもまぁ、こんなんじゃBasicEffectだってできますし、
HLSLだなんてわけのわからないものを使うくらいなら
BasicEffectを使ったほうがずっとましだといえるでしょう。
しかし、HLSLをつかうと、いろいろとおもしろいことができるのです。
次回はそのことについて書くことにします。



拍手[2回]