忍者ブログ

Memeplexes

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

かんたんXNA4.0 その22 深度バイアス

3DCGには"z-ファイティング"という現象があります。
カメラからの距離があまりにも同じような物体が2つあると、
深度バッファの計算が狂って(丸め誤差などが原因だそうです)
描画がおかしくなります。

zFighting2.jpg

zFighting.jpg


これは、同じ平面上に2つの三角形を重ねてクルクルと回転させて表示したものです。
なんだか縦にギザギザしているのがわかると思います。
これが"z-ファイティング"です。

これが問題になるのは、物が隠れて出来る"影(シャドウ)"を描画するときなどです。
戦闘機が太陽光をさえぎって地面に影を落とすとすると、
地面を表すポリゴンと影を表すポリゴンが重なることになります。
すると、このような変なギザギザが発生してしまうことがあるのです。

この問題を解決する方法のひとつとして、深度バイアスがあります。
深度が同じならプラスαの情報を使って
どっちのポリゴンが前か決めればいいのです。
例えばカメラからの距離が同じ2つの三角形があるとしましょう。
このままだとzファイティングが発生するのですが、
ここで片方の深度バイアスを0.1fに設定してやります。(デフォルトでは0)
そうすると、0.1の方がより奥にあると認識されて0の後ろに隠れるのです。
z-ファイティングになりません。

この深度バイアスをセットするには
RasterizerState.DepthBiasプロパティを使います。

public float DepthBias { get; set; }

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


class Triangle
{
    VertexPositionColor[] vertices = new VertexPositionColor[3];

    public Triangle(Vector3 p1, Vector3 p2, Vector3 p3, Color color)
    {
        vertices[0] = new VertexPositionColor(p1, color);
        vertices[1] = new VertexPositionColor(p2, color);
        vertices[2] = new VertexPositionColor(p3, color);
    }

    public void Draw(GraphicsDevice graphicsDevice, BasicEffect effect)
    {
        effect.VertexColorEnabled = true;

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

            graphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );
        }
    }
}

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    Triangle blueTriangle = new Triangle
    (
        new Vector3(0, 1, 0),
        new Vector3(1, -0.5f, 0),
        new Vector3(-1, -0.5f, 0),
        Color.Blue
    );
    Triangle redTriangle = new Triangle
    (
        new Vector3(1, 0.5f, 0),
        new Vector3(0, -1, 0),
        new Vector3(-1, 0.5f, 0),
        Color.Red
    );
    RasterizerState depthBiasEnabled;

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

    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            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     //カメラからこれより遠い物体は画面に映らない
            )
        };
        depthBiasEnabled = new RasterizerState
        {
            DepthBias = 0.1f,
            CullMode = CullMode.None 
        };
    }

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

    protected override void Update(GameTime gameTime)
    {
        //(カメラではなく)三角形そのものを回転させる。
        effect.World *= Matrix.CreateRotationY(MathHelper.ToRadians(1));
    }

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

        
        GraphicsDevice.RasterizerState = RasterizerState.CullNone;
        blueTriangle.Draw(GraphicsDevice, effect);
        GraphicsDevice.RasterizerState = depthBiasEnabled;
        redTriangle.Draw(GraphicsDevice, effect);
    }
}
depthBias.jpg
深度バイアスをセットして、青い三角形を常に赤い三角形の上に描画するようにしました。
赤い三角形に深度バイアスをセットしたのでより遠くにあると認識されたようです。
カメラからの距離は同じですが、z-ファイティングにはなりません!


拍手[0回]

PR

かんたんXNA4.0 その21 深度バッファへの書き込みの無効化

前回は深度バッファそのものを無効化しましたが、
深度バッファのチェックはそのままに、
書き込みだけを無効化することも出来ます。

書き込みを無効にすると、
深度バッファそのものを無効化したときのように
やはり奥の物が手前のものより前に描画されてしまいます。

深度が記録されなくなって、どの物体が手前に、
どの物体が奥にあるのかわからなくなるからです。

しかし使いようによっては変わったこともできます。
深度バッファそのものは有効なので
半透明のビームなどを描きたいときには
壁に隠れてかつ、ビーム同士では半透明で隠れない
といったことができます。

深度バッファへの書き込みを無効にするには
DepthStencilState.DepthRead
を使います。

public static readonly DepthStencilState DepthRead;

このオブジェクトをGraphicsDevice.DepthStencilStateにセットすると、
書き込みが無効になります。
デフォルトではもちろん書き込み有効です。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


class Triangle
{
    VertexPositionColor[] vertices = new VertexPositionColor[3];

    public Triangle(Vector3 p1, Vector3 p2, Vector3 p3, Color color)
    {
        vertices[0] = new VertexPositionColor(p1, color);
        vertices[1] = new VertexPositionColor(p2, color);
        vertices[2] = new VertexPositionColor(p3, color);
    }

