HoloLens Terrain Generation Demo Part 5 – Animated Terrain Generation

Two weeks with no posts. Not good. For the record, I blame Civilization 6.
Today’s post will be a short one. All I’m looking to accomplish is animating the terrain generation we added last time. This is reasonably straight forward since the algorithm we’re using is iterative. All we need to do is update the height map each frame. We even already have an Update() method for our terrain.

I added a member variable to the Terrain class so we can track how many ticks we’ve already run of our generator, since we only want it to run a limited number of times.

if (m_iIter < 200) {
		IterateFaultFormation(5, 0.002f);
		FIRFilter(0.1f);
	} 
	m_iIter = m_iIter < 200 ? m_iIter + 1 : m_iIter;

But we still need to update the height map texture on the GPU. When we created the texture, we already made sure we had set the resource flags to ensure we can write to it.

	D3D11_TEXTURE2D_DESC descTex = { 0 };
	descTex.MipLevels = 1;
	descTex.ArraySize = 1;
	descTex.Width = m_wHeightmap * m_resHeightmap + 1;
	descTex.Height = m_hHeightmap * m_resHeightmap + 1;
	descTex.Format = DXGI_FORMAT_R32_FLOAT;
	descTex.SampleDesc.Count = 1;
	descTex.SampleDesc.Quality = 0;
	descTex.Usage = D3D11_USAGE_DYNAMIC;
	descTex.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	descTex.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;

Now we need to add code to actually upload the new data. There are a few ways of doing this. For one, we could have used a staging resource to upload to, then copied from there to a default resource on the GPU. I’ve chosen to just create the texture as a CPU-writable dynamic resource that we can directly map to. This probably wouldn’t be reasonable for a mip-mapped texture or a 3D texture, but this is a pretty simple case.
The one complication is that the GPU might not necessarily be storing the data in the same format we have it in on the CPU. It’s entirely possible that each row of the 2D texture might have some padding on it. We need to take this into account when we upload the data.

	if (m_iIter < 200) {
		IterateFaultFormation(5, 0.002f);
		FIRFilter(0.1f);

		D3D11_MAPPED_SUBRESOURCE mappedTex = { 0 };
		DX::ThrowIfFailed(context->Map(m_hmTexture.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedTex));
		unsigned int rowSpan = (m_wHeightmap * m_resHeightmap + 1) * sizeof(float);
		BYTE* mappedData = reinterpret_cast<BYTE*>(mappedTex.pData);
		BYTE* buffer = reinterpret_cast<BYTE*>(m_heightmap);
		for (unsigned int i = 0; i < (m_hHeightmap * m_resHeightmap + 1); ++i) {
			memcpy(mappedData, buffer, rowSpan);
			mappedData += mappedTex.RowPitch;
			buffer += rowSpan;
		}

		context->Unmap(m_hmTexture.Get(), 0);
	} 
	m_iIter = m_iIter < 200 ? m_iIter + 1 : m_iIter;

As it happens, my GPU, or at least the emulator for the HoloLens, does actually store the 2D texture data exactly as the CPU does. I could have just used the following code:

	if (m_iIter < 200) {
		IterateFaultFormation(5, 0.002f);
		FIRFilter(0.1f);

		D3D11_MAPPED_SUBRESOURCE mappedTex = { 0 };
		DX::ThrowIfFailed(context->Map(m_hmTexture.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedTex));
		memcpy(mappedTex.pData, m_heightmap, mappedTex.DepthPitch);
		context->Unmap(m_hmTexture.Get(), 0);
	} 
	m_iIter = m_iIter < 200 ? m_iIter + 1 : m_iIter;

That’s obviously a lot simpler, but I stuck with the first version since I know it is more generic and I can’t be sure the actual HoloLens works the same as the emulator.

The only other thing I wanted to be able to do at the moment with the animation is reset it so that it generates a new terrain. The original sample code included code that recentered the hologram to in front of you on click. That code is in the Update() method of the main cpp file. I co-opted that code and changed it to call a reset function in the Terrain class that just resets the height values and the iterator variable back to 0.

    // Check for new input state since the last frame.
    SpatialInteractionSourceState^ pointerState = m_spatialInputHandler->CheckForInput();
    if (pointerState != nullptr) {
        // When a Pressed gesture is detected, the sample hologram will be repositioned
        // two meters in front of the user.
		// Once we've figured out how to attach the terrain to a surface, we won't want to do this.
  //      m_terrain->PositionHologram(pointerState->TryGetPointerPose(currentCoordinateSystem));
		m_terrain->ResetHeightMap();
    }
void Terrain::ResetHeightMap() {
	unsigned int h = m_hHeightmap * m_resHeightmap + 1;
	unsigned int w = m_wHeightmap * m_resHeightmap + 1;

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

	m_iIter = 0;
}

That’s all there is to getting the animation I wanted. Getting video footage was a little tricky to figure out. I used the Mixed Reality Capture (MCR) feature of the HoloLens. It turns out it works even in the emulator, but the controls feel kind of awkward so the video has some dead air at the beginning and end.

I’m hoping I can get back on track with this project from now on and get my next post out on schedule. What I need to do next is modify my terrain generation to take into account the fact that we want this terrain to be attached to a flat surface in the real world. Right now, it works fine as a free-floating hologram, but we need the edges to remain at zero so it is attached to the surface. Also, we can’t have negative height values as that would mean the hologram goes inside the real world object and wouldn’t be visible.
We’ll fix that next time.

For the latest version of the code, see GitHub.

Traagen