忍者ブログ

Memeplexes

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

C#でDirectX11 SlimDXチュートリアルその10 ライト

 今回は、三角形を光で照らします。

Tutorial10Lighting01.jpg
Tutorial10Lighting02.jpg
Tutorial10Lighting03.jpg
Tutorial10Lighting04.jpg

以前とは違い、今度は本当に三角形を回転させています。
以前のようにカメラのほうを回転させ、三角形は静止状態なのでは、光の移り変わりがわかりませんからね。

Tutorial10HowTheLightIsThrown.jpg


回転マトリクス

3DCGで物をY軸方向に回転させるにはMatrix.RotationY()メソッドによって作られたマトリクスを利用します。

public static Matrix RotationY(float angle);

angleは回転角度です。単位はラジアンです。
戻り値はY軸回転を司るマトリクスです。



光の強さを計算

三角形に光を当てて、どのくらいの明るさになるかはHLSLで自分でプログラムしなければいけません。
と言っても実際には大して難しくなくて、明るさはdot()関数で計算できます。
通称、内積です。

float dot(float3 x, float3 y);

この関数は2つのベクトルが同じ向きであるほど大きく、向きが食い違うほど小さくなります。

howTheDotProductWorks.jpg

そしてまさにこの関数の戻り値こそ、光のあたたったものの明るさを計算するのに必要なものなのです。
物の明るさは、その法線ベクトル(面と垂直なベクトル)の方向と、光の方向ベクトルのなす角度で決まるからです。

relationshipBetweenLightAndAngle.jpg

-dot(法線ベクトル, 光の方向)で面の明るさが計算できます。
上の図では左が暗く、右が明るくなります。

なお、法線ベクトルは頂点座標の中に含めることになります。
そのフィールドに付けるセマンティクスはNORMALです。


コード


Program.cs
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.D3DCompiler;

class Program
{
    static void Main()
    {
        using (Game game = new MyGame())
        {
            game.Run();
        }
    }
}

class MyGame : Game
{
    Effect effect;
    InputLayout vertexLayout;
    Buffer vertexBuffer;

    protected override void Draw()
    {
        GraphicsDevice.ImmediateContext.ClearRenderTargetView(
            RenderTarget,
            new SlimDX.Color4(1, 0.39f, 0.58f, 0.93f)
            );

        updateCamera();
        initTriangleInputAssembler();
        initLight();
        drawRotatingTriangle();

        SwapChain.Present(0, PresentFlags.None);
    }


    private void updateCamera()
    {
        Matrix view = Matrix.LookAtRH(
            new Vector3(0, 0, 1),
            new Vector3(),
            new Vector3(0, 1, 0)
            );
        Matrix projection = Matrix.PerspectiveFovRH(
            (float)System.Math.PI / 2,
            ClientSize.Width / ClientSize.Height,
            0.1f, 1000
            );
        effect.GetVariableByName("ViewProjection")
            .AsMatrix().SetMatrix(view * projection);

    }

    private void initTriangleInputAssembler()
    {
        GraphicsDevice.ImmediateContext.InputAssembler.InputLayout = vertexLayout;
        GraphicsDevice.ImmediateContext.InputAssembler.SetVertexBuffers(
            0,
            new VertexBufferBinding(vertexBuffer, VertexPositionNormal.SizeInBytes, 0)
            );
        GraphicsDevice.ImmediateContext.InputAssembler.PrimitiveTopology
            = PrimitiveTopology.TriangleList;
    }

    private void initLight()
    {
        effect.GetVariableByName("LightDirection")
            .AsVector().Set(Vector3.Normalize(new Vector3(1, 0, -1)));
    }

    private void drawRotatingTriangle()
    {
        double time = System.Environment.TickCount / 500d;
        effect.GetVariableByName("World")
            .AsMatrix().SetMatrix(Matrix.RotationY((float)time));
        drawTriangle();
    }

    private void drawTriangle()
    {
        effect.GetTechniqueByIndex(0).GetPassByIndex(0).Apply(GraphicsDevice.ImmediateContext);
        GraphicsDevice.ImmediateContext.Draw(3, 0);
    }


    protected override void LoadContent()
    {
        initEffect();
        initVertexLayout();
        initVertexBuffer();
    }


    private void initEffect()
    {
        using (ShaderBytecode shaderBytecode = ShaderBytecode.CompileFromFile(
            "myEffect.fx", "fx_5_0",
            ShaderFlags.None,
            EffectFlags.None
            ))
        {
            effect = new Effect(GraphicsDevice, shaderBytecode);
        }
    }

    private void initVertexLayout()
    {
        vertexLayout = new InputLayout(
            GraphicsDevice,
            effect.GetTechniqueByIndex(0).GetPassByIndex(0).Description.Signature,
            VertexPositionNormal.VertexElements
            );
    }

