tag:blogger.com,1999:blog-47221992901681502382024-03-19T03:43:05.269+01:00Apparat - Engineering a 3D Engine with C# and SlimDXDownload current Version here:
<a href="http://apparat.codeplex.com/">http://apparat.codeplex.com/</a>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.comBlogger32125tag:blogger.com,1999:blog-4722199290168150238.post-18801059086259569592013-11-03T20:39:00.001+01:002013-11-03T20:39:45.855+01:00Making a Screenshot with SlimDX<h2>
Making a Screenshot with SlimDX</h2>
<div>
<br /></div>
<div>
Making a screenshot is rather simple with SlimDX. In general, you take</div>
<div>
the render surface, read its content and save it to a file.</div>
<div>
Luckily, there is a function for this in the SDK:</div>
<div>
<br /></div>
<div>
<span style="font-family: Courier New, Courier, monospace;">SlimDX.Direct3D11.Resource.SaveTextureToFile</span><br />
<br />
<span style="font-family: inherit;">This is a code fragment using this method:</span><br />
<span style="font-family: inherit;"><br /></span>
<br />
<pre class="brush:csharp"> SlimDX.Direct3D11.Resource.SaveTextureToFile(dm.context,
dm.renderTarget.Resource,
SlimDX.Direct3D11.ImageFileFormat.Jpg,
"screenshot.jpg");
</pre>
<br />
You can make a function to encapsulate the call and call this function at the end of your render loop.<br />
I made it this way:<br />
<br />
<br /></div>
<pre class="brush:csharp">public void RenderScene()
{
while (true)
{
if (resize)
{
DeviceManager.Instance.Resize();
resize = false;
}
fc.Count();
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearDepthStencilView(dm.depthStencil,
DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil,
1.0f,
0);
dm.context.ClearRenderTargetView(dm.renderTarget,
new Color4(0.75f, 0.75f, 0.75f));
Scene.Instance.Render();
dm.swapChain.Present(syncInterval, PresentFlags.None);
if (makeScreenshot)
{
screenShots.MakeScreenshot(DeviceManager.Instance, ImageFileFormat.Jpg);
makeScreenshot = false;
}
}
}
</pre>
<div>
<br />
The boolean value <span style="font-family: Courier New, Courier, monospace;">makeScreenshot</span> has to be triggered from outside this class, for example<br />
from you GUI code. The screenshot is made with the current width and height of the render window.<br />
<br />
If you want to capture the frame in an other resolution, you have to render the scene to the render surface,<br />
reset and resize the buffers to the wanted resolution, write the screenshot and then resize the buffers<br />
back to the current resolution of the window you are rendering to.<br />
<br />
This is, how my helper method looks, for rendering a screenshot with a different resolution:<br />
<br />
<br />
<pre class="brush:csharp">public void MakeScreenshot(DeviceManager dm, Scene scene, int width, int height, SlimDX.Direct3D11.ImageFileFormat format, string fileName)
{
try
{
Viewport OriginalViewport = dm.viewport;
dm.Resize(width, height);
dm.context.ClearDepthStencilView(dm.depthStencil,
DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil,
1.0f, 0);
dm.context.ClearRenderTargetView(dm.renderTarget,
new Color4(0.75f, 0.75f, 0.75f));
scene.Render();
SlimDX.Direct3D11.Resource.SaveTextureToFile(dm.context,
dm.renderTarget.Resource,
format,
fileName + "." + format.ToString());
dm.Resize((int)OriginalViewport.Width, (int)OriginalViewport.Height);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
</pre>
<br />
This is, for example, a screenshot with a resolution of 3000x2000:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCunxktNRHJV-ro7nL_H78To4U9PEfY1blMSpY-NNxVc2DQxT6q9WGh16RMcBQVx6U2YRq5isakMCp3LTF1mJXDMC9NA1WFdKHf4ng3xlWkqiQhD4LUNnhNg1tqj2SKeCVG6Dofzf6ecJM/s1600/Screenshot.Jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCunxktNRHJV-ro7nL_H78To4U9PEfY1blMSpY-NNxVc2DQxT6q9WGh16RMcBQVx6U2YRq5isakMCp3LTF1mJXDMC9NA1WFdKHf4ng3xlWkqiQhD4LUNnhNg1tqj2SKeCVG6Dofzf6ecJM/s1600/Screenshot.Jpg" height="213" width="320" /></a></div>
<br />
When using the png format, the colors are somehow false. I don't know if it is problem in the<br />
SDK or if I am using a wrong format.</div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-33465416937756438052013-04-28T19:07:00.002+02:002013-04-28T19:07:56.759+02:00Resizing the RenderControl and Buffers<h3>
Problem</h3>
Up to now we did not the address resizing the buffer of the render target and depth/stencil buffer when resizing the control we are rendering on. This means the buffers stay the same when resizing the control and after a resize the rendered image looks pixelated and has the wrong aspect ratio which leads to a distorted image:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHYg0Fxr5793nsvtp7gqS2eFTAkW6ccM5RI3cmeNx8pq7A83-sTn11HbyU-XtvKP6OQIWQIEt_EY40wMlCFOA4d9tfN3J_J1TP2xuc7yc-noNAZy6sBE2BKkglK9xNT0h4f3FCWImX557a/s1600/Resize1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHYg0Fxr5793nsvtp7gqS2eFTAkW6ccM5RI3cmeNx8pq7A83-sTn11HbyU-XtvKP6OQIWQIEt_EY40wMlCFOA4d9tfN3J_J1TP2xuc7yc-noNAZy6sBE2BKkglK9xNT0h4f3FCWImX557a/s1600/Resize1.PNG" height="240" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiQybvDu3J6XeXBC7Din2G74GkCZJ5a6rVMRIcydv2cciITtOotdkjooPqZHktEad2V4lHUh2o0pHX3wyY9slDKraLbdiWormElSJUu0MtqWchtbYU6QakrcroAFvGxDiuCh35pb7uJ-nh/s1600/Resize2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiQybvDu3J6XeXBC7Din2G74GkCZJ5a6rVMRIcydv2cciITtOotdkjooPqZHktEad2V4lHUh2o0pHX3wyY9slDKraLbdiWormElSJUu0MtqWchtbYU6QakrcroAFvGxDiuCh35pb7uJ-nh/s1600/Resize2.PNG" height="174" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
When resizing, we have to perform several steps:<br />
<br />
<ol>
<li>Dispose resources bound to the render target</li>
<li>Dipose the render target itself</li>
<li>Dispose the depth buffer</li>
<li>Resize the buffers of the swap chain</li>
<li>Create a new render target buffer from the swap chain</li>
<li>Create a new render target</li>
<li>Create a new depth/stencil buffer</li>
<li>Create a new viewport and assign it to the rasterizer</li>
<li>Set the render target buffer and the depth/stencil buffer in the output merger</li>
<li>Take care, that the new aspect ratio is used, when creating the perspective matrix of the camera</li>
</ol>
<div>
As you can see, these are all critical resources when rendering. So we also have to take care that resizing is performed, while none of these resources are used during the rendering process. For example: disposing the render target while writing to it in the render thread is not a good idea and leads to an exception and might crash your application.</div>
<br />
<br />
In order to prevent accessing these resources while rendering, I check if a resize is needed in every render cycle <i>before </i>the actual rendering happens.<br />
<br />
<h3>
Concept</h3>
There are mainly three objects involved for a resize operation:<br />
<br />
<ol>
<li>The RenderControl: the control I am rendering to</li>
<li>DeviceManager: responsible for managing the device and its buffers</li>
<li>RenderManager: responsible for rendering the scene and where the render thread is</li>
</ol>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq7Iux7mxFn7iyObZFvuMsmm-QYAlqdep9qJJZgKSlDktRe2vx8k9ow5qgk0fnRww1Ol_6JgJt2tAfibTLWwAoOrqXuVd9wqrFJYhhO7TkTpWvKP26yLEo-5z3zOyywW1gL9PhfWqDkzM2/s1600/ResizeConcept.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjq7Iux7mxFn7iyObZFvuMsmm-QYAlqdep9qJJZgKSlDktRe2vx8k9ow5qgk0fnRww1Ol_6JgJt2tAfibTLWwAoOrqXuVd9wqrFJYhhO7TkTpWvKP26yLEo-5z3zOyywW1gL9PhfWqDkzM2/s1600/ResizeConcept.png" height="233" width="640" /></a></div>
<br />
If a resize of the <span style="font-family: Courier New, Courier, monospace;">RenderControl </span>occurs, I set the <span style="font-family: Courier New, Courier, monospace;">resize </span>variable in the <span style="font-family: Courier New, Courier, monospace;">RenderManager </span>true. This resize variable is checked in every frame. If it is true, I call the <span style="font-family: Courier New, Courier, monospace;">Resize </span>method of the <span style="font-family: Courier New, Courier, monospace;">DeviceManager </span>and the buffers are resized there. The <span style="font-family: Courier New, Courier, monospace;">DeviceManager</span> holds a reference to the <span style="font-family: Courier New, Courier, monospace;">RenderControl</span>, so we know about the new width and height of the <span style="font-family: Courier New, Courier, monospace;">RenderControl</span>.<br />
<br />
The <span style="font-family: Courier New, Courier, monospace;">resize </span>variable in the <span style="font-family: Courier New, Courier, monospace;">RenderManager </span>is set back to false and we can continue rendering with new buffers.<br />
<br />
<h3>
RenderControl</h3>
All we have to do in the <span style="font-family: Courier New, Courier, monospace;">RenderControl </span>is to handle the resize event and to set the <span style="font-family: Courier New, Courier, monospace;">resize </span>variable in the <span style="font-family: Courier New, Courier, monospace;">RenderManager </span>from here to true:<br />
<br />
<pre class="brush:csharp">private void RenderControl_Resize(object sender, EventArgs e)
{
RenderManager.Instance.resize = true;
}
</pre>
<br />
<h3>
RenderManager</h3>
If the resize event in the <span style="font-family: Courier New, Courier, monospace;">RenderControl </span>is triggered and the <span style="font-family: Courier New, Courier, monospace;">resize </span>variable is set to true, we call the <span style="font-family: Courier New, Courier, monospace;">Resize </span>method of the <span style="font-family: Courier New, Courier, monospace;">DeviceManager</span> from here. This prevents disposing and creating critical resources while rendering, which would lead to an exception. The actual rendering is performed in the line <span style="font-family: Courier New, Courier, monospace;">Scene.Instance.render()</span>, where I iterate through all objects of the scene and render them.<br />
<br />
<pre class="brush:csharp">public bool resize = false;
public void RenderScene()
{
while (true)
{
if (resize)
{
DeviceManager.Instance.Resize();
resize = false;
}
fc.Count();
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearDepthStencilView(dm.depthStencil,
DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil,
1.0f,
0);
dm.context.ClearRenderTargetView(dm.renderTarget,
new Color4(0.75f, 0.75f, 0.75f));
Scene.Instance.render();
dm.swapChain.Present(syncInterval, PresentFlags.None);
}
}
</pre>
<br />
<h3>
DeviceManager</h3>
The <span style="font-family: Courier New, Courier, monospace;">Resize </span>method of the <span style="font-family: Courier New, Courier, monospace;">DeviceManager </span>executes the disposing of the buffers and resources and recreates them.<br />
<br />
<pre class="brush:csharp">Texture2D resource;
internal void Resize()
{
try
{
if (device == null)
return;
float aspectRatio = (float)form.Width / (float)form.Height;
CameraManager.Instance.currentCamera.setPerspective((float)Math.PI / 4, aspectRatio, 0.1f, 1000.0f);
// Dispose before resizing.
if (renderTarget != null)
renderTarget.Dispose();
if (resource != null)
resource.Dispose();
if (depthStencil != null)
depthStencil.Dispose();
swapChain.ResizeBuffers(1,
form.ClientSize.Width,
form.ClientSize.Height,
Format.R8G8B8A8_UNorm,
SwapChainFlags.AllowModeSwitch);
resource = Texture2D.FromSwapChain<texture2d>(swapChain, 0);
renderTarget = new RenderTargetView(device, resource);
CreateDepthStencilBuffer(form);
viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
context.Rasterizer.SetViewports(viewport);
context.OutputMerger.SetTargets(depthStencil, renderTarget);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
</texture2d></pre>
<br />
I call the method <span style="font-family: Courier New, Courier, monospace;">CreateDepthStencilBuffer</span><span style="font-family: inherit;"> in the code above and paste it here, for the sake of completeness. Here the depth/stencil buffer is created again and the </span><span style="font-family: Courier New, Courier, monospace;">DepthStencilState</span><span style="font-family: inherit;"> is set again:</span><span style="font-family: inherit;"> </span><br />
<br />
<pre class="brush:csharp">public void CreateDepthStencilBuffer(System.Windows.Forms.Control form)
{
Texture2D DSTexture = new Texture2D(
device,
new Texture2DDescription()
{
ArraySize = 1,
MipLevels = 1,
Format = Format.D32_Float,
Width = form.ClientSize.Width,
Height = form.ClientSize.Height,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default
}
);
depthStencil = new DepthStencilView(
device,
DSTexture,
new DepthStencilViewDescription()
{
ArraySize = 0,
FirstArraySlice = 0,
MipSlice = 0,
Format = Format.D32_Float,
Dimension = DepthStencilViewDimension.Texture2D
}
);
context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
device,
new DepthStencilStateDescription()
{
DepthComparison = Comparison.Always,
DepthWriteMask = DepthWriteMask.All,
IsDepthEnabled = true,
IsStencilEnabled = false
}
);
context.OutputMerger.SetTargets(depthStencil, renderTarget);
DepthStencilStateDescription dssd = new DepthStencilStateDescription
{
IsDepthEnabled = true,
IsStencilEnabled = false,
DepthWriteMask = DepthWriteMask.All,
DepthComparison = Comparison.Less,
};
DepthStencilState depthStencilStateNormal;
depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;
}
</pre>
<br />
<br />
<h3>
Result</h3>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The following two pictures show two windows with resized buffers, corresponding to the controls width and height.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmvo7xvaZksqZhyEfgPmXaQiRSiVRPnm2dtDpGy3On8960A3P3IETgtmnfyfyyAErY4ojNmWvvKu1p-YTlgSVBg4Rxr33Y4n7aDsuTlakmtDLSe70dgsyDHFkgpQ8C41vkJCpvc8uv1qqS/s1600/Result1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmvo7xvaZksqZhyEfgPmXaQiRSiVRPnm2dtDpGy3On8960A3P3IETgtmnfyfyyAErY4ojNmWvvKu1p-YTlgSVBg4Rxr33Y4n7aDsuTlakmtDLSe70dgsyDHFkgpQ8C41vkJCpvc8uv1qqS/s1600/Result1.PNG" height="85" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCki0d3AwajktUlAI8hh3ePsdr6ilLkHY6eHL_LSWrSOhHSwd-7nyDwvi8G2WXKKPtxbgFf2FgNtF3Xr8GVbHSt7AdLGRqmYAy6BOyij1hPcxywzg6WqPccCwRxN-J6Ndm5c58eoxa5VgC/s1600/Result21.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgCki0d3AwajktUlAI8hh3ePsdr6ilLkHY6eHL_LSWrSOhHSwd-7nyDwvi8G2WXKKPtxbgFf2FgNtF3Xr8GVbHSt7AdLGRqmYAy6BOyij1hPcxywzg6WqPccCwRxN-J6Ndm5c58eoxa5VgC/s1600/Result21.PNG" height="320" width="208" /></a></div>
<br />
<br />
You can download the source code for this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/d79bea2b10a430303d5737b748393a7404fb7067">here</a>.Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com6tag:blogger.com,1999:blog-4722199290168150238.post-51691435150003900152013-04-27T18:32:00.003+02:002013-04-27T18:32:59.204+02:00Procedural Meshes: The Superellipsoid<h3>
<span style="font-family: inherit;">Introduction</span></h3>
<span style="background-color: white; color: #444444; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 18px;"><br /></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">This tutorial is one part of a series of tutorials about generating procedural meshes. See </span><a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html" style="background-color: white; color: #4d469c; line-height: 18px; text-decoration: none;">here</a><span style="background-color: white; color: #444444; line-height: 18px;"> for an outline.</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>The superellipsoid is basically a sphere with additional exponents n1 and n2 for manipulating the trigonometric functions. You can create cubes with round edges, a cube, a sphere, a cylinder and many other geometric objects with it, when using the according values for n1 and n2.<br />
<span style="background-color: white; color: #444444; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">The Formula</span></h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">I took the formula for the superellipsoid from:</span><br />
<a href="http://paulbourke.net/geometry/superellipse/">http://paulbourke.net/geometry/superellipse/</a><br />
<br />
x = radius * Cos^n1(theta) * Cos^n2(phi)<br />
y = radius * Cos^n1(theta) * Sin^n2(phi)<br />
z = radius * Sin^n1(theta)<br />
<br />
Basically, this is the same formula as for the <a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-sphere.html">sphere</a>, with two additional exponents n1 and n2. Both, n1 and n2 have to be bigger than 0 to produce valid meshes. Although values over 10 don't add any significant change to the appearence of the mesh. Play around with the parameters and see for yourself ;)<br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Creating the Vertex Buffer</span></h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Theta runs in the outer loop from PI/2 to -PI/2 and phi runs from zero to 2*PI.</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">I think the only obscure thing in the code is use of the Math.Pow function.</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">At first, I check if cosTheta, sinTheta, cosPhi and sinPhi are zero. If they are zero, 0 is asigned to the according powX variables. Then I have to check, if for example cosTheta is a negative value.</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Small negative values can produce a NaN (Not A Number) value from the Math.Pow function.</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Instead of </span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"> Math.Pow( - littleNumber, anyOtherNumber ), </span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">I am calculating:</span><br />
<span style="background-color: white; color: #444444; line-height: 18px;">- Math.Pow( littleNumber, anyOtherNumber )</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<pre class="brush:csharp">int numHorizontalVertices = numVerticesPerLayer + 1;
int numVerticalVertices = numVerticesPerLayer + 1;
numVertices = numHorizontalVertices * numVerticalVertices;
vertexSizeInBytes = 12;
int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;
vertices = new DataStream(vertexBufferSizeInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;
for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
{
theta = ((float)Math.PI / 2) - verticalStep * verticalIt;
for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
{
phi = horizontalStep * horizontalIt;
double cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(theta);
double cosPhi = Math.Cos(phi);
double sinPhi = Math.Sin(phi);
double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
double powCosPhi = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
double x = radius * powCosTheta * powCosPhi;
double y = radius * powCosTheta * powSinPhi;
double z = radius * powSinTheta;
Vector3 v = new Vector3((float)x, (float)z, (float)y);
vertices.Write(v);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
vertexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None, 0);
</pre>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Creating the Index Buffer</span></h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Nothing new here, please refer to the <a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-indexbuffer-and.html">grid mesh</a> tutorial for details.</span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<pre class="brush:csharp">numIndices = 6 * numVertices;
indices = new DataStream(2 * numIndices, true, true);
try
{
for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
{
short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));
short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));
indices.Write(lu);
indices.Write(rd);
indices.Write(ld);
indices.Write(rd);
indices.Write(lu);
indices.Write(ru);
}
}
}
catch (Exception ex)
{
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Source Code</span></h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
namespace Apparat.Renderables
{
public class SuperEllipsoid : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
int vertexSizeInBytes;
InputLayout layout;
int numVertices = 0;
int numIndices = 0;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
EffectVectorVariable mCol;
EffectVectorVariable wfCol;
double n1 = 0;
double n2 = 0;
float radius = 0.0f;
public SuperEllipsoid(int numVerticesPerLayer, float radius, float n1, float n2)
{
try
{
this.n1 = n1;
this.n2 = n2;
this.radius = radius;
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/transformEffectWireframe.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
mCol = effect.GetVariableByName("colorSolid").AsVector();
wfCol = effect.GetVariableByName("colorWireframe").AsVector();
mCol.Set(new Color4(1, 0, 1, 0));
wfCol.Set(new Color4(1, 0, 0, 0));
int numHorizontalVertices = numVerticesPerLayer + 1;
int numVerticalVertices = numVerticesPerLayer + 1;
numVertices = numHorizontalVertices * numVerticalVertices;
vertexSizeInBytes = 12;
int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;
vertices = new DataStream(vertexBufferSizeInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;
for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
{
theta = -((float)Math.PI / 2) + verticalStep * verticalIt;
for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
{
phi = horizontalStep * horizontalIt;
double cosTheta = Math.Cos(theta);
double sinTheta = Math.Sin(theta);
double cosPhi = Math.Cos(phi);
double sinPhi = Math.Sin(phi);
double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
double powCosPhi = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
double x = radius * powCosTheta * powCosPhi;
double y = radius * powCosTheta * powSinPhi;
double z = radius * powSinTheta;
Vector3 v = new Vector3((float)x, (float)z, (float)y);
vertices.Write(v);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
vertexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None, 0);
// creating the index buffer
numIndices = 6 * numVertices;
indices = new DataStream(2 * numIndices, true, true);
try
{
for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
{
short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));
short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));
indices.Write(lu);
indices.Write(rd);
indices.Write(ld);
indices.Write(rd);
indices.Write(lu);
indices.Write(ru);
}
}
}
catch (Exception ex)
{
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexSizeInBytes, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
indexBuffer.Dispose();
vertexBuffer.Dispose();
effect.Dispose();
}
}
}
</pre>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<br />
<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Result</span></h3>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">All meshes shown below were created with radius = 1.0 and 32 * 32 vertices mesh.</span></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Only the values for n1 and n2 differ.</span></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">n1 = 0 and n2 = 0:</span></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQaoOXbiI5JP69GfevFBz7B30bHK1rL5_tvMcX-znPLhbktNSSTLFfwRqEYtxN5rj5L_gptYjydOrjO1_pKVkIT5E7qX29eTydafLIxRcnAcnNYt3sB6W8Cqy3OtdYJdzOnR_dSuzngmsv/s1600/Superellipsoid_0_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQaoOXbiI5JP69GfevFBz7B30bHK1rL5_tvMcX-znPLhbktNSSTLFfwRqEYtxN5rj5L_gptYjydOrjO1_pKVkIT5E7qX29eTydafLIxRcnAcnNYt3sB6W8Cqy3OtdYJdzOnR_dSuzngmsv/s1600/Superellipsoid_0_0.PNG" height="240" width="320" /></a></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span></div>
<div>
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 0 and n2 = 1:</span></div>
<div>
<span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvWuy46wY02Xk6KdwkT3yj0o6d2VYeWR9Qgh_OzC7-CK-Qbv_xIXfh5KnoLsbC0xSa6gbg66ffBuN25cUgxwOoAFs4g_iJppHm1WQKkLyP2ieOzsZaK2seP5mru54cbQpddLs8d_x4G80P/s1600/Superellipsoid_0_0__1_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhvWuy46wY02Xk6KdwkT3yj0o6d2VYeWR9Qgh_OzC7-CK-Qbv_xIXfh5KnoLsbC0xSa6gbg66ffBuN25cUgxwOoAFs4g_iJppHm1WQKkLyP2ieOzsZaK2seP5mru54cbQpddLs8d_x4G80P/s1600/Superellipsoid_0_0__1_0.PNG" height="240" width="320" /></a></div>
<div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span></div>
<div>
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 1 and n2 = 0:</span></div>
<div>
<span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVDMITY4SlSsGfz8s9ffx8icIivKTHb-_EK621FUrdNqqS3tc0b3g7r5FP9NHvlMmJqxVwUWbtKc2HoUBxqgEIwMXOybgRBrAb0KSbDUTj9KFlrxIf53OHooVsKPjJsq6vhibCpBsDDlx1/s1600/Superellipsoid_1_0__0_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVDMITY4SlSsGfz8s9ffx8icIivKTHb-_EK621FUrdNqqS3tc0b3g7r5FP9NHvlMmJqxVwUWbtKc2HoUBxqgEIwMXOybgRBrAb0KSbDUTj9KFlrxIf53OHooVsKPjJsq6vhibCpBsDDlx1/s1600/Superellipsoid_1_0__0_0.PNG" height="240" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 1 and n2 = 1:</span></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlhsbDilrDAJJj7ANDBHb2EnwCIPeBEExU4h_RdJFIsbbpxKFtqY75NJ9MnoSTZBgXZiGJeUbzKkv6srAfZJT5E730E3ui2XjP1j2jZZd5auxRV6W9hXJQEkKCM1IeWzMVeuGfHQGimy9p/s1600/Superellipsoid_1_0__1_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlhsbDilrDAJJj7ANDBHb2EnwCIPeBEExU4h_RdJFIsbbpxKFtqY75NJ9MnoSTZBgXZiGJeUbzKkv6srAfZJT5E730E3ui2XjP1j2jZZd5auxRV6W9hXJQEkKCM1IeWzMVeuGfHQGimy9p/s1600/Superellipsoid_1_0__1_0.PNG" height="240" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 0.4 and n2 = 0.4:</span></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxyWKQESRYTOZ6vsmmhizAyWLLvkfhr7KNHjMzjeEhyPxTvv9-v4fRPvyctqux9UJxuVQHgNA2W-QR9G3O1u3QVtuOaL3hyphenhyphenvTyCIvUdJNiiponTa5J3-YJHnDdKwEXgUnyn5QezxFWXxr5/s1600/Superellipsoid_04_04.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxyWKQESRYTOZ6vsmmhizAyWLLvkfhr7KNHjMzjeEhyPxTvv9-v4fRPvyctqux9UJxuVQHgNA2W-QR9G3O1u3QVtuOaL3hyphenhyphenvTyCIvUdJNiiponTa5J3-YJHnDdKwEXgUnyn5QezxFWXxr5/s1600/Superellipsoid_04_04.PNG" height="240" width="320" /></a></div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 2 and n2 = 2:</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpcabQPbFcjciVZ7KL17XblTuOikjKRoHMuPyvlefu2I88tVFruwYjAtgakepA_D0Qmxtw6lUaV16VGR7v-JsOcfOCn93kF9Tjcb6kpzxoAAGuZ9eWJj1Wkz_7pMpMfNFEGjUoE4vhFWEi/s1600/Superellipsoid_2_0__2_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpcabQPbFcjciVZ7KL17XblTuOikjKRoHMuPyvlefu2I88tVFruwYjAtgakepA_D0Qmxtw6lUaV16VGR7v-JsOcfOCn93kF9Tjcb6kpzxoAAGuZ9eWJj1Wkz_7pMpMfNFEGjUoE4vhFWEi/s1600/Superellipsoid_2_0__2_0.PNG" height="240" width="320" /></a></div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; line-height: 18px;">n1 = 4 and n2 = 4:</span><span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUAGylc7AycnprMNYbBm67vKDZsgu5OmY1xL-JMw-96PGCQMjm17B-I-MJE-VrsTgajM94jhXdmJ4FmjAAXUkRLLvSl3k3_IBMmjjLzUPQUec4mcHL1H7CgHdeY6Nm0M5ikUHQ28BBNW_p/s1600/Superellipsoid_4_0__4_0.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUAGylc7AycnprMNYbBm67vKDZsgu5OmY1xL-JMw-96PGCQMjm17B-I-MJE-VrsTgajM94jhXdmJ4FmjAAXUkRLLvSl3k3_IBMmjjLzUPQUec4mcHL1H7CgHdeY6Nm0M5ikUHQ28BBNW_p/s1600/Superellipsoid_4_0__4_0.PNG" height="240" width="320" /></a></div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">I added a second class SuperEllipsoidCol which has the same colors as the color cube:</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">With 32 x 32 vertices, radius 0.5, n1 = 0.2 and n2 = 0.2:</span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVkgLdhs8jC6dXdtQvZYmDqFAjJc-2d9MnVIde4mmyfagAA6eT-IzkOuYUqAjVSoZculAT-qPTcJeMpQOAWx9BDlXp8s_aAa-0ZKVm91QTxYjCT-FX21JM_DdAprUTNeLptUGGrsENU0BR/s1600/SuperellipsoidCol_0_2__0_2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVkgLdhs8jC6dXdtQvZYmDqFAjJc-2d9MnVIde4mmyfagAA6eT-IzkOuYUqAjVSoZculAT-qPTcJeMpQOAWx9BDlXp8s_aAa-0ZKVm91QTxYjCT-FX21JM_DdAprUTNeLptUGGrsENU0BR/s1600/SuperellipsoidCol_0_2__0_2.PNG" height="240" width="320" /></a></div>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="color: #444444;"><span style="line-height: 18px;">You can download the source code for this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/060df092d2ca1f04eff43520406b84a1bcdf8309">here</a>. You can play around with the parameters in the RenderControl.cs class.</span></span><br />
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span>
<span style="color: #444444;"><span style="line-height: 18px;">Have fun!</span></span>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-65706834785360533642013-04-27T12:44:00.002+02:002013-04-27T12:45:34.006+02:00Procedural Meshes: The Torus<h3>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">Introduction</span></h3>
<span style="background-color: white; color: #444444; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 18px;"><br /></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">This tutorial is one part of a series of tutorials about generating procedural meshes. See </span><a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html" style="background-color: white; color: #4d469c; line-height: 18px; text-decoration: none;">here</a><span style="background-color: white; color: #444444; line-height: 18px;"> for an outline.</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>
<br />
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">In this tutorial I will show you how to create a torus. The torus has two radii: a radius from the center of the torus to the middle of the ring section and the ring radius:</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9tLh-YZUzjsJinrvQuzSivA5oVxuXht-qSkP_M-pMIOsEqWLk881wZZbo_F4tLNTkXMYa8KjgvqGzybTCsEBcsJo-7oVOQy9c9NcZUsqdXAAGJx7gYQwNtECBcY-3Ox3y3Vf5F2Z8cgdK/s1600/Torus.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9tLh-YZUzjsJinrvQuzSivA5oVxuXht-qSkP_M-pMIOsEqWLk881wZZbo_F4tLNTkXMYa8KjgvqGzybTCsEBcsJo-7oVOQy9c9NcZUsqdXAAGJx7gYQwNtECBcY-3Ox3y3Vf5F2Z8cgdK/s1600/Torus.png" height="288" width="320" /></a></div>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">Further parameters are number of sides and number of rings. These parameters are needed for the creation of the vertex and index buffers in order to determine how many vertices and indices are needed. The number of rings tells, how many sections the torus has. The number of sides is the number of sections for each ring.</span></span><br />
<h3>
</h3>
<h3>
<span style="color: #444444;"><span style="line-height: 18px;">The Formula</span></span></h3>
<span style="color: #444444;"><span style="line-height: 18px;">x = Cos(theta) * (radius + ringRadius * Cos(phi))</span></span><br />
<span style="color: #444444;"><span style="line-height: 18px;">y = Sin(theta) * (radius + ringRadius * </span></span><span style="color: #444444; line-height: 18px;">Cos</span><span style="color: #444444;"><span style="line-height: 18px;">(phi))</span></span><br />
<span style="color: #444444;"><span style="line-height: 18px;">z = ringRadius * Sin(phi)</span></span><br />
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span>
<br />
<span style="color: #444444;"><span style="line-height: 18px;">Both, theta and phi run from 0 to 2 * PI.</span></span><br />
<h3>
<span style="color: #444444;"><span style="line-height: 18px;">Creating the Vertex Buffer</span></span></h3>
<span style="color: #444444;"><span style="line-height: 18px;">I create the vertex buffer with a double nested for loop. The outer loop iterates over the number of sections and the variable theta is used to increment the angle of the current circle of vertices that is created. The inner for loop iterates over the number of vertices the torus will have per section. Here, the variable phi is incremented in the inner loop, to calculate the position of the current vertex.</span></span><br />
<br />
<pre class="brush:csharp">int numVerticesPerRow = sides + 1;
int numVerticesPerColumn = rings + 1;
numVertices = numVerticesPerRow * numVerticesPerColumn;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;
for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
theta = verticalAngularStride * verticalIt;
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
phi = horizontalAngularStride * horizontalIt;
// position
float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
float z = ringRadius * (float)Math.Sin(phi);
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<h3>
<span style="color: #444444;"><span style="line-height: 18px;">Creating the Index Buffer</span></span></h3>
If you followed the tutorial on procedural meshes so far, the method of creating the index buffer will not as a surprise. I iterate again in a double nested for loop: the outer loop is for iterating the ring sections and the inner loop for iterating the rings themselves. The iterators are one less than the iterators in the double nested for loop above, because at each position of the innerloop, I create a patch of 2 triangles, looking one vertex index ahead.<br />
<pre class="brush:csharp"></pre>
<pre class="brush:csharp"></pre>
<pre class="brush:csharp">numIndices = sides * rings * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < rings; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < sides; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<h3>
<span style="color: #444444;"><span style="line-height: 18px;">Source Code</span></span></h3>
Complete sources code of the class Torus:<br />
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Apparat.Renderables
{
public class Torus : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
InputLayout layout;
int numVertices = 0;
int numIndices = 0;
int vertexStride = 0;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
EffectVectorVariable mCol;
EffectVectorVariable wfCol;
public Torus(float radius, float ringRadius, int sides, int rings)
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/transformEffectWireframe.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
mCol = effect.GetVariableByName("colorSolid").AsVector();
wfCol = effect.GetVariableByName("colorWireframe").AsVector();
mCol.Set(new Color4(1, 0, 1, 0));
wfCol.Set(new Color4(1, 0, 0, 0));
int numVerticesPerRow = sides + 1;
int numVerticesPerColumn = rings + 1;
numVertices = numVerticesPerRow * numVerticesPerColumn;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;
for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
theta = verticalAngularStride * verticalIt;
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
phi = horizontalAngularStride * horizontalIt;
// position
float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
float z = ringRadius * (float)Math.Sin(phi);
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
numIndices = sides * rings * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < rings; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < sides; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
}
}
}
</pre>
<br />
<br />
<h3>
<span style="color: #444444;"><span style="line-height: 18px;">Result</span></span></h3>
<div>
<span style="color: #444444;"><span style="line-height: 18px;">This torus was created with the parameters radius = 1, ring radius = 0.25, number of sides = 36 and number of rings = 36.</span></span></div>
<div>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span></div>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgPgD3R4YKe0diOFwj23vhRr1-oczsFtzBEcRcZJgDEKL8xm44zmax1Q39dRvYFWwRNHcHxW25lAE6hcgd8Yl8_xJ103dzIiz0PlyisDvwU-KBKAsmqaDrQ1jkn2aJE4VJdeYtwngatcf4/s1600/Torus1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgPgD3R4YKe0diOFwj23vhRr1-oczsFtzBEcRcZJgDEKL8xm44zmax1Q39dRvYFWwRNHcHxW25lAE6hcgd8Yl8_xJ103dzIiz0PlyisDvwU-KBKAsmqaDrQ1jkn2aJE4VJdeYtwngatcf4/s1600/Torus1.PNG" height="240" width="320" /></a></div>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span><span style="color: #444444; line-height: 18px;"><br /></span><br />
<span style="color: #444444; line-height: 18px;">This torus was created with the parameters radius = 1, ring radius = 0.5, number of sides = 8 and number of rings = 16.</span><br />
<span style="color: #444444; line-height: 18px;"><br /></span>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaCpOquVB6iA5rwQSeZ8AuZr3-BQYBpCmaCv9SAD3O8blOzTqgPXmL7zKcQ_LBwl1zlvaKhSCrxv2N0nLs8QDCbPQyD64vRXBm3yhtlIA8sWaD_6mEvKDeU0a9xzz-Lf3a-HuC0TCOyNUJ/s1600/Torus2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaCpOquVB6iA5rwQSeZ8AuZr3-BQYBpCmaCv9SAD3O8blOzTqgPXmL7zKcQ_LBwl1zlvaKhSCrxv2N0nLs8QDCbPQyD64vRXBm3yhtlIA8sWaD_6mEvKDeU0a9xzz-Lf3a-HuC0TCOyNUJ/s1600/Torus2.PNG" height="240" width="320" /></a></div>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span>
<span style="color: #444444;"><span style="line-height: 18px;"><br /></span></span><br />
<span style="color: #444444;"><span style="line-height: 18px;">You can download the code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/9e3c528f335fa24fd9ec9bed4dc56740c2fc54a0">here</a>.</span></span>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-40693393223033776812013-04-26T19:06:00.001+02:002013-04-26T19:06:08.651+02:00Procdural Meshes: The Cylinder<h3>
<span style="background-color: white; color: #444444; line-height: 18px;"><span style="font-family: inherit;">Introduction</span></span></h3>
<span style="background-color: white; color: #444444; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 18px;"><br /></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">This tutorial is one part of a series of tutorials about generating procedural meshes. See </span><a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html" style="background-color: white; color: #4d469c; line-height: 18px; text-decoration: none;">here</a><span style="background-color: white; color: #444444; line-height: 18px;"> for an outline.</span></span><br />
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;"><br /></span><span style="background-color: white; color: #444444; line-height: 18px;">In this tutorial I will show you how to create procedurally a cylinder mesh. The cylinders bottom is in the x-z plane and the top at a given height. You can also parametrize the radius of the cylinders top and bottom.</span></span><br />
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;"><br /></span>
<span style="background-color: white; color: #444444; font-family: inherit; line-height: 18px;">The idea behind generating the cylinder is quite simple: create two circles of vertices and afterwards create the indices for the triangles between the two circles. This leaves us with an open top and bottom of the cylinder. So we need to append two vertices to the vertex buffer: one vertex at the bottom of the cylinder and one vertex at the top of the cylinder. Finally, we can create the indices for the top and bottom of the cylinder.</span><span style="background-color: white; color: #444444; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13px; line-height: 18px;"><br /></span>
<br />
<h3>
The Formula</h3>
The formula for creating the circle is as follows:<br />
<br />
x = radius * Cos(theta)<br />
y = radius * Sin(theta)<br />
z = height<br />
<br />
Theta runs from 0 to 2 * PI.<br />
<h3>
<span style="font-family: inherit;">Creating the Vertex Buffer</span></h3>
At first I create the vertices for the upper circle and then the vertices at the bottom of the cylinder are created. After that, I append the two vertices at the top and the bottom to the vertex buffer, which are needed to close the mesh at the top and bottom.<br />
<br />
<pre class="brush:csharp">int numVerticesPerRow = slices + 1;
numVertices = numVerticesPerRow * 2 + 2;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;
for (int verticalIt = 0; verticalIt < 2; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
float x;
float y;
float z;
theta = (horizontalAngularStride * horizontalIt);
if (verticalIt == 0)
{
// upper circle
x = radiusTop * (float)Math.Cos(theta);
y = radiusTop * (float)Math.Sin(theta);
z = height;
}
else
{
// lower circle
x = radiusBottom * (float)Math.Cos(theta);
y = radiusBottom * (float)Math.Sin(theta);
z = 0;
}
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Write(new Vector3(0, height, 0));
vertices.Write(new Vector3(0, 0, 0));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<h3>
<span style="font-family: inherit;">Creating the Index Buffer</span></h3>
At first the two circles are filled with triangles, by patching the mesh with triangles between the upper circle of vertices and the lower circle of vertices. After that, the triangles at the top and the bottom are created in the index buffer with the help of the two vertices at the middle of the top and the bottom of the cylinder.<br />
<br />
<br />
<pre class="brush:csharp">numIndices = slices * 2 * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short patchIndexTop = (short)(numVerticesPerRow * 2);
indices.Write(lt);
indices.Write(patchIndexTop);
indices.Write(rt);
}
}
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
indices.Write(lb);
indices.Write(rb);
indices.Write(patchIndexBottom);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<h3>
<span style="font-family: inherit;">Source Code</span></h3>
<br />
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Apparat.Renderables
{
public class Cylinder : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
InputLayout layout;
int numVertices = 0;
int numIndices = 0;
int vertexStride = 0;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
EffectVectorVariable mCol;
EffectVectorVariable wfCol;
float radius = 0;
public Cylinder(float height, float radiusBottom, float radiusTop, int slices)
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/transformEffectWireframe.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
mCol = effect.GetVariableByName("colorSolid").AsVector();
wfCol = effect.GetVariableByName("colorWireframe").AsVector();
mCol.Set(new Color4(1, 0, 1, 0));
wfCol.Set(new Color4(1, 0, 0, 0));
int numVerticesPerRow = slices + 1;
numVertices = numVerticesPerRow * 2 + 2;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;
for (int verticalIt = 0; verticalIt < 2; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
float x;
float y;
float z;
theta = (horizontalAngularStride * horizontalIt);
if (verticalIt == 0)
{
// upper circle
x = radiusTop * (float)Math.Cos(theta);
y = radiusTop * (float)Math.Sin(theta);
z = height;
}
else
{
// lower circle
x = radiusBottom * (float)Math.Cos(theta);
y = radiusBottom * (float)Math.Sin(theta);
z = 0;
}
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Write(new Vector3(0, height, 0));
vertices.Write(new Vector3(0, 0, 0));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
numIndices = slices * 2 * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short patchIndexTop = (short)(numVerticesPerRow * 2);
indices.Write(lt);
indices.Write(patchIndexTop);
indices.Write(rt);
}
}
for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
indices.Write(lb);
indices.Write(rb);
indices.Write(patchIndexBottom);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
}
}
}
</pre>
<br />
<h3>
<span style="font-family: inherit;">Result</span></h3>
<div>
<span style="font-family: inherit;"><br /></span></div>
<span style="font-family: inherit;">For this cylinder, I used the following par</span>ameters: height = 2, radius at the bottom = 1, radius at the top = 1 and slices = 16.<br />
<span style="font-family: inherit;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVD1-kr0svX4ky2JoJPXvOTf23asbPsluZNFiZTU1GpP42Pfff-HFwEd_yZnsoMPPl7-tkBqFQ4DUQeRfalFhRvATToUV5yRtlMfxDZQ5nI850_XUns8f10AqsgnuESyoxPMRI_4lp3f3i/s1600/Cylinder1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVD1-kr0svX4ky2JoJPXvOTf23asbPsluZNFiZTU1GpP42Pfff-HFwEd_yZnsoMPPl7-tkBqFQ4DUQeRfalFhRvATToUV5yRtlMfxDZQ5nI850_XUns8f10AqsgnuESyoxPMRI_4lp3f3i/s1600/Cylinder1.PNG" height="240" width="320" /></a></div>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<br />
<span style="font-family: inherit;">If you want to create a tip, you can set the radius at to top to zero:</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYmbFyMMMQ4TmCQvdwLKc6s7_T6N29ZJx9YBdFl0Zeg2aoLuRzQhpdxT-_yoZNQtztxCpWFWEyyEVaswwypdjA2heEllf00Htnfk66gX7-R2ywC0aaSQl3CqsDKR50uyj2hYZnhWCVHLjP/s1600/Cylinder2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYmbFyMMMQ4TmCQvdwLKc6s7_T6N29ZJx9YBdFl0Zeg2aoLuRzQhpdxT-_yoZNQtztxCpWFWEyyEVaswwypdjA2heEllf00Htnfk66gX7-R2ywC0aaSQl3CqsDKR50uyj2hYZnhWCVHLjP/s1600/Cylinder2.PNG" height="240" width="320" /></a></div>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;"><span style="background-color: white; color: #444444; line-height: 18px;">You can download the source code for this tutorial </span><a href="http://apparat.codeplex.com/SourceControl/changeset/view/9f181c1e6f3211680e5a13ba56d94b0da1f59048">here</a><span style="background-color: white; color: #444444; line-height: 18px;">.</span></span>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-1522334137705683222013-04-25T01:58:00.002+02:002013-04-25T01:58:36.637+02:00Procedural Meshes: The Box<h3>
Introduction</h3>
<br />
This tutorial is one part of a series of tutorials about generating procedural meshes. See <a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html">here</a> for an outline.<br />
<br />
In this tutorial I will show how to create a box with parameters for width, height and depth.<br />
<h3>
<br /></h3>
<h3>
Creating the Vertex Buffer</h3>
<br />
The center of the box is in the origin of the coordinate system (0,0,0). The width of the box corresponds with its extension in direction of the x-axis, the height expands along the the y-axis and the depth of the box expands along the z-axis.<br />
<br />
In order to create the vertex buffer, we need eight vertices, which are created with the appropriate offsets:<br />
<br />
<pre class="brush:csharp">// half length of an edge
float offsetWidth = width / 2.0f;
float offsetHeight = height / 2.0f;
float offsetDepth = depth / 2.0f;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
numVertices = 8;
vertexBufferSizeInBytes = vertexStride * numVertices;
vertices = new DataStream(vertexBufferSizeInBytes, true, true);
vertices.Write(new Vector3(+offsetWidth, +offsetHeight, +offsetDepth)); // 0
vertices.Write(new Vector3(+offsetWidth, +offsetHeight, -offsetDepth)); // 1
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, -offsetDepth)); // 2
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, +offsetDepth)); // 3
vertices.Write(new Vector3(-offsetWidth, -offsetHeight, +offsetDepth)); // 4
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, +offsetDepth)); // 5
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, -offsetDepth)); // 6
vertices.Write(new Vector3(-offsetWidth, -offsetHeight, -offsetDepth)); // 7
vertices.Position = 0;
vertexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
vertices,
vertexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
This picture shows the indices of the vertices. The indices written in the sources code above in the comments.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifDen9fP00X47cyk9L9FhPxjPgZ5FGicL8YYOmDOu3QdD53jPYf9hdUxuCBIPjz7p50FPWfHQYlWnct8xKsX4A-4Y86Iv1ER47-Qg_2oit4Obv3JtRP2xHRjZgw4i54YRddk2Uh_7ylnH3/s1600/TheBoxIndices.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifDen9fP00X47cyk9L9FhPxjPgZ5FGicL8YYOmDOu3QdD53jPYf9hdUxuCBIPjz7p50FPWfHQYlWnct8xKsX4A-4Y86Iv1ER47-Qg_2oit4Obv3JtRP2xHRjZgw4i54YRddk2Uh_7ylnH3/s1600/TheBoxIndices.png" /></a></div>
<br />
<br />
<h3>
Creating the Index Buffer</h3>
<br />
We need 36 indices for the box, as each face of the box consists of two triangles and we have six sides:<br />
<br />
6 sides * 2 triangles * 3 indices = 36 indices<br />
<br />
The triangles are defined by enumerating the indices is a clockwise order:<br />
<br />
<pre class="brush:csharp">numIndices = 36;
indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
indexBufferSizeInBytes = numIndices * indexStride;
indices = new DataStream(indexBufferSizeInBytes, true, true);
// Cube has 6 sides: top, bottom, left, right, front, back
// top
indices.WriteRange(new short[] { 0, 1, 2 });
indices.WriteRange(new short[] { 2, 3, 0 });
// right
indices.WriteRange(new short[] { 0, 5, 6 });
indices.WriteRange(new short[] { 6, 1, 0 });
// left
indices.WriteRange(new short[] { 2, 7, 4 });
indices.WriteRange(new short[] { 4, 3, 2 });
// front
indices.WriteRange(new short[] { 1, 6, 7 });
indices.WriteRange(new short[] { 7, 2, 1 });
// back
indices.WriteRange(new short[] { 3, 4, 5 });
indices.WriteRange(new short[] { 5, 0, 3 });
// bottom
indices.WriteRange(new short[] { 6, 5, 4 });
indices.WriteRange(new short[] { 4, 7, 6 });
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
indexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<h3>
Source Code</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
namespace Apparat.Renderables
{
public class Box : Renderable
{
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
InputLayout layout;
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
int vertexStride = 0;
int numVertices = 0;
int indexStride = 0;
int numIndices = 0;
int vertexBufferSizeInBytes = 0;
int indexBufferSizeInBytes = 0;
EffectMatrixVariable tmat;
EffectVectorVariable mCol;
EffectVectorVariable wfCol;
public Box(float width, float height, float depth)
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/transformEffectWireframe.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
tmat = effect.GetVariableByName("gWVP").AsMatrix();
wfCol = effect.GetVariableByName("colorWireframe").AsVector();
mCol = effect.GetVariableByName("colorSolid").AsVector();
Vector4 col = new Vector4(0, 0, 0, 1);
mCol.Set(new Color4(1, 0, 1, 0));
wfCol.Set(col);
// half length of an edge
float offsetWidth = width / 2.0f;
float offsetHeight = height / 2.0f;
float offsetDepth = depth / 2.0f;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
numVertices = 8;
vertexBufferSizeInBytes = vertexStride * numVertices;
vertices = new DataStream(vertexBufferSizeInBytes, true, true);
vertices.Write(new Vector3(+offsetWidth, +offsetHeight, +offsetDepth)); // 0
vertices.Write(new Vector3(+offsetWidth, +offsetHeight, -offsetDepth)); // 1
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, -offsetDepth)); // 2
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, +offsetDepth)); // 3
vertices.Write(new Vector3(-offsetWidth, -offsetHeight, +offsetDepth)); // 4
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, +offsetDepth)); // 5
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, -offsetDepth)); // 6
vertices.Write(new Vector3(-offsetWidth, -offsetHeight, -offsetDepth)); // 7
vertices.Position = 0;
vertexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
vertices,
vertexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
numIndices = 36;
indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
indexBufferSizeInBytes = numIndices * indexStride;
indices = new DataStream(indexBufferSizeInBytes, true, true);
// Cube has 6 sides: top, bottom, left, right, front, back
// top
indices.WriteRange(new short[] { 0, 1, 2 });
indices.WriteRange(new short[] { 2, 3, 0 });
// right
indices.WriteRange(new short[] { 0, 5, 6 });
indices.WriteRange(new short[] { 6, 1, 0 });
// left
indices.WriteRange(new short[] { 2, 7, 4 });
indices.WriteRange(new short[] { 4, 3, 2 });
// front
indices.WriteRange(new short[] { 1, 6, 7 });
indices.WriteRange(new short[] { 7, 2, 1 });
// back
indices.WriteRange(new short[] { 3, 4, 5 });
indices.WriteRange(new short[] { 5, 0, 3 });
// bottom
indices.WriteRange(new short[] { 6, 5, 4 });
indices.WriteRange(new short[] { 4, 7, 6 });
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
indexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
effect.Dispose();
inputSignature.Dispose();
vertexBuffer.Dispose();
layout.Dispose();
}
}
}
</pre>
<br />
<br />
<h3>
Conclusion</h3>
<div>
<br /></div>
<div>
This is a box, created with the parameters width = 2.0, height = 1.0 and depth = 1.0:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiODCcg6mArvHqSLn3jUXBeEcfvjXImbq8xJmNElLpdBBxiGTo34Dqx0S2C1HJA18ZyRhkbrYwiKxYV_gKN82FtM5KP-APFaHwVYyR-Z1PfpKEVUXZ6GhOeJh5ADbCMhJI15XZ0jxqiI4Hq/s1600/Box.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiODCcg6mArvHqSLn3jUXBeEcfvjXImbq8xJmNElLpdBBxiGTo34Dqx0S2C1HJA18ZyRhkbrYwiKxYV_gKN82FtM5KP-APFaHwVYyR-Z1PfpKEVUXZ6GhOeJh5ADbCMhJI15XZ0jxqiI4Hq/s1600/Box.PNG" height="240" width="320" /></a></div>
<div>
<br />
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/b4cee67d6169ef5740b5302fc9a6c0d8db4942d9">here</a>.</div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-39977340208174738862013-04-25T00:02:00.000+02:002013-04-25T01:29:27.962+02:00Procedural Meshes: The Sphere<h3>
Introduction</h3>
This tutorial is one part of a series of tutorials about generating procedural meshes. See <a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html">here</a> for an outline.<br />
<br />
In this tutorial I will show how to create a sphere mesh procedurally.<br />
<br />
<h3>
Formula</h3>
I use the following formula to create the vertices of the sphere:<br />
<br />
x = radius * Cos(theta) * Cos(phi)<br />
y = radius * Cos(theta) * Sin(phi)<br />
z = radius * Sin(theta)<br />
<br />
If you are looking at the origin an down the positive z-axis, theta is the angle between the x-axis and the line made from this angle. Theta runs in the interval from PI/2 to -PI/2 which corresponds to the latitude if you compare it to a globe:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEDUkPlQafUpjxTEskFoQo_UznMRO0WueNNByL_Vwrtg6GG7dFuvqmJximLMmfL2jVDvQUTfieUHuKQjFaqotG5a2kxD73DhEyXM9qNDRuXW6Ra2dp-XYUToZCXe0il_7b9NCXBxGo_jMO/s1600/SphereTheta.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiEDUkPlQafUpjxTEskFoQo_UznMRO0WueNNByL_Vwrtg6GG7dFuvqmJximLMmfL2jVDvQUTfieUHuKQjFaqotG5a2kxD73DhEyXM9qNDRuXW6Ra2dp-XYUToZCXe0il_7b9NCXBxGo_jMO/s1600/SphereTheta.png" height="320" width="294" /></a></div>
<br />
<br />
<br />
If you look from top down on the sphere, phi is the angle between the x-axis and the line from the angle. Phi runs from 0 to 2 * PI, which corresponds to the longitude, compared with a globe:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIPNjTA26OtqHdr5HM6PbxvdtbdfbRz_sewH2eda3U3P33V0qrFrUB7gPYMoyjWJjO9Cpp8WOK-UJqR5NzZ3xOGKtyGFxpbfuA-w1HzWUcpvfsmIH54TE8XJ6XChqxXSn5bluy5p6kFS8b/s1600/SpherePhi.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIPNjTA26OtqHdr5HM6PbxvdtbdfbRz_sewH2eda3U3P33V0qrFrUB7gPYMoyjWJjO9Cpp8WOK-UJqR5NzZ3xOGKtyGFxpbfuA-w1HzWUcpvfsmIH54TE8XJ6XChqxXSn5bluy5p6kFS8b/s1600/SpherePhi.png" height="269" width="320" /></a></div>
<br />
<br />
If you fill in the values in radian for theta and phi, you will get the according point on the sphere.<br />
<br />
From here on it is pretty straight forward: have a double nested for-loop and iterate from the top to the bottom of the sphere in the outer loop (theta) and create the vertices to on this circle in the inner loop by circling once around the y-axis (phi).<br />
<h3>
Creating the Vertex Buffer</h3>
This is, how it looks in source code. Please note, that I swap the y and z values when creating the vertices. I find it easier to do the math in a coordinate system where the z-axis points up, while in 3D coordinate systems used in computer graphics the y-axis points up.<br />
<br />
<pre class="brush:csharp">int numVerticesPerRow = slices + 1;
int numVerticesPerColumn = stacks + 1;
numVertices = numVerticesPerRow * numVerticesPerColumn;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalAngularStride = (float)Math.PI / (float)stacks;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;
for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
// beginning on top of the sphere:
theta = ((float)Math.PI / 2.0f) - verticalAngularStride * verticalIt;
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
phi = horizontalAngularStride * horizontalIt;
// position
float x = radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
float y = radius * (float)Math.Cos(theta) * (float)Math.Sin(phi);
float z = radius * (float)Math.Sin(theta);
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<h3>
Creating the Index Buffer</h3>
Just like in the <a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-indexbuffer-and.html">tutorial for the grid mesh</a>, I am iterating through the rows of vertices and create two triangles at each position.<br />
<br />
<pre class="brush:csharp">numIndices = slices * stacks * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < stacks; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
<br />
<h3>
Source Code</h3>
This is the source code of the sphere:<br />
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
using System.Drawing;
namespace Apparat.Renderables
{
public class Sphere : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
InputLayout layout;
int numVertices = 0;
int numIndices = 0;
int vertexStride = 0;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
EffectVectorVariable mCol;
EffectVectorVariable wfCol;
float radius = 0;
public Sphere(float radius, int slices, int stacks )
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/transformEffectWireframe.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
this.radius = radius;
tmat = effect.GetVariableByName("gWVP").AsMatrix();
mCol = effect.GetVariableByName("colorSolid").AsVector();
wfCol = effect.GetVariableByName("colorWireframe").AsVector();
mCol.Set(new Color4(1, 0, 1, 0));
wfCol.Set(new Color4(1, 0, 0, 0));
int numVerticesPerRow = slices + 1;
int numVerticesPerColumn = stacks + 1;
numVertices = numVerticesPerRow * numVerticesPerColumn;
vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;
vertices = new DataStream(SizeOfVertexBufferInBytes, true, true);
float theta = 0.0f;
float phi = 0.0f;
float verticalAngularStride = (float)Math.PI / (float)stacks;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;
for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
// beginning on top of the sphere:
theta = ((float)Math.PI / 2.0f) - verticalAngularStride * verticalIt;
for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
{
phi = horizontalAngularStride * horizontalIt;
// position
float x = radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
float y = radius * (float)Math.Cos(theta) * (float)Math.Sin(phi);
float z = radius * (float)Math.Sin(theta);
Vector3 position = new Vector3(x, z, y);
vertices.Write(position);
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,
vertices,
SizeOfVertexBufferInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
numIndices = slices * stacks * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int verticalIt = 0; verticalIt < stacks; verticalIt++)
{
for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
{
short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));
short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
indices.Write(lt);
indices.Write(rt);
indices.Write(lb);
indices.Write(rt);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
}
}
}
</pre>
<br />
As I am using a different shader than before which sets an uniform color for the mesh and a color for the wireframe, I will post the complete code of the shader used here. I will use this shader also for the next tutorial in which I will show how to create other geometric primitives. I use the variables <span style="font-family: Courier New, Courier, monospace;">mCol </span>and <span style="font-family: Courier New, Courier, monospace;">wfCol </span>to set the colors of the mesh and the wireframe in the code above via the effect framework.<br />
<br />
<br />
<pre class="brush:csharp">matrix gWVP;
float4 colorSolid;
float4 colorWireframe;
float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return colorSolid;
}
float4 PShaderWireframe(float4 position : SV_POSITION) : SV_Target
{
return colorWireframe;
}
RasterizerState SolidState
{
FillMode = Solid;
};
RasterizerState WireframeState
{
FillMode = Wireframe;
SlopeScaledDepthBias = -0.5f;
};
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
SetRasterizerState(SolidState);
}
pass P1
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShaderWireframe() ));
SetRasterizerState(WireframeState);
}
}
</pre>
<br />
<br />
<br />
<h3>
Conclusion</h3>
<div>
<br /></div>
<div>
This picture shows a sphere created with 8 slices and 8 stacks:</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkBCrOWwk-29fpIc5yc_GMhBm5uVHmF_ZxCQeblIY4EpGHMgoZXjxD23RYKsu9_KKvBcg-SYL9CwcLN47qfVoO_ubtzW2FNaHGqEhWma8aFwgB5qbFJ_bXSdXMjKqDWAgJfwCE2Kw0Ulut/s1600/Sphere1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkBCrOWwk-29fpIc5yc_GMhBm5uVHmF_ZxCQeblIY4EpGHMgoZXjxD23RYKsu9_KKvBcg-SYL9CwcLN47qfVoO_ubtzW2FNaHGqEhWma8aFwgB5qbFJ_bXSdXMjKqDWAgJfwCE2Kw0Ulut/s1600/Sphere1.PNG" height="240" width="320" /></a></div>
<br />
This sphere was created with 32 slices and 32 stacks:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXiJaXkRaxhDr7TVPIyvn5BYTLJr4T19m7Z7sntnAD09YjcZLJc3doqz39wYSYpQp9bgPU8J5Vo1_wXpbQQ5gzo5wIawwgkEtD06rV0grkzTXll4WxwSebzKiRieSC-SZzHK-VBofOlAnL/s1600/Sphere2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhXiJaXkRaxhDr7TVPIyvn5BYTLJr4T19m7Z7sntnAD09YjcZLJc3doqz39wYSYpQp9bgPU8J5Vo1_wXpbQQ5gzo5wIawwgkEtD06rV0grkzTXll4WxwSebzKiRieSC-SZzHK-VBofOlAnL/s1600/Sphere2.PNG" height="240" width="320" /></a></div>
<br />
You can download the code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/e6dbaf6c800a36bd12f92f79ff3a8dd25cfb147a">here</a>.Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-9370202232850797042013-04-18T18:31:00.000+02:002013-04-22T23:30:55.031+02:00Depth/Stencil Buffer<h3>
<span style="font-size: small; font-weight: normal;">This tutorial is one part of a series of tutorials about generating procedural meshes. See </span><a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html" style="font-size: medium; font-weight: normal;">here</a><span style="font-size: small; font-weight: normal;"> for an outline.</span></h3>
<h3>
Problem</h3>
So far, we haven't cared about setting up and using the depth buffer. This results in the following problem, when rendering more than one object: the objects that is drawn last is painted over all other objects, regardless of their real order in 3D space.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm88x9oiUXxz2bmVYOcuHGV5z6flFVRhioy7_Pr8O0zg4lIS11Gw8uZo4eWmOWKb-lhtUkxj87PsQhLzxecDDKceYQ5X5Q95wznazeqXzAVqj0L3XYP7Xa42GzKmvcBAWW8wSBe-px5t-R/s1600/DepthBufferProblem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgm88x9oiUXxz2bmVYOcuHGV5z6flFVRhioy7_Pr8O0zg4lIS11Gw8uZo4eWmOWKb-lhtUkxj87PsQhLzxecDDKceYQ5X5Q95wznazeqXzAVqj0L3XYP7Xa42GzKmvcBAWW8wSBe-px5t-R/s1600/DepthBufferProblem.png" height="240" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
In the picture above the grid is drawn first and then the cube is drawn. Because the cube is painted over the grid, the line of the gird marked in the red rectangle is not visible.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSMtkYe8NOHLE9T-MkVlU0tbP3I63Cmz-ekPgpTUpZWW5SM-KOYqNPJj_MBZvb6yVeb8vR5YKB_ggzonkeqGo9AI1ydhjsCcgx-_Gj1UxSKojTVEaU1igTGdfT0QbIu3pe7ukJyWikdgeX/s1600/DepthBuffer2Problem.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSMtkYe8NOHLE9T-MkVlU0tbP3I63Cmz-ekPgpTUpZWW5SM-KOYqNPJj_MBZvb6yVeb8vR5YKB_ggzonkeqGo9AI1ydhjsCcgx-_Gj1UxSKojTVEaU1igTGdfT0QbIu3pe7ukJyWikdgeX/s1600/DepthBuffer2Problem.png" height="240" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
Conversely, if the grid is drawn last, the grid is always visible and the cube looks transparent. This looks cool when playing around with the camera, but is not exactly what we want in every situation ;)<br />
<br />
The solution for painting objects in the right order to the screen according to their 3D positions is using a depth buffer.<br />
<h3>
Depth/Stencil Buffer </h3>
<div>
A depth buffer, also called z-buffer, is a texture with the same size as the render target. The depth buffer is applied in one of the last stages of the pipeline, namely the output merger stage. What happens at the output merger stage?<br />
<br />
To explain this, we need to take a step back in the rending pipeline and look at the rasterizer stage. (See <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ff476882(v=vs.85).aspx">here</a> for the MSDN documentation on the Direct3D 11 graphics pipeline) When you provided some geometry and applied all the transformations to finally render your objects to the screen, your objects have to be rasterized. This means the scene gets sampled and discretized into pixels, or in other words: a raster is laid over your scene and each cell of this raster represents a pixel. This pixel is written to the render target.<br />
<br />
<br />
At this point the depth buffer and the output merger stage comes into play. The depth buffer has the same height and width as you render target, because each pixel in the depth buffer corresponds to a pixel on the render target. When a object is rasterized into pixels and painted to the render target, the GPU has information about the depth of this pixel, or how far it is away from the camera. The depth buffer is for holding the depth of the current pixel on the render target. If the pixel of the current object in nearer to the camera than the pixel that is already at the according position, the old pixel is overwritten on the render target and the depth value in the depth buffer is updated to the new pixels depth. If the depth of a pixel is higher than an existing pixel, the old pixel will not be overwritten.<br />
To sum up: the depth buffer keeps track of the depth of the pixels on the render target during the rasterization process. Only the nearest pixels of the scene make it to the final render target buffer.<br />
<br />
As we don't need the stencil buffer right now, I won't go into much detail. Depth and stencil buffer are often combined in one texture. A common format is using 24 bit for the depth buffer and 8 bit for the stencil buffer.<br />
The stencil is, as its name implies, a template that can be applied in the output merger stage. This buffer can be used in various ways according to the flags set in the GPU. You can use it as a mask and don't allow rending to these pixels or you can use the buffer to count your write accesses to this pixel in one render cycle.</div>
<h3>
Creating the Depth/Stencil Buffer</h3>
<div>
Like I mentioned above, we need a texture for the depth buffer. This is initialized with the width and height of the control, we are rendering on. With this texture we can now create the depth/stencil buffer, which is in SlimDX a class called <span style="font-family: Courier New, Courier, monospace;">DepthStencilView</span>:</div>
<div>
<br /></div>
<div>
<br />
<pre class="brush:csharp">Texture2D DSTexture = new Texture2D(
device,
new Texture2DDescription()
{
ArraySize = 1,
MipLevels = 1,
Format = Format.D32_Float,
Width = form.ClientSize.Width,
Height = form.ClientSize.Height,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default
}
);
depthStencil = new DepthStencilView(
device,
DSTexture,
new DepthStencilViewDescription()
{
ArraySize = 0,
FirstArraySlice = 0,
MipSlice = 0,
Format = Format.D32_Float,
Dimension = DepthStencilViewDimension.Texture2D
}
);
</pre>
<br /></div>
<h3>
Setting the DepthStencil State</h3>
<div>
Now that we have set up the depth buffer, we need to create a depth/stencil state and assign it to the <span style="font-family: Courier New, Courier, monospace;">DepthStencilState </span>of the output merger stage.<br />
<br />
<pre class="brush:csharp">context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
device,
new DepthStencilStateDescription()
{
DepthComparison = Comparison.Always,
DepthWriteMask = DepthWriteMask.All,
IsDepthEnabled = true,
IsStencilEnabled = false
}
);
context.OutputMerger.SetTargets(depthStencil, renderTarget);
DepthStencilStateDescription dssd = new DepthStencilStateDescription
{
IsDepthEnabled = true,
IsStencilEnabled = false,
DepthWriteMask = DepthWriteMask.All,
DepthComparison = Comparison.Less,
};
DepthStencilState depthStencilStateNormal;
depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;
</pre>
<br />
<h3>
Putting it all together</h3>
<div>
This is the complete code of my method to create the depth/stencil buffer and to set the state:</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
<pre class="brush:csharp">public void CreateDepthStencilBuffer(System.Windows.Forms.Control form)
{
Texture2D DSTexture = new Texture2D(
device,
new Texture2DDescription()
{
ArraySize = 1,
MipLevels = 1,
Format = Format.D32_Float,
Width = form.ClientSize.Width,
Height = form.ClientSize.Height,
BindFlags = BindFlags.DepthStencil,
CpuAccessFlags = CpuAccessFlags.None,
SampleDescription = new SampleDescription(1, 0),
Usage = ResourceUsage.Default
}
);
depthStencil = new DepthStencilView(
device,
DSTexture,
new DepthStencilViewDescription()
{
ArraySize = 0,
FirstArraySlice = 0,
MipSlice = 0,
Format = Format.D32_Float,
Dimension = DepthStencilViewDimension.Texture2D
}
);
context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
device,
new DepthStencilStateDescription()
{
DepthComparison = Comparison.Always,
DepthWriteMask = DepthWriteMask.All,
IsDepthEnabled = true,
IsStencilEnabled = false
}
);
context.OutputMerger.SetTargets(depthStencil, renderTarget);
DepthStencilStateDescription dssd = new DepthStencilStateDescription
{
IsDepthEnabled = true,
IsStencilEnabled = false,
DepthWriteMask = DepthWriteMask.All,
DepthComparison = Comparison.Less,
};
DepthStencilState depthStencilStateNormal;
depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;
}
</pre>
<br /></div>
<div>
<br /></div>
</div>
<h3>
Clearing the Depth/Stencil Buffer</h3>
<div>
Because the GPU writes in every render cycle to the depth/stencil buffer, we need to clear this buffer in every render cycle. This is done with <span style="font-family: Courier New, Courier, monospace;">ClearDepthStencilView</span>.<br />
<br />
<pre class="brush:csharp">public void RenderScene()
{
while (true)
{
fc.Count();
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearDepthStencilView(dm.depthStencil, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.75f, 0.75f, 0.75f));
Scene.Instance.render();
dm.swapChain.Present(syncInterval, PresentFlags.None);
}
}
</pre>
<br /></div>
<h3>
Camera</h3>
<div>
The next thing to do is to check, if you are setting the values for znear and zfar right, when setting up the perspective for the camera. When using the depth buffer, these values come into play in order to decide in which interval (from znear to zfar) objects are rendered.<br />
<br />
I create my perspective matrix this way:<br />
<br />
<pre class="brush:csharp">perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.1f, 1000.0f);
</pre>
<br />
<br /></div>
<h3>
Result</h3>
<div>
As you can see in the picture in the green rectangle, the grid is now rendered above the cube, if a line is in front of the cube. </div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2nuUrEjdaU7zhww7cH6JQjt6icUao62UOdEP-zaooDHJ7arjlPtIABjkcflEiN2IFQl3jssY5Zoitdb87ZJGOOY5NZ-6VSEuIIiTtoFVkrHM4_LFLScjjoAev0BBj_nISs-8-NhKo8-df/s1600/DepthBufferWorkingMarks.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2nuUrEjdaU7zhww7cH6JQjt6icUao62UOdEP-zaooDHJ7arjlPtIABjkcflEiN2IFQl3jssY5Zoitdb87ZJGOOY5NZ-6VSEuIIiTtoFVkrHM4_LFLScjjoAev0BBj_nISs-8-NhKo8-df/s1600/DepthBufferWorkingMarks.png" height="240" width="320" /></a></div>
<div>
<br />
If you take a look at the right rectangle, you can see that the wireframe on the cube seems to be painted in dotted lines. But this is not how it is supposed to look. What is happening here is called z-buffer fighting. Because the cube and the wireframe are rendered on base of the same geometry, the output merger stage can't decide which pixel to render, because both pixels have the same depth.<br />
<br />
To avoid this z-fighting, I add a depth bias to the wireframe in the rasterizer state of the shader:<br />
<br />
<pre class="brush:csharp">RasterizerState WireframeState
{
FillMode = Wireframe;
SlopeScaledDepthBias = -0.5f;
};
</pre>
<pre class="brush:csharp"></pre>
Here is the documentation on MSDN on depth bias:<br />
<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx">http://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx</a><br />
<br />
Now everything looks OK:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOTlJjsDURYgqpZ_cwFG6gNI-kFOetj8BTjewTQQWwz3QatRQz4qzH08E8xN1q-073FXfqpDPHlKzDF93DBZ0BlBrfUftwnZ5Kb5Sn6Mj5jVCUaAHvuKZV9a3unRR4wThCZcC2pNbNcq7y/s1600/DepthBufferFinal.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiOTlJjsDURYgqpZ_cwFG6gNI-kFOetj8BTjewTQQWwz3QatRQz4qzH08E8xN1q-073FXfqpDPHlKzDF93DBZ0BlBrfUftwnZ5Kb5Sn6Mj5jVCUaAHvuKZV9a3unRR4wThCZcC2pNbNcq7y/s1600/DepthBufferFinal.PNG" height="240" width="320" /></a></div>
<br />
<br />
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/de3ecf9e59f7e6416a54652647560a716554bc78">here</a>.<br />
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div>
<br /></div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-47145691450540089422013-04-17T22:56:00.000+02:002013-04-22T23:30:38.906+02:00Rendering a Wireframe over a MeshThis tutorial is one part of a series of tutorials about generating procedural meshes. See <a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html">here</a> for an outline.<br />
<br />
Rendering a wireframe over a given mesh is relatively simple and requires just a second pass in the shader and two states for the rasterizer.<br />
<br />
So far we used an effect technique with one pass:<br />
<br />
<pre class="brush:csharp">technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
}
}</pre>
<pre class="brush:csharp"></pre>
<pre class="brush:csharp"></pre>
<br />
This is the shader code for rendering the wireframe over the mesh:<br />
<br />
<br />
<pre class="brush:csharp">matrix gWVP;
float4 wireFrameColor;
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = mul( position, gWVP);
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
float4 PShaderWireframe(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return wireFrameColor;
}
RasterizerState WireframeState
{
FillMode = Wireframe;
};
RasterizerState SolidState
{
FillMode = Solid;
};
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
SetRasterizerState(SolidState);
}
pass P1
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShaderWireframe() ));
SetRasterizerState(WireframeState);
}
}
</pre>
<br />
In the first pass <span style="font-family: Courier New, Courier, monospace;">P0</span> I set the fillmode state to <span style="font-family: Courier New, Courier, monospace;">Solid</span>, to render the mesh. In the second pass <span style="font-family: Courier New, Courier, monospace;">P1</span> the fillmode state is set to <span style="font-family: Courier New, Courier, monospace;">Wireframe</span>. Observe, that while I use in P0 and P1 the same vertex shader (which is kind of obious, because the wireframe needs the same transformations as the solid mesh), but use a different pixel shader, called <span style="font-family: Courier New, Courier, monospace;">PShaderWireframe. </span><span style="font-family: inherit;">In this second pixel shader I set the color of the wireframe pixels to the variable </span><span style="font-family: Courier New, Courier, monospace;">wireFrameColor, </span><span style="font-family: inherit;">to render the wireframe in a given color.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">The variable wireFrameColor is set via the effect framework in the renderable object. This way there is no need to recompile the shader in case I want to use a different color for the wireframe.</span><br />
<br />
In the code for the renderable I have to declare a variable of the type <span style="font-family: Courier New, Courier, monospace;">EffectVectorVariable</span><span style="font-family: inherit;">:</span><br />
<br />
<pre class="brush:csharp">EffectVectorVariable wireFrameColor;
</pre>
<br />
This variable is bound to the shader variable in the constructor of the renderable object with this statement:<br />
<br />
<pre class="brush:csharp">wireFrameColor = effect.GetVariableByName("wireFrameColor").AsVector();
Vector4 col = new Vector4(0, 0, 0, 1);
wireFrameColor.Set(col);
</pre>
<br />
I just need to set this variable once in the constructor. If you want to do things like changing the color at runtime, you need to put the asignment <span style="font-family: Courier New, Courier, monospace;">wireFrameColor.Set(col) </span><span style="font-family: inherit;">into the render method to make sure, it gets called in every frame.</span><br />
<br />
<h3>
Result</h3>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZY7rPKsDIao9rMYUC6lupm1L-ONYMeCLkXVs2ywgdTblOh9K0E9ED2G5__T0O-vzWoqsk0DfH2ErQc4jWbV6EWPKdXHl9DTpVLiMaj-JRndzAXKipelqmhR3JJXPQBb2l78d0NdYwc_vI/s1600/MeshAndWireframe.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZY7rPKsDIao9rMYUC6lupm1L-ONYMeCLkXVs2ywgdTblOh9K0E9ED2G5__T0O-vzWoqsk0DfH2ErQc4jWbV6EWPKdXHl9DTpVLiMaj-JRndzAXKipelqmhR3JJXPQBb2l78d0NdYwc_vI/s1600/MeshAndWireframe.PNG" height="240" width="320" /></a></div>
<div>
<br />
<br />
You can download the source code for this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/045eb168d32785fe2544c5b13ec08aa1004f158c">here</a>.</div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-79367445246483312032013-04-17T12:54:00.001+02:002013-04-22T23:30:25.386+02:00The Color Cube: Vertices with Color<h3>
<span style="font-size: small; font-weight: normal;">This tutorial is one part of a series of tutorials about generating procedural meshes. See </span><a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html" style="font-size: medium; font-weight: normal;">here</a><span style="font-size: small; font-weight: normal;"> for an outline.</span></h3>
<h3>
Vertices</h3>
So far I used simple vertices with only information about position in it. The vertex buffer consisted of an array of <span style="font-family: Courier New, Courier, monospace;">Vector3 </span>structs. As I mentioned earlier, vertices can be more complex objects, holding further information about color, normals, texture coordinates and so on. In previous tutorials I used the pixel shader and hardcoded the color of the the pixel of an object:<br />
<br />
<pre class="brush:csharp">float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return float4(0.0f, 1.0f, 0.0f, 1.0f);
}
</pre>
<br />
This simple pixel shader just colors every of an object lime green, as the first three values of the <span style="font-family: Courier New, Courier, monospace;">float4 </span>struct correspond to the RGB color model (standing for red, greed, blue).<br />
<br />
First, we need a vertex structure, that can hold additional information about color:<br />
<br />
<pre class="brush:csharp">[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
public Vector3 Position;
public int Color;
public Vertex(Vector3 position, int color)
{
this.Position = position;
this.Color = color;
}
}
</pre>
<br />
From here on we need to create a DataStream and write new vertices to this stream like in this statement:<br />
<br />
<pre class="brush:csharp">vertices.Write(new Vertex(new Vector3(1.0f, 1.0f, 1.0f), Color.FromArgb(255, 0, 0).ToArgb()));
</pre>
<br />
We create a new vertex at position x = 1, y = 1 and z = 1 and we tell the <span style="font-family: Courier New, Courier, monospace;">Color </span>struct that we want the color red.<br />
<br />
We are not done yet. The vertex buffer is just a stream of bytes and we need to tell our device how to interpret this data. This is exactly was the <span style="font-family: Courier New, Courier, monospace;">InputLayout </span>is made for.<br />
In previous tutorials I used this <span style="font-family: Courier New, Courier, monospace;">InputLayout</span>:<br />
<br />
<pre class="brush:csharp">var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
</pre>
<br />
The <span style="font-family: Courier New, Courier, monospace;">InputLayout </span>needs an array of <span style="font-family: Courier New, Courier, monospace;">InputElements</span>. The <span style="font-family: Courier New, Courier, monospace;">InputElement </span>array so far just consisted of the one element defined above, holding only information about the position. So we need to add a further <span style="font-family: Courier New, Courier, monospace;">InputElement </span>for color:<br />
<br />
<pre class="brush:csharp">var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
new InputElement("COLOR", 0, Format.B8G8R8A8_UNorm, 12, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
</pre>
<br />
The second <span style="font-family: Courier New, Courier, monospace;">InputElement </span>for color also gives information about its offset from the beginning of <span style="font-family: Courier New, Courier, monospace;">InputElement </span>structure. As the first <span style="font-family: Courier New, Courier, monospace;">InputElement </span>consists of three floats and one float is four bytes big, the color <span style="font-family: Courier New, Courier, monospace;">InputElement </span>starts at byte 12.<br />
<br />
Just like before, we need to set the input layout in the device before making the draw call:<br />
<br />
<pre class="brush:csharp">DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
</pre>
<br />
We also have to adjust the shader, but I will come to this later. First let us create some geometry to render.<br />
<br />
<br />
<h3>
Color Cube</h3>
<div>
I will use the color cube as an example and this is what we are aiming at:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaYGPV4BaqUUGRc8-g-hfsCGDexi8baXgsLohZMCWahO132WGPP7nMfCi0WKzucFVWdWqPSPoNmqviKFGwYpXyA-2XZT0SXjj8qXhAQCUkWncMg6pyfGXFay2OAEoQMDgQdWoA1STofOA2/s1600/RenderedColorCube.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaYGPV4BaqUUGRc8-g-hfsCGDexi8baXgsLohZMCWahO132WGPP7nMfCi0WKzucFVWdWqPSPoNmqviKFGwYpXyA-2XZT0SXjj8qXhAQCUkWncMg6pyfGXFay2OAEoQMDgQdWoA1STofOA2/s320/RenderedColorCube.PNG" height="240" width="320" /></a></div>
<br />
The cube consists of 8 vertices and each has a different color. Pixels that lie on the surface of the cube are being interpolated according to their position in the corresponding triangle.<br />
<br />
I define the vertices of the cube, so that the center of the cube corresponds with the origin of its local coordinate system. Shorter: the center of the cube is (0,0,0).<br />
<br />
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5nJTtuwYYeig6eLWKUvhcIJmGhsZRhxhmSbPYb_1Q9lg9ax6V3pRTe-GrcukzmFsjF4swIt1aJ0XgEfiWxZHTujhf23_FswuAng_Q3ZUMOaOBBk_jmQNCLAUSd_iesa8A3QI4W2NNCix/s1600/ColorCube2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY5nJTtuwYYeig6eLWKUvhcIJmGhsZRhxhmSbPYb_1Q9lg9ax6V3pRTe-GrcukzmFsjF4swIt1aJ0XgEfiWxZHTujhf23_FswuAng_Q3ZUMOaOBBk_jmQNCLAUSd_iesa8A3QI4W2NNCix/s1600/ColorCube2.png" height="301" width="400" /></a></div>
<br />
<br />
In the center of the cube is the coordinate frame. A widely used color scheme is to map the axis to RGB color model: x-axis: red, y-axis: green, z-axis: blue. So what is up with those plusses and minusses? In order to keep the graphic clear, I omitted the values of the positions and depicted only the signs of the vector elements. Take a look at the x-axis: every vertex of the cube that lies in the positive x-axis, has a plus sign (all vertices on the right) and every vertex in the negative x-axis (all vertices on the left) have a negative sign.<br />
<br />
And what is the purpose of this? If I have a negative sign at the position element (x,y or z), I set the corresponding color element (R,G or B) value to zero and if I have a positive sign, I set the color element to 255. This<br />
is how I fill the vertex buffer and I colored the corresponding values green and red, to make this pattern more visible:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDSoBso2XmzTf3iMd4UA9cAb1p-jId8GNFuFXFp41HDdQXFOofgiL0PvVcGtAoqUFfiYjsxDvDGEDzY5WrN1Kzv1dZ6skUYhGr1gOJjsprCMBmU4_SfD-gPW4OreRYgkfyW1YvE4Wz-8dk/s1600/Pattern.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDSoBso2XmzTf3iMd4UA9cAb1p-jId8GNFuFXFp41HDdQXFOofgiL0PvVcGtAoqUFfiYjsxDvDGEDzY5WrN1Kzv1dZ6skUYhGr1gOJjsprCMBmU4_SfD-gPW4OreRYgkfyW1YvE4Wz-8dk/s1600/Pattern.png" height="125" width="640" /></a></div>
<br />
Now that we have set up the vertex buffer it is time to set up the index buffer. This picture depicts the order in which I have defined the vertices:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoOIi7YUq6izkz-FqtQYDwTXYEKTqSIEv9yx9cVNt9lL8nhgVyNaWNSSIyxPUjX99EocQaCcZjiwhlGARKt5pRprmZi9GQYQH3Wik9GpO80KnWFGLaIReNYknNx2myq9G6Ku1z3BILC3kR/s1600/ColorCubeIndices.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjoOIi7YUq6izkz-FqtQYDwTXYEKTqSIEv9yx9cVNt9lL8nhgVyNaWNSSIyxPUjX99EocQaCcZjiwhlGARKt5pRprmZi9GQYQH3Wik9GpO80KnWFGLaIReNYknNx2myq9G6Ku1z3BILC3kR/s1600/ColorCubeIndices.png" height="297" width="320" /></a></div>
<br />
The sequence of vertex definitions is completely arbitrary, but once we have defined the vertices we need to stay consistent with this definition to get the triangles rendered in a right way. The default way DirectX handles triangle definition is by enumerating the vertices clockwise. If you are looking at a particular side, you have to enumerate the indices in the right order:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz1ptC4YOGEwBk_oC20ASpw3tRuwZ25Kbl3A6xBEk36Kp35facqrWgCzznpjsxY7OrkfwtXVM9TlmSwNM0r2bVAn-ukZLIVwH_oTU9bKhLzHnlTbApA_Xpuw_4CmuumxQZxU5RVQ5F_Atk/s1600/ColorCubeTesselation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgz1ptC4YOGEwBk_oC20ASpw3tRuwZ25Kbl3A6xBEk36Kp35facqrWgCzznpjsxY7OrkfwtXVM9TlmSwNM0r2bVAn-ukZLIVwH_oTU9bKhLzHnlTbApA_Xpuw_4CmuumxQZxU5RVQ5F_Atk/s1600/ColorCubeTesselation.png" height="400" width="260" /></a></div>
<br />
Look at the picture above and look at the case when looking straight at the top of the cube. We have indices 0,1,2 and 3. The triangulation I chose is: (0,1,2) and (2,3,0). This is also arbitrary as you also could triangulate this side with (3,0,1) and (1,2,3). As long as you enumerate the indices in a clockwise order you get a valid triangulation.<br />
<br />
I fill the index buffer corresponding to the picture above:<br />
<br />
<pre class="brush:csharp">// Cube has 6 sides: top, bottom, left, right, front, back
// top
indices.WriteRange(new short[] { 0, 1, 2 });
indices.WriteRange(new short[] { 2, 3, 0 });
// right
indices.WriteRange(new short[] { 0, 5, 6 });
indices.WriteRange(new short[] { 6, 1, 0 });
// left
indices.WriteRange(new short[] { 2, 7, 4 });
indices.WriteRange(new short[] { 4, 3, 2 });
// front
indices.WriteRange(new short[] { 1, 6, 7 });
indices.WriteRange(new short[] { 7, 2, 1 });
// back
indices.WriteRange(new short[] { 3, 4, 5 });
indices.WriteRange(new short[] { 5, 0, 3 });
// bottom
indices.WriteRange(new short[] { 6, 5, 4 });
indices.WriteRange(new short[] { 4, 7, 6 });
</pre>
<br />
<br />
<h3>
Source Code</h3>
<div>
Putting everything together, this is the complete source code for the <span style="font-family: Courier New, Courier, monospace;">ColorCube </span>Renderable:</div>
<div>
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
using System.Runtime.InteropServices;
namespace Apparat.Renderables
{
public class ColorCube : Renderable
{
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
InputLayout layout;
SlimDX.Direct3D11.Buffer vertexBuffer;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream vertices;
DataStream indices;
int vertexStride = 0;
int numVertices = 0;
int indexStride = 0;
int numIndices = 0;
int vertexBufferSizeInBytes = 0;
int indexBufferSizeInBytes = 0;
EffectMatrixVariable tmat;
[StructLayout(LayoutKind.Sequential)]
public struct Vertex
{
public Vector3 Position;
public int Color;
public Vertex(Vector3 position, int color)
{
this.Position = position;
this.Color = color;
}
}
public ColorCube()
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"Shaders/colorEffect.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
var elements = new[] {
new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
new InputElement("COLOR", 0, Format.B8G8R8A8_UNorm, 12, 0)
};
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
tmat = effect.GetVariableByName("gWVP").AsMatrix();
// half length of an edge
float offset = 0.5f;
vertexStride = Marshal.SizeOf(typeof(Vertex)); // 16 bytes
numVertices = 8;
vertexBufferSizeInBytes = vertexStride * numVertices;
vertices = new DataStream(vertexBufferSizeInBytes, true, true);
vertices.Write(new Vertex(new Vector3(+offset, +offset, +offset), Color.FromArgb(255, 255, 255).ToArgb())); // 0
vertices.Write(new Vertex(new Vector3(+offset, +offset, -offset), Color.FromArgb(255, 255, 000).ToArgb())); // 1
vertices.Write(new Vertex(new Vector3(-offset, +offset, -offset), Color.FromArgb(000, 255, 000).ToArgb())); // 2
vertices.Write(new Vertex(new Vector3(-offset, +offset, +offset), Color.FromArgb(000, 255, 255).ToArgb())); // 3
vertices.Write(new Vertex(new Vector3(-offset, -offset, +offset), Color.FromArgb(000, 000, 255).ToArgb())); // 4
vertices.Write(new Vertex(new Vector3(+offset, -offset, +offset), Color.FromArgb(255, 000, 255).ToArgb())); // 5
vertices.Write(new Vertex(new Vector3(+offset, -offset, -offset), Color.FromArgb(255, 000, 000).ToArgb())); // 6
vertices.Write(new Vertex(new Vector3(-offset, -offset, -offset), Color.FromArgb(000, 000, 000).ToArgb())); // 7
vertices.Position = 0;
vertexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
vertices,
vertexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
numIndices = 36;
indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
indexBufferSizeInBytes = numIndices * indexStride;
indices = new DataStream(indexBufferSizeInBytes, true, true);
// Cube has 6 sides: top, bottom, left, right, front, back
// top
indices.WriteRange(new short[] { 0, 1, 2 });
indices.WriteRange(new short[] { 2, 3, 0 });
// right
indices.WriteRange(new short[] { 0, 5, 6 });
indices.WriteRange(new short[] { 6, 1, 0 });
// left
indices.WriteRange(new short[] { 2, 7, 4 });
indices.WriteRange(new short[] { 4, 3, 2 });
// front
indices.WriteRange(new short[] { 1, 6, 7 });
indices.WriteRange(new short[] { 7, 2, 1 });
// back
indices.WriteRange(new short[] { 3, 4, 5 });
indices.WriteRange(new short[] { 5, 0, 3 });
// bottom
indices.WriteRange(new short[] { 6, 5, 4 });
indices.WriteRange(new short[] { 4, 7, 6 });
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
indexBufferSizeInBytes,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
effect.Dispose();
inputSignature.Dispose();
vertexBuffer.Dispose();
layout.Dispose();
}
}
}
</pre>
<br />
<br />
<h3>
Shader</h3>
Like I mentioned above, we also have to modify our shader in order to render the color of a vertex:<br />
<br />
<pre class="brush:csharp">matrix gWVP;
struct VOut
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = mul( position, gWVP);
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
RasterizerState WireframeState
{
FillMode = Wireframe;
CullMode = None;
FrontCounterClockwise = false;
};
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
//SetRasterizerState(WireframeState);
}
}
</pre>
<br />
Not much going on in the vertex shader <span style="font-family: Courier New, Courier, monospace;">VShader</span>. The position of the vertex is multiplied with the WorldViewPerspective matrix from our camera to transform it to the right screen position and the color of the vertex is just handed through to the output of the shader.<br />
<br />
Well, something is new. Take a look at the vertex shaders used in previous tutorials:<br />
<br />
<pre class="brush:csharp">float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
</pre>
<br />
This shader performed the above mentioned transformation from the local coordinate system of the model to the screen space and it returned a <span style="font-family: Courier New, Courier, monospace;">float4 </span>structure.<br />
<br />
Compare this to the new vertex shader. This has as output a new defined struct called <span style="font-family: Courier New, Courier, monospace;">VOut.</span><span style="font-family: inherit;"> To be able to hand down the color information of the vertex to the pixel shader, we need to have a structure, that also holds the color information.</span><br />
<span style="font-family: inherit;"><br /></span>
<br />
<h3>
Result</h3>
Now we can render vertices with color and get a nice color cube:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYQLsjio8FhO8xwY1YOgpxQpZKzMwp6fHCq4OU8ON2FY-AI9_Mm600Wc06GGZdqWgGQB05Og0KV0ZQp9M8BqAx6BLIaDA6l_RKbG_4QnZE6Minkn2fe8ttM0NydNL4SsfTMjSp-bAMMEdM/s1600/RenderedColorCube.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYQLsjio8FhO8xwY1YOgpxQpZKzMwp6fHCq4OU8ON2FY-AI9_Mm600Wc06GGZdqWgGQB05Og0KV0ZQp9M8BqAx6BLIaDA6l_RKbG_4QnZE6Minkn2fe8ttM0NydNL4SsfTMjSp-bAMMEdM/s1600/RenderedColorCube.PNG" height="240" width="320" /></a></div>
<br />
In the next tutorial I will show how to render a wireframe over this colored cube. If you download the code and play around with this example, you will notice that the grid will not be rendered over the cube even if it is in between the camera and the cube. This is because we haven't set up a depth buffer by now and this will be addressed in a further tutorial.<br />
<br />
<br />
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/297823d20609576ae3c1e000b36906fb3f83d247">here</a>.<br />
<br />Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-35733899320981838262013-04-15T23:31:00.000+02:002013-04-22T23:29:54.294+02:00GridMesh: Creating the IndexBuffer and Rendering as TriangleList<h3>
Introduction</h3>
This tutorial is one part of a series of tutorials about generating procedural meshes. See <a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html">here</a> for an outline.<br />
<br />
We already created the vertices in the <a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-vertex-buffer-and.html">last tutorial</a>. In order to triangulate the mesh, we need to create the index buffer, set it in the input assembler stage, change the primitive topology from point list to triangle list and change the draw call to draw indexed primitives.<br />
<br />
<br />
<h3>
VertexBuffer and IndexBuffer</h3>
There are two ways to draw triangles:<br />
<br />
<ol>
<li>explicitly listing every vertex of each triangle of a mesh</li>
<li>having a list of vertices and describe each triangle by the indices in the vertex buffer</li>
</ol>
<div>
The first approach is useful for very small meshes like a rectangle. The second way is used to consume less memory for a mesh. Consider the size of a vertex with only the information for its position x,y,z: these are already three floats and you need to use 12 byte (as one float is 4 bytes). Having more complex vertices with position, color, normal, texture coordinates and what ever information you need for you shaders, the size of a single vertex can grow significantly. In contrast to this, you need some sort of integer data type (byte (1), short (2), int (4), long (8) - with size in bytes in brackets) to point to an index in the vertex buffer.</div>
<div>
<br /></div>
<div>
Let's take a look at this rectangle, defined by four points p0, p1, p2 and p3:</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0bD38D80reXZHeagLMEa4XERpvWtkQzq3ZFK82YimRl3SuUFiACzsm8e0x8zimVPwiU3vruGhre3qTH8r0Yci9RH0BDfZzcWIaF29JEHYsI8ty4a2GBuxULS4rXjAfOPUpsSD0craIegj/s1600/Rectangle.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj0bD38D80reXZHeagLMEa4XERpvWtkQzq3ZFK82YimRl3SuUFiACzsm8e0x8zimVPwiU3vruGhre3qTH8r0Yci9RH0BDfZzcWIaF29JEHYsI8ty4a2GBuxULS4rXjAfOPUpsSD0craIegj/s1600/Rectangle.png" /></a></div>
<div>
<br /></div>
<div>
Following the approach to explicitly list the vertices, we can triangulate the rectangle by creating a list of vertices: v0, v1, v2, v3 ,v4, v5</div>
<div>
<br /></div>
<div>
<div>
This results in the two triangles v0, v1, v2 (red) and v3, v4, v5 (blue):</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPfCv26CQDKzuI-khX2P0-lxBibJTNSi3YB6bo0ePkBFO-m2zSf-uC-nly4o1HbBENs1NKewaspRbNuo0pFJxkw2Nhiu-rEi0W2Rceyu2unTgJrx_yTv85euXPd2glNYeu5AYxD9uh_zxQ/s1600/RectangleTriangulated2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPfCv26CQDKzuI-khX2P0-lxBibJTNSi3YB6bo0ePkBFO-m2zSf-uC-nly4o1HbBENs1NKewaspRbNuo0pFJxkw2Nhiu-rEi0W2Rceyu2unTgJrx_yTv85euXPd2glNYeu5AYxD9uh_zxQ/s1600/RectangleTriangulated2.png" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Now we have a list of 6 vertices that have the according positions of the points. As you can see, we have to duplicate the positions p1 and p3 in our list of vertices. In this example I created the triangles clockwise by enumerating the vertices of a triangle in a clockwise manner (p0, p1, p3). In contrast to this the enumeration (p0, p3, p1) is counterclockwise. This list of 6 vertices would be our vertex buffer and we would tell the device to draw two triangles.</div>
<div>
<br /></div>
<div>
In order to avoid duplication of vertices when triangulating a mesh, we can use an index buffer. Again we need to set up a vertex buffer, but this holds this time just four vertices:</div>
</div>
<div>
<br /></div>
<div>
Vertex Buffer: v0, v1, v2, v3</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg6ZQgy188EV2UvNjISSeapH_kRtF22Fnd4xpAVCjrYtf_50-nn8vl9PwU8QSEtIm0C5ze-ZSH1bKbWuOjzmjOgwp7QmSF_YM1BgpZ-oQlExPGQQhI7A0QUbJpY1hIPNFZ4ECCjEAl6AJQ/s1600/RectangleIndexed.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhg6ZQgy188EV2UvNjISSeapH_kRtF22Fnd4xpAVCjrYtf_50-nn8vl9PwU8QSEtIm0C5ze-ZSH1bKbWuOjzmjOgwp7QmSF_YM1BgpZ-oQlExPGQQhI7A0QUbJpY1hIPNFZ4ECCjEAl6AJQ/s1600/RectangleIndexed.png" /></a></div>
<div>
<br /></div>
<div>
Now, this picture does not look too much different from the picture above. Essential is the content of the index buffer, which points at the indices of the vertex buffer:</div>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSDYSZrk0GxBWmtS3E3T8gWJAZHslAybAHBGivrN8Dx3IddBJFrcc99gIvJzk_tGq5bW1uMME6bfesov_CUBLjirJFgAIepoqlv3UKIMtuSBueWcQnfP_-oGhGAfJSf5j6tLXNWndH97Vh/s1600/VBandIB.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSDYSZrk0GxBWmtS3E3T8gWJAZHslAybAHBGivrN8Dx3IddBJFrcc99gIvJzk_tGq5bW1uMME6bfesov_CUBLjirJFgAIepoqlv3UKIMtuSBueWcQnfP_-oGhGAfJSf5j6tLXNWndH97Vh/s1600/VBandIB.png" height="205" width="400" /></a></div>
<br />
<br />
Now we can create the index buffer of the grid mesh.<br />
<br />
<h3>
Grid Mesh Source Code</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
public class GridMesh2 : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
DataStream vertices;
int numVertices = 0;
SlimDX.Direct3D11.Buffer indexBuffer;
DataStream indices;
int numIndices = 0;
InputLayout layout;
int stride;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
float stepSize;
public GridMesh2(int witdh, int height, float stepSize)
{
int numVerticesWidth = witdh + 1;
int numVerticesHeight = height + 1;
this.stepSize = stepSize;
numVertices = numVerticesWidth * numVerticesHeight;
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"transformEffect.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
stride = 12;
int sizeInBytes = stride * numVertices;
vertices = new DataStream(sizeInBytes, true, true);
float posX, posY;
float startX = -witdh * stepSize / 2.0f;
float startY = height * stepSize / 2.0f;
for (int y = 0; y < numVerticesHeight; y++)
{
for (int x = 0; x < numVerticesWidth; x++)
{
posX = startX + x * stepSize;
posY = startY - y * stepSize;
vertices.Write(new Vector3(posX, posY, 0));
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, vertices, sizeInBytes, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
// create the index buffer
int numPatches = (numVerticesWidth - 1) * (numVerticesHeight - 1);
numIndices = numPatches * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int y = 0; y < numVerticesHeight-1; y++)
{
for (int x = 0; x < numVerticesWidth-1; x++)
{
short lu = (short)(x + (y * (numVerticesWidth)));
short ru = (short)((x + 1) + (y * (numVerticesWidth)));
short rb = (short)((x + 1) + ((y + 1) * (numVerticesWidth)));
short lb = (short)(x + ((y + 1) * (numVerticesWidth)));
// clockwise
indices.Write(lu);
indices.Write(ru);
indices.Write(lb);
indices.Write(ru);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, stride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
public override void dispose()
{
}
}
}
</pre>
<h3>
Creating the IndexBuffer</h3>
As we created the vertex buffer with a double nested for loop, so we create the index buffer with a double nested for loop. To give you an intuition how I create the index buffer, take a look at this picture:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN2z0RPcaCJnB9UA0SgDxTCAYnZ2pBfHp-tB5c6kJuyfIkr8ZjgQ_a34tutBivuRwkNWwYIJ1F-ijnLDSZnqRU53l5vjzYqI85tqLVgE2BJGFM-MAa57M_kVTcj3ELuI88bGgpAlECMhf_/s1600/Grid+Linearized+Vertices+Triangulation.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjN2z0RPcaCJnB9UA0SgDxTCAYnZ2pBfHp-tB5c6kJuyfIkr8ZjgQ_a34tutBivuRwkNWwYIJ1F-ijnLDSZnqRU53l5vjzYqI85tqLVgE2BJGFM-MAa57M_kVTcj3ELuI88bGgpAlECMhf_/s1600/Grid+Linearized+Vertices+Triangulation.png" /></a></div>
I start at vertex v0 and look at a patch of four vertices. Two from the current column (v0 and v1) and two from the next column (v5 and v6). From these four vertices I create two triangles by adding six indices to the index buffer. This way I iterate to vertex v3 and create the last two triangles for this column and then move to the next column and so on ...<br />
<br />
<pre class="brush:csharp">SlimDX.Direct3D11.Buffer indexBuffer;
DataStream indices;
int numIndices = 0;
</pre>
<br />
<br />
<br />
<pre class="brush:csharp">// create the index buffer
int numPatches = (numVerticesWidth - 1) * (numVerticesHeight - 1);
numIndices = numPatches * 6;
indices = new DataStream(2 * numIndices, true, true);
for (int y = 0; y < numVerticesHeight-1; y++)
{
for (int x = 0; x < numVerticesWidth-1; x++)
{
short lu = (short)(x + (y * (numVerticesWidth)));
short ru = (short)((x + 1) + (y * (numVerticesWidth)));
short rb = (short)((x + 1) + ((y + 1) * (numVerticesWidth)));
short lb = (short)(x + ((y + 1) * (numVerticesWidth)));
// clockwise
indices.Write(lu);
indices.Write(ru);
indices.Write(lb);
indices.Write(ru);
indices.Write(rb);
indices.Write(lb);
}
}
indices.Position = 0;
indexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
indices,
2 * numIndices,
ResourceUsage.Default,
BindFlags.IndexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
</pre>
<br />
<br />
Let's compare the memory used for this approach and the example depicted in the picture above to the memory used for explicitly listing the vertices for triangulation:<br />
<br />
<h4>
The Explicit Case</h4>
<div>
We have 4 x 3 = 12 cells in the grid. Each cell consists of two triangles, so we get 12 x 2 = 24 triangles. For each triangle we need three vertices: 24 x 3 = 72 vertices. As we use the cheapest vertice type with position x,y,z only, one vertice costs 12 bytes. This results in 72 x 12 = 864 bytes.</div>
<div>
<br /></div>
<h4>
Using an Index Buffer</h4>
<div>
We have 5 x 4 = 20 vertices, which cost 20 x 12 = 240 byte. As we still have to describe the triangles in the index buffer, we need 72 indices. We use shorts, so one index costs 2 bytes: 72 x 2 = 144 bytes. Summing the costs for vertex and index buffer, we get 240 + 144 = 384 bytes.</div>
<div>
<br /></div>
<div>
This effect of saving space for vertex data amplifies with the number of triangles one vertice is used for. The four vertices at the corner of the mesh are just used once for a triangle, the other vertices on the border of the grid are used twice for triangles and each vertice inside of the grid is used for four triangles. Some meshes have an even higher connectivity (meaning the use for triangles, for example on vertex is used for six triangles). </div>
<div>
<br /></div>
<h3>
Rendering the GridMesh</h3>
<div>
The next thing we have to adjust is the code in the render method. First we have to change the primitive typology to <span style="font-family: Courier New, Courier, monospace;">TriangleList</span>. Next we have to set the index buffer with <span style="font-family: Courier New, Courier, monospace;">SetIndexBuffer</span>. Finally we have to switch the <span style="font-family: Courier New, Courier, monospace;">Draw </span>call to <span style="font-family: Courier New, Courier, monospace;">DrawIndexed. </span><br />
<br />
<pre class="brush:csharp">public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, stride, 0));
DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 0);
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.DrawIndexed(numIndices, 0, 0);
}
}
</pre>
<br />
As a result we get a tessellated (triangulated) mesh:</div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4_FJGA1_Ws5cjoZqyxR3yvngvO_2KKLGhW7430SIzAB6PbH_7kY5-oLe6c5XkRDcY6HkcT1kvg-hxSF832KYJWw7ei-odwPzRehXl7pPpMmGoG71bC5Z1v8hwSsWXz8B9KAu-H4etEH_o/s1600/GridMeshSolid.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4_FJGA1_Ws5cjoZqyxR3yvngvO_2KKLGhW7430SIzAB6PbH_7kY5-oLe6c5XkRDcY6HkcT1kvg-hxSF832KYJWw7ei-odwPzRehXl7pPpMmGoG71bC5Z1v8hwSsWXz8B9KAu-H4etEH_o/s1600/GridMeshSolid.PNG" height="240" width="320" /></a></div>
<br />
<br />
<h3>
Rendering as Wireframe</h3>
</div>
<div>
In order to see the triangulation of the mesh, we have to extend the shader with a <span style="font-family: Courier New, Courier, monospace;">RasterizerState</span>. You can play around with <span style="font-family: Courier New, Courier, monospace;">FillMode</span>, <span style="font-family: Courier New, Courier, monospace;">CullMode </span><span style="font-family: inherit;">and </span><span style="font-family: Courier New, Courier, monospace;">FrontCounterClockwise </span><span style="font-family: inherit;">to see what effects they have. </span></div>
<div>
<span style="font-family: inherit;"><br /></span></div>
<div>
<span style="font-family: inherit;">See <a href="http://msdn.microsoft.com/en-us/library/windows/desktop/ff476198(v=vs.85).aspx">MSDN </a>for the reference of the </span><span style="font-family: Courier New, Courier, monospace;">RasterizerState</span><span style="font-family: inherit;">. Interesting are the default values for these states: </span><span style="font-family: Courier New, Courier, monospace;">FillMode </span><span style="font-family: inherit;">(<b>Solid</b>), </span><span style="font-family: Courier New, Courier, monospace;">CullMode </span><span style="font-family: inherit;">(<b>Back</b>) and </span><span style="font-family: Courier New, Courier, monospace;">FrontCouterClockwise </span><span style="font-family: inherit;">(<b>false</b>). If you do not set the RasterizerState, Directx assumes, you want to fill your triangles, the front of your triangle is defined clockwise (like I did in my code above) and the back of the triangle will not be rendered.</span></div>
<div>
<br /></div>
<pre class="brush:csharp">matrix gWVP;
float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return float4(0.0f, 1.0f, 0.0f, 1.0f);
}
RasterizerState WireframeState
{
FillMode = Wireframe;
//CullMode = Front;
//FrontCounterClockwise = true;
};
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
SetRasterizerState(WireframeState);
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
<div>
Using this shader, we get this wireframe of the grid mesh:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaxr94jCPc5xHQ4exeZZ2TXGUjWLcMsGcgZvL8I-m8ozrxt5xe57sXStL4BZA7Bw_ujnTZnWTcdld5oqXbPnuPUeP6fdmFqkAxHnlWwDLDdVh7Xokh_YMionjhgWafRXWocb1u7hGayhF4/s1600/GridMeshWireframe.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaxr94jCPc5xHQ4exeZZ2TXGUjWLcMsGcgZvL8I-m8ozrxt5xe57sXStL4BZA7Bw_ujnTZnWTcdld5oqXbPnuPUeP6fdmFqkAxHnlWwDLDdVh7Xokh_YMionjhgWafRXWocb1u7hGayhF4/s1600/GridMeshWireframe.PNG" height="240" width="320" /></a></div>
<div>
<br /></div>
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/4015c7c2e24d4acf2c3e95429af8ccb030ac3b89">here</a>.<br />
<br />Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-83029333560419963062013-04-13T12:48:00.003+02:002013-04-22T23:29:30.585+02:00GridMesh: Creating the Vertex Buffer and Rendering as PointList<h3>
Grid Mesh</h3>
This tutorial is one part of a series of tutorials about generating procedural meshes. See <a href="http://apparat-engine.blogspot.de/2013/04/outline-procedural-meshes.html">here</a> for an outline.<br />
<br />
This tutorial deals with the creation of a vertex buffer for a mesh and rendering this mesh with the PointList primitive. We will start with a mesh for a rectangular grid of points. The grid extends along the x-axis and the y-axis and has three parameters:<br />
<br />
<br />
<ul>
<li>width: number of cells in direction of the x-axis</li>
<li>height: number of cells in direction of the y-axis</li>
<li>cell size: length of each edge of a cell</li>
</ul>
<br />
A grid with the width of 4 and a height of 3 is shown in the following picture:<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVPOQ-Upt4Tu5WSg2ypGxzbOcAAI4fXn6dVXHOl_tpCSMS6tYk2oZpqrYiaA8z2aJjYA_a1zDjpKhWxa1GTP1Rtqjdwjjc9HMfiXaS7f7bNfCE_F9M_bm-IYbrzCuNKshojKxy-eVnWhXR/s1600/Grid.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVPOQ-Upt4Tu5WSg2ypGxzbOcAAI4fXn6dVXHOl_tpCSMS6tYk2oZpqrYiaA8z2aJjYA_a1zDjpKhWxa1GTP1Rtqjdwjjc9HMfiXaS7f7bNfCE_F9M_bm-IYbrzCuNKshojKxy-eVnWhXR/s1600/Grid.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Grid with 4 Cells in x and 3 Cells in y.</td></tr>
</tbody></table>
In our coordinate system the x-axis goes to the right and the positive y-axis extends up.<br />
<br />
<h3>
Vertices</h3>
<div>
Vertices are the basis of meshes. Vertices can hold information about position, color, normals, texture coordinates and any other information you need for your shaders.<br />
To keep things simple, we will start with vertices, that only have have a position. In order to create the mesh, we need a grid of vertices, as shown in the following picture:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_5cz_UHZV74aRK8vhyphenhyphenhH8nmhxuf46vzkqOAJANt3hZNDKjiYQUY0ekVDBcQFcflWCizRBpDPY-oZGqP3vFaejfk4wqwl6N265JOp6Bf3b_8XHOzIBxLq5TiVEEhKJBw8V4m1XCddX4WDr/s1600/Vertices+on+Grid.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_5cz_UHZV74aRK8vhyphenhyphenhH8nmhxuf46vzkqOAJANt3hZNDKjiYQUY0ekVDBcQFcflWCizRBpDPY-oZGqP3vFaejfk4wqwl6N265JOp6Bf3b_8XHOzIBxLq5TiVEEhKJBw8V4m1XCddX4WDr/s1600/Vertices+on+Grid.png" /></a></div>
<div>
Because one cell consists of 4 vertices, we need one more vertex for the width and height of the mesh, than we have cells. The 4 (width) x 3 (height) grid above needs therefore 5 vertices for each row and 4 vertices for each column of the mesh.<br />
<br /></div>
<div>
<h3>
Iterative Creation of Vertices</h3>
How can we create such a mesh? One option is do define each vertex by hand. This is a valid approach, if you have simple objects like a cell made of two triangles that serves as a surface for a texture. For larger meshes and a flexible ways to create these (for example a grid with parameters for width and height) we need a different approach, which is called procedural mesh creation. A procedural mesh is created with a function that takes some parameters and computes the according mesh.<br />
<br />
We start by filling the vertex buffer of the mesh. As we need a simple grid, we use a double nested for-loop to create the vertices iteratively.<br />
<br />
<u>We use the outer loop to iterate on the y value and the inner loop for the x value:</u><br />
<br />
<pre class="brush:csharp">int width = 5;
int height = 4;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
Console.Write(y.ToString() + "," + x.ToString() + "\t");
}
Console.WriteLine();
}
</pre>
<br />
This is the output on the console:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_NDXsCKDvRDJmrcYVnA7haO70cxlxmWN6cDXjH0S8zxTtTz4C30N4zry85I2kw1mDbwhUba9NGTY3u3Jz81kvTlGvVPvoGBdrpkjl3mmD1fc7FIHV3AajT4buirT-uy4OgnEe7Lwd_z6c/s1600/ForLoopIndices.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_NDXsCKDvRDJmrcYVnA7haO70cxlxmWN6cDXjH0S8zxTtTz4C30N4zry85I2kw1mDbwhUba9NGTY3u3Jz81kvTlGvVPvoGBdrpkjl3mmD1fc7FIHV3AajT4buirT-uy4OgnEe7Lwd_z6c/s1600/ForLoopIndices.png" /></a></div>
<br />
This gives us so far the indices of the positions. The first index is the row index (y) and the second index is the column index (x):</div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcCecI9qndBxs1hXoLNOx1pbhsVr92618p3BNcJPc26Ol4oVahQzaDli3mNYs-T7E3q38s4DR5B36gfgVmL8i7WUkXywlvKcNHa96ZnOwY3kaURtMwnm1IZXyBhAvoGaU_vQ4NRO_TryDL/s1600/Grid+Indexed+Vertices.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcCecI9qndBxs1hXoLNOx1pbhsVr92618p3BNcJPc26Ol4oVahQzaDli3mNYs-T7E3q38s4DR5B36gfgVmL8i7WUkXywlvKcNHa96ZnOwY3kaURtMwnm1IZXyBhAvoGaU_vQ4NRO_TryDL/s1600/Grid+Indexed+Vertices.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Vertices with Indices according to their position in row and column.</td></tr>
</tbody></table>
<br />
Because the VertexBuffer is not a matrix but an array, we need to linearize these indices, meaning, we have to match the indices in this matrix to an according array. We can do this by multiplying the y index of the outer loop with the width of the inner loop and adding the current x index to it:<br />
<br />
<pre class="brush:csharp">int width = 5;
int height = 4;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int index = x + y * width;
Console.Write(index.ToString() + "\t");
}
Console.WriteLine();
}
</pre>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYjMDeyx7jMdqPnv74S8rbTuR7A7hFVSTE3C7frTWrgcD7T5vWGnqAfqzmAy9GNnPp76STKakI9djJZK8yqpDI02e67IO3dXznfGBBkIa_wnPC8CGmKY9STvjMUIf2-CWbOVgTvY4J6gwj/s1600/ForLoopLinearized.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYjMDeyx7jMdqPnv74S8rbTuR7A7hFVSTE3C7frTWrgcD7T5vWGnqAfqzmAy9GNnPp76STKakI9djJZK8yqpDI02e67IO3dXznfGBBkIa_wnPC8CGmKY9STvjMUIf2-CWbOVgTvY4J6gwj/s1600/ForLoopLinearized.png" /></a></div>
<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-tCLdyn5hj7Sg1ssyrB3MawmrwwrdZKN2srCLvbtymAbz26uVc8g_4fTujy6S6Fzz5DCcWkOTvx4HmM12U7qEiJZItz2gjn4TokqJ3Ag2Vl40dyEesCZ2ZG3v00OCkM-T9QM1ne-QV0bW/s1600/Grid+Linearized+Vertices.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-tCLdyn5hj7Sg1ssyrB3MawmrwwrdZKN2srCLvbtymAbz26uVc8g_4fTujy6S6Fzz5DCcWkOTvx4HmM12U7qEiJZItz2gjn4TokqJ3Ag2Vl40dyEesCZ2ZG3v00OCkM-T9QM1ne-QV0bW/s1600/Grid+Linearized+Vertices.png" /></a></div>
We do not need to access the indices in this tutorial, but this will come in handy, when we have to triangulate the mesh.<br />
<br />
<h3>
Source Code</h3>
<div>
In this code of the grid mesh, I use the double nested for loop to create the vertices. Because it is convenient to place the center of the mesh in the origin of the coordinate system, I start in the upper left to write the vertices in the buffer. Also note, that I write the vertices from top to bottom, starting at the top row and going to the lowest row, by decreasing the y-position of the vertex:</div>
<div>
<br /></div>
<pre class="brush:csharp">float posX, posY;
float startX = -witdh * stepSize / 2.0f;
float startY = height * stepSize / 2.0f;
for (int y = 0; y < numVerticesHeight; y++)
{
for (int x = 0; x < numVerticesWidth; x++)
{
posX = startX + x * stepSize;
posY = startY - y * stepSize;
vertices.Write( new Vector3(posX, posY, 0 ));
}
}
</pre>
<div>
<br /></div>
<h3>
</h3>
<div>
The decision to iterate top down and from left to right is completely arbitrary but I think it is easier this way to imagine the access to the indices for further triangulation of the mesh.<br />
<br />
This is the complete code of the grid:<br />
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
public class GridMesh : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
DataStream vertices;
InputLayout layout;
int numVertices = 0;
int stride;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
float stepSize;
public GridMesh(int witdh, int height, float stepSize )
{
int numVerticesWidth = witdh + 1;
int numVerticesHeight = height + 1;
this.stepSize = stepSize;
numVertices = numVerticesWidth * numVerticesHeight;
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"transformEffect.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
stride = 12;
int sizeInBytes = stride * numVertices;
vertices = new DataStream(sizeInBytes, true, true);
float posX, posY;
float startX = -witdh * stepSize / 2.0f;
float startY = height * stepSize / 2.0f;
for (int y = 0; y < numVerticesHeight; y++)
{
for (int x = 0; x < numVerticesWidth; x++)
{
posX = startX + x * stepSize;
posY = startY - y * stepSize;
vertices.Write( new Vector3(posX, posY, 0 ));
}
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, vertices, sizeInBytes, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
}
public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, stride, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(numVertices, 0);
}
}
public override void dispose()
{
}
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
<h3>
Rendering</h3>
The only thing new at the render Method is, that I use the PointList primitive to render the points of the grid:<br />
<br />
<pre class="brush:csharp">DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
</pre>
<br />
<h3>
Conclusion</h3>
<div>
In order to make the points of the grid more visible, I modified the pixel shader to paint green pixels and set the background to black. If you enlarge the picture, you might be able to see the green dots ;)</div>
<div>
<br /></div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTzg1_SCZD3VfqO8JsrU3S8RhoygThXhKKkIb2Rd6ttxQeYis77aMsjoCse5sEvvKQO2tLm1wwQXqMHlJvaxxYmrU8upV0IpPPMlBdRc9JxqghSn48Qpsr3EELdsv0nrFtUVOJKZyn9szY/s1600/GridMeshVertices.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiTzg1_SCZD3VfqO8JsrU3S8RhoygThXhKKkIb2Rd6ttxQeYis77aMsjoCse5sEvvKQO2tLm1wwQXqMHlJvaxxYmrU8upV0IpPPMlBdRc9JxqghSn48Qpsr3EELdsv0nrFtUVOJKZyn9szY/s1600/GridMeshVertices.PNG" height="300" width="400" /></a></div>
<div>
<br /></div>
<div>
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/ef9f0ee0bb5d84b13440197043119175fe46fe43">here</a>.</div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-50560894010445055002013-04-07T01:08:00.001+02:002013-04-27T18:35:03.971+02:00Outline: Procedural MeshesIn the next tutorials I will go into detail about vertices, vertex and index buffers and procedural mesh generation.<br />
<br />
The <a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-vertex-buffer-and.html">first tutorial</a> will be about procedural generation of vertices. For this I will use a simple grid mesh and we will render just the points of this grid.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-vertex-buffer-and.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDumjLqmF48JJVSDI16RhqW5dk4gvzpqTv_hBByzZtdNI8H9ifcNHx1d_Qk-bqcOq3EzOsuPv7xhg2UW4HA8S5LZVakRVzRr-g7J0qkB9Q7yEkVf4yOCZlp-82qT93BUQk1W1WZ4HTkhD7/s320/GridMeshVertices.PNG" height="240" width="320" /></a></div>
<br />
<br />
The <a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-indexbuffer-and.html">second tutorial</a> will deal with creating an index buffer for the previous grid mesh and rendering the grid as a mesh of triangles. In order to view the mesh, we need to render the mesh as a wireframe and I will show you how to set the according state in a shader.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/gridmesh-creating-indexbuffer-and.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjM0qSU5vz9vL2JQkfrZZ4rkdSimOrhfUqvPQttkH41Q7XhONwepwfxO0KTUIXJ42CiiJ-3pdyJwe-MqiqXrkQS07FbvtZWpOYmnFkUF1BwmN6Q1DjSXO41bj1nOGtODQ7VJu4_xREEXZpx/s320/GridMeshWireframe.PNG" height="240" width="320" /></a></div>
<br />
<br />
The <a href="http://apparat-engine.blogspot.de/2013/04/the-color-cube-vertices-with-color.html">third tutorial</a> will be about bringing color to objects. For this we need to use different vertices and a new shader that is suited for displaying color.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/the-color-cube-vertices-with-color.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgaYGPV4BaqUUGRc8-g-hfsCGDexi8baXgsLohZMCWahO132WGPP7nMfCi0WKzucFVWdWqPSPoNmqviKFGwYpXyA-2XZT0SXjj8qXhAQCUkWncMg6pyfGXFay2OAEoQMDgQdWoA1STofOA2/s320/RenderedColorCube.PNG" height="240" width="320" /></a></div>
<br />
<br />
The <a href="http://apparat-engine.blogspot.de/2013/04/rendering-wireframe-over-mesh.html">fourth tutorial</a> will bring together wireframe rendering and color rendering. For this we modify our shader to perform two passes.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/rendering-wireframe-over-mesh.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSNeSzf15YX4foHHKpPhyphenhyphenOZNpjUrzw_DcZeWX7TKTuIvX1wTlor4tkCoy4M5gj5HLy6LZ_CU-qqgrEe79BctckYo7kMFNtjphpI50O5ru0FwCgXPVIGSVBBPjuYwvzQ4gb_Zq6_SBsHZdc/s1600/MeshAndWireframe.PNG" height="240" width="320" /></a></div>
<br />
<br />
The <a href="http://apparat-engine.blogspot.de/2013/04/depthstencil-buffer.html">fifth tutorial</a> will deal with the creation of the depth buffer and biased rendering to prevent z-fighting.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/depthstencil-buffer.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9K92ODizmyrtBf9y0nd9sa3PlTKBQ2cRpRYRjGRcMEH9lcQgiEib9la-k2EB5OXCMc6l7qRMbXEKyN7-_VJYhLoyMQuSW4CqIUKG3F4x1l7h9IKYKzPQys71-BITlBcLDsTS5a0GV9q5X/s1600/DepthBuffer2Problem.png" height="240" width="320" /></a></div>
<br />
<br />
The next tutorials will show how to create the geometric primitives <a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-box.html">box</a>, <a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-sphere.html">sphere</a>, <a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-torus.html">torus</a> and <a href="http://apparat-engine.blogspot.de/2013/04/procdural-meshes-cylinder.html">cylinder</a> with procedures.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-box.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicMh7sKGWfy08kPJ6Qr4cOLWdFB1R7eUIbOQMmyY5PUA-4RYKcOF-JEtSodkuYHBU_g88366dxGAjpmJpBevszKb1Qf8kNIvpNE94MGU1iF5N-3IBhnEAe20Niex1f6PiC4ZMFsbFHRIz2/s1600/Box.PNG" height="240" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-sphere.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhEmG8nPMgkwMn3aBP5CQQDt4AwyHZiODk8b0KI_ut7d4ZY8Qs5pLYgwmwfeBIrt5xzZm8vXR1kyaA-EzmUeEx2tJjZLw-pPCgE0qum4jxR1dub8EuXO_ZBOebWxeohX4r_mgOpF_wxlpOW/s1600/Sphere2.PNG" height="240" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/procdural-meshes-cylinder.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVD1-kr0svX4ky2JoJPXvOTf23asbPsluZNFiZTU1GpP42Pfff-HFwEd_yZnsoMPPl7-tkBqFQ4DUQeRfalFhRvATToUV5yRtlMfxDZQ5nI850_XUns8f10AqsgnuESyoxPMRI_4lp3f3i/s1600/Cylinder1.PNG" height="240" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-torus.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjgPgD3R4YKe0diOFwj23vhRr1-oczsFtzBEcRcZJgDEKL8xm44zmax1Q39dRvYFWwRNHcHxW25lAE6hcgd8Yl8_xJ103dzIiz0PlyisDvwU-KBKAsmqaDrQ1jkn2aJE4VJdeYtwngatcf4/s1600/Torus1.PNG" height="240" width="320" /></a></div>
<br />
Finally, I will close this series of tutorials with the geometric primitive of a <a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-superellipsoid.html">superellipsoid</a>, which is very versatile and fun to play around with. Here is a teaser, what will await you at the end of this series:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://apparat-engine.blogspot.de/2013/04/procedural-meshes-superellipsoid.html"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhkeZHs-kQkLj2CAUyTCMjQC-P2xvwRXuALqDMf2sMLbk8sIc5L9MV1xA41R7Biw7Q47ELLszH1IHU1zKadG8S3CEEhQ9sGuMZLpsLfPHYCg-ssaqcQp5XQ4TB6-0hLF5FBaYDhQSP33Tgm/s1600/RoundedCube.PNG" height="300" width="400" /></a></div>
<br />Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-44799731790811367962013-03-30T22:14:00.001+01:002013-03-30T22:14:32.845+01:00Calculating Frames per Second<h3>
Motivation</h3>
<div>
<div>
One widely used metric to measure the performance of a render engine is frames per second. I will show you in this tutorial how to implement a class to perform this measurement.</div>
</div>
<h3>
</h3>
<h3>
FrameCounter Class</h3>
<h4>
Source Code</h4>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace Apparat
{
public class FrameCounter
{
[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceCounter(
out long lpPerformanceCount);
[DllImport("Kernel32.dll")]
private static extern bool QueryPerformanceFrequency(
out long lpFrequency);
#region Singleton Pattern
private static FrameCounter instance = null;
public static FrameCounter Instance
{
get
{
if (instance == null)
{
instance = new FrameCounter();
}
return instance;
}
}
#endregion
#region Constructor
private FrameCounter()
{
msPerTick = (float)MillisecondsPerTick;
}
#endregion
float msPerTick = 0.0f;
long frequency;
public long Frequency
{
get
{
QueryPerformanceFrequency(out frequency);
return frequency;
}
}
long counter;
public long Counter
{
get
{
QueryPerformanceCounter(out counter);
return counter;
}
}
public double MillisecondsPerTick
{
get
{
return (1000L) / (double)Frequency;
}
}
public delegate void FPSCalculatedHandler(string fps);
public event FPSCalculatedHandler FPSCalculatedEvent;
long now;
long last;
long dc;
float dt;
float elapsedMilliseconds = 0.0f;
int numFrames = 0;
float msToTrigger = 1000.0f;
public float Count()
{
last = now;
now = Counter;
dc = now - last;
numFrames++;
dt = dc * msPerTick;
elapsedMilliseconds += dt;
if (elapsedMilliseconds > msToTrigger)
{
float seconds = elapsedMilliseconds / 1000.0f;
float fps = numFrames / seconds;
if (FPSCalculatedEvent != null)
FPSCalculatedEvent("fps: " + fps.ToString("0.00"));
elapsedMilliseconds = 0.0f;
numFrames = 0;
}
return dt;
}
}
}
</pre>
<div>
<br />
<h4>
QueryPerformanceFrequency and QueryPerformanceCounter</h4>
</div>
<div>
To count the milliseconds during a render cycle, I use the two native methods <a href="http://msdn.microsoft.com/de-de/library/windows/desktop/ms644905(v=vs.85).aspx">QueryPerformanceFrequency</a> and <a href="http://msdn.microsoft.com/de-de/library/windows/desktop/ms644904(v=vs.85).aspx">QueryPerformanceCounter</a>. QueryPerformanceFrequency returns, how many ticks the high-resolution performance counter of your CPU makes in a second, you have to determine the frequency of your system just once because this value will not change on your system. QueryPerformanceCounter returns the number of current ticks of your system. To measure the number of ticks during a certain time span you have to get the number of ticks of your system at the beginning and the end of this time span and calculate the difference between the two.</div>
<div>
<br /></div>
<div>
Because you know how many ticks your system makes in a second, you can calculate the time span between the two measurements.</div>
<div>
<br /></div>
<h4>
Count Function </h4>
<div>
Let's have a look at the <span style="font-family: Courier New, Courier, monospace;">Count </span>function in detail:<br />
<br /></div>
<pre class="brush:csharp">long now;
long last;
long dc;
float dt;
float elapsedMilliseconds = 0.0f;
int numFrames = 0;
float msToTrigger = 1000.0f;
public float Count()
{
last = now;
now = Counter;
dc = now - last;
numFrames++;
dt = dc * msPerTick;
elapsedMilliseconds += dt;
if (elapsedMilliseconds > msToTrigger)
{
float seconds = elapsedMilliseconds / 1000.0f;
float fps = numFrames / seconds;
if (FPSCalculatedEvent != null)
FPSCalculatedEvent("fps: " + fps.ToString("0.00"));
elapsedMilliseconds = 0.0f;
numFrames = 0;
}
return dt;
}
</pre>
<div>
<br /></div>
<div>
Every time this function gets called, I get the current value of the counter by calling the <span style="font-family: Courier New, Courier, monospace;">Counter </span><span style="font-family: inherit;">Property. The previous value of the counter is assigned to the variable </span><span style="font-family: Courier New, Courier, monospace;">last </span><span style="font-family: inherit;">and I calculate the difference </span><span style="font-family: Courier New, Courier, monospace;">dc</span><span style="font-family: inherit;"> of the two counter values, which is the number of ticks performed in the time span between two calls to this function. Because I calculated how many milliseconds it takes between two ticks (</span><span style="font-family: Courier New, Courier, monospace;">msPerTick</span><span style="font-family: inherit;">), I can multiply </span><span style="font-family: Courier New, Courier, monospace;">dc </span><span style="font-family: inherit;">with </span><span style="font-family: Courier New, Courier, monospace;">msPerTick </span><span style="font-family: inherit;">to get the time span </span><span style="font-family: Courier New, Courier, monospace;">dt</span><span style="font-family: inherit;"> in milliseconds between two calls of this function.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">The time span </span><span style="font-family: Courier New, Courier, monospace;">dt</span><span style="font-family: inherit;"> gets added to the variable </span><span style="font-family: Courier New, Courier, monospace;">elapsedMilliseconds</span><span style="font-family: inherit;">. Furthermore I increment the variable </span><span style="font-family: Courier New, Courier, monospace;">numFrames </span><span style="font-family: inherit;">with every call to the </span><span style="font-family: Courier New, Courier, monospace;">Count </span><span style="font-family: inherit;">function. If </span><span style="font-family: Courier New, Courier, monospace;">elapsedMilliseconds </span><span style="font-family: inherit;">is greater than the predefined time span </span><span style="font-family: Courier New, Courier, monospace;">msToTrigger</span><span style="font-family: inherit;">, I calculate the frames per second </span><span style="font-family: Courier New, Courier, monospace;">fps </span><span style="font-family: inherit;">and fire the event </span><span style="font-family: Courier New, Courier, monospace;">FPSCalculatedEvent</span><span style="font-family: inherit;">.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">I call the </span><span style="font-family: Courier New, Courier, monospace;">Count</span><span style="font-family: inherit;"> function in every render cycle in the </span><span style="font-family: Courier New, Courier, monospace;">RenderManager</span><span style="font-family: inherit;">:</span><br />
<br />
<pre class="brush:csharp">FrameCounter fc = FrameCounter.Instance;
public void renderScene()
{
while (true)
{
fc.Count();
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.75f, 0.75f, 0.75f));
Scene.Instance.render();
dm.swapChain.Present(syncInterval, PresentFlags.None);
}
}
</pre>
<span style="font-family: inherit;"><br /></span></div>
<h4>
FPSCalculatedEvent</h4>
<div>
I defined a delegate and an event in the <span style="font-family: Courier New, Courier, monospace;">FrameCounter </span>class:</div>
<div>
<div>
<br /></div>
</div>
<pre class="brush:csharp">public delegate void FPSCalculatedHandler(string fps);
public event FPSCalculatedHandler FPSCalculatedEvent;
</pre>
<div>
<br />
The event gets fired in the <span style="font-family: Courier New, Courier, monospace;">Count </span>function, when the frames per secondes have been calculated. I'll get back to this delegate and event when it comes to presenting the fps on the <span style="font-family: Courier New, Courier, monospace;">RenderControl</span>.</div>
<h3>
</h3>
<h3>
SyncInterval</h3>
<div>
Let's take a look at the render loop again:<br />
<br />
<pre class="brush:csharp">public void renderScene()
{
while (true)
{
fc.Count();
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.75f, 0.75f, 0.75f));
Scene.Instance.render();
dm.swapChain.Present(syncInterval, PresentFlags.None);
}
}
</pre>
<br />
I introduced the variable <span style="font-family: Courier New, Courier, monospace;">syncInterval </span>when calling the <span style="font-family: Courier New, Courier, monospace;">Present </span>method of the swap chain.<br />
The value of <span style="font-family: Courier New, Courier, monospace;">syncInterval </span>determines, how the rendering is synchronized with the vertical blank.<br />
If <span style="font-family: 'Courier New', Courier, monospace;">syncInterval </span> is 0, no synchronisation takes place, if <span style="font-family: 'Courier New', Courier, monospace;">syncInterval</span> is 1,2,3 or 4, the frame is rendered after the <i>n</i>th interval (<a href="http://msdn.microsoft.com/en-us/library/windows/desktop/bb174576(v=vs.85).aspx">MSDN docs</a>).<br />
<br />
Furthermore I implemented a method to switch the <span style="font-family: 'Courier New', Courier, monospace;">syncInterval </span><span style="font-family: inherit;">in the RenderManager externally:</span><br />
<span style="font-family: inherit;"><br /></span>
<br />
<pre class="brush:csharp">int syncInterval = 1;
public void SwitchSyncInterval()
{
if (syncInterval == 0)
{
syncInterval = 1;
}
else if (syncInterval == 1)
{
syncInterval = 0;
}
}</pre>
<br />
This <span style="font-family: Courier New, Courier, monospace;">SwitchSyncInterval </span>method is called in the <span style="font-family: Courier New, Courier, monospace;">RenderControl </span>and you can switch the syncInterval with the F2 key:<br />
<br />
<pre class="brush:csharp">private void RenderControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
CameraManager.Instance.CycleCameras();
}
else if (e.KeyCode == Keys.F2)
{
RenderManager.Instance.SwitchSyncInterval();
}
CameraManager.Instance.currentCamera.KeyUp(sender, e);
}
</pre>
<br />
<h3>
Displaying Frames per Second</h3>
</div>
<div>
I added a label control called <span style="font-family: Courier New, Courier, monospace;">DebugTextLabel </span>to the <span style="font-family: Courier New, Courier, monospace;">RenderControl </span><span style="font-family: inherit;">in order to display a string on top of the </span><span style="font-family: Courier New, Courier, monospace;">RenderControl</span><span style="font-family: inherit;"> to have a method to display text. Rendering text seems to be a bit more complicated with DirectX 11 than it was with DirectX 9. (If you know a good reference for rendering text in DirectX 11, please leave a comment). I will use this interim solution for displaying text until I wrote a parser for true type fonts ;)</span><br />
<br />
The delegate and event for sending the calculated frames per second is defined in the <span style="font-family: Courier New, Courier, monospace;">FrameCounter </span>class (see above) and the event is fired when the frames per second are calculated.<br />
<br />
The method <span style="font-family: Courier New, Courier, monospace;">Instance_FPSCalculatedEvent </span><span style="font-family: inherit;">in the class </span><span style="font-family: Courier New, Courier, monospace;">RenderControl </span><span style="font-family: inherit;">is a handler for the </span><span style="font-family: 'Courier New', Courier, monospace;">FPSCalculatedEvent </span><span style="font-family: inherit;">and is registered in the constructor of the </span><span style="font-family: Courier New, Courier, monospace;">RenderControl:</span><br />
<br />
<pre class="brush:csharp">public RenderControl()
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(RenderControl_MouseWheel);
FrameCounter.Instance.FPSCalculatedEvent += new FrameCounter.FPSCalculatedHandler(Instance_FPSCalculatedEvent);
}
</pre>
<br />
This is the code for the handler <span style="font-family: 'Courier New', Courier, monospace;">Instance_FPSCalculatedEvent </span><span style="font-family: inherit;">in the </span><span style="font-family: Courier New, Courier, monospace;">RenderControl</span><span style="font-family: inherit;">:</span><br />
<span style="font-family: inherit;"><br /></span>
<br />
<pre class="brush:csharp">delegate void setFPS(string fps);
void Instance_FPSCalculatedEvent(string fps)
{
if (this.InvokeRequired)
{
setFPS d = new setFPS(Instance_FPSCalculatedEvent);
this.Invoke(d, new object[] { fps });
}
else
{
this.DebugTextLabel.Text = fps;
}
}
</pre>
<br />
The label is set with the string <span style="font-family: Courier New, Courier, monospace;">fps</span>, that comes as an argument from the event. Because the render loop works in a different thread than the <span style="font-family: Courier New, Courier, monospace;">DebugTextLabel </span>was created in and we try to set this control from the render loop thread, we have to use the <span style="font-family: Courier New, Courier, monospace;">InvokeRequired </span>property of the <span style="font-family: Courier New, Courier, monospace;">RenderControl.</span></div>
<h3>
Results</h3>
<div>
Now we can display the current frame rate of the render engine:</div>
<div>
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh54GxEXbUpFfN0w1tiCYdtgTOAhp19A_2T7IPAcu108KNMNfhKHZGtRPt8q59K-BIwoxs3PcOD8Xb_vR54lckXux8_00smBs-bP-UwBMb7Kq42NKA9yIccHRUIQqhFBLKg1EL5s8v1NXJJ/s1600/FrameCounter1.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh54GxEXbUpFfN0w1tiCYdtgTOAhp19A_2T7IPAcu108KNMNfhKHZGtRPt8q59K-BIwoxs3PcOD8Xb_vR54lckXux8_00smBs-bP-UwBMb7Kq42NKA9yIccHRUIQqhFBLKg1EL5s8v1NXJJ/s1600/FrameCounter1.PNG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">~60 Frames per Second with SyncInterval = 1</td></tr>
</tbody></table>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyMfNmJMXLbdBmVkNh0nSzEuru5zIP002jsDj23M5g_27_cZSAvLqRaXVc1KkDLDre5lSozmx2yQ8gfM0gVZOnWYa4SZTtlaou-PQcegTJeJ2FLTRq_JgVIKWCWm6egwVKsALTm9Y94WDE/s1600/FrameCounter2.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyMfNmJMXLbdBmVkNh0nSzEuru5zIP002jsDj23M5g_27_cZSAvLqRaXVc1KkDLDre5lSozmx2yQ8gfM0gVZOnWYa4SZTtlaou-PQcegTJeJ2FLTRq_JgVIKWCWm6egwVKsALTm9Y94WDE/s1600/FrameCounter2.PNG" height="240" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Several thousand Frames per Second with SyncInterval = 0</td></tr>
</tbody></table>
<div>
<br /></div>
<div>
To play around a bit, insert a <span style="font-family: Courier New, Courier, monospace;">Thread.Sleep(</span><i><span style="font-family: inherit;">ms</span></i><span style="font-family: Courier New, Courier, monospace;">)</span><span style="font-family: inherit;">statement to the method </span><span style="font-family: Courier New, Courier, monospace;">renderScene </span><span style="font-family: inherit;">in the </span><span style="font-family: Courier New, Courier, monospace;">RenderManager </span><span style="font-family: inherit;">class and observe how the frame rate changes with different values for <i>ms </i>and depending on if you use syncInterval = 1 or syncInterval = 0. Also try to set the </span><span style="font-family: Courier New, Courier, monospace;">syncInterval </span><span style="font-family: inherit;">in the render loop to values of 2,3,4 and observe the effect on the frames per second.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">The source code to this tutorial is <a href="http://apparat.codeplex.com/SourceControl/changeset/view/0fcc031fb4182752ae044d28dbc55b09f6da7c5b">here</a>.</span><br />
<span style="font-family: inherit;"><br /></span>
<span style="font-family: inherit;">Have fun!</span></div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-17250375180035743972013-03-24T12:48:00.001+01:002013-03-27T00:39:51.832+01:00The Ego CameraWith an Ego Camera you use the mouse to control the pitch and yaw of the camera and the WSAD keys to move forward and backward and strafe left and right. I constrain the pitch of the camera at +90 and -90 degree.<br />
<br />
<h3>
Abstract Camera Class</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SlimDX;
namespace Apparat
{
public abstract class Camera
{
public Vector3 eye;
public Vector3 target;
public Vector3 up;
public Matrix view = Matrix.Identity;
public Matrix perspective = Matrix.Identity;
public Matrix viewPerspective = Matrix.Identity;
public Matrix View
{
get { return view; }
}
public void setPerspective(float fov, float aspect, float znear, float zfar)
{
perspective = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar);
}
public void setView(Vector3 eye, Vector3 target, Vector3 up)
{
view = Matrix.LookAtLH(eye, target, up);
}
public Matrix Perspective
{
get { return perspective; }
}
public Matrix ViewPerspective
{
get { return view * perspective; }
}
public bool dragging = false;
public int startX = 0;
public int deltaX = 0;
public int startY = 0;
public int deltaY = 0;
public abstract void MouseUp(object sender, MouseEventArgs e);
public abstract void MouseDown(object sender, MouseEventArgs e);
public abstract void MouseMove(object sender, MouseEventArgs e);
public abstract void MouseWheel(object sender, MouseEventArgs e);
public abstract void KeyPress(object sender, KeyPressEventArgs e);
public abstract void KeyDown(object sender, KeyEventArgs e);
public abstract void KeyUp(object sender, KeyEventArgs e);
}
}
</pre>
Because we need the WSAD keys for strafing, the abstract class needs the declaration of the handlers for handling input from keys. These have also to be implemented in the <i>OrbitCamera </i>and in the <i>OrbitPanCamera</i>, but remain empty.<br />
<br />
<h3>
Ego Camera Code</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat
{
public class EgoCamera : Camera
{
Vector3 look;
public EgoCamera()
{
look = new Vector3(1, 0, 0);
up = new Vector3(0, 1, 0);
eye = new Vector3(0, 1, 0);
target = eye + look;
view = Matrix.LookAtLH(eye, target, up);
perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.0f, 1.0f);
}
new public Matrix ViewPerspective
{
get
{
if (strafingLeft)
strafe(1);
if (strafingRight)
strafe(-1);
if (movingForward)
move(1);
if (movingBack)
move(-1);
return view * perspective;
}
}
public void yaw(int x)
{
Matrix rot = Matrix.RotationY(x / 100.0f);
look = Vector3.TransformCoordinate(look, rot);
target = eye + look;
view = Matrix.LookAtLH(eye, target, up);
}
float pitchVal = 0.0f;
public void pitch(int y)
{
Vector3 axis = Vector3.Cross(up, look);
float rotation = y / 100.0f;
pitchVal = pitchVal + rotation;
float halfPi = (float)Math.PI / 2.0f;
if (pitchVal < -halfPi)
{
pitchVal = -halfPi;
rotation = 0;
}
if (pitchVal > halfPi)
{
pitchVal = halfPi;
rotation = 0;
}
Matrix rot = Matrix.RotationAxis(axis, rotation);
look = Vector3.TransformCoordinate(look, rot);
look.Normalize();
target = eye + look;
view = Matrix.LookAtLH(eye, target, up);
}
public override void MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
dragging = false;
}
public override void MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
dragging = true;
startX = e.X;
startY = e.Y;
}
public override void MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (dragging)
{
int currentX = e.X;
deltaX = startX - currentX;
startX = currentX;
int currentY = e.Y;
deltaY = startY - currentY;
startY = currentY;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
pitch(deltaY);
yaw(-deltaX);
}
}
}
public void strafe(int val)
{
Vector3 axis = Vector3.Cross(look, up);
Matrix scale = Matrix.Scaling(0.1f, 0.1f, 0.1f);
axis = Vector3.TransformCoordinate(axis, scale);
if (val > 0)
{
eye = eye + axis;
}
else
{
eye = eye - axis;
}
target = eye + look;
view = Matrix.LookAtLH(eye, target, up);
}
public void move(int val)
{
Vector3 tempLook = look;
Matrix scale = Matrix.Scaling(0.1f, 0.1f, 0.1f);
tempLook = Vector3.TransformCoordinate(tempLook, scale);
if (val > 0)
{
eye = eye + tempLook;
}
else
{
eye = eye - tempLook;
}
target = eye + look;
view = Matrix.LookAtLH(eye, target, up);
}
// Nothing to do here
public override void MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e){}
public override void KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
{
}
bool strafingLeft = false;
bool strafingRight = false;
bool movingForward = false;
bool movingBack = false;
public override void KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == System.Windows.Forms.Keys.W)
{
movingForward = true;
}
else if (e.KeyCode == System.Windows.Forms.Keys.S)
{
movingBack = true;
}
else if (e.KeyCode == System.Windows.Forms.Keys.A)
{
strafingLeft = true;
}
else if (e.KeyCode == System.Windows.Forms.Keys.D)
{
strafingRight = true;
}
}
public override void KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
{
if (e.KeyCode == System.Windows.Forms.Keys.W)
{
movingForward = false;
}
else if (e.KeyCode == System.Windows.Forms.Keys.S)
{
movingBack = false;
}
else if (e.KeyCode == System.Windows.Forms.Keys.A)
{
strafingLeft = false;
}
else if (e.KeyCode == System.Windows.Forms.Keys.D)
{
strafingRight = false;
}
}
}
}
</pre>
<h4>
Key Handling</h4>
In the lowest section of the code I implemented four booleans in order to flag, if a key keeps being pressed. As long as a key is pressed, these variables stay true. You may wonder, why I don't use the <i>KeyPress </i>handler for this. As soon as a key is pressed, the KeyPress event is fired and the KeyPress handler is called. If the key remains pressed, the event is fired repeatedly and the therefore the handler is called repeatedly. The problem is: this event is fired once a key is pressed, followed by a pause and then the event is fired at a low frequency at about 15 Hz (roughly estimated, I haven't found any reference).<br />
<br />
This video illustrates the issue:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/1qDLtfM3D10?feature=player_embedded' frameborder='0'></iframe></div>
I opened notepad and kept the 'a' key pressed. After a short pause, the events keeps being fired at a low frequency.<br />
<br />
As the Render Loop works with 60Hz or more, using the KeyPress event would result in a stuttering motion of the camera, if used for triggering the strafing methods, as the ViewProjection matrix of the camera would only be fired every fourth frame (again, roughly estimated).<br />
<br />
<h4>
The ViewPerspective Property</h4>
Also observe, that I overrode the ViewPerspective property. The objects in the scene call this property in every render cycle, in order to make sure, that these objects get a updated ViewPerspective matrix, the update of the strafing methods happens here.<br />
<br />
Warning: this is not a good implementation and only to prevent the camera from stuttering in this tutorial. The problem with the current approach is, that the objects in the scene trigger a transformation by using this property. So every call to this property results in a transformation of the camera. Having many objects would give noticeable effects on the displayed scene. In a later tutorial the call to ViewPerspective matrix will be moved into the beginning of the render loop and called once and the objects in the scene will all see the same ViewPerspective matrix. This approach has also the advantage, that expensive calculations will be only performed once per render loop.<br />
<br />
<h4>
Strafing</h4>
<div>
Strafing is a translation along the cameras x-axis and z.axis. Up to now we just used the <i>eye</i>, <i>target </i>and <i>up</i> vectors for creating the view matrix of the camera. In order to do implement strafing, I need two additional vectors: <i>look </i>and <i>axis.</i> <i>look </i>is the direction the camera is looking in and <i>axis </i>is orthogonal to <i>up </i>and <i>look</i>. </div>
<div>
Strafing forward and backward is then adding a scaled <i>look </i>vector to the <i>eye </i>vector. Strafing left and right is accomplished by adding a scaled <i>axis </i>vector to the <i>eye </i>vector. To keep the creation of the view matrix consistent, the <i>target </i>vector has to be updated with the same vector as the <i>eye </i>vector.<br />
<br /></div>
<div>
<b>Looking</b></div>
<br />
To look around, I take the <i>look </i>vector and rotate this vector around the cameras y axis for looking left and right. In order to look up and down the <i>look </i>vector is rotated around the cameras z axis. The y axis is always the <i>up </i>vector, which isn't touched at all and is (0,1,0) at all times. Because the camera is rotating, the current z axis of the camera has to be recomputed, with every rotation around the y axis. The cameras current z axis (just called <i>axis</i> in the source code) is therefore computed by taking the cross product of the <i>up </i>vector and the <i>look </i>vector.<br />
To constrain looking up and down, the pitch angle is limited to +PI/2 and -PI/2.<br />
<h3>
Camera Manager</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
namespace Apparat
{
public class CameraManager
{
#region Singleton Pattern
private static CameraManager instance = null;
public static CameraManager Instance
{
get
{
if (instance == null)
{
instance = new CameraManager();
}
return instance;
}
}
#endregion
#region Constructor
private CameraManager()
{
OrbitPanCamera ocp = new OrbitPanCamera();
OrbitCamera oc = new OrbitCamera();
EgoCamera ec = new EgoCamera();
cameras.Add(ocp);
cameras.Add(oc);
cameras.Add(ec);
currentIndex = 0;
currentCamera = cameras[currentIndex];
}
#endregion
List<camera> cameras = new List<camera>();
public Camera currentCamera;
int currentIndex;
public Matrix ViewPerspective
{
get
{
if (currentCamera is EgoCamera)
{
return ((EgoCamera)currentCamera).ViewPerspective;
}
else
{
return currentCamera.ViewPerspective;
}
}
}
public string CycleCameras()
{
int numCameras = cameras.Count;
currentIndex = currentIndex + 1;
if (currentIndex == numCameras)
currentIndex = 0;
currentCamera = cameras[currentIndex];
return currentCamera.ToString();
}
}
}
</camera></camera></pre>
<h3>
<span style="font-size: small; font-weight: normal;">The EgoCamera is added to the camera manager by creating an object of it and adding it to the </span><i style="font-size: medium; font-weight: normal;">cameras </i><span style="font-size: small; font-weight: normal;">list. I had to add the </span><i style="font-size: medium; font-weight: normal;">ViewPerspective </i><span style="font-size: small; font-weight: normal;">property, to be able to cast the current camera to the </span><i style="font-size: medium; font-weight: normal;">EgoCamera </i><span style="font-size: small; font-weight: normal;">if the current camera is of this type. This is necessary to call the </span><i style="font-size: medium; font-weight: normal;">ViewPerspective </i><span style="font-size: small; font-weight: normal;">property of the </span><i style="font-size: medium; font-weight: normal;">EgoCamera</i><span style="font-size: small; font-weight: normal;">, because I did override the </span><i style="font-size: medium; font-weight: normal;">ViewPerspective </i><span style="font-size: small; font-weight: normal;">property of the abstract </span><i style="font-size: medium; font-weight: normal;">Camera </i><span style="font-size: small; font-weight: normal;">class in the </span><i style="font-size: medium; font-weight: normal;">EgoCamera</i><span style="font-size: small; font-weight: normal;">.</span></h3>
<h3>
Results</h3>
<div>
This video demonstrates the behaviour of the Ego Camera:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/jSQ63i6slmU?feature=player_embedded' frameborder='0'></iframe></div>
<br /></div>
<div>
<br />
At this point I am using constants for the translations and rotations. In order to have defined velocities for these motions, we need to know, how much time has passed. This will be addressed in the next tutorial.<br />
<br />
You can download the source code of this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/955f98682eb3">here</a>.</div>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-15848224839895826212013-03-23T11:58:00.001+01:002013-03-27T00:39:51.831+01:00The Camera ManagerIn the last tutorial we saw, that with a growing number of cameras we need some additional code to manage different cameras. The reasons were: Renderables need access to the current camera, the different cameras share a set of variables and the handling of mouse and keyboard events is for each camera different.<br />
<div>
<br /></div>
<div>
The obvious way to address this problem is either by providing an interface or an abstract class. I chose an abstract class:<br />
<h3>
Abstract Class Camera</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SlimDX;
namespace Apparat
{
public abstract class Camera
{
public Vector3 eye;
public Vector3 target;
public Vector3 up;
public Matrix view = Matrix.Identity;
public Matrix perspective = Matrix.Identity;
public Matrix viewPerspective = Matrix.Identity;
public Matrix View
{
get { return view; }
}
public void setPerspective(float fov, float aspect, float znear, float zfar)
{
perspective = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar);
}
public void setView(Vector3 eye, Vector3 target, Vector3 up)
{
view = Matrix.LookAtLH(eye, target, up);
}
public Matrix Perspective
{
get { return perspective; }
}
public Matrix ViewPerspective
{
get { return view * perspective; }
}
public bool dragging = false;
public int startX = 0;
public int deltaX = 0;
public int startY = 0;
public int deltaY = 0;
public abstract void MouseUp(object sender, MouseEventArgs e);
public abstract void MouseDown(object sender, MouseEventArgs e);
public abstract void MouseMove(object sender, MouseEventArgs e);
public abstract void MouseWheel(object sender, MouseEventArgs e);
}
}
</pre>
<br />
These are the variables and methods all cameras share. Furthermore cameras deriving from this abstract class have to implement the handlers for interacting with the control, like <i>MouseUp</i>. I deleted the corresponding variables and methods from the <i>OrbitCamera </i>and <i>OrbitPanCamera </i>classes did override the handlers. For the sake of brevity I will not post the code here but refer to the source code at the bottom of this tutorial.<br />
<br /></div>
<div>
<h3>
CameraManager Class</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Apparat
{
public class CameraManager
{
#region Singleton Pattern
private static CameraManager instance = null;
public static CameraManager Instance
{
get
{
if (instance == null)
{
instance = new CameraManager();
}
return instance;
}
}
#endregion
#region Constructor
private CameraManager()
{
OrbitPanCamera ocp = new OrbitPanCamera();
OrbitCamera oc = new OrbitCamera();
cameras.Add(ocp);
cameras.Add(oc);
currentIndex = 0;
currentCamera = cameras[currentIndex];
}
#endregion
List<camera> cameras = new List<camera>();
public Camera currentCamera;
int currentIndex;
public string CycleCameras()
{
int numCameras = cameras.Count;
currentIndex = currentIndex + 1;
if (currentIndex == numCameras)
currentIndex = 0;
currentCamera = cameras[currentIndex];
return currentCamera.ToString();
}
}
}
</pre>
<br />
The CameraManager is now responsible for creating the cameras and uses the Singleton pattern, as it is the only object, the rest of the engine is talking to, when cameras need to be accessed. Consequently, the OrbitCamera and OrbitPanCamera are not Singletons anymore.<br />
<br />
The CameraManager holds a list of cameras, which I populate in its contructor. In order to change the camera, I added the method <i>CycleCameras</i>. The engine can gain access to the current camera via the <i>currentCamera </i>variable with<i> CameraManager.Instance.currentCamera</i>.<br />
<h3>
RenderControl</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat.Renderables;
namespace Apparat
{
public partial class RenderControl : UserControl
{
public RenderControl()
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(RenderControl_MouseWheel);
}
public void init()
{
DeviceManager.Instance.createDeviceAndSwapChain(this);
RenderManager.Instance.init();
Grid grid = new Grid(10, 1.0f);
TriangleEF triangle = new TriangleEF();
Scene.Instance.addRenderObject(triangle);
Scene.Instance.addRenderObject(grid);
}
public void shutDown()
{
RenderManager.Instance.shutDown();
DeviceManager.Instance.shutDown();
}
private void RenderControl_MouseUp(object sender, MouseEventArgs e)
{
CameraManager.Instance.currentCamera.MouseUp(sender, e);
}
private void RenderControl_MouseDown(object sender, MouseEventArgs e)
{
CameraManager.Instance.currentCamera.MouseDown(sender, e);
}
private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
CameraManager.Instance.currentCamera.MouseMove(sender, e);
}
void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
CameraManager.Instance.currentCamera.MouseWheel(sender, e);
}
private void RenderControl_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.F1)
{
CameraManager.Instance.CycleCameras();
}
}
}
}
</pre>
In the <i>RenderControl</i> the mouse handlers refer to the current camera of the <i>CameraManger </i>and call<br />
the according handler. Furthermore I use the KeyUp handler and the F1 key to cycle through the cameras.<br />
<br />
<h3>
Renderables</h3>
</div>
<div>
Now the render methods of the Renderables have to updated like in this example, where the <i>ViewPerspective </i>matrix is set via the <i>CameraManager</i>.</div>
<div>
<br /></div>
<pre class="brush:csharp">public override void render()
{
Matrix ViewPerspective = CameraManager.Instance.currentCamera.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(numVertices, 0);
}
}
</pre>
<div>
<br /></div>
<div>
<h3>
Conclusion</h3>
</div>
<div>
When dealing with several cameras a CameraManager is needed to deal with them in a flexible way. This CameraManager will be extended in future tutorials. </div>
<div>
<br /></div>
<div>
You can download the code for this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/4c28eccb2023">here</a>.</div>
<div>
<br /></div>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-48576417884128413462013-03-22T22:21:00.000+01:002013-03-27T00:39:51.835+01:00Orbit and Pan CameraIn the last tutorial I explained how to implement an Orbit Camera, with which you can circle around a given point. In this tutorial I will explain how to add the capability to pan. Panning means translating the camera parallel to the X-Z plane.<br />
<br />
<h3>
Source Code of the OrbitPanCamera</h3>
<div>
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat
{
public class OrbitPanCamera
{
#region Singleton Pattern
private static OrbitPanCamera instance = null;
public static OrbitPanCamera Instance
{
get
{
if (instance == null)
{
instance = new OrbitPanCamera();
}
return instance;
}
}
#endregion
#region Constructor
private OrbitPanCamera()
{
eye = new Vector3(4, 2, 0);
target = new Vector3(0, 0, 0);
up = new Vector3(0, 1, 0);
view = Matrix.LookAtLH(eye, target, up);
perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.0f, 1.0f);
}
#endregion
Vector3 eye;
Vector3 target;
Vector3 up;
Matrix view = Matrix.Identity;
Matrix perspective = Matrix.Identity;
Matrix viewPerspective = Matrix.Identity;
public Matrix View
{
get { return view; }
}
public void setPerspective(float fov, float aspect, float znear, float zfar)
{
perspective = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar);
}
public void setView(Vector3 eye, Vector3 target, Vector3 up)
{
view = Matrix.LookAtLH(eye, target, up);
}
public Matrix Perspective
{
get { return perspective; }
}
public Matrix ViewPerspective
{
get { return view * perspective; }
}
float rotY = 0;
public void rotateY(int value)
{
rotY = (value / 100.0f);
Vector3 eyeLocal = eye - target;
Matrix rotMat = Matrix.RotationY(rotY);
eyeLocal = Vector3.TransformCoordinate(eyeLocal, rotMat);
eye = eyeLocal + target;
setView(eye, target, up);
}
float rotOrtho = 0;
public void rotateOrtho(int value)
{
Vector3 viewDir = target - eye;
Vector3 orhto = Vector3.Cross(viewDir, up);
rotOrtho = (value / 100.0f);
Matrix rotOrthoMat = Matrix.RotationAxis(orhto, rotOrtho);
Vector3 eyeLocal = eye - target;
eyeLocal = Vector3.TransformCoordinate(eyeLocal, rotOrthoMat);
Vector3 newEye = eyeLocal + target;
Vector3 newViewDir = target - newEye;
float cosAngle = Vector3.Dot(newViewDir, up) / (newViewDir.Length() * up.Length());
if (cosAngle < 0.999f && cosAngle > -0.999f)
{
eye = eyeLocal + target;
setView(eye, target, up);
}
}
public void panX(int value)
{
float scaleFactor = 0.0f;
if (value > 1)
{
scaleFactor = -0.05f;
}
else if (value < -1 )
{
scaleFactor = 0.05f;
}
Vector3 viewDir = target - eye;
Vector3 orhto = Vector3.Cross(viewDir, up);
orhto.Normalize();
scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
orhto = Vector3.TransformCoordinate(orhto, scaling);
target = target + orhto;
eye = eye + orhto;
setView(eye, target, up);
}
public void panY(int value)
{
float scaleFactor = 0.00f;
if (value > 1)
{
scaleFactor = -0.05f;
}
else if (value < -1 )
{
scaleFactor = 0.05f;
}
Vector3 viewDir = target - eye;
scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
viewDir.Y = 0.0f;
viewDir.Normalize();
Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
viewDir = Vector3.TransformCoordinate(viewDir, scaling);
target = target + viewDir;
eye = eye + viewDir;
setView(eye, target, up);
}
float maxZoom = 3.0f;
public void zoom(int value)
{
Vector3 viewDir = eye - target;
float scaleFactor = 1.0f;
if (value > 0)
{
scaleFactor = 1.1f;
}
else
{
if (viewDir.Length() > maxZoom)
scaleFactor = 0.9f;
}
Matrix scale = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
viewDir.Normalize();
viewDir = Vector3.TransformCoordinate(viewDir, scale);
if (value > 0)
{
eye = eye + viewDir;
}
else
{
eye = eye - viewDir;
}
setView(eye, target, up);
}
}
}
</pre>
<div>
The source code for the OrbitPanCamera is in large parts the same as for the OrbitCamera. New are the methods for translating in the x-direction (Method <i>panX</i>) and translating in the y-direction (Method <i>panY</i>) of the screen.<br />
Lets take a look at the <i>panX</i> Method:<br />
<br />
<pre class="brush:csharp">public void panX(int value)
{
float scaleFactor = 0.0f;
if (value > 1)
{
scaleFactor = -0.05f;
}
else if (value < -1)
{
scaleFactor = 0.05f;
}
Vector3 viewDir = target - eye;
Vector3 orhto = Vector3.Cross(viewDir, up);
orhto.Normalize();
scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
orhto = Vector3.TransformCoordinate(orhto, scaling);
target = target + orhto;
eye = eye + orhto;
setView(eye, target, up);
}
</pre>
The pose (position and orientation) is determined by the three vectors eye, target and up. The eye vector holds the current position of the camera, the target vector is the point to look at and the up vector defines the up direction. In order to pan sidewards, the general idea is to translate the position of the camera and the target to look at simultaneously. Therefore we calculate the direction we are looking (<i>viewDir</i>) and calculate the cross product with the up vector, which results in a vector that is pointing orthogonal to the <i>viewDir</i> vector.<br />
This orthogonal vector is added to the target and eye vectors and we create a new view matrix, by calling <i>setView</i>. I perform scaling of the orthogonal vector depending in the distance to the target, so that the translation is little, if the camera is next to the target and bigger if the camera is far away.<br />
<br />
The implementation of the panY method in analogue:<br />
<pre class="brush:csharp">public void panY(int value)
{
float scaleFactor = 0.00f;
if (value > 1)
{
scaleFactor = -0.05f;
}
else if (value < -1)
{
scaleFactor = 0.05f;
}
Vector3 viewDir = target - eye;
scaleFactor = scaleFactor * (float)Math.Sqrt(viewDir.Length()) * 0.5f;
viewDir.Y = 0.0f;
viewDir.Normalize();
Matrix scaling = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
viewDir = Vector3.TransformCoordinate(viewDir, scaling);
target = target + viewDir;
eye = eye + viewDir;
setView(eye, target, up);
}
</pre>
This time we don't need the orthogonal vector but only the view direction of the camera. Again this vector is scaled corresposing to the distance to the target and added to the target vector and eye vector of the camera. Like above, the new view matrix is created with this new values.<br />
<br />
<h3>
Adapting the Mouse Event Handlers of the RenderControl</h3>
The only handlers we have to adapt are <i>RenderControl_MouseMove </i>and <i>RenderControl_MouseWheel</i>.<br />
These are the handlers of the <i>OrbitCamera</i>:<br />
<br />
<br />
<pre class="brush:csharp">private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
int currentX = e.X;
deltaX = startX - currentX;
startX = currentX;
int currentY = e.Y;
deltaY = startY - currentY;
startY = currentY;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
OrbitCamera.Instance.rotateY(-deltaX);
OrbitCamera.Instance.rotateOrtho(deltaY);
}
}
}
void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
int delta = e.Delta;
OrbitCamera.Instance.zoom(delta);
}
</pre>
We have to update the references from OrbitCamera to OrbitPanCamera and call the methods for panning in the <i>RenderControl_MouseMove </i>handler. I will use the right mouse button for panning:<br />
<br />
<pre class="brush:csharp">private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
int currentX = e.X;
deltaX = startX - currentX;
startX = currentX;
int currentY = e.Y;
deltaY = startY - currentY;
startY = currentY;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
OrbitPanCamera.Instance.rotateY(-deltaX);
OrbitPanCamera.Instance.rotateOrtho(deltaY);
}
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
OrbitPanCamera.Instance.panX(deltaX);
OrbitPanCamera.Instance.panY(deltaY);
}
}
}
void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
int delta = e.Delta;
OrbitPanCamera.Instance.zoom(delta);
}
</pre>
<br />
<h3>
Adapting the Renderables</h3>
We are not quite done yet, because we need the <i>ViewPerspective </i>matrix of our <i>OrbitPanCamera </i>to set the transformation in our Renderables. I will omit the code for this here, because it is just replacing the references to <i>OrbitCamera </i>to <i>OrbitPanCamera </i>in the Renderable classes.<br />
<br />
In order to make the handling of cameras more flexible, I will introduce a CameraManager in the next tutorial, so we can have several cameras and do not need to hardcode the handling of mouse events and switching of cameras in the Renderables.<br />
<br />
<h3>
Result</h3>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/u2hqxg2xY4g?feature=player_embedded' frameborder='0'></iframe></div>
<br />
You can download the source code <a href="http://apparat.codeplex.com/SourceControl/changeset/view/e7de5eb18001">here</a>.</div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-18794200209070212222013-03-21T00:08:00.000+01:002013-03-27T00:39:51.834+01:00Orbit Camera<h3>
Requirements</h3>
An Orbit Camera is a camera that orbits around a given point. We have to consider two angles: azimuth and pitch. Usually you rotate in a horizontal fashion (azimuth) and in vertical (pitch). When rotating around the poles of the resulting sphere the camera is moving on, it is not preferred to go over the poles. Therefore we lock the camera at the poles and prohibit walking over the poles.<br />
<br />
Walking over a pole would result in two unwanted behaviours, depending on the implementation: if the up vector of the camera switches its sign when transgressing the pole, this would result in looking from upside-down at the scene. If the up vector keeps its sign while transgressing a pole, the view rotates instantaneously by 180° around the vertical axis, when wandering over the pole, which is uncomfortable to watch.<br />
<br />
Rotating around the vertical axis is accomplished by moving the mouse right and left. Moving the mouse up and down results in a motion of the camera around the horizontal axis. The mouse wheel is for zooming in and out.<br />
<h3>
Source Code for the Orbit Camera
</h3>
using System;<br />
<pre class="brush:csharp">using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat
{
public class OrbitCamera
{
#region Singleton Pattern
private static OrbitCamera instance = null;
public static OrbitCamera Instance
{
get
{
if (instance == null)
{
instance = new OrbitCamera();
}
return instance;
}
}
#endregion
#region Constructor
private OrbitCamera()
{
eye = new Vector3(4, 2, 0);
target = new Vector3(0, 0, 0);
up = new Vector3(0, 1, 0);
view = Matrix.LookAtLH(eye, target, up);
perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.0f, 1.0f);
}
#endregion
Vector3 eye;
Vector3 target;
Vector3 up;
Matrix view = Matrix.Identity;
Matrix perspective = Matrix.Identity;
Matrix viewPerspective = Matrix.Identity;
public Matrix View
{
get { return view; }
}
public void setPerspective(float fov, float aspect, float znear, float zfar)
{
perspective = Matrix.PerspectiveFovLH(fov, aspect, znear, zfar);
}
public void setView(Vector3 eye, Vector3 target, Vector3 up)
{
view = Matrix.LookAtLH(eye, target, up);
}
public Matrix Perspective
{
get { return perspective; }
}
public Matrix ViewPerspective
{
get { return view * perspective; }
}
float rotY = 0;
public void rotateY(int value)
{
rotY = (value / 100.0f);
Matrix rotMat = Matrix.RotationY(rotY);
eye = Vector3.TransformCoordinate(eye, rotMat);
setView(eye, target, up);
}
float rotOrtho = 0;
public void rotateOrtho(int value)
{
Vector3 viewDir = target - eye;
Vector3 orhto = Vector3.Cross(viewDir, up);
rotOrtho = (value / 100.0f);
Matrix rotOrthoMat = Matrix.RotationAxis(orhto, rotOrtho);
Vector3 eyeLocal = eye - target;
eyeLocal = Vector3.TransformCoordinate(eyeLocal, rotOrthoMat);
Vector3 newEye = eyeLocal + target;
Vector3 newViewDir = target - newEye;
float cosAngle = Vector3.Dot(newViewDir, up) / (newViewDir.Length() * up.Length());
if (cosAngle < 0.9f && cosAngle > -0.9f)
{
eye = eyeLocal + target;
setView(eye, target, up);
}
}
float maxZoom = 3.0f;
public void zoom(int value)
{
float scaleFactor = 1.0f;
if (value > 0)
{
scaleFactor = 1.1f;
}
else
{
if ((eye - target).Length() > maxZoom)
scaleFactor = 0.9f;
}
Matrix scale = Matrix.Scaling(scaleFactor, scaleFactor, scaleFactor);
eye = Vector3.TransformCoordinate(eye, scale);
setView(eye, target, up);
}
}
}
</pre>
<pre class="brush:csharp"></pre>
The pose of the camera is defined by three vectors: up, eye and target. Up is a direction vector, that defines the up direction. Eye is the position of the camera and target is the position to look at. These vectors are set in the constructor of this class and are needed to create the look-at matrix, which we call view.<br />
<br />
Here is the reference to the look at matrix:<br />
<a href="http://slimdx.org/docs/html/M_SlimDX_Matrix_LookAtLH.htm">http://slimdx.org/docs/html/M_SlimDX_Matrix_LookAtLH.htm</a><br />
Every time, we change one of the three vectors up, eye or target, we call the <i>setView </i>method, in which we create a new view matrix.<br />
<br />
Next we create the perspective matrix.<br />
Reference: <a href="http://slimdx.org/docs/html/M_SlimDX_Matrix_PerspectiveFovLH.htm">http://slimdx.org/docs/html/M_SlimDX_Matrix_PerspectiveFovLH.htm</a><br />
<br />
The method <i>rotateY</i> is called, when moving the mouse left or right and preform a rotation around the y-axis.<br />
<br />
The method <i>rotateOrtho</i> is called, when moving the mouse up or down. This method is named <i>rotateOrtho</i>, because the axis of rotation is orthogonal to up vector and the direction vector from the eye to the target. Here we also prevent the camera to transit the poles.<br />
<br />
The <i>zoom</i> method is called, when using the mouse wheel.<br />
<br />
<h3>
Adapt the RenderControl</h3>
<div>
In order to control the camera with the mouse, we need to interact with the <i>RenderControl</i>. In order to do so, I handle four events:<br />
<ul>
<li>MouseUp</li>
<li>MouseDown</li>
<li>MouseMove</li>
<li>MouseWheel</li>
</ul>
<div>
<br /></div>
</div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat.Renderables;
namespace Apparat
{
public partial class RenderControl : UserControl
{
public RenderControl()
{
InitializeComponent();
this.MouseWheel += new MouseEventHandler(RenderControl_MouseWheel);
}
public void init()
{
DeviceManager.Instance.createDeviceAndSwapChain(this);
RenderManager.Instance.init();
Grid grid = new Grid(10, 1.0f);
TriangleEF triangle = new TriangleEF();
Scene.Instance.addRenderObject(triangle);
Scene.Instance.addRenderObject(grid);
}
public void shutDown()
{
RenderManager.Instance.shutDown();
DeviceManager.Instance.shutDown();
}
public bool dragging = false;
int startX = 0;
int deltaX = 0;
int startY = 0;
int deltaY = 0;
private void RenderControl_MouseUp(object sender, MouseEventArgs e)
{
dragging = false;
}
private void RenderControl_MouseDown(object sender, MouseEventArgs e)
{
dragging = true;
startX = e.X;
startY = e.Y;
}
private void RenderControl_MouseMove(object sender, MouseEventArgs e)
{
if (dragging)
{
int currentX = e.X;
deltaX = startX - currentX;
startX = currentX;
int currentY = e.Y;
deltaY = startY - currentY;
startY = currentY;
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
OrbitCamera.Instance.rotateY(-deltaX);
OrbitCamera.Instance.rotateOrtho(deltaY);
}
}
}
void RenderControl_MouseWheel(object sender, MouseEventArgs e)
{
int delta = e.Delta;
OrbitCamera.Instance.zoom(delta);
}
}
}
</pre>
<h3>
Adapt Renderables</h3>
<div>
The camera has a method called <i>ViewPerspective</i>, which return the result of a multiplication of the cameras view matrix with its perspective matrix. To set the according transformation in the renderables, this method has to be called in the render method of the renderables, e.g. the grid renderable:<br />
<br />
<pre class="brush:csharp">public override void render()
{
Matrix ViewPerspective = OrbitCamera.Instance.ViewPerspective;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(numVertices, 0);
}
}
</pre>
<br />
Here we get the ViewPerspective matrix from the camera and pass it to the Effect variable. The results can be seen in the videos below.</div>
<h3>
Results</h3>
<div>
This video shows how the camera rotates around the center of the global coordinate system. Because the transformations of the triangle were not adjusted to the transformation from the camera, the triangle still rotates in the middle of the window.</div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/7-PJI5bpWFY?feature=player_embedded' frameborder='0'></iframe></div>
<br />
This video was made, after the transformation of the triangle was adapted. Now the triangle is stationary. Because the triangle is culled just from one side, it is invisible, if the camera is looking at it from the other side.<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.youtube.com/embed/LgBiLlooO-E?feature=player_embedded' frameborder='0'></iframe></div>
<br />
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/5c16c4db08c6">here</a>.Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-14983236691289778282013-03-19T23:58:00.005+01:002013-03-27T00:45:11.107+01:00Rendering a Grid with the LineList PrimitiveIn the next tutorials I am going to integrate a class for a camera. In order to have an orientation where we are going with the camera it is common to render a grid as reference.
<br />
<br />
<h3>
Source Code of the Grid Renderable</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
public class Grid : Renderable
{
SlimDX.Direct3D11.Buffer vertexBuffer;
DataStream vertices;
InputLayout layout;
int numVertices = 0;
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
EffectMatrixVariable tmat;
public Grid(int cellsPerSide, float cellSize)
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"transformEffectRasterizer.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
int numLines = cellsPerSide+1;
float lineLength = cellsPerSide * cellSize;
float xStart = -lineLength / 2.0f;
float yStart = -lineLength / 2.0f;
float xCurrent = xStart;
float yCurrent = yStart;
numVertices = 2 * 2 * numLines;
int SizeInBytes = 12 * numVertices;
vertices = new DataStream(SizeInBytes, true, true);
for (int y = 0; y < numLines; y++)
{
vertices.Write(new Vector3(xCurrent, 0, yStart));
vertices.Write(new Vector3(xCurrent, 0, yStart + lineLength));
xCurrent += cellSize;
}
for (int x = 0; x < numLines; x++)
{
vertices.Write(new Vector3(xStart, 0, yCurrent));
vertices.Write(new Vector3(xStart + lineLength, 0, yCurrent));
yCurrent += cellSize;
}
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, vertices, SizeInBytes, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
}
public override void render()
{
Matrix ViewPerspective = Matrix.Identity;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(numVertices, 0);
}
}
public override void dispose()
{
inputSignature.Dispose();
}
}
}
</pre>
<br />
The constructor of the grid takes the number of cells per side and the cell size as arguments. In the constructor the vertices of the grid are created. I have arranged the grid in a way, that the center of the grid<br />
corresponds with the origin of the grids local coordinate system. If you compare this code to the code for the triangle used in the previous tutorial, only the creation of the vertices differs.<br />
<br />
<h3>
The Render Method</h3>
This is the render method of the triangle of the last tutorial. The transformation matrix of the triangle is set via the Effect Framework.<br />
<br />
<pre class="brush:csharp">public override void render()
{
rot += 0.01f;
rotMat = Matrix.RotationY(rot);
tmat.SetMatrix(Matrix.Transpose(rotMat));
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(3, 0);
}
}
</pre>
<br />
Compare this to the render method of the grid:<br />
<br />
<pre class="brush:csharp">public override void render()
{
Matrix ViewPerspective = Matrix.Identity;
tmat.SetMatrix(ViewPerspective);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(numVertices, 0);
}
}
</pre>
Here the transformation matrix, called <i>ViewPerspective </i>is set to the Identity Matrix, resulting in no transformation. This is the point, where the transformation from the camera will come into play in the next tutorial.<br />
<br />
While we could call in the triangle class the Draw method with 3 vertices, we have to use for the grid class a variable called numVertices, as the vertices of the lines are created in the constructor and depend on the number of cells of our grid.<br />
<br />
The next thing to note is that the primitive typology for the triangle was <i>PrimitiveTopology.TriangleList </i>and in the primitive typology for the grid is<i> PrimitiveTopology.LineList.</i><br />
<i><br /></i>
The reference to the SlimDX Primitive Topology Enumeration is here:<br />
<a href="http://slimdx.org/docs/html/T_SlimDX_Direct3D11_PrimitiveTopology.htm">http://slimdx.org/docs/html/T_SlimDX_Direct3D11_PrimitiveTopology.htm</a><br />
<br />
The most common primitives are:<br />
<br />
<ul>
<li>PointList</li>
<li>LineList</li>
<li>LineStrip</li>
<li>TriangleList</li>
<li>TriangleStrip</li>
</ul>
<div>
We have used the LineList and the TriangleList so far.</div>
<br />
<h3>
Result</h3>
So far, we get the following picture, when compiling and executing the code:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqmltWWTRZZd89A004QPQYC1mwVelawAwaCqFlsfYTTtW4zQN3OV06Dz397DS82gaKlAtG8J42NxQpk9v3W4hwZerCQxCgIe80YPFruIqEm7WYwV0jPE0lIRa5BSuPXp-u0boZ28hWwapA/s1600/Grid.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqmltWWTRZZd89A004QPQYC1mwVelawAwaCqFlsfYTTtW4zQN3OV06Dz397DS82gaKlAtG8J42NxQpk9v3W4hwZerCQxCgIe80YPFruIqEm7WYwV0jPE0lIRa5BSuPXp-u0boZ28hWwapA/s1600/Grid.PNG" height="241" width="320" /></a></div>
<br />
The result is quite sobering, as we just see an additional line in the center of the window. This is because, the view is aligned with the horizontal plane and we see the grid from the side.<br />
You can try to rotate the grid programmatically like in the triangle class. Hint: Matrix.RotationX(float angle) is your friend.<br />
In the next tutorial I will introduce an Orbit Camera, that allows you to zoom and rotate around the origin of the global coordinate system.<br />
<br />
You can download the source code to this tutorial <a href="http://apparat.codeplex.com/SourceControl/changeset/view/5e8fc8562eb4">here</a>.Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-47200725994610848852013-03-19T00:36:00.000+01:002013-03-27T00:41:20.753+01:00Setting Transformations in a Shader with the Effect FrameworkIn the previous tutorial I showed how to set a transformation in a shader via a Contant Buffer, resulting<br />
in a rotating triangle. In this tutorial I will implement the same functionality with the Effect Framework.<br />
<br />
<h3>
</h3>
<h3>
</h3>
<h3>
Triangle Source Code</h3>
<div>
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
public class TriangleEF : Renderable
{
ShaderSignature inputSignature;
EffectTechnique technique;
EffectPass pass;
Effect effect;
InputLayout layout;
SlimDX.Direct3D11.Buffer vertexBuffer;
EffectMatrixVariable tmat;
public TriangleEF()
{
try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"transformEffect.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
// create test vertex data, making sure to rewind the stream afterward
var vertices = new DataStream(12 * 3, true, true);
vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
vertices,
12 * 3,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
}
public override void dispose()
{
effect.Dispose();
inputSignature.Dispose();
vertexBuffer.Dispose();
layout.Dispose();
}
float rot = 0.0f;
Matrix rotMat;
public override void render()
{
rot += 0.01f;
rotMat = Matrix.RotationY(rot);
tmat.SetMatrix(rotMat);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(3, 0);
}
}
}
}
</pre>
<h3>
Shader Source Code</h3>
<div>
<br /></div>
<pre class="brush:csharp">matrix gWVP;
float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return float4(0.0f, 0.0f, 1.0f, 1.0f);
}
technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
}
}
</pre>
<div>
<h3>
Explaining the Shader Source Code</h3>
</div>
<div>
In contrast to the previous shader in the last tutorial I do not declare the matrix variable gWVP in a</div>
<div>
ConstantBuffer but directly as a matrix.</div>
<div>
<br /></div>
<div>
Also. when using the Effect Framework you have to define a Technique with at least one Pass:</div>
<div>
<br /></div>
<div>
<br /></div>
<pre class="brush:csharp">technique10 Render
{
pass P0
{
SetVertexShader( CompileShader( vs_4_0, VShader() ));
SetGeometryShader( NULL );
SetPixelShader( CompileShader( ps_4_0, PShader() ));
}
}
</pre>
<div>
<br />
The Technique is your interface to your shader from your code and in the Pass the shaders are set. I will explain in the next section how to interface with your shader.<br />
<br />
<h3>
Explaining the Triangle Source Code</h3>
<div>
In the last tutorial you had to load the VertexShader and the PixelShader seperately. With the Effect Framework you just have to load the ShaderBytecode for the effect:</div>
<div>
<br /></div>
<pre class="brush:csharp">try
{
using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
"transformEffect.fx",
"Render",
"fx_5_0",
ShaderFlags.EnableStrictness,
EffectFlags.None))
{
effect = new Effect(DeviceManager.Instance.device, effectByteCode);
technique = effect.GetTechniqueByIndex(0);
pass = technique.GetPassByIndex(0);
inputSignature = pass.Description.Signature;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
tmat = effect.GetVariableByName("gWVP").AsMatrix();
</pre>
<div>
<br /></div>
<div>
When compiling the ShaderBytecode you have to set the name of the Technique as a parameter, in this case "Render". You have to create the effect by calling the constructor of the Effect Class with the device and the ShaderBytecode as parameters. Also you have access the technique and pass to get the InputSignature of the shader.<br />
<br />
In order to access the matrix variable in your shader, you have to use the getVariableByName function of the effect. The matrix from the effect file is asigned to a matrix called tmat, which is of the type EffectMatrixVariable. We will use the variable tmat again in the render function of the triangle to set the transformation of the triangle:<br />
<br /></div>
</div>
<pre class="brush:csharp">public override void render()
{
rot += 0.01f;
rotMat = Matrix.RotationY(rot);
tmat.SetMatrix(rotMat);
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
technique = effect.GetTechniqueByName("Render");
EffectTechniqueDescription techDesc;
techDesc = technique.Description;
for (int p = 0; p < techDesc.PassCount; ++p)
{
technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
DeviceManager.Instance.context.Draw(3, 0);
}
}
</pre>
<div>
<br />
The statement <i>tmat.SetMatrix(rotMat)</i> sets the variable for the transformation matrix in the effect.<br />
<br />
Now we get a rotating triangle again:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyv8MGtXjt-DUA7lY-8XOUjmju_9zljBlv9OqnD2I4ZzPqUNpxTn8aFNOxyFIwx96-US4tU_itDTQIDewXsmu57033tBpaxF0EjUsCmtHtuTU9CmILKBaYAKYr0hIRo9JqxD1WmrOanCpi/s1600/Triangle.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgyv8MGtXjt-DUA7lY-8XOUjmju_9zljBlv9OqnD2I4ZzPqUNpxTn8aFNOxyFIwx96-US4tU_itDTQIDewXsmu57033tBpaxF0EjUsCmtHtuTU9CmILKBaYAKYr0hIRo9JqxD1WmrOanCpi/s1600/Triangle.PNG" height="241" width="320" /></a></div>
<br />
Observe, that the triangle is rotating counter-clockwise, while the triangle in the previous tutorial was rotating clockwise. As far as i know, DirectX uses per default a left-handed coordinate system and the triangle should rotate clockwise with positively growing values for rotation around the y-axis. This can be resolved by transposing the matrix before passing it to the Effect Framework: <i>tmat.SetMatrix(Matrix.Transpose(rotMat)) </i>and the triangle is rotating clockwise again.<br />
<br />
You can download the source code <a href="http://apparat.codeplex.com/SourceControl/changeset/view/3a74ea97bb15">here</a>.<br />
<br />
<br /></div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com2tag:blogger.com,1999:blog-4722199290168150238.post-73159251333301028922012-11-10T15:49:00.000+01:002013-03-27T00:41:20.754+01:00Setting Transformations in a Shader with a Constant BufferIn this tutorial I will show you, how you can set Transformations in a Shader via the Constant Buffer.<br />
We will render a Triangle and perform a rotation on it while rendering.<br />
<br />
<h3>
Triangle Source Code</h3>
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
public class TriangleCB : Renderable
{
ShaderSignature inputSignature;
VertexShader vertexShader;
PixelShader pixelShader;
InputLayout layout;
SlimDX.Direct3D11.Buffer vertexBuffer;
public TriangleCB()
{
#region shader and triangle
try
{
// load and compile the vertex shader
using (var bytecode = ShaderBytecode.CompileFromFile("transform.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
{
inputSignature = ShaderSignature.GetInputSignature(bytecode);
vertexShader = new VertexShader(DeviceManager.Instance.device, bytecode);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
// load and compile the pixel shader
using (var bytecode = ShaderBytecode.CompileFromFile("transform.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
{
pixelShader = new PixelShader(DeviceManager.Instance.device, bytecode);
}
// create test vertex data, making sure to rewind the stream afterward
var vertices = new DataStream(12 * 3, true, true);
vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
vertexBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device,
vertices,
12 * 3,
ResourceUsage.Default,
BindFlags.VertexBuffer,
CpuAccessFlags.None,
ResourceOptionFlags.None,
0);
#endregion
}
public override void dispose()
{
pixelShader.Dispose();
vertexShader.Dispose();
inputSignature.Dispose();
}
float rot = 0.0f;
Matrix rotMat;
public override void render()
{
rot += 0.01f;
rotMat = Matrix.RotationY(rot);
var matStream = new DataStream(64, true, true);
matStream.Write(rotMat);
matStream.Position = 0;
using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, //Device
matStream, //Stream
64, // Size
// Flags
ResourceUsage.Dynamic,
BindFlags.ConstantBuffer,
CpuAccessFlags.Write,
ResourceOptionFlags.None,
4))
{
DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
}
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
//// set the shaders
DeviceManager.Instance.context.VertexShader.Set(vertexShader);
DeviceManager.Instance.context.PixelShader.Set(pixelShader);
// render the triangle
DeviceManager.Instance.context.Draw(3, 0);
}
}
}
</pre>
<h3>
Shader Source Code</h3>
<pre class="brush:csharp"></pre>
<pre class="brush:csharp">cbuffer ConstBuffer : register(c0)
{
float4x4 gWVP;
}
float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return float4(1.0f, 0.0f, 0.0f, 1.0f);
}</pre>
<h3>
Explanation of the Shader Source Code</h3>
<div>
In order to set transformations, we need to have a variable in the Shader to assign the transformation.</div>
<div>
In this shader I call the variable<i> gWVP </i>and it is declared in the <i>ConstantBuffer:</i></div>
<div>
<i><br /></i></div>
<div>
<i><br /></i></div>
<div>
<pre class="brush:csharp">cbuffer ConstBuffer : register(c0)
{
float4x4 gWVP;
}
</pre>
<br /></div>
<div>
<br /></div>
<div>
To apply the transformation to each vertex of our triangle, we have to apply a Multiplication with</div>
<div>
the Transformation Matrix.</div>
<div>
<br /></div>
<pre class="brush:csharp">float4 VShader(float4 position : POSITION) : SV_POSITION
{
return mul( position, gWVP);
}
</pre>
<div>
<i><br /></i>
<br />
<h3>
Explanation of the Triangle Source Code</h3>
<div>
If you compare the Constructor of the TriangleCB class above with the Constructor of the Triangle class</div>
<div>
in the "Rendering a Triangle" Tutorial, everything remains the same:</div>
<div>
<br /></div>
<div>
We create a VertexShader, a Pixel Shader, we write the Vertices of the Triangle to a DataStream and create a VertexBuffer.</div>
<div>
<br /></div>
<div>
I added two Variables for the rotation of the Triangle and a Matrix to hold the Transformation Matrix:</div>
<div>
<br /></div>
<i><br /></i></div>
<pre class="brush:csharp">float rot = 0.0f;
Matrix rotMat;
</pre>
<div>
<br />
The interesting part happens in the render Function of our Triangle. Let's have a short look at our original Version of our Triangle:<br />
<br /></div>
<pre class="brush:csharp">public override void render()
{
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
// set the shaders
DeviceManager.Instance.context.VertexShader.Set(vertexShader);
DeviceManager.Instance.context.PixelShader.Set(pixelShader);
// render the triangle
DeviceManager.Instance.context.Draw(3, 0);
}
</pre>
<br />
The Input Assembler Stage is set with an InputLayout, we provide a Primitive Topology and the Vertex Buffer is set. The Vertex and Pixel Shader are set in the Device and we can finally draw the Triangle.<br />
<br />
Our new render function looks like this:<br />
<br />
<br />
<br />
<pre class="brush:csharp">public override void render()
{
rot += 0.01f;
rotMat = Matrix.RotationY(rot);
var matStream = new DataStream(64, true, true);
matStream.Write(rotMat);
matStream.Position = 0;
using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(
DeviceManager.Instance.device, //Device
matStream, //Stream
64, // Size
// Flags
ResourceUsage.Dynamic,
BindFlags.ConstantBuffer,
CpuAccessFlags.Write,
ResourceOptionFlags.None,
4)){ DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
}
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
// set the shaders
DeviceManager.Instance.context.VertexShader.Set(vertexShader);
DeviceManager.Instance.context.PixelShader.Set(pixelShader);
// render the triangle
DeviceManager.Instance.context.Draw(3, 0);
}
</pre>
<br />
The lower part of the Source Code is exactly the same. In the upper part I add 0.01f of rotation in every frame. Then this rotation value is used to create a Rotation Matrix, that performs a Rotation around the Y-Axis:<br />
<br />
<pre class="brush:csharp">rot += 0.01f;
rotMat = Matrix.RotationY(rot);
</pre>
<br />
Next I create a DataStream, to write the Rotation Matrix to:<br />
<br />
<pre class="brush:csharp">var matStream = new DataStream(64, true, true);
matStream.Write(rotMat);
matStream.Position = 0;
</pre>
<br />
The SlimDX DataStream Class has the following Constructor:<br />
<br />
<pre class="brush:csharp">public DataStream(long sizeInBytes, bool canRead, bool canWrite);
</pre>
<br />
So when creating the DataStream the Size of the Matrix in Bytes has to be given as a Parameter.<br />
A Matrix is a struct with 4x4 float, a float consists of 4 Bytes, so the Matrix struct has the size of 64 Bytes, thus we have to create the DataStream with a size of 64 Bytes. And we assign the Matrix rotMat by writing it to the stream. Finally we have to rewind the Stream by setting its Position to 0.<br />
<br />
Finally we have to assign the Transformation Matrix to the VertexShader:<br />
<br />
<pre class="brush:csharp">using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, //Device
matStream, //Stream
64, // Size
// Flags
ResourceUsage.Dynamic,
BindFlags.ConstantBuffer,
CpuAccessFlags.Write,
ResourceOptionFlags.None,
4))
{
DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
}
</pre>
<br />
This is done by creating a Buffer that holds the DataStream with the Data of the Rotation Matrix. Then we use this Buffer to set the ConstantBuffer of the VertexShader.<br />
<br />
When Creating the Triangle, adding it to the Scene and let the Program run, you a get a rotating red Triangle:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfbGy2fEQmweumgmHBIZ2jc6VTcrVc8S6jPrso2RugWhsH18I-R171-HN87nonhO-4EdMdD5FQSOlYF-rW1SBvgYiO8EJRRo8MAXFzlLph2y7BR8jJ1NNlIpp73xsLKJvf9nUCnJLC02nq/s1600/Triangle..png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfbGy2fEQmweumgmHBIZ2jc6VTcrVc8S6jPrso2RugWhsH18I-R171-HN87nonhO-4EdMdD5FQSOlYF-rW1SBvgYiO8EJRRo8MAXFzlLph2y7BR8jJ1NNlIpp73xsLKJvf9nUCnJLC02nq/s1600/Triangle..png" width="320" /></a></div>
<br />
<br />
<br />
<br />
You can download the Project here:<br />
<a href="http://apparat.codeplex.com/SourceControl/changeset/9d048db48302">http://apparat.codeplex.com/SourceControl/changeset/9d048db48302</a>Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com2tag:blogger.com,1999:blog-4722199290168150238.post-35970326500879571432012-11-04T21:32:00.000+01:002012-11-04T21:32:02.302+01:00Creating a UserControl for the Render EngineIn this tutorial I will show you, how you can create a User Control that you can add to new projects just by dragging and dropping it from your toolbox to your form.<br />
<br />
<h3>
Adding a UserControl</h3>
<br />
Start by Right-Clicking on your Apparat Project and select Add/UserControl:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizfwRo6Gn-DnbDYysobQneNA7X85ldlk7huUff_qGo0ZsFKTAHak9u3phJpTqQxNUilFR-toRhVFSd70tWoq4m-BZSqkGdEcCUE90NrHbCb909wy_Y2oyYcT1mOEKpDBUadaKM7P4j4FCs/s1600/CreateUserControl.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="299" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizfwRo6Gn-DnbDYysobQneNA7X85ldlk7huUff_qGo0ZsFKTAHak9u3phJpTqQxNUilFR-toRhVFSd70tWoq4m-BZSqkGdEcCUE90NrHbCb909wy_Y2oyYcT1mOEKpDBUadaKM7P4j4FCs/s1600/CreateUserControl.png" width="320" /></a></div>
<br />
<br />
<br />
In the following Dialogue select User Control. I will name this Control <i>RenderControl</i>. To add the Control, click <i>Add</i> on the right bottom of the Dialogue:
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0FlNzxwp6LYpT41XsJCX73haKkwVGhCnIsgxbhEV7EsslOptqvII5j3apxgVwc5JcnsJzmnChR1ycODtX1po6Lwtihr-aoNQON5krPpuHNRK7jptYespcRGHGvntytF8n8E5KcO9G562M/s1600/AddUserControl.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="221" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0FlNzxwp6LYpT41XsJCX73haKkwVGhCnIsgxbhEV7EsslOptqvII5j3apxgVwc5JcnsJzmnChR1ycODtX1po6Lwtihr-aoNQON5krPpuHNRK7jptYespcRGHGvntytF8n8E5KcO9G562M/s1600/AddUserControl.PNG" width="320" /></a></div>
<br />
<br />
<h3>
Functions in the UserControl</h3>
<div>
Let's take a look at our class <i>Form1</i><b> </b>in the <i>MDX11Form</i> Project:</div>
<div>
<br /></div>
<div>
<br /></div>
<pre class="brush:csharp">namespace MDX11Form
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DeviceManager.Instance.createDeviceAndSwapChain(this);
RenderManager.Instance.init();
Triangle triangle = new Triangle();
Scene.Instance.addRenderObject(triangle);
}
public void shutDown()
{
RenderManager.Instance.shutDown();
DeviceManager.Instance.shutDown();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
shutDown();
}
}
}
</pre>
<div>
<br /></div>
<div>
<br /></div>
Observe, that the function <i>createDeviceAndSwapChain </i>has the following signature:
<br />
<pre class="brush:csharp">public void createDeviceAndSwapChain(System.Windows.Forms.Control form)
</pre>
<div>
<br /></div>
<div>
All User Controls inherit from System.Windows.Controls.Control and we can reuse this code<br />
in our <i>RenderControl. </i>Furthermore I move the shutDown Method into the User Control.<br />
To edit the <i>RenderControl </i>Right-Click on it in the Apparat-Project and select <i>View Code</i>.<br />
<br />
Visual Studio created the following Code for us, when we added the User Control:<br />
<br />
<br />
<br />
<br />
<br />
<br /></div>
<pre class="brush:csharp">namespace Apparat
{
public partial class RenderControl : UserControl
{
public RenderControl()
{
InitializeComponent();
}
}
}
</pre>
<div>
<br />
Copying the above mentioned code, and moving the code to create the DeviceManager and RenderManager to an init function, the code in our RenderControl now looks like this:<br />
<br />
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat.Renderables;
namespace Apparat
{
public partial class RenderControl : UserControl
{
public RenderControl()
{
InitializeComponent();
}
public void init()
{
DeviceManager.Instance.createDeviceAndSwapChain(this);
RenderManager.Instance.init();
Triangle triangle = new Triangle();
Scene.Instance.addRenderObject(triangle);
}
public void shutDown()
{
RenderManager.Instance.shutDown();
DeviceManager.Instance.shutDown();
}
}
}
</pre>
<div>
<br />
In order to make the <i>RenderControl </i>visible, when I drag it to a Form, I set the BackColor of the<br />
<i>RenderControl </i>to Orange in the Properties of the <i>RenderControl</i>.<br />
And we are done with the User Control!<br />
At this stage you have to Rebuild the Apparat Library.<br />
<br />
<h3>
Adding the RenderControl to the Form</h3>
Now Double-Click the Form1 in the MDX11Form Project. If you select the Toolbox of Visual Studio,<br />
the <i>RenderControl </i>shows up in our Toolbox:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnHqQuhWqa7SUpoZZvQT7zDdSwY-_fPIFkgmz10FIQ7hn-gI1mzPuUq9Hp4xMhhXYTSzkaMH8pgDAaooqhZl0v_maazOihozXiDoec-G82TvvBq5-PMkLjhm5XOUvABzhyphenhyphenKQMEtPxLjWAt/s1600/ToolBox.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnHqQuhWqa7SUpoZZvQT7zDdSwY-_fPIFkgmz10FIQ7hn-gI1mzPuUq9Hp4xMhhXYTSzkaMH8pgDAaooqhZl0v_maazOihozXiDoec-G82TvvBq5-PMkLjhm5XOUvABzhyphenhyphenKQMEtPxLjWAt/s1600/ToolBox.PNG" width="320" /></a></div>
<br />
<br />
Click the RenderControl in the Toolbox and click somewhere in the Form1 to drop our <i>RenderControl </i>there, or you can use Drag&Drop from the Toolbox. Adjusting the sizes of the Form1 and the RenderControl, the Form1 looks now like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDxwSs2lv683EJEowuhQmjlr35sRWd1Gd9krwkqkyt3EBNzFC2TPIEFc7gBOpYgrfIILZRfEalRB0iZ0UYfljmv3fsojEFQLzAi-BxJ4CVLutF1BTu4FKeS9WoJgMe40Nn5MNnj23_5A7_/s1600/Form1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="247" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDxwSs2lv683EJEowuhQmjlr35sRWd1Gd9krwkqkyt3EBNzFC2TPIEFc7gBOpYgrfIILZRfEalRB0iZ0UYfljmv3fsojEFQLzAi-BxJ4CVLutF1BTu4FKeS9WoJgMe40Nn5MNnj23_5A7_/s1600/Form1.PNG" width="320" /></a></div>
<br />
If your compile the MDX11Form Project right now, you see nothing but the orange rectangle in the Form1.<br />
Double-Click in the Designer on the Form1 to edit the Handler for the Load event of the Form.<br />
Visual Studio created a renderControl1 object of our RenderControl, when we added it to the Form.<br />
We can access the functions via this renderControl1 object.<br />
<br />
All you have to do, to get the RenderControl working, is to call its init function. I do it in the Handler<br />
of the Load event. To shut down, properly, I call the shutDown Method in the Handler of the FormClosing event:<br />
<br /></div>
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat;
using Apparat.Renderables;
namespace MDX11Form
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
renderControl1.shutDown();
}
private void Form1_Load(object sender, EventArgs e)
{
renderControl1.init();
}
}
}
</pre>
<div>
<br />
If you start the Solution now, you can see that our Render Engine is running in our UserControl:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUJxhVVbBITEAs_vQK8_YpnbK3BplxXwpkiXVoMGycLXuvpnvak1dfvEqmjU0RjvUYGCAp0ZwLeUMDNgLDi3aa28MT3GVVR5QdtOKBv7gJdoDa5c-JwKM-XBSZDJZcqElZsRYFaxdIUbFj/s1600/Done.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiUJxhVVbBITEAs_vQK8_YpnbK3BplxXwpkiXVoMGycLXuvpnvak1dfvEqmjU0RjvUYGCAp0ZwLeUMDNgLDi3aa28MT3GVVR5QdtOKBv7gJdoDa5c-JwKM-XBSZDJZcqElZsRYFaxdIUbFj/s1600/Done.PNG" height="239" width="320" /></a></div>
<br />
You can download the Solution here:<br />
<a href="http://apparat.codeplex.com/SourceControl/changeset/dc772f719b14">http://apparat.codeplex.com/SourceControl/changeset/dc772f719b14</a><br />
<br />
<br /></div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-62771185888915008902012-05-20T15:47:00.000+02:002012-11-06T23:42:10.514+01:00The Scene ClassUp to now we created a triangle and called its render method in the RenderManager:<br />
<br />
<br />
<pre class="brush:csharp">public void renderScene()
{
Renderables.Triangle triangle = new Renderables.Triangle();
while (true)
{
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));
triangle.render();
dm.swapChain.Present(0, PresentFlags.None);
}
}
</pre>
<br />
<h3>
The Renderable Class</h3>
<br />
But what you usually want to do is add and remove objects to render dynamically. So we have to have a data structure to hold all our render objects. For the sake of simplicity lets start with a list. To be able to iterate through the List, all renderable objects have to inherit from a base class called Renderable.<br />
<br />
So this is the bare minimum we need:<br />
<br />
<br />
<pre class="brush:csharp">public abstract class Renderable
{
public abstract void render();
}
</pre>
<br />
Now let us derive our Triangle Class from this abstract class override our render method:<br />
<br />
<br />
<pre class="brush:csharp">class Triangle : Renderable
{
ShaderSignature inputSignature;
VertexShader vertexShader;
PixelShader pixelShader;
.
.
.
</pre>
<br />
<br />
<pre class="brush:csharp">public override void render()
{
// configure the Input Assembler portion of the pipeline with the vertex data
DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
// set the shaders
DeviceManager.Instance.context.VertexShader.Set(vertexShader);
DeviceManager.Instance.context.PixelShader.Set(pixelShader);
// render the triangle
DeviceManager.Instance.context.Draw(3, 0);
}
</pre>
<br />
<h3>
The Scene Class</h3>
Now lets add a Singleton Class called Scene. This holds a List of Renderables, a method to add Renderables, a method to remove Renderables and a method to iterate our List to render the objects.<br />
To prevent altering the List while the render-loop is calling the render method, I use the lock Statement.<br />
<br />
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Apparat.Renderables;
namespace Apparat
{
public class Scene
{
#region Singleton Pattern
private static Scene instance = null;
public static Scene Instance
{
get
{
if (instance == null)
{
instance = new Scene();
}
return instance;
}
}
#endregion
#region Constructor
private Scene() { }
#endregion
List<Renderable> RenderObjects = new List<Renderable>();
public void addRenderObject(Renderable renderObject)
{
lock (RenderObjects)
{
RenderObjects.Add(renderObject);
}
}
public void removeRenderObject(Renderable renderObject)
{
lock (RenderObjects)
{
if( RenderObjects.Contains( renderObject ))
{
RenderObjects.Remove( renderObject );
}
}
}
public void render()
{
lock (RenderObjects)
{
foreach (Renderable renderable in RenderObjects)
{
renderable.render();
}
}
}
}
}
</pre>
<br />
<h3>
Using the Scene</h3>
<div>
Now we can change the code in our renderScene method in our RenderManager to call the render method of our Scene (compare with the first method at the top):</div>
<div>
<br /></div>
<pre class="brush:csharp">public void renderScene()
{
while (true)
{
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));
Scene.Instance.render();
dm.swapChain.Present(0, PresentFlags.None);
}
}
</pre>
<div>
<br /></div>
<div>
While we created the triangle at first in our renderScene method, we are now able to add and remove Renderables from our Application!<br />
<br />
For example:<br />
<br />
<br />
<pre class="brush:csharp">public Form1()
{
InitializeComponent();
DeviceManager.Instance.createDeviceAndSwapChain(this);
RenderManager.Instance.init();
Triangle triangle = new Triangle();
Scene.Instance.addRenderObject(triangle);
}
</pre>
<br />
You can download the code to this project here:<br />
<br />
<a href="http://apparat.codeplex.com/SourceControl/changeset/changes/ba452d5587ac">http://apparat.codeplex.com/SourceControl/changeset/changes/ba452d5587ac</a></div>
Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-16118730855025281812012-05-19T17:32:00.003+02:002013-03-27T00:45:11.108+01:00Rendering a Triangle: Cleaning UpThe SlimDX tutorial about rendering a triangle puts all the code for setting up the device and the swapchain<br />
into one file. Let's organize the code some more by creating a Class for the Triangle. I added a Folder<br />
for Renderables, where I will place all further Classes, that are responsible for holding the resources and methods to render things.<br />
<br />
First let's copy and paste the code for rendering the triangle into the contructor of the Triangle class a method called render. After adding the needed using directices the Triangle class looks like this:<br />
<br />
<br />
<br />
<br />
<pre class="brush:csharp">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;
namespace Apparat.Renderables
{
class Triangle
{
ShaderSignature inputSignature;
VertexShader vertexShader;
PixelShader pixelShader;
public Triangle()
{
#region shader and triangle
// load and compile the vertex shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
{
inputSignature = ShaderSignature.GetInputSignature(bytecode);
vertexShader = new VertexShader(device, bytecode);
}
// load and compile the pixel shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
pixelShader = new PixelShader(device, bytecode);
// create test vertex data, making sure to rewind the stream afterward
var vertices = new DataStream(12 * 3, true, true);
vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
var layout = new InputLayout(device, inputSignature, elements);
var vertexBuffer = new SlimDX.Direct3D11.Buffer(device, vertices, 12 * 3, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
// configure the Input Assembler portion of the pipeline with the vertex data
context.InputAssembler.InputLayout = layout;
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
// set the shaders
context.VertexShader.Set(vertexShader);
context.PixelShader.Set(pixelShader);
#endregion
}
public void render()
{
}
public void dispose()
{
pixelShader.Dispose();
vertexShader.Dispose();
inputSignature.Dispose();
}
}
}
</pre>
<br />
Now we move all the code that configures the input assembler and sets the shaders to the render method.<br />
If we have more objects, shaders have to be set for every object on every render call.<br />
<br />
Further the calls to context and device have to be adjusted, because those are member variables of the DeviceManager.<br />
So for example <br />
<pre class="brush:csharp">context.VertexShader.Set(vertexShader);</pre>
becomes <br />
<pre class="brush:csharp">DeviceManager.Instance.context.VertexShader.Set(vertexShader);</pre>
<br />
Finally we have to create an object of the triangle and call its render method in the render loop.<br />
<br />
<pre class="brush:csharp">public void renderScene()
{
Renderables.Triangle triangle = new Renderables.Triangle();
while (true)
{
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));
triangle.render();
dm.swapChain.Present(0, PresentFlags.None);
}
}
</pre>
<br />
You can download the source code here: <a href="http://apparat.codeplex.com/SourceControl/changeset/changes/3882dce2a134">http://apparat.codeplex.com/SourceControl/changeset/changes/3882dce2a134</a><br />
<br />Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0tag:blogger.com,1999:blog-4722199290168150238.post-73800849150759082742012-05-17T23:02:00.001+02:002013-03-27T00:45:11.110+01:00Rendering a TriangleNow let's integrate the last of the <a href="http://slimdx.org/">SlimDX</a> tutorials into our Engine. It is called<br />
<a href="http://slimdx.org/tutorials/SimpleTriangle.php">Direct3D11 - SimpleTriangle</a>. I will start by hardcoding the relevant code from<br />
the SlimDX tutorial into the Engine. I will not go into any detail about shaders, vertices and<br />
the render pipeline in this post.<br />
<br />
Take the following code and copy-paste it into the DeviceManager Class of our Engine<br />
just behind the line<br />
<br />
<br />
<pre class="brush:csharp">context.Rasterizer.SetViewports(viewport);</pre>
<br />
<br />
<pre class="brush:csharp">#region shader and triangle
// load and compile the vertex shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
{
inputSignature = ShaderSignature.GetInputSignature(bytecode);
vertexShader = new VertexShader(device, bytecode);
}
// load and compile the pixel shader
using (var bytecode = ShaderBytecode.CompileFromFile("triangle.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
pixelShader = new PixelShader(device, bytecode);
// create test vertex data, making sure to rewind the stream afterward
var vertices = new DataStream(12 * 3, true, true);
vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
vertices.Position = 0;
// create the vertex layout and buffer
var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
var layout = new InputLayout(device, inputSignature, elements);
var vertexBuffer = new SlimDX.Direct3D11.Buffer(device, vertices, 12 * 3, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
// configure the Input Assembler portion of the pipeline with the vertex data
context.InputAssembler.InputLayout = layout;
context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));
// set the shaders
context.VertexShader.Set(vertexShader);
context.PixelShader.Set(pixelShader);
#endregion
</pre>
<br />
Add this using directive to your usings:<br />
<br />
<br />
<pre class="brush:csharp">using SlimDX.D3DCompiler;</pre>
<br />
Add the following declarations to the member variables of the DeviceManager Class:<br />
<br />
<br />
<pre class="brush:csharp">ShaderSignature inputSignature;
VertexShader vertexShader;
PixelShader pixelShader;
</pre>
<br />
And add the following lines at the beginning of our shutDown Method in our DeviceManager Class:<br />
<br />
<br />
<pre class="brush:csharp">pixelShader.Dispose();
vertexShader.Dispose();
inputSignature.Dispose();
</pre>
<br />
Now add a new Element to our Engine Class Library by right-clicking on it and select Add->New Item.<br />
Type "triangle.fx" as name and create the element.<br />
<br />
Open the file in your Solution Explorer, delete all content and copy-paste the following code:<br />
<br />
<br />
<pre class="brush:csharp">float4 VShader(float4 position : POSITION) : SV_POSITION
{
return position;
}
float4 PShader(float4 position : SV_POSITION) : SV_Target
{
return float4(1.0f, 1.0f, 0.0f, 1.0f);
}
</pre>
<br />
Right-click on the file "triangle.fx" in your Solution Explorer and click on Properies.<br />
Select "Copy to Output Directory" and select "copy always".<br />
<br />
Change the code in the RenderManager Class in the renderScene Method and call<br />
dm.context.Draw(3,0) :<br />
<br />
<br />
<pre class="brush:csharp">public void renderScene()
{
while (true)
{
DeviceManager dm = DeviceManager.Instance;
dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.25f, 0.75f, 0.25f));
dm.context.Draw(3, 0);
dm.swapChain.Present(0, PresentFlags.None);
}
}
</pre>
<br />
After compiling the Class Library you can recompile your Main Project and should get the following result:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeORJrIcXhbMyZEH7sdUmOhQp3PSnC-wXEFqkTEy_eoNJ7LzifIQBqvVCIuWeobtJ4bDp8H9CxAMgK8hZ8Jx3tBEXGkpF7XYKduX5iZF1b8qXOt2zKbSnu2Yy3YJv4KiomAjMOlg9Nabvx/s1600/Result.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="171" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeORJrIcXhbMyZEH7sdUmOhQp3PSnC-wXEFqkTEy_eoNJ7LzifIQBqvVCIuWeobtJ4bDp8H9CxAMgK8hZ8Jx3tBEXGkpF7XYKduX5iZF1b8qXOt2zKbSnu2Yy3YJv4KiomAjMOlg9Nabvx/s320/Result.PNG" width="320" /></a></div>
<br />
You can download the Project here: <a href="http://apparat.codeplex.com/SourceControl/changeset/changes/a13132898eaa">http://apparat.codeplex.com/SourceControl/changeset/changes/a13132898eaa</a><br />
<br />Sönkehttp://www.blogger.com/profile/12791098108918984124noreply@blogger.com0