Custom Fantasy World Map

February 19, 2013

aglarond1Open Street Map is a wonderful tool. One might say it an opensource clone of Google Maps. It has a built in editor and a permissive use license. There is a stand alone editor called JOSM that allows bulk edits. Interestingly enough you can use JOSM to create maps from scratch.

I play a weekly Dungeon & Dragons game. The dungeon master has created his own world and has provided sketches of the various continents, and some additional detail for the city-state we are playing in. This got me to thinking about taking those sketches and turning them into a OSM style tile map. Using JOSM (plus a plugin) I was able to import the sketches, scale them, and outline the landmasses, add in some roads, rivers, lakes, etc. It was interesting, but far from graphically pleasing.

Some more research turned up a product called TileMill that creates good looking map tiles. There is some middleware needed to import the JOSM data, but I managed to create my own set of custom tiles and post them on my groups website.

I haven’t quite gotten the hang of the city detail. TileMill seems to crash if I try to generate all 22 zoom levels. By default TileMill stores the tiles in a single MBTile format. Perhaps the file is just too big for my computer to manage. We are talking about 580,000+ tiles!

 

 

0

Beginnings of a Silverlight Map Drawing Toolset

January 9, 2012

Added a point, road/polyline, and area/polygon drawing toolset.  All items have handles which can be selected and dragged, similar to ArcGis, only less sophistated.  Dropdown box allows picking of named points (lets call them cities).  Selecting a city, centers the map to the appropraite point/city.

Roads are drawn when zoomed, but are collapsed (not-visible) when zoomed out.  Have a simplistic load/save feature for all vector items.  A default vector item list is preloaded at application startup.  Silverlight makes saving awkward and I haven’t figured out how to make a data web service on a unix server yet.

My working prototype is  available.

0

Faerun Tile Maps in Silverlight

January 6, 2012

I dabbled a bit in Silverlight this week and stumbled across a prototype of a custom map control that uses OpenStreetMap tile servers. For some reason MapTool came to mind and I threw together a Faerun Tile Server.

The results are surprisingly good for 3 hours worth of work. The Earth tile servers have up to 18 zoom levels. My Faeurn tile server has 4 zoom levels. The zoom level was largely a limitation of the original 4763×3185 image I nabbed. I used the standard process of carving the image into 256×256 tiles as described on OpenStreeMap site. This is the same basic technology that Bing & Google use.

I have also made an attempt at drawing polygons on top of the Faerun map. My creative side is imagining an OpenStreet map type of interface that renders MapTool style fantasy maps. Add some monster & hero tokens and you would have something pretty special.

One drawback to MapTool 1.3 is that the underlying map and vision blocking are not tied together. They are on seperate layers. The map is basicly a multilayer raster map. The vision blocking layer (VBL) is vector based. Creating the VBL is a pain and if change the underlying map, you have to redo portions of the VBL. I know RpTools is working on MapTool 1.4, perhaps they will entertain my suggestion.

If the map was vector based it might be possible to tie the map and vision blocking together. This might provide the possibility of auto generating the VBL.

Another bonus to making a fantasy map based on a geographic information system (GIS), is the ability to search for places and route via roads just like Google maps. The layering system for Dungeon Master vs Player is already built in to most of these tools.

I have made my SL_VectorMap Silverlight application temporarily available. I’ll likely make changes & upgrades later on.

0

Spherical projection on a cube

September 10, 2011

I decided to apply an earth texture to my sphere/cube. Standard technique is to use TEXCOORD, but things get a bit messy when you are working with a cube as your base. Particularly at the top and bottom faces. Traditionally you must have a vertex at the north and south pole to make the spherical projection work properly.  Since I want to leverage the direct3d11 tessellation features, I needed to find a different solution.  My first attempt was to calculate the TEXCOORD in the domain shader.

?View Code CSHARP
	float phi = acos(norm.y);
	float v = phi / PI ;
	float u = acos(max(min(norm.x / sin(phi), 1.0), -1.0)) / (2.0 * PI);
	if (norm.z >0 )
	{
		u = 1 - u;
	}
 	Output.vTexCoord.x = u;
	Output.vTexCoord.y = v;



However I found a nasty seam issue when the TEXCOORD.x wrapped from 1 to 0. In directx9 there is a WRAP0 function in the renderstate that supposedly resolves this issue.
But in directx10 and higher there is no such WRAP0 function, apparently it has been depreciated with no replacement. The articles I read suggested Microsoft recommends you leverage your underlying geometry to fix the 1 and 0 coordinates on a vertex. That is the solution I normally used with directx9 when I constructed my spheres by hand, doubling up on the vertices at the seam.