    public void Draw(GraphicsDevice graphicsDevice, BasicEffect effect)
    {
        effect.VertexColorEnabled = true;

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

            graphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );
        }
    }
}

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;

    Triangle blueTriangle = new Triangle
    (
        new Vector3(0, 1, 0),
        new Vector3(1, -0.5f, 0),
        new Vector3(-1, -0.5f, 0),
        Color.Blue
    );
    Triangle redTriangle = new Triangle
    (
        new Vector3(1, 0.5f, -1),
        new Vector3(0, -1, -1),
        new Vector3(-1, 0.5f, -1),
        Color.Red
    );

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

    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            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);

        GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;

        blueTriangle.Draw(GraphicsDevice, effect);
        redTriangle.Draw(GraphicsDevice, effect);
    }
}

depthBufferWriteDisabled.jpg
ここでも、2つの三角形を描画しています。
手前に青い三角形、奥に赤い三角形があるのですが、
深度バッファへ書き込みがされていないので
ポリゴンが常に描画されてしまい、
赤い三角形が上に描画されてしまいます。

さて、これだけではDepthStencilState.None
との違いがわかりにくいですね。
そういうときには次のようにすると違いがわかります。
    protected override void Draw(GameTime gameTime)
    {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        GraphicsDevice.DepthStencilState = DepthStencilState.Default;
        blueTriangle.Draw(GraphicsDevice, effect);
        GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
        redTriangle.Draw(GraphicsDevice, effect);
    }

これは三角形を正常に描画します。
最後の赤い三角形の次にはもう描画するポリゴンがないので
深度バッファを書き込まなくても問題はないのです。



拍手[0回]


かんたんXNA4.0 その20 深度バッファ

3Dグラフィックスでは、奥にある物体は手前にある物体に
隠されなければなりません。

例えば、壁の向こうに敵がいる場合、敵は壁に隠れて見えません。
(当たり前です!)

XNAではこのことを特に気にする必要はありません。
ふつうに描画するだけで自動的に奥のものが隠れてくれます。

depthBufferEnabled.jpg
(奥に赤い三角形、手前に青い三角形があります。
奥の三角形は手前の三角形に隠れています。)


これは内部で深度バッファというものを使っているからです。
深度バッファというのは色のデータと共に各ピクセルごとに
存在するデータのことで、
今一番手前の物体のカメラからの距離を表しています。
(もっともこれは相対的な値で、0.0から1.0までです)

もし今描画しようとしている物体の距離が深度バッファのものより遠ければ
(つまり今描画しようとしている物体が隠れてしまうのなら)
描画しないことにしています。
もし今から描画する方が近いのなら、描画して、
深度バッファを今描いたものに更新します。

このように深度バッファを使うことによって適切な描画が出来るのですが、
特殊な効果を狙ってあえて深度バッファを使わないようにすることが可能です。

これにはGraphicsDevice.DepthStencilStateプロパティを使って設定します。

public DepthStencilState DepthStancilState { get; set; }

これを使うと、深度バッファを有効にしたり無効にしたり出来ます。
デフォルトはもちろん有効です。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


class Triangle
{
    VertexPositionColor[] vertices = new VertexPositionColor[3];

    public Triangle(Vector3 p1, Vector3 p2, Vector3 p3, Color color)
    {
        vertices[0] = new VertexPositionColor(p1, color);
        vertices[1] = new VertexPositionColor(p2, color);
        vertices[2] = new VertexPositionColor(p3, color);
    }

    public void Draw(GraphicsDevice graphicsDevice, BasicEffect effect)
    {
        effect.VertexColorEnabled = true;

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

            graphicsDevice.DrawUserPrimitives<VertexPositionColor>(
                PrimitiveType.TriangleList,
                vertices,
                0,
                1
                );
        }
    }
}

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices;

    Triangle blueTriangle = new Triangle
    (
        new Vector3(0, 1, 0),
        new Vector3(1, -0.5f, 0),
        new Vector3(-1, -0.5f, 0),
        Color.Blue
    );
    Triangle redTriangle = new Triangle
    (
        new Vector3(1, 0.5f, -1),
        new Vector3(0, -1, -1),
        new Vector3(-1, 0.5f, -1),
        Color.Red
    );

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

    }

    Vector3 getPosition(int index, int count)
    {
        return new Vector3(
            (float)Math.Sin(2 * Math.PI * index / count),
            0,
            (float)Math.Cos(2 * Math.PI * index / count)
            );
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            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);

        GraphicsDevice.DepthStencilState = DepthStencilState.None;

        blueTriangle.Draw(GraphicsDevice, effect);
        redTriangle.Draw(GraphicsDevice, effect);
    }
}


depthBufferDisabled.jpg
ここでは、2つの三角形を描画しています。
手前にあるのが青い三角形
奥にあるのが赤い三角形です。

しかし、深度バッファを無効にしたため、
隠れて見えないはずの赤い三角形が
青い三角形の上に描画されています。