    private void initVertexBuffer()
    {
        vertexBuffer = MyDirectXHelper.CreateVertexBuffer(
            GraphicsDevice,
            new[] {
                new VertexPositionNormal
                {
                    Position = new Vector3(0, 0.5f, 0),
                    Normal = new Vector3(0, 0, 1) 
                },
                new VertexPositionNormal
                {
                    Position = new Vector3(0.5f, 0, 0),
                    Normal = new Vector3(0, 0, 1)
                },
                new VertexPositionNormal
                {
                    Position = new Vector3(-0.5f, 0, 0),
                    Normal = new Vector3(0, 0, 1) 
                },
            });
    }

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

struct VertexPositionNormal
{
    public Vector3 Position;
    public Vector3 Normal;

    public static readonly InputElement[] VertexElements = new[]
    {
        new InputElement
        {
            SemanticName = "SV_Position",
            Format = Format.R32G32B32_Float
        },
        new InputElement
        {
            SemanticName = "NORMAL",
            Format = Format.R32G32B32_Float,
            AlignedByteOffset = InputElement.AppendAligned//自動的にオフセット決定
        }
    };

    public static int SizeInBytes
    {
        get
        {
            return System.Runtime.InteropServices.
                Marshal.SizeOf(typeof(VertexPositionNormal));
        }
    }
}

class Game : System.Windows.Forms.Form
{
    public SlimDX.Direct3D11.Device GraphicsDevice;
    public SwapChain SwapChain;
    public RenderTargetView RenderTarget;


    public void Run()
    {
        initDevice();
        SlimDX.Windows.MessagePump.Run(this, Draw);
        disposeDevice();
    }

    private void initDevice()
    {
        MyDirectXHelper.CreateDeviceAndSwapChain(
            this, out GraphicsDevice, out SwapChain
            );

        initRenderTarget();
        initViewport();

        LoadContent();
    }

    private void initRenderTarget()
    {
        using (Texture2D backBuffer
            = SlimDX.Direct3D11.Resource.FromSwapChain<Texture2D>(SwapChain, 0)
            )
        {
            RenderTarget = new RenderTargetView(GraphicsDevice, backBuffer);
            GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(RenderTarget);
        }
    }

    private void initViewport()
    {
        GraphicsDevice.ImmediateContext.Rasterizer.SetViewports(
            new Viewport
            {
                Width = ClientSize.Width,
                Height = ClientSize.Height,
            }
            );
    }

    private void disposeDevice()
    {
        UnloadContent();
        RenderTarget.Dispose();
        GraphicsDevice.Dispose();
        SwapChain.Dispose();
    }

    protected virtual void Draw() { }
    protected virtual void LoadContent() { }
    protected virtual void UnloadContent() { }
}

class MyDirectXHelper
{
    public static void CreateDeviceAndSwapChain(
        System.Windows.Forms.Form form,
        out SlimDX.Direct3D11.Device device,
        out SlimDX.DXGI.SwapChain swapChain
        )
    {
        SlimDX.Direct3D11.Device.CreateWithSwapChain(
            DriverType.Hardware,
            DeviceCreationFlags.None,
            new SwapChainDescription
            {
                BufferCount = 1,
                OutputHandle = form.Handle,
                IsWindowed = true,
                SampleDescription = new SampleDescription
                {
                    Count = 1,
                    Quality = 0
                },
                ModeDescription = new ModeDescription
                {
                    Width = form.ClientSize.Width,
                    Height = form.ClientSize.Height,
                    RefreshRate = new SlimDX.Rational(60, 1),
                    Format = Format.R8G8B8A8_UNorm
                },
                Usage = Usage.RenderTargetOutput
            },
            out device,
            out swapChain
            );
    }

    public static Buffer CreateVertexBuffer(
        SlimDX.Direct3D11.Device graphicsDevice,
        System.Array vertices
        )
    {
        using (SlimDX.DataStream vertexStream 
            = new SlimDX.DataStream(vertices, true, true))
        {
            return new Buffer(
                graphicsDevice,
                vertexStream,
                new BufferDescription
                {
                    SizeInBytes= (int)vertexStream.Length,
                    BindFlags = BindFlags.VertexBuffer,
                }
                );
        }
    }
}

myEffect.fx
matrix World;
matrix ViewProjection;

float3 LightDirection;

struct VertexPositionNormal
{
float4 Position : SV_Position;
float4 Normal : NORMAL;
};

VertexPositionNormal MyVertexShader(VertexPositionNormal input)
{
    VertexPositionNormal output = input;
output.Position = mul(output.Position, World);
    output.Position = mul(output.Position, ViewProjection);
output.Normal = mul(output.Normal, World);
    return output;
}

float4 MyPixelShader(VertexPositionNormal input) : SV_Target
{
return -dot(LightDirection, input.Normal);
}


technique10 MyTechnique
{
pass MyPass
{
SetVertexShader( CompileShader( vs_5_0, MyVertexShader() ) );
SetPixelShader( CompileShader( ps_5_0, MyPixelShader() ) );
}
}




このプログラムは斜め手前から光のあたった回転する三角形を表示します。
光が垂直にあたったときは一番明るく、光が当たらなくなると暗くなります。
 
三角形の表面と光の方向を元に、三角形の明るさを計算しているのです。



















拍手[0回]

PR