My second attempt was to move the spherical projection code into the hull shader. This worked great until I noticed the seam again. It is smaller (only a pixel wide) but extends across the z=0 plane. I struggled with this for several days, but eventually stumbled upon a solution. The trick is to do the UV calculations within the sampler call. I was making a separate u variable and then when normal.z < 0 was setting it to 1-u to account for the back side of the sphere/planet. Moving the 1-u into the sampler call magically fixed the seam. I suspect is has something to do with the precision of the float values, but I'm not really sure.

?View Code CSHARP
	float3 norm = normalize(input.vPlanetNormal);
	float phi = acos(norm.y);
	float v = phi / PI ;
	float u = -acos(max(min(norm.x / sin(phi), 1.0), -1.0)) / (2.0 * PI);
	float4 diffuse;
	float4 diffuse1 = g_texture.Sample(g_samLinear, float2(u,v));
	float4 diffuse2 =  g_texture.Sample(g_samLinear, float2(1-u,v));
	if (norm.z < 0)
		diffuse = diffuse2;
	else
		diffuse = diffuse1;



0

DepthStencilView & Transparency Woes

August 27, 2011


The SlimDX examples have no reference to the depth buffer. XNA creates one by default with SlimDX you have to create one yourself.

?Download effect.fx
Texture2DDescription depthdesc = new Texture2DDescription();
            depthdesc.BindFlags = BindFlags.DepthStencil;
            depthdesc.Format = Format.D32_Float_S8X24_UInt;
            depthdesc.Width = Window.Width;
            depthdesc.Height = Window.Height;
            depthdesc.MipLevels = 1;
            depthdesc.SampleDescription = new SampleDescription(1, 0);
            depthdesc.Usage = ResourceUsage.Default;
            depthdesc.OptionFlags = ResourceOptionFlags.None;
            depthdesc.CpuAccessFlags = CpuAccessFlags.None;
            depthdesc.ArraySize = 1;
Texture2D depthTexture = new Texture2D(GraphicsDevice, depthdesc);
depthStencilView = new DepthStencilView(GraphicsDevice, depthTexture);
GraphicsDevice.ImmediateContext.OutputMerger.SetTargets(depthStencilView, renderTargetView);

And for some reason the DepthStencilState is disabled by default, so you have to create a new DepthStencilState and apply it in your draw code.

?Download effect.fx
DepthStencilStateDescription dssd = new DepthStencilStateDescription
{
     IsDepthEnabled = true,
     IsStencilEnabled = false,
     DepthWriteMask = DepthWriteMask.All,
     DepthComparison = Comparison.Less,
};
depthStencilStateNormal = DepthStencilState.FromDescription(GraphicsDevice, dssd);
 
GraphicsDevice.ImmediateContext.OutputMerger.DepthStencilState = Game.depthStencilStateNormal;

This works almost as expected. That nasty sharedsurface workaround for text is troublesome. It is technically a texture drawn over the entire screen, most of the texture is transparent (Alpha=0) with the rest as test with an Alpha=1. The GPU doens’t care what the Alpha is, it treats the transparent pixels in my text overlay as “in front of” everything. So my nicely tessellated sphere dissapears. I hacked around a bit trying to turn off the depth functions for the text overlay, no luck so far. I did however find an interesting workaround. If you modify the text overlay pixel shader you can use the discard command to exit before writing anything, thus leaving the depthbuffer unchanged, also leaving my new sphere visible.

?Download effect.fx
float4 PS( PS_IN input ) : SV_Target
{
	float4 color =  g_textOverlay.Sample(g_samLinear, input.Tex);
	if (color.a <=0)
		discard;
	return color;
}

This seems to work for the moment. I suspect I will need to revisit transparency in the future. There are several articles on the Internet that discuss sorting transparent objects, and drawing them last. Seems like a lot of work, and since I have only 1 transparent object at the moment I’m going to defer the problem until later.

0

Cube to Sphere

August 27, 2011

I know my progress is slow, job, education, family, other hobbies, etc. I did manage to create a 3D cube and convert it into a sphere this week. The tried and true technique is to start with the 6 faces of a cube.

In XNA you create each face with 2 triangles. Converting that to a sphere is somewhat difficult in XNA. My solution was to use a quadtree for each face, then modify the vertices to rough out the sphere shape. The deeper the quadtree the better the sphere approximation. The end result seems to roughly resemble tessellation, but requires a lot of work.

With SlimDX Direct3d11 you create each face with a single quad using the PatchListWith4ControlPoints PrimitiveTopology. I modified the domain shader to reposition the vertex position based on a sphere.

