This little projet is about rendering animated grass. And because I'd like to learn something new, for this projet, I will use Direct3D the simplest way, using C# and native wrapper that is SlimDX, time to get back to Visual Studio environnment.
Let's face it, although I praise the speed of native language like C++, for a hobbist programmer, C# nad Java offer a much higher productivity due to the well documented and feature-rich framework, ease of debugging, and of course garbage collection.
Camera camera; publicvoid init() { form =new RenderForm("My HelloWorld SlimDX with Matrix Model View app"); var description =new SwapChainDescription() { BufferCount =1, Usage = Usage.RenderTargetOutput, OutputHandle = form.Handle, IsWindowed =true, ModeDescription =new ModeDescription(0, 0, new Rational(60, 1), Format.R8G8B8A8_UNorm), SampleDescription =new SampleDescription(1, 0), Flags = SwapChainFlags.AllowModeSwitch, SwapEffect = SwapEffect.Discard };
Device.CreateWithSwapChain(DriverType.Hardware, DeviceCreationFlags.None, description, out device, out swapChain);
// create a view of our render target, which is the backbuffer of the swap chain we just created
using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0)) renderTarget =new RenderTargetView(device, resource);
// setting a viewport is required if you want to actually see anything context = device.ImmediateContext; var viewport =new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
// prevent DXGI handling of alt+enter, which doesn't work properly with Winforms using (var factory = swapChain.GetParent<Factory>()) factory.SetWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAltEnter);
publicvoid render() { // configure the Input Assembler portion of the pipeline with the vertex data context.InputAssembler.InputLayout= layout; context.InputAssembler.PrimitiveTopology= PrimitiveTopology.TriangleList;
// set the shaders context.VertexShader.Set(vertexShader); context.PixelShader.Set(pixelShader);
Then, every frame, copy the transformation matrix to the buffer.
DataBox input = device.ImmediateContext.MapSubresource(inputBuffer, MapMode.WriteDiscard, SlimDX.Direct3D11.MapFlags.None); for(int i =0; i <16; i++) input.Data.Write((float)finalMatrix.ToArray()[i]); input.Data.Write(costime); device.ImmediateContext.UnmapSubresource(inputBuffer, 0);
First approach
When trying to mimic real world elements, I start by observing what is a grass patch in real life : it's made of randomly spawned blade of individual leaf from different height and that are moving together due the wind condition.
To start my algorithm : * each grass blade is composed of quads * each blade is moving back and forth with a growing amplitude from the base (no movement) to the top (maximum movement) * grass blade are spawned on the ground in a uniform but random fashion
To make blades move gently back we have the cosinus function, that goes from -1 to 1, with current time as argument, it will oscillate smoothly. In the shader, to determine whether the position of the vertex on the grass blade and move it accordingly, we use texture coordinates where the y will be the bottom vertex and 1.0 the top vertex.
Here is the result: 65536 grass blades are moved in a smooth fashion.
Way of improvements * A solid ground should be more convincing ! * Blades are strictly straights and verticals, in real life, blades don't grow straight, generating blade with a little random bend would be bette. * Blades are only facing one direction so when approaching or looking from another direction one can see that it's fake. I can billboard them but that will still be an obivous fake, generating blades in every direction will add much more complexity and will stress the hardware too much. In NVIDIA's paper, blade are not managed individually but as an object that faces all direction. * Blades are moving together in the same way, in real life, grass will move depending of wind conditions that are not the same in every area. * Complexity is the same everywhere, for optimisation purpose, the level of detail should be adjusted depending on viewing distance.
24/09/2012 Added generated heightmap, mouse-look. Not bad at all !
15/12/2012 Let's add a skydome to this landscape. Why skydome instead of skybox ? because skydome show less artifact and are easier to move around to appear as moving clouds.
Drawing a sphere is easy if you remember your math courses about spherical coordinates :
$x = r * \sin(\theta) * \cos(\phi)$ $y = r * \sin(\theta) * \sin(\phi)$ $z = r * \cos(\theta)$
Iterate for $\phi$ from $0$ to $2\pi$ and $\theta$ from $0$ to $\frac{\pi}{2}$ with and here is the sphere !