Jump to content

Finalizing Terrain


Josh

2,677 views

 Share

I'm wrapping up the terrain features now for the initial release. Here's a summary of terrain in Ultra Engine.

Terrains are an entity, just like anything else, and can be positioned, rotated, or scaled. Non-square terrains are supported, so you can create something with a 1024x512 or whatever resolution. (Power-of-two sizes are required.)

Editing Terrain

Ultra Engine includes an API that lets you modify terrain in real-time. I took something very complicated and distilled it down to a very simple API for you:

terrain->SetElevation(x, y, height);

That's it! The engine will automatically update normals, physics, raycasting, rendering, and other data for you behind the scenes any time you modify the terrain, in a manner that maximizes efficiency.

Materials

Leadwerks supports 32 terrain texture layers. Unity supports eight. Thanks to the flexibility of shaders in Vulkan, any terrain in Ultra can have up to a whopping 256 different materials applied to it, and it will always be rendered in a single pass at maximum speed.

To apply a material at any point on the terrain, you just call this method. The weight value lets you control how much influence the material as at that point, i.e. its alpha transparency:

terrain->SetMaterial(x, y, material, weight)

This example shows how to paint materials onto the terrain.

terrain_setmaterial.jpg

I'm quite happy with how the documentation system has turned out, and I feel it is representative of the quality I want your entire user experience to be.

Untitled.thumb.jpg.52f6008e39abcff77529a49b05be0cb3.jpg

All the API examples load media from the web, so there's no need to manually download any extra files. Copy and paste the code into any project, and it just works.

//Create base material
auto diffusemap = LoadTexture("https://raw.githubusercontent.com/Leadwerks/Documentation/master/Assets/Materials/Ground/river_small_rocks_diff_4k.dds");
auto normalmap = LoadTexture("https://raw.githubusercontent.com/Leadwerks/Documentation/master/Assets/Materials/Ground/river_small_rocks_nor_gl_4k.dds");
auto ground = CreateMaterial();
ground->SetTexture(diffusemap, TEXTURE_DIFFUSE);
ground->SetTexture(normalmap, TEXTURE_NORMAL);

Tessellation and Displacement

Tessellation works great with terrain, which is modeled with quads. There are a lot of high-quality free PBR materials that are great for VR available on the web, so you will have no shortage of interesting materials to paint your terrains with. Here are a few sites for you to peruse:

https://matlib.gpuopen.com/main/materials/all
https://icons8.com/l/3d-textures
https://ambientcg.com/
https://polyhaven.com/textures
https://freepbr.com/
https://www.cgbookcase.com/textures

Untitled.jpg.fc8712ac76404feb00502c2115de3b3b.thumb.jpg.ac9792ddd3ef5eb77c2b75d9e2f30bac.jpg

Cutting Holes

You can cut holes in the terrain by hiding any tile. This is useful for making caves, and can be used in the future for blending voxel geometry into a heightmap terrain.

Untitled.thumb.jpg.d1df97c009882ed961242c1b20e0aa49.jpg

Shader Design

Shaders in Ultra are big and quite complicated. I don't expect anyone to make 100% custom shaders like you could with the simpler shaders in Leadwerks. I've structured shaders so that the entry point shader defines a user function, then includes the base file, which calls the function:

#version 450
#extension GL_GOOGLE_include_directive : enable
#extension GL_ARB_separate_shader_objects : enable

#define LIGHTING_PBR
#define USERFUNCTION

#include "../Base/Materials.glsl"
#include "../Base/TextureArrays.glsl"

void UserFragment(inout vec4 color, inout vec3 emission, inout vec3 normal, inout float metalness, inout float roughness, inout float ambientocclusion, in vec3 position, in vec2 texcoords, in vec3 tangent, in vec3 bitangent, in Material material)
{
	// Custom code goes here...
}

#include "../Base/base_frag.glsl"

This organizes shaders so custom behavior can be added on top of the lighting and other systems, and paves the way for a future node-based shader designer.

Future Development

Ideas for future development to add to this system include voxel-based terrains for caves and overhangs, roads (using the decals system), large streaming terrains, and seamless blending of models into the terrain surface.

  • Like 5
 Share

12 Comments


Recommended Comments

3 hours ago, Thirsty Panther said:

Ooooh caves!

You could also take another terrain and turn it upside down to make the ceiling of a cave level.

  • Like 2
Link to comment

I think these coordinates are based on the terrain resolution. The real size and height later displayed on screen is based on the used scale factors u set with setscale. I would assume with a scale of 1 it could be interpreted as 1 m but this depends what you define in your app as 1m. 

  • Like 1
Link to comment
7 hours ago, Thirsty Panther said:

terrain->SetElevation(x, y, height);

Are the value of these in metres.? ie the distance from 1,0 to 2,0 is 1m.

By default, a terrain's size is (resolution.x, 1.0, resolution.y), so one terrain point occurs every meter, and the maximum height is 1.0. You can scale the terrain however you want to adjust the maximum height. So this would give you a terrain that is 512x512 meters, with a max height of 100 meters:

auto terrain = CreateTerrain(world, 512, 512);
terrain->SetScale(1,100,1);

When you call SetElevation(), the vertical scale of the terrain is taken into account, so the height you set is in meters. If you call SetHeight() instead, then the range is between 0.0 and 1.0.

I tried some other designs, but this is the only one I could consistently remember when working with it, so I think it is the most intuitive.

  • Like 1
Link to comment

I think the shader families comes close to that what unity does, but slightly different. Under the hood, there are different shaders compiled and linked for each configuration ( i guess only when needed of course) . In unity, you define this more or less directly in the shader and not in a separate file format. It is more like CG and HLSL style of shaders. 

I like the separation in UltraEngine, so shaders are shaders and nothing more. And with SPIRV under the hood you have a lot of more control flow in the shaders than in direct GLSL, just to name the #include directive as one example. 

Link to comment

One big change in Ultra is I use a lot of branching logic (if statements). I've seen absolutely no downsides to this on modern GPUs, and it lowers the total number of shaders used, which in turn means most of the scene is drawn in one or two super batches.

Link to comment

If the branching is based on uniform data the performance impact is nearly zero. ( depends on gpu and driver) other branching has still a big impact on performance. Normally the shader will execute every possible branch for each processing unit ( vertex, fragment, etc.) and only keep the latest valid result. As the first this can be optimized by the gpu itself or the driver but can vary from gpu to gpu and vendor to vendor.

Link to comment
6 hours ago, klepto2 said:

Normally the shader will execute every possible branch for each processing unit ( vertex, fragment, etc.) and only keep the latest valid result.

I don't think that is true on today's hardware.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...