?View Code CSHARP
[domain("quad")] 
DS_OUTPUT DS_quad( HS_CONSTANT_DATA_OUTPUT_quad input, 
	float2 UV : SV_DomainLocation, 
	const OutputPatch<HS_OUTPUT, 4> quad ) 
{ 
	DS_OUTPUT Output; 
	float3 verticalPos1 = lerp(quad[0].vPosition,quad[3].vPosition,UV.y);
	float3 verticalPos2 = lerp(quad[1].vPosition,quad[2].vPosition,UV.y);
	float3 finalPos = lerp(verticalPos1,verticalPos2,UV.x);
 
	float3 norm = normalize(finalPos);
	float3 spherePos = norm * g_fRadius;
 
	Output.vPosition = mul (float4(spherePos, 1), mul(g_mWorld, g_mViewProjection));
 
	float4 verticalColor1 = lerp(quad[0].vColor, quad[1].vColor, UV.y);
	float4 verticalColor2 = lerp(quad[3].vColor, quad[2].vColor, UV.y);
	Output.vColor =lerp(verticalColor1,verticalColor2,UV.x); 
	return Output; 
}

The tessellation is limited to a factor of 64, so I suspect the quadtree solution will need to be revisited. There is a possibility I will try some voxel terrain, but I’m not quite done with my initial foyer into DirectX 11.

2

Basic Triangle Tessellation

August 20, 2011

The next obious step after creating a simple traingle with some text is to figure out how DirectX 11 tessellation works.  Tessellation will likely be handy when I need level of detail with terrain patches.  In this case I’ve started with a simple triangle and 3 control points.  Each control point is a one-to-one match with the 3 verticies of the original triangle.

The C# code is practically identical to the previous post (and the MiniTri example).  There are only three easy changes. The first is to define a wireframe rasterstate:

?View Code CSHARP
RasterizerStateDescription rsd = new RasterizerStateDescription();
            rsd.CullMode = CullMode.Back;
            rsd.FillMode = FillMode.Wireframe;
            rsd.IsMultisampleEnabled = true;
            rsd.IsAntialiasedLineEnabled = true;
            rsd.IsDepthClipEnabled = false;
            rsd.IsScissorEnabled = false;
RasterState_WireFrame = RasterizerState.FromDescription(GraphicsDevice, rsd);

and set the rasterstate just prior to drawing the triangle:

?View Code CSHARP
GraphicsDevice.ImmediateContext.Rasterizer.State = RasterState_WireFrame;

Also don’t forget to set the rasterstate back to null just prior to drawing the text.

The second change is changing the PrimitiveTopology to PatchListWith3ControlPoints.

The third change is to create a new Effect or add an additional technique to an existing effect.fx file. The effect/technique is where all the tessellation happens.

My code is a complete mess at the moment so I’m not going to share it, but I will share my effect file. This effect file is base completed off of Understanding Shader Model 5.0 with DirectX 11. One gotcha is to make sure you set your HullShader and DomainShader to 0 in your any techniques that do not use Hull or Domain shaders (such as your Text technique).

?Download effect.fx
MATRIX g_mViewProjection;
float g_fTessellationFactor = 1;
 
struct VS_CONTROL_POINT_INPUT
{
	float3 vPosition : POSITION;
	float4 vColor : COLOR;
 
}; 
 
struct VS_CONTROL_POINT_OUTPUT
{
	float3 vPosition : POSITION;
	float4 vColor : COLOR;
};
 
struct HS_CONSTANT_DATA_OUTPUT
{
	float Edges[3] : SV_TessFactor;
	float Inside : SV_InsideTessFactor;
};
 
struct HS_OUTPUT
{
	float3 vPosition : POSITION;
	float4 vColor : COLOR;
};
 
VS_CONTROL_POINT_OUTPUT VS( VS_CONTROL_POINT_INPUT input )
{
	VS_CONTROL_POINT_OUTPUT output = (VS_CONTROL_POINT_INPUT)0;
 
	output.vPosition = input.vPosition;
	output.vColor = input.vColor;
 
	return output;
}
 
HS_CONSTANT_DATA_OUTPUT ConstantHS( InputPatch ip, uint PatchID : SV_PrimitiveID )
{
	HS_CONSTANT_DATA_OUTPUT Output;
	Output.Edges[0] = Output.Edges[1] = Output.Edges[2] = g_fTessellationFactor;
	Output.Inside = g_fTessellationFactor;
	return Output;
}
 
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("ConstantHS")]
HS_OUTPUT HS( InputPatch p,
	uint i : SV_OutputControlPointID,
	uint PatchID : SV_PrimitiveID )
{
	HS_OUTPUT Output;
	Output.vPosition = p[i].vPosition;
	Output.vColor = p[i].vColor;
	return Output;
}
 
struct DS_OUTPUT
{
	float4 vPosition : SV_POSITION;
	float4 vColor : COLOR;
};
 
