忍者ブログ

Memeplexes

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

[XNA] 高さマップでバンプマッピング

前回は法線マップでバンプマッピングをしましたが、法線はどうもエディタで描きにくいので、今度は代わりに高さマップを使ってバンプマッピングをしてみることにしました。

今回使うのはこいつです:

HeightMap.png
HeightMap.png

しばらく起動していなかったフォトショップ(の、エレメント)を使って、まずはじめに黒く塗りつぶし、そのあと白で文字を書いています。
黒いところは高さが低く、白いところは高いつもりです。
ですから、これをバンプマッピングに使うとXNAという文字が飛び出して見えるはずです。

結果を先に言うと、四角形を動かしまくらないとライトの位置が把握しにくいからか、飛び出しているというよりもへこんでいるように見えますね・・・。

heightMappingResult1.jpg

ライトは右手前から左奥に向かって照らしているのですが、ちょっと動かしたくらいでは、人間の心理的効果からか、飛び出しているかへこんでいるかの判定はビミョーです(ネッカーキューブを思い出します)

heightMappingResult2.jpg
でも一応バンプマッピングが働いているのは確かなので、文句は言わないことにしましょう!



コード

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

struct VertexPositionNormalTextureTangent
{
    public Vector3 Position;
    public Vector3 Normal;
    public Vector2 TextureCoordinate;
    public Vector3 NormalMapXDirection;

    public VertexPositionNormalTextureTangent(
        Vector3 position,
        Vector3 normal,
        Vector2 textureCoordinate,
        Vector3 normalMapXDirection)
    {
        this.Position = position;
        this.Normal = normal;
        this.TextureCoordinate = textureCoordinate;
        this.NormalMapXDirection = normalMapXDirection;
    }

    public static readonly VertexElement[] VertexElements = {
        new VertexElement(
            0,
            0,
            VertexElementFormat.Vector3,
            VertexElementMethod.Default, 
            VertexElementUsage.Position,
            0),
        new VertexElement(
            0,
            sizeof(float) * 3, 
            VertexElementFormat.Vector3, 
            VertexElementMethod.Default,
            VertexElementUsage.Normal,
            0),
        new VertexElement(
            0, 
            sizeof(float) * (3 + 3), 
            VertexElementFormat.Vector2, 
            VertexElementMethod.Default, 
            VertexElementUsage.TextureCoordinate, 
            0),
        new VertexElement(
            0,
            sizeof(float) * (3 + 3 + 2),
            VertexElementFormat.Vector3, 
            VertexElementMethod.Default, 
            VertexElementUsage.TextureCoordinate,
            1)
    };
}

