忍者ブログ

Memeplexes

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

かんたんXNA4.0 その18 BasicEffectで平行光

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

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices = new[]
    { 
        new VertexPositionNormalTexture
        {
            Position = new Vector3(0, 1, 0),
            Normal = new Vector3(0, 0, 1),
        },
        new VertexPositionNormalTexture
        {
            Position = new Vector3(1, 0, 0),
            Normal = new Vector3(0, 0, 1),
        },
        new VertexPositionNormalTexture
        {
            Position = new Vector3(-1, 0, 0),
            Normal = new Vector3(0, 0, 1),
        },
    };

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

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            LightingEnabled = true,
            View = Matrix.CreateLookAt
            (
                new Vector3(0, 0, 3),  //カメラの位置
                new Vector3(0, 0, 0),   //カメラの見る点
                new Vector3(0, 1, 0)    //カメラの上向きベクトル
            ),
            Projection = Matrix.CreatePerspectiveFieldOfView
            (
                MathHelper.ToRadians(45),   //視野の角度。ここでは45°
                GraphicsDevice.Viewport.AspectRatio,//画面のアスペクト比(=横/縦)
                1,      //カメラからこれより近い物体は画面に映らない
                100     //カメラからこれより遠い物体は画面に映らない
            )
        };
    }

    protected override void UnloadContent()
    {
        effect.Dispose();
    }

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

        effect.DirectionalLight0.Direction = new Vector3(1, 0, -1);
        effect.DirectionalLight0.DiffuseColor = Color.Gray.ToVector3();
        effect.DirectionalLight0.SpecularColor = Color.White.ToVector3();

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

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


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


public bool LightingEnabled { get; set; }

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

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

public BasicDirectionalLight DirectionalLight0 { get; }

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

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


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

その後、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.JPG
directionalLightSpecularMoved2.JPG
directionalLightSpecularMoved1.JPG



directionalLightSpecularMoved0.5f.JPG
directionalLightSpecular.JPG

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

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

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

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

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

public Vector3 AmbientLightColor { get; set; }

拍手[1回]

PR