[domain("tri")]
DS_OUTPUT DS( HS_CONSTANT_DATA_OUTPUT input,
	float3 UVW : SV_DomainLocation,
	const OutputPatch quad )
{
	DS_OUTPUT Output;
	float3 finalPos = UVW.x * quad[0].vPosition + UVW.y * quad[1].vPosition + UVW.z * quad[2].vPosition;
	Output.vPosition = float4(finalPos, 1);
	Output.vColor = UVW.x * quad[0].vColor + UVW.y * quad[1].vColor + UVW.z * quad[2].vColor;
	return Output;
}
 
float4 PS( DS_OUTPUT input ) : SV_Target
{
	return input.vColor;
}
 
technique11 TerrainQuad
{
	pass P0
	{
		SetHullShader(		CompileShader( hs_5_0, HS() ) );
		SetDomainShader(	CompileShader( ds_5_0, DS() ) );
		SetVertexShader(	CompileShader( vs_5_0, VS() ) );
		SetPixelShader(		CompileShader( ps_5_0, PS() ) );
	}
}

The tessellation factor is hardcoded in the effect file. Here are some examples of the tessellation:
 

Tessellation = 1


 

Tessellation = 2


 

Tessellation = 16


Tessellation = 64 (apparent maximum)

1

Drawing DirectX11 text using SlimDX

August 18, 2011

I am teaching myself how to program the GPU.  My end goal is to make a planet renderer and possibly a game based on the planet, however I’m having fun just learning graphics programming.

I have become dissatisfied with XNA and it’s lack of directX 11 support, so I started using SlimDX.

Oddly Microsoft doesn’t provide a straight forward mechanism to draw text onto a directX 11 surface.  The naive solution is to draw text onto a bitmap, convert it into a texture2D, then draw to the screen.  I tried this solution and the performance was horrible (45-50 fps).  After some searching around I found out Microsoft recommends using directX10 and a sharedsurface.

After searching the web I only found a few working C++ examples.  The SlimDX C# posts were mostly about how people couldn’t get drawText to work with directX 11.  These failed C# attempts along with the successful C++ examples gave me enough to create this unofficial tutorial.  The shared surface solution nets me over 1000 fps.

It really isn’t a tutorial, just the source code of how to draw text and use an effect with SlimDX and DirectX 11.

?View Code CSHARP
/*
 * Dx11TriangleText
 * Requires SlimDX (slimdx.org)
 * Intended as an unofficial Tutorial 4
 *
 * Shows how to use an Effect.
 * Shows how to drawText with DirectX 11 as discussed at
 * http://msdn.microsoft.com/en-us/library/ee913554(v=vs.85).aspx
 *
 * Coded by Aaron Auseth
 *
 * Freeware: The author, of this software accepts no responsibility for damages resulting
 * from the use of this product and makes no warranty or representation, either
 * express or implied, including but not limited to, any implied warranty of
 * merchantability or fitness for a particular purpose. This software is provided
 * "AS IS", and you, its user, assume all risks when using it.
 *
 * All I ask is that I be given credit if you use as a tutorial or for educational purposes.
 *
*/
 
using System.Windows.Forms;
using SlimDX;
using SlimDX.D3DCompiler;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using SlimDX.Windows;
using Device = SlimDX.Direct3D11.Device;
using Resource = SlimDX.Direct3D11.Resource;
using System.Runtime.InteropServices;
 
namespace Dx11TriangleText
{
    static class Program
    {
        // Vertex Structure
        // LayoutKind.Sequential is required to ensure the public variables
        // are written to the datastream in the correct order.
        [StructLayout(LayoutKind.Sequential)]
        struct VertexPositionColor
        {
            public Vector4 Position;
            public Color4 Color;
            public static readonly InputElement[] inputElements = new[] {
                new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
                new InputElement("COLOR",0,Format.R32G32B32A32_Float,16,0)
            };
            public static readonly int SizeInBytes = Marshal.SizeOf(typeof(VertexPositionColor));
            public VertexPositionColor(Vector4 position, Color4 color)
            {
                Position = position;
                Color = color;
            }
            public VertexPositionColor(Vector3 position, Color4 color)
            {
                Position = new Vector4(position, 1);
                Color = color;
            }
        }
 
        [StructLayout(LayoutKind.Sequential)]
        struct VertexPositionTexture
        {
            public Vector4 Position;
            public Vector2 TexCoord;
            public static readonly InputElement[] inputElements = new[] {
                new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
                new InputElement("TEXCOORD",0,Format.R32G32_Float, 16 ,0)
            };
            public static readonly int SizeInBytes = Marshal.SizeOf(typeof(VertexPositionTexture));
            public VertexPositionTexture(Vector4 position, Vector2 texCoord)
            {
                Position = position;
                TexCoord = texCoord;
            }
            public VertexPositionTexture(Vector3 position, Vector2 texCoord)
            {
                Position = new Vector4(position, 1);
                TexCoord = texCoord;
            }
        }
 
