HoloLens Terrain Generation Demo Part 2 – A Simple Terrain Mesh

Now that we have a basic working application and some sample code to follow, we need to start adding in our own code to support our terrain hologram. I started by copying the SpinningCubeRenderer code to my Terrain class. I modified the Terrain’s position variable so that it would render another cube offset from the first, then deleted the code that makes it spin since we don’t want our Terrain to do that. I then added our Terrain to the main class and called it everywhere the sample cube code was called.

Initialization of the object happens in SetHolographicSpace(). The objects in this application follow RAII principles and are wrapped in std::unique_ptrs so it’s just a one liner:

m_terrain = std::make_unique<Terrain>(m_deviceResources, 1.0f, 1.0f, 4);

The update function has two calls. One call to perform any updates for the object itself, and one to set the focus point for the camera.

m_timer.Tick([&] () {
     //
     // TODO: Update scene objects.
     //
     // Put time-based updates here. By default this code will run once per frame,
     // but if you change the StepTimer to use a fixed time step this code will
     // run as many times as needed to get to the current step.
     //
	m_terrain->Update(m_timer);
});

// We complete the frame update by using information about our content positioning
// to set the focus point.
for (auto cameraPose : prediction->CameraPoses) {
    // The HolographicCameraRenderingParameters class provides access to set
    // the image stabilization parameters.
    HolographicCameraRenderingParameters^ renderingParameters = holographicFrame->GetRenderingParameters(cameraPose);

    // SetFocusPoint informs the system about a specific point in your scene to
    // prioritize for image stabilization. The focus point is set independently
    // for each holographic camera.
    // You should set the focus point near the content that the user is looking at.
    // In this example, we put the focus point at the center of the sample hologram,
    // since that is the only hologram available for the user to focus on.
    // You can also set the relative velocity and facing of that content; the sample
    // hologram is at a fixed point so we only need to indicate its position.
	renderingParameters->SetFocusPoint(currentCoordinateSystem, m_terrain->GetPosition());
 }

The render call of course happens on Render. You also need to release the object’s DirectX resources if the device is lost for some reason, and reload them once the device is restored.
The result of all of this is what you’d expect:

The cube on the left is the original sample. The right is our new cube.

The cube on the left is the original sample. The right is our new cube.


From here, I removed the sample code from the main class. I left the code for the SpatialInputHandler class in but commented out. We don’t need it right now, but we may need it in the future if I want to add some sort of interface for changing variables for the terrain generator.
The next step is to change the cube into the mesh that will represent our terrain. We’ll need to know how big our terrain will be and at what resolution we want to generate it. In the project introduction, I talked about defining the size of the terrain based on the size of the surface in the scene and the resolution as the number of vertices per inch. As it turns out, the HoloLens uses a scale of 1 = 1m, so most of the surfaces we’ll be working with will likely be measured in centimeters. The resolution will therefore be the number of vertices per centimeter. Thus, our initial height map is defined like this:

void Terrain::InitializeHeightmap() {
	unsigned int h = m_hHeightmap * m_resHeightmap + 1;
	unsigned int w = m_wHeightmap * m_resHeightmap + 1;
	m_heightmap = new float*[h];

	for (auto i = 0u; i < h; ++i) {
		m_heightmap[i] = new float[w];

		for (auto j = 0u; j < w; ++j) {
			m_heightmap[i][j] = 0.0f;
		}
	}
}

We’ll be iteratively modifying the height map later, but for now a flat terrain is fine for defining the mesh.
The mesh itself is pretty straight forward. We want a vertex for each pixel of our height map. The original sample code used std::arrays for the vertex and index arrays, but since we need to define our array sizes dynamically, that doesn’t really work. We’ll use std::vectors instead.
Rather than getting fancy and using a triangle strip like we’ve done previously, I just stuck with a simple triangle list for this project. Our mesh isn’t likely to be that big anyway. Generating a triangle list is easy since you just have to specify two triangles, six indices, per quad.
gridwarrows
So the new code for generating the mesh is reasonably compact.

task<void> createTerrainTask = shaderTaskGroup.then([this]() {
		// Load mesh vertices. Each vertex has a position and a color.
		auto h = m_hHeightmap * m_resHeightmap + 1;
		auto w = m_wHeightmap * m_resHeightmap + 1;
		float d = 100.0f * m_resHeightmap;
		std::vector<VertexPositionColor> terrainVertices;
		for (auto i = 0u; i < h; ++i) {
			float y = (float)i / d;
			for (auto j = 0u; j < w; ++j) {
				float x = (float)j / d;
				terrainVertices.push_back({XMFLOAT3(x, m_heightmap[i][j], y), XMFLOAT3(x, y, 0.0f)});
			}
		}

		D3D11_SUBRESOURCE_DATA vertexBufferData = { 0 };
		vertexBufferData.pSysMem = terrainVertices.data();
		vertexBufferData.SysMemPitch = 0;
		vertexBufferData.SysMemSlicePitch = 0;
		const CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(VertexPositionColor) * terrainVertices.size(), D3D11_BIND_VERTEX_BUFFER);
		DX::ThrowIfFailed(m_deviceResources->GetD3DDevice()->CreateBuffer(&vertexBufferDesc, &vertexBufferData, &m_vertexBuffer));

		// Load mesh indices. Each trio of indices represents
		// a triangle to be rendered on the screen.
		std::vector<unsigned short> terrainIndices;
		for (auto i = 0u; i < h - 1; ++i) {
			for (auto j = 0u; j < w - 1; ++j) {
				auto index = j + (i * h);

				terrainIndices.push_back(index);
				terrainIndices.push_back(index + h + 1);
				terrainIndices.push_back(index + h);

				terrainIndices.push_back(index);
				terrainIndices.push_back(index + 1);
				terrainIndices.push_back(index + h + 1);
			}
		}

		m_indexCount = terrainIndices.size();

		D3D11_SUBRESOURCE_DATA indexBufferData = { 0 };
		indexBufferData.pSysMem = terrainIndices.data();
		indexBufferData.SysMemPitch = 0;
		indexBufferData.SysMemSlicePitch = 0;
		CD3D11_BUFFER_DESC indexBufferDesc(sizeof(unsigned short) * m_indexCount, D3D11_BIND_INDEX_BUFFER);
		DX::ThrowIfFailed(m_deviceResources->GetD3DDevice()->CreateBuffer(&indexBufferDesc, &indexBufferData, &m_indexBuffer));
	});

I set the new terrain mesh to be located just in front of and below the eye. The screenshot is pretty boring but shows that the new mesh at least works.
meshinposition
My next goal is to actually load the height map into a texture with some data in it and then load that data in the shaders and displace our mesh so it’s no longer flat. We’ll also add some basic shading and get rid of the rainbow colour scheme so we can actually make out details in the terrain.

The most recent version of the code is available on GitHub.

Traagen