まず青い三角形を描画し、その後赤い三角形を描画したため、
赤い三角形で上書きされてしまうのです。
(もし深度バッファが有効なら、赤い三角形を後から描いてもきちんと隠れてくれます!)


 

拍手[0回]


かんたんXNA4.0 その19 マテリアル

物の色はライトの色だけで決まるわけではありません。
真っ黒いものは赤い光が当たろうと緑の光が当たろうと
青い光が当たろうと白い光が当たろうと
黒です。

緑の葉っぱに白い光を当てると、
反射する光は白ではなく緑です。

どういうことかというと、
物の見かけの色はライトの色だけでなく、物そのものの色にも影響される
といういことです。
物体にどんな光が当たるかということだけではなく、
それがどれだけ物体によって跳ね返されるかにも影響されるのです。

この「物そのものの色」をマテリアルといい、
BasicEffectのプロパティで設定します。

public Vector3 DiffuseColor { get; set; }

DirectionalLightのDiffuseColorをどれだけ跳ね返すかを表します。

public Vector3 SpecularColor {get; set; }

DirectionalLightのSpecularColorをどれだけ跳ね返すかを表します。

public float SpecularPower { get; set; }

SpecularColorの色の収束っぷりを表します。
大きいほどよりよく収束します。
specularPower1.JPGSpecularPower = 1
specularPower10.JPGSpecularPower = 10
specularPower100.JPGSpecularPower = 100

public Vector3 AmbientLightColor { get; set; }

環境光による色を表します。
周りで複雑な反射を繰り返し、最終的に物体に当たる光による色です。
これは物体やカメラがどんな位置や角度にあろうと必ず物体の色に反映されます。

public Vector3 EmissiveColor { get; set; }

自分自身が放つ色です。
他のプロパティが光の反射による、惑星の光だとすれば、
このプロパティは自分から光を発する恒星の光といったところでしょうか。

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

class MyGame : Game
{
    GraphicsDeviceManager graphics;
    BasicEffect effect;
    VertexPositionNormalTexture[] vertices;

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

        int planeCount = 30;
        vertices = new VertexPositionNormalTexture[planeCount * 6];

        for (int i = 0; i < planeCount; i++)
        {
            VertexPositionNormalTexture[] plane
                = new VertexPositionNormalTexture[4];

            plane[0] = new VertexPositionNormalTexture(
                    getPosition(i, planeCount) + new Vector3(0, 1, 0),
                    getPosition(i, planeCount),
                    new Vector2((float)i / planeCount, 0)
                );
            plane[1] = new VertexPositionNormalTexture(
                    getPosition(i + 1, planeCount) + new Vector3(0, 1, 0),
                    getPosition(i + 1, planeCount),
                    new Vector2((float)(i + 1) / planeCount, 0)
                );
            plane[2] = new VertexPositionNormalTexture(
                    getPosition(i, planeCount) + new Vector3(0, -1, 0),
                    getPosition(i, planeCount),
                    new Vector2((float)i / planeCount, 1)
                );
            plane[3] = new VertexPositionNormalTexture(
                    getPosition(i + 1, planeCount) + new Vector3(0, -1, 0),
                    getPosition(i + 1, planeCount),
                    new Vector2((float)(i + 1) / planeCount, 1)
                );

            vertices[i * 6] = plane[0];
            vertices[i * 6 + 1] = plane[1];
            vertices[i * 6 + 2] = plane[2];

            vertices[i * 6 + 3] = plane[1];
            vertices[i * 6 + 4] = plane[3];
            vertices[i * 6 + 5] = plane[2];
        }
    }

    Vector3 getPosition(int index, int count)
    {
        return new Vector3(
            (float)Math.Sin(2 * Math.PI * index / count),
            0,
            (float)Math.Cos(2 * Math.PI * index / count)
            );
    }

    protected override void LoadContent()
    {
        effect = new BasicEffect(GraphicsDevice)
        {
            LightingEnabled = true,
            View = Matrix.CreateLookAt
            (
                new Vector3(0, 0, 8),  //カメラの位置
                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();

        effect.DiffuseColor = Color.Green.ToVector3();
        effect.SpecularColor = Color.Blue.ToVector3();
        effect.SpecularPower = 100;

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

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

material.JPG

このプログラムは、円筒形を表示して、そのマテリアルを設定しています。
マテリアルのDiffuseColorは緑なので、ライトの緑成分しか跳ね返しません。
(つまりライトに緑成分がなければ真っ黒に見えます。
lightDiffuseNoGreen.JPGライトのDiffuseColor = (0.5f, 0, 0.5f)


一方マテリアルのSpecularColorは青なので、
円筒のハイライト部分は青くなっています(不自然なことに!)

自然な感じにしたいのならマテリアルの
SpecularColorは白にしたほうがいいでしょうね。
specularWhite.JPG

拍手[0回]


かんたん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回]