        static void Main()
        {
            SlimDX.Direct3D11.Device device11;
            SwapChain swapChain;
 
            // DirectX DXGI 1.1 factory
            Factory1 factory1 = new Factory1();
 
            // The 1st graphics adapter
            Adapter1 adapter1 = factory1.GetAdapter1(0);
 
            var form = new RenderForm("Tutorial 4: Dx11 Triangle + Text");
 
            var description = new SwapChainDescription()
            {
                BufferCount = 2,
                Usage = Usage.RenderTargetOutput,
                OutputHandle = form.Handle,
                IsWindowed = true,
                ModeDescription = new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm),
                SampleDescription = new SampleDescription(1, 0),
                Flags = SwapChainFlags.AllowModeSwitch,
                SwapEffect = SwapEffect.Discard
            };
 
            SlimDX.Direct3D11.Device.CreateWithSwapChain(adapter1, DeviceCreationFlags.Debug, description, out device11, out swapChain);
 
            // create a view of our render target, which is the backbuffer of the swap chain we just created
            RenderTargetView renderTarget;
            using (var resource = Resource.FromSwapChain(swapChain, 0))
                renderTarget = new RenderTargetView(device11, resource);
 
            // setting a viewport is required if you want to actually see anything
            var context = device11.ImmediateContext;
            var viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
            context.OutputMerger.SetTargets(renderTarget);
            context.Rasterizer.SetViewports(viewport);
 
            // A DirectX 10.1 device is required because DirectWrite/Direct2D are unable
            // to access DirectX11.  BgraSupport is required for DXGI interaction between
            // DirectX10/Direct2D/DirectWrite.
            SlimDX.Direct3D10_1.Device1 device10_1 = new SlimDX.Direct3D10_1.Device1(
                adapter1,
                SlimDX.Direct3D10.DriverType.Hardware,
                SlimDX.Direct3D10.DeviceCreationFlags.BgraSupport | SlimDX.Direct3D10.DeviceCreationFlags.Debug,
                SlimDX.Direct3D10_1.FeatureLevel.Level_10_0
            );
 
            // Create the DirectX11 texture2D.  This texture will be shared with the DirectX10
            // device.  The DirectX10 device will be used to render text onto this texture.  DirectX11
            // will then draw this texture (blended) onto the screen.
            // The KeyedMutex flag is required in order to share this resource.
            SlimDX.Direct3D11.Texture2D textureD3D11 = new Texture2D(device11, new Texture2DDescription
            {
                Width = form.Width,
                Height = form.Height,
                MipLevels = 1,
                ArraySize = 1,
                Format = Format.B8G8R8A8_UNorm,
                SampleDescription = new SampleDescription(1, 0),
                Usage = ResourceUsage.Default,
                BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
                CpuAccessFlags = CpuAccessFlags.None,
                OptionFlags = ResourceOptionFlags.KeyedMutex
            });
 
            // A DirectX10 Texture2D sharing the DirectX11 Texture2D
            SlimDX.DXGI.Resource sharedResource = new SlimDX.DXGI.Resource(textureD3D11);
            SlimDX.Direct3D10.Texture2D textureD3D10 = device10_1.OpenSharedResource(sharedResource.SharedHandle);
 
            // The KeyedMutex is used just prior to writing to textureD3D11 or textureD3D10.
            // This is how DirectX knows which DirectX (10 or 11) is supposed to be writing
            // to the shared texture.  The keyedMutex is just defined here, they will be used
            // a bit later.
            KeyedMutex mutexD3D10 = new KeyedMutex(textureD3D10);
            KeyedMutex mutexD3D11 = new KeyedMutex(textureD3D11);
 
            // Direct2D Factory
            SlimDX.Direct2D.Factory d2Factory = new SlimDX.Direct2D.Factory(
                SlimDX.Direct2D.FactoryType.SingleThreaded,
                SlimDX.Direct2D.DebugLevel.Information
            );
 
            // Direct Write factory
            SlimDX.DirectWrite.Factory dwFactory = new SlimDX.DirectWrite.Factory(
                SlimDX.DirectWrite.FactoryType.Isolated
            );
 
            // The textFormat we will use to draw text with
            SlimDX.DirectWrite.TextFormat textFormat = new SlimDX.DirectWrite.TextFormat(
                dwFactory,
                "Arial",
                SlimDX.DirectWrite.FontWeight.Normal,
                SlimDX.DirectWrite.FontStyle.Normal,
                SlimDX.DirectWrite.FontStretch.Normal,
                24,
                "en-US"
            );
            textFormat.TextAlignment = SlimDX.DirectWrite.TextAlignment.Center;
            textFormat.ParagraphAlignment = SlimDX.DirectWrite.ParagraphAlignment.Center;
 
