忍者ブログ

Memeplexes

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

かんたんXNA その18 BasicEffectで平行光

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

BasicEffect.EnableDefaultLightingメソッドを使うと
簡単なライティングを行うことが出来ますが、
BasicEffect.DirectionalLight0プロパティを使うと、
もう少し詳しいライティングの設定が出来ます。
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect basicEffect;

    VertexPositionNormalTexture[] vertices;

    ContentManager content;
    Texture2D texture;

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

        vertices  = new VertexPositionNormalTexture[3];
        vertices[0].Position = new Vector3(0, 1, 0);
        vertices[0].Normal = new Vector3(0, 0, 1);
        vertices[0].TextureCoordinate = new Vector2(0.5f, 0);

        vertices[1].Position = new Vector3(1, 0, 0);
        vertices[1].Normal = new Vector3(0, 0, 1);
        vertices[1].TextureCoordinate = new Vector2(1, 1);

        vertices[2].Position = new Vector3(-1, 0, 0);
        vertices[2].Normal = new Vector3(0, 0, 1);
        vertices[2].TextureCoordinate = new Vector2(0, 1);
        
    }

    protected override void LoadGraphicsContent(bool loadAllContent)
    {
        if (loadAllContent)
        {
            basicEffect = new BasicEffect(graphics.GraphicsDevice, null);
            basicEffect.View = Matrix.CreateLookAt(
                new Vector3(0, 0, 3),
                new Vector3(0, 0, 0),
                new Vector3(0, 1, 0)
                );
            basicEffect.Projection = Matrix.CreatePerspectiveFieldOfView(
                MathHelper.ToRadians(45), 
                Window.ClientBounds.Width/(float)Window.ClientBounds.Height,
                1, 100
                );


            texture = content.Load<Texture2D>("FlyingSpaghettiMonster");
        }
    }

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

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

        graphics.GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            graphics.GraphicsDevice,
            VertexPositionNormalTexture.VertexElements
            );

        basicEffect.TextureEnabled = true;
        basicEffect.Texture = texture;

        basicEffect.LightingEnabled = true;
        basicEffect.DirectionalLight0.Enabled = true;
        basicEffect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
        basicEffect.DirectionalLight0.DiffuseColor = new Vector3(0.5f, 0.5f, 0.5f);
        basicEffect.DirectionalLight0.SpecularColor = Color.White.ToVector3();

        basicEffect.Begin();

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

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

            pass.End();
        }

        basicEffect.End();
    }
}

directionalLight1.JPG
このサンプルプログラムでは、斜め45度の角度から三角形に光を照射しています。
左が明るく、右が暗くなっています。
ここでは、太陽光のように光源が十分に遠くにある、光をシミュレートしています。
(そのため、指定するのは光の方向だけで、光源の位置は指定しません
「十分に遠くにある」という情報だけで十分なのです。
十分に遠くにあれば光の方向は大体どこでも同じだからです。
もし光源が近くにあるのなら、
光の方向はちょっと動いただけでも変わりますが、
遠くにあるのならほとんど変わりません。
ちょうど車で移動しているときに月がどこまでも
追いかけてくるように見えるのと同じ理由です。)


まず、BasicEffectで(手作業で)ライティングを行いたいときには、
BasicEffect.LightingEnabledプロパティをtrueにセットします。
(EnableDefaultLightingメソッドを使うときにはこの作業がいらなかったのは、
多分メソッドの内部でこのプロパティをtrueにセットしてくれているからでしょう。)


public bool LightingEnabled { get; set; }

これをtrueにセットしないとライティングが考慮されなくなって
普通に描画されてしまいます。(影がつきません)
xnaSimplextTriangleTextured.JPG

そして、無限遠から来る光、平行光を使うには
BasicEffect.DirectionalLight0プロパティを使います。

public BasicDirectionalLight DirectionalLight0 { get; }

一番最後に"0"がついているのは、平行光を複数使いたいときのために、
まだまだ他にも用意されているからです。
(ここで使っているのは最初の0だけですが)

public BasicDirectionalLight DirectionalLight1 { get; }
public BasicDirectionalLight DirectionalLight2 { get; }


こういうのは本当は配列にすべきなのですが、そうなっていないのは
BasicEffectが実はきたならしいHLSLのラッパー
であることと関係があるのでしょう。

BasicDirectionalLightを使うにはまずEnabledプロパティをtrueにセットします。

public bool Enabled { get; set; }

これをtrueにしないと、そもそも光が当たりません。
真っ黒になります。
(BasicEffect.LightingEnabledとは違います。)

directionalLightWithoutEnabled.JPG

その後、BasicDirectionalLight.Directionプロパティで光の方向を決めます。

public Vector3 Direction { get; set; }

光の色を決めるのがBasicDirectionalLight.DiffuseColorプロパティと
BasicDirectionalLight.SpecularColorプロパティです。
(この2つは色を表すプロパティですが、
型がColorではなくVector3になっています。
これはBasicEffectが実はきたならしいHLSLのラッパーで
あることと関係があるのでしょう。)


public Vector3 DiffuseColor { get; set; }
public Vector3 SpecularColor { get; set; }


どうして光の色を決めるプロパティが2つもあるのかというと、
この2つは性質が違うからです。
DiffuseColorは乱反射による色ですが、
SpecularColorは鏡面反射による色です。
この2つを合成した色が実際には表示されます。

この違いをはっきりさせるには、片方だけセットしてみるといいでしょう。

乱反射(Diffuse)

まず、DiffuseColor(乱反射)だけセットするとこうなります。
directionalLightDiffuse.JPG

全体的に暗めでのっぺりしています。
どこも同じような明るさです。
この明るさは面に当たる光の角度に依存しています。

diffuse90.JPG90度(三角形に平行:横からの光)diffuse75.JPG75度
diffuse60.JPG60度diffuse30.JPG30度
diffuse15.JPG15度diffuse0.JPG0度(三角形に垂直:まっすぐな光)

面に垂直に光が当たるようになるほど明るくなるのがわかると思います。
これは赤道直下が暑くて北極や南極が寒くなる理由と同じです。(赤道は緯度0度で、北極点は北緯90度です。)

鏡面反射(Specular)

次に、SpecularColor(鏡面反射)だけセットするとこうなります。

directionalLightSpecular.JPG

左側がぼんやりと明るく、右側は暗くなっています。
この明るくなっている部分は鏡に映った太陽とでも思えばいいでしょう。

実際、三角形を動かしていくとこうなります。
directionalLightSpecularMoved3.JPGdirectionalLightSpecularMoved2.JPGdirectionalLightSpecularMoved1.JPG


directionalLightSpecularMoved0.5f.JPGdirectionalLightSpecular.JPG

光の方向と面とカメラの位置によって明るさが変わります。
カメラの位置によって明るさが変わるというのは乱反射ではありえないことです。
こういうのは鏡面反射でのみおこります。
ちょうどメガネがキラリと光るようなものです。

最終的には、この2つの効果が組み合わさります。
そうすることによって、そこそこ自然な描画が出来るのです。
環境光(Ambient Color)

このサンプルでは使いませんでしたが、環境光というものもあります。
これは、光がどのような角度で当たっても、
カメラがどのような方向から見ても、
同じような明るさになります。

どういういうことかというと、環境光とは周囲を反射しまくった微妙な光のことなのですが、
それをわざわざ計算してシミュレートするのはとても莫大な計算が必要になるため、
とりあえず「どんなときにも当たる光」ということにしているのです。

これを表すプロパティは、BasicEffect.AmbientColorです。

public Vector3 AmbientLightColor { get; set; }

拍手[0回]

PR