public class MyGame : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;

    Effect effect;

    //四角形の頂点です。
    VertexPositionNormalTextureTangent[] rectangleVertices =
    {
        new VertexPositionNormalTextureTangent(
            new Vector3(-1, 1, 0), Vector3.UnitZ, new Vector2(), Vector3.UnitX
        ),
        new VertexPositionNormalTextureTangent(
            new Vector3(1,1,0), Vector3.UnitZ, new Vector2(1,0), Vector3.UnitX
        ),
        new VertexPositionNormalTextureTangent(
            new Vector3(-1,-1,0), Vector3.UnitZ, new Vector2(0,1), Vector3.UnitX
        ),

        new VertexPositionNormalTextureTangent(
            new Vector3(1,1,0), Vector3.UnitZ, new Vector2(1,0), Vector3.UnitX
        ),
        new VertexPositionNormalTextureTangent(
            new Vector3(1,-1,0), Vector3.UnitZ, new Vector2(1,1), Vector3.UnitX
        ),
        new VertexPositionNormalTextureTangent(
            new Vector3(-1,-1,0), Vector3.UnitZ, new Vector2(0, 1), Vector3.UnitX
        )
    };

    Matrix worldTransform = Matrix.Identity;


    public MyGame()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";
    }

    protected override void LoadContent()
    {
        effect = Content.Load<Effect>("BumpMapping");
        effect.Parameters["View"].SetValue(
            Matrix.CreateLookAt(new Vector3(0, 0, 3), new Vector3(), Vector3.Up)
            );
        effect.Parameters["Projection"].SetValue(
            Matrix.CreatePerspectiveFieldOfView(
            MathHelper.ToRadians(90),
            (float)GraphicsDevice.Viewport.Width / GraphicsDevice.Viewport.Height,
            0.1f, 1000
            ));
        effect.Parameters["Light0Direction"].SetValue(
            new Vector3(-1, 0, -1)
            );
        effect.Parameters["HeightMap"].SetValue(
            Content.Load<Texture2D>("HeightMap")
            );


        GraphicsDevice.VertexDeclaration = new VertexDeclaration(
            GraphicsDevice,
            VertexPositionNormalTextureTangent.VertexElements
            );
    }

    //↑↓←→キーで
    //四角形を回転します。
    protected override void Update(GameTime gameTime)
    {
        KeyboardState keyboardState = Keyboard.GetState();

        if (keyboardState.IsKeyDown(Keys.Left))
        {
            worldTransform *= Matrix.CreateRotationY(-0.03f);
        }
        if (keyboardState.IsKeyDown(Keys.Right))
        {
            worldTransform *= Matrix.CreateRotationY(0.03f);
        }
        if (keyboardState.IsKeyDown(Keys.Up))
        {
            worldTransform *= Matrix.CreateRotationX(-0.03f);
        }
        if (keyboardState.IsKeyDown(Keys.Down))
        {
            worldTransform *= Matrix.CreateRotationX(0.03f);
        }
    }


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

        effect.Parameters["World"].SetValue(worldTransform);


        effect.Begin();
        effect.CurrentTechnique.Passes[0].Begin();

        GraphicsDevice.DrawUserPrimitives<VertexPositionNormalTextureTangent>(
            PrimitiveType.TriangleList,
            rectangleVertices,
            0,
            2
            );

        effect.CurrentTechnique.Passes[0].End();
        effect.End();

        base.Draw(gameTime);
    }


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


BumpMapping.fx
float4x4 World;
float4x4 View;
float4x4 Projection;

float3 Light0Direction;

texture HeightMap;
sampler HeightSampler = sampler_state
{
	Texture = (HeightMap);
};

struct VertexShaderInput
{
	float4 Position : POSITION;
	float4 Normal : NORMAL;
	float2 TextureCoordinate : TEXCOORD0;
	float4 NormalMapXDirection : TEXCOORD1;
};

struct VertexShaderOutput
{
	float4 Position : POSITION0;
	float2 TextureCoordinate : TEXCOORD0;
	float3 LightDirectionToPolygon : TEXCOORD1;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
	//Position
	VertexShaderOutput output;
	float4 worldPosition = mul(input.Position, World);
	float4 viewPosition = mul(worldPosition, View);
	output.Position = mul(viewPosition, Projection);
    
	//TextureCoordinate
	output.TextureCoordinate = input.TextureCoordinate;
    
	//LightDirectionToPolygon
	float3 zDirection = normalize(mul(input.Normal, (float3x3)World));
	float3 xDirection = normalize(mul(input.NormalMapXDirection, (float3x3)World));
	float3 normalizedLight0Direction = normalize(Light0Direction);
	output.LightDirectionToPolygon = float3(
		dot(normalizedLight0Direction, xDirection),
		dot(normalizedLight0Direction, cross(zDirection, xDirection)),
		dot(normalizedLight0Direction, zDirection)
	);
	
	
    return output;
}

float3 getNormal(float2 texCoord)
{
	const float HeightMapWidth = 128;
	const float HeightMapHeight = 128;
	
	float3 current = 2 * tex2D(HeightSampler, texCoord);
	float3 left = 2 * tex2D(
		HeightSampler,
		float2(texCoord.x - 1.0 / HeightMapWidth, texCoord.y)
	 );
	float3 up = 2 * tex2D(
		HeightSampler, 
		float2(texCoord.x, texCoord.y - 1.0 / HeightMapHeight)
		);
	return normalize(float3(left.x - current.x, up.x - current.x, 1));
}

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
	float4 diffuse = dot(
		getNormal(input.TextureCoordinate),
		-input.LightDirectionToPolygon
		);
	
	return diffuse;
}

technique Technique1
{
	pass Pass1
	{
		VertexShader = compile vs_1_1 VertexShaderFunction();
		PixelShader = compile ps_2_0 PixelShaderFunction();
	}
}




拍手[0回]

PR