            // Query for a IDXGISurface.
            // DirectWrite and DirectX10 can interoperate thru DXGI.
            Surface surface = textureD3D10.AsSurface();
            SlimDX.Direct2D.RenderTargetProperties rtp = new SlimDX.Direct2D.RenderTargetProperties();
            rtp.MinimumFeatureLevel = SlimDX.Direct2D.FeatureLevel.Direct3D10;
            rtp.Type = SlimDX.Direct2D.RenderTargetType.Hardware;
            rtp.Usage = SlimDX.Direct2D.RenderTargetUsage.None;
            rtp.PixelFormat = new SlimDX.Direct2D.PixelFormat(Format.Unknown, SlimDX.Direct2D.AlphaMode.Premultiplied);
            SlimDX.Direct2D.RenderTarget dwRenderTarget = SlimDX.Direct2D.RenderTarget.FromDXGI(d2Factory, surface, rtp);
 
            // Brush used to DrawText
            SlimDX.Direct2D.SolidColorBrush brushSolidWhite = new SlimDX.Direct2D.SolidColorBrush(
                dwRenderTarget,
                new Color4(1, 1, 1, 1)
            );
 
            // Think of the shared textureD3D10 as an overlay.
            // The overlay needs to show the text but let the underlying triangle (or whatever)
            // show thru, which is accomplished by blending.
            BlendStateDescription bsd = new BlendStateDescription();
            bsd.RenderTargets[0].BlendEnable = true;
            bsd.RenderTargets[0].SourceBlend = BlendOption.SourceAlpha;
            bsd.RenderTargets[0].DestinationBlend = BlendOption.InverseSourceAlpha;
            bsd.RenderTargets[0].BlendOperation = BlendOperation.Add;
            bsd.RenderTargets[0].SourceBlendAlpha = BlendOption.One;
            bsd.RenderTargets[0].DestinationBlendAlpha = BlendOption.Zero;
            bsd.RenderTargets[0].BlendOperationAlpha = BlendOperation.Add;
            bsd.RenderTargets[0].RenderTargetWriteMask = ColorWriteMaskFlags.All;
            BlendState BlendState_Transparent = BlendState.FromDescription(device11, bsd);
 
            // Load Effect. This includes both the vertex and pixel shaders.
            // Also can include more than one technique.
            ShaderBytecode shaderByteCode = ShaderBytecode.CompileFromFile(
                "effectDx11.fx",
                "fx_5_0",
                ShaderFlags.EnableStrictness,
                EffectFlags.None);
 
            Effect effect = new Effect(device11, shaderByteCode);
 
            // create triangle vertex data, making sure to rewind the stream afterward
            var verticesTriangle = new DataStream(VertexPositionColor.SizeInBytes * 3, true, true);
            verticesTriangle.Write(
                new VertexPositionColor(
                    new Vector3(0.0f, 0.5f, 0.5f),
                    new Color4(1.0f, 0.0f, 0.0f, 1.0f)
                )
            );
            verticesTriangle.Write(
                new VertexPositionColor(
                    new Vector3(0.5f, -0.5f, 0.5f),
                    new Color4(0.0f, 1.0f, 0.0f, 1.0f)
                )
            );
            verticesTriangle.Write(
                new VertexPositionColor(
                    new Vector3(-0.5f, -0.5f, 0.5f),
                    new Color4(0.0f, 0.0f, 1.0f, 1.0f)
                )
            );
 
            verticesTriangle.Position = 0;
 
            // create the triangle vertex layout and buffer
            InputLayout layoutColor = new InputLayout(device11, effect.GetTechniqueByName("Color").GetPassByIndex(0).Description.Signature, VertexPositionColor.inputElements);
            Buffer vertexBufferColor = new Buffer(device11, verticesTriangle, (int)verticesTriangle.Length, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            verticesTriangle.Close();
 
            // create text vertex data, making sure to rewind the stream afterward
            // Top Left of screen is -1, +1
            // Bottom Right of screen is +1, -1
            var verticesText = new DataStream(VertexPositionTexture.SizeInBytes * 4, true, true);
            verticesText.Write(
                new VertexPositionTexture(
                        new Vector3(-1, 1, 0),
                        new Vector2(0, 0f)
                )
            );
            verticesText.Write(
                new VertexPositionTexture(
                    new Vector3(1, 1, 0),
                    new Vector2(1, 0)
                )
            );
            verticesText.Write(
                new VertexPositionTexture(
                    new Vector3(-1, -1, 0),
                    new Vector2(0, 1)
                )
            );
            verticesText.Write(
                new VertexPositionTexture(
                    new Vector3(1, -1, 0),
                    new Vector2(1, 1)
                )
            );
 
            verticesText.Position = 0;
 
            // create the text vertex layout and buffer
            InputLayout layoutText = new InputLayout(device11, effect.GetTechniqueByName("Text").GetPassByIndex(0).Description.Signature, VertexPositionTexture.inputElements);
            Buffer vertexBufferText = new Buffer(device11, verticesText, (int)verticesText.Length, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
            verticesText.Close();
 
            // prevent DXGI handling of alt+enter, which doesn't work properly with Winforms
            factory1.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);
 
            // handle alt+enter ourselves
            form.KeyDown += (o, e) =&gt;
            {
                if (e.Alt &amp;&amp; e.KeyCode == Keys.Enter)
                    swapChain.IsFullScreen = !swapChain.IsFullScreen;
            };
 
            // handle form size changes
            form.UserResized += (o, e) =&gt;
            {
                renderTarget.Dispose();
 
                swapChain.ResizeBuffers(2, 0, 0, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);
                using (var resource = Resource.FromSwapChain(swapChain, 0))
                    renderTarget = new RenderTargetView(device11, resource);
 
                context.OutputMerger.SetTargets(renderTarget);
            };
 
            MessagePump.Run(form, () =&gt;
            {
                // clear the render target to black
                context.ClearRenderTargetView(renderTarget, new Color4(0, 0, 0));
 
                // Draw the triangle
                // configure the Input Assembler portion of the pipeline with the vertex data
                context.InputAssembler.InputLayout = layoutColor;
                context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBufferColor, VertexPositionColor.SizeInBytes, 0));
                context.OutputMerger.BlendState = null;
                EffectTechnique currentTechnique = effect.GetTechniqueByName("Color");
                for (int pass = 0; pass &lt; currentTechnique.Description.PassCount; ++pass)
                {
                    EffectPass Pass = currentTechnique.GetPassByIndex(pass);
                    System.Diagnostics.Debug.Assert(Pass.IsValid, "Invalid EffectPass");
                    Pass.Apply(context);
                    context.Draw(3, 0);
                };
 
                // Draw Text on the shared Texture2D
                // Need to Acquire the shared texture for use with DirectX10
                mutexD3D10.Acquire(0, 100);
                dwRenderTarget.BeginDraw();
                dwRenderTarget.Clear(new Color4(0, 0, 0, 0));
                string text = adapter1.Description1.Description;
                dwRenderTarget.DrawText(text, textFormat, new System.Drawing.Rectangle(0, 0, form.Width, form.Height), brushSolidWhite);
                dwRenderTarget.EndDraw();
                mutexD3D10.Release(0);
 
                // Draw the shared texture2D onto the screen
                // Need to Aquire the shared texture for use with DirectX11
                mutexD3D11.Acquire(0, 100);
                ShaderResourceView srv = new ShaderResourceView(device11, textureD3D11);
                effect.GetVariableByName("g_textOverlay").AsResource().SetResource(srv);
                context.InputAssembler.InputLayout = layoutText;
                context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleStrip;
                context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBufferText, VertexPositionTexture.SizeInBytes, 0));
                context.OutputMerger.BlendState = BlendState_Transparent;
                currentTechnique = effect.GetTechniqueByName("Text");
                for (int pass = 0; pass &lt; currentTechnique.Description.PassCount; ++pass)
                {
                    EffectPass Pass = currentTechnique.GetPassByIndex(pass);
                    System.Diagnostics.Debug.Assert(Pass.IsValid, "Invalid EffectPass");
                    Pass.Apply(context);
                    context.Draw(4, 0);
                }
                srv.Dispose();
                mutexD3D11.Release(0);
 
                swapChain.Present(0, PresentFlags.None);
            });
 
            // clean up all resources
            // anything we missed will show up in the debug output
 
            vertexBufferColor.Dispose();
            vertexBufferText.Dispose();
            layoutColor.Dispose();
            layoutText.Dispose();
            effect.Dispose();
            shaderByteCode.Dispose();
            renderTarget.Dispose();
            swapChain.Dispose();
            device11.Dispose();
            device10_1.Dispose();
            mutexD3D10.Dispose();
            mutexD3D11.Dispose();
            textureD3D10.Dispose();
            textureD3D11.Dispose();
            factory1.Dispose();
            adapter1.Dispose();
            sharedResource.Dispose();
            d2Factory.Dispose();
            dwFactory.Dispose();
            textFormat.Dispose();
            surface.Dispose();
            dwRenderTarget.Dispose();
            brushSolidWhite.Dispose();
            BlendState_Transparent.Dispose();
 
        }
    }
}
?View Code CSHARP
Texture2D g_textOverlay;
 
SamplerState g_samLinear
{
	Filter = MIN_MAG_MIP_LINEAR;
	AddressU = CLAMP;
	AddressV = CLAMP;
};
 
// ------------------------------------------------------
// A very simple shader
// ------------------------------------------------------
 
float4 SimpleVS(float4 position : POSITION) : SV_POSITION
{
	return position;
}
 
float4 SimplePS(float4 position : SV_POSITION) : SV_Target
{
	return float4(1.0f, 1.0f, 0.0f, 1.0f);
}
 
// ------------------------------------------------------
// A shader that accepts Position and Color
// ------------------------------------------------------
 
struct ColorVS_IN
{
	float4 pos : POSITION;
	float4 col : COLOR;
};
 
struct ColorPS_IN
{
	float4 pos : SV_POSITION;
	float4 col : COLOR;
};
 
ColorPS_IN ColorVS( ColorVS_IN input )
{
	ColorPS_IN output = (ColorPS_IN)0;
	output.pos = input.pos;
	output.col = input.col;
	return output;
}
 
float4 ColorPS( ColorPS_IN input ) : SV_Target
{
	return input.col;
}
 
// ------------------------------------------------------
// A shader that accepts Position and Texture
// Used as a text overlay
// ------------------------------------------------------
 
struct TextVS_IN
{
	float4 pos : POSITION;
	float2 tex : TEXCOORD0;
};
 
struct TextPS_IN
{
	float4 pos : SV_POSITION;
	float2 tex : TEXCOORD0;
};
 
TextPS_IN TextVS( TextVS_IN input )
{
	TextPS_IN output = (TextPS_IN)0;
	output.pos = input.pos;
	output.tex = input.tex;
	return output;
}
 
float4 TextPS( TextPS_IN input ) : SV_Target
{
	float4 color =  g_textOverlay.Sample(g_samLinear, input.tex);
	return color;
}
 
// ------------------------------------------------------
// Techniques
// ------------------------------------------------------
 
technique11 Simple
{
	pass P0
	{
		SetGeometryShader( 0 );
		SetVertexShader( CompileShader( vs_4_0, SimpleVS() ) );
		SetPixelShader( CompileShader( ps_4_0, SimplePS() ) );
	}
}
 
technique11 Color
{
	pass P0
	{
		SetGeometryShader( 0 );
		SetVertexShader( CompileShader( vs_4_0, ColorVS() ) );
		SetPixelShader( CompileShader( ps_4_0, ColorPS() ) );
	}
}
 
technique11 Text
{
	pass P0
	{
		SetGeometryShader( 0 );
		SetVertexShader( CompileShader( vs_4_0, TextVS() ) );
		SetPixelShader( CompileShader( ps_4_0, TextPS() ) );
	}
}
6

Planetary Horizon Culling

August 17, 2011

I overwrote my old blog site. The only article worth saving was this one:

I have started a planet renderer based on a quadtree structure.I needed to find a good horizon culling technique and the best one I found was at CrappyCoding, although it lacks a robust method to handle mountains over the horizon that are actually visible. I reworked the algorithm to use bounding spheres.I create a bounding sphere for each quad, and a horizon culling bounding sphere (HCBS). The HCBS starts with Dave Carlile’s technique but instead of keeping track of the angle between the camera vector and radius vector I keep track of the length of the horizon line (h). I then use ray casting to determine the intersection with the larger radius that includes the maximum mountain height.When processing the planet terrain quads I can compare the HCBS with the terrain quad bounding sphere. If they don’t intersect then it is culled.

Bounding Sphere Trigonometry

 

?Download download.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Determine Length of h (see diagram)
// Pythagoreans theorem
// a2 + b2 = c2 (basic theorem)
// h2 + r2 = c2 (h = horizon, r = radius, c = camera)
float r = radius;
float r2 = r * r;
float c = Game1.cameraComp.Camera.Position.Length();
float c2 = c * c;
float h2 = c2-r2;
float h = (float)Math.Sqrt(h2);
 
// Horizon Vector Length
// Determine arbritary point on horizon
double angleCamera = Math.Asin(radius / Game1.cameraComp.Camera.Position.Length());
Vector3 horizonNormal = Vector3.Normalize(newVector3((float)Math.Cos(angleCamera), (float)Math.Sin(angleCamera), 0));
Vector3 horizonPosition = Game1.cameraComp.Camera.Position + horizonNormal * h;
 
// Pick a point along the horizon line (h) that is well outside of the planet
// and then cast the ray back along the horizon line to determine a point
// on the expanded planetary bounding sphere. The expanded sphere includes the
// highest point (mountain).
Ray horizonRay = newRay(horizonPosition + horizonNormal * r, -horizonNormal);
BoundingSphere expandedPlanetSphere = newBoundingSphere(Vector3.Zero, r + highestPoint);float expandedDistance = horizonRay.Intersects(expandedPlanetSphere).Value;
 
// Make the bouding sphere (around camera) that represents any point within
// the expanded horizon
horizonSphere = newBoundingSphere(Game1.cameraComp.Camera.Position, h + expandedDistance);
0