Heightmap, Lightmap and Textures

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By baq99

Hi everyone,

I have made a terrain from a heightmap and applied a single texture to the entire height and width of the terrain on a 1 to 1 scale. This is a cheap substitute for lighting but looks terrible close up. Is there a way to also tile textures based on the slope of geometry and blend them with the scaled up lightmap?

Here’s the code I used from an online tutorial

tool
extends Spatial

var width;
var height;

var heightData = {}

var vertices = PoolVector3Array()
var UVs = PoolVector2Array()
var normals = PoolVector3Array()

var tmpMesh = Mesh.new()

#paths
var p_heightmap = "res://Textures/Heightmaps/heightmap.png"
var p_material = "res://Materials/terrain_material.tres"

func _ready():
    var heightmap = load(p_heightmap).get_data()
    width = heightmap.get_width()
    height = heightmap.get_height()

    # parse image file
    heightmap.lock()
    for x in range(0,width):
	    for y in range(0,height):
		    heightData[Vector2(x,y)] = heightmap.get_pixel(x,y).r*10
    heightmap.unlock()

    # generate terrain
    for x in range(0,width-1):
	    for y in range(0,height-1):
		    createQuad(x,y)

    var st = SurfaceTool.new()
    st.begin(Mesh.PRIMITIVE_TRIANGLES)
    st.set_material(load(p_material))

    for v in vertices.size():
	    st.add_color(Color(1,1,1))
	    st.add_uv(UVs[v])
	    st.add_normal(normals[v])
	    st.add_vertex(vertices[v])

    st.commit(tmpMesh)

    $MeshInstance.mesh = tmpMesh
    var shape = ConcavePolygonShape.new()
    shape.set_faces(tmpMesh.get_faces())
    #$MeshInstance/StaticBody/CollisionShape.shape = shape
    $MeshInstance.create_trimesh_collision()

func createQuad(x,y):
    var vert1 # vertex positions (Vector2)
    var vert2
    var vert3

    var side1 # sides of each triangle (Vector3)
    var side2

    var normal # normal for each triangle (Vector3)

    # triangle 1
    vert1 = Vector3(x,heightData[Vector2(x,y)],-y)
    vert2 = Vector3(x,heightData[Vector2(x,y+1)],-y-1)
    vert3 = Vector3(x+1,heightData[Vector2(x+1,y+1)],-y-1)
    vertices.push_back(vert1)
    vertices.push_back(vert2)
    vertices.push_back(vert3)

    UVs.push_back(Vector2(vert1.x/width, -vert1.z/height))
    UVs.push_back(Vector2(vert2.x/width, -vert2.z/height))
    UVs.push_back(Vector2(vert3.x/width, -vert3.z/height))

    side1 = vert2-vert1
    side2 = vert2-vert3
    normal = side1.cross(side2)

    for _i in range(0,3):
	    normals.push_back(normal)

    # triangle 2
    vert1 = Vector3(x,heightData[Vector2(x,y)],-y)
    vert2 = Vector3(x+1,heightData[Vector2(x+1,y+1)],-y-1)
    vert3 = Vector3(x+1,heightData[Vector2(x+1,y)],-y)
    vertices.push_back(vert1)
    vertices.push_back(vert2)
    vertices.push_back(vert3)

    UVs.push_back(Vector2(vert1.x/width, -vert1.z/height))
    UVs.push_back(Vector2(vert2.x/width, -vert2.z/height))
    UVs.push_back(Vector2(vert3.x/width, -vert3.z/height))

    side1 = vert2-vert1
    side2 = vert2-vert3
    normal = side1.cross(side2)

    for _i in range(0,3):
	    normals.push_back(normal)

My question is: how I would add a second material containing “grass” and “cliff” textures, that’s not scaled to width and height and then adjust that to apply texture based on slope the slope of the triangle in the mesh?

Thanks in advance.

I’ve seen other questions that ask

“can I apply two or more materials to the same mesh?”

answered with

“no you can’t, but you can use multiple textures in one material”.

I’m also aware that you can find the slope of the tris in the mesh and apply textures based off this value, however I don’t know if you can apply two textures at different scales in the same material.

On top of all of that, I have transparency in the lightmap texture which I wouldn’t want to be covered by tiling ground textures.

baq99 | 2021-03-25 18:41

:bust_in_silhouette: Reply From: archeron

You can do pretty much anything you can imagine with materials if you write your own shader for the material. Out of the box, Godot doesn’t provide what you need, though.

Basically, what you’d do in your custom shader is write a vertex shader that calculates the slope of the triangle based on the 3 triangle coordinates, and passes that slope to the fragment shader which can use it to select the correct texture out of a number of textures you pass into the shader as uniforms.

Scaling the textures involved is perfectly possible - your fragment shader decides which texel to read for each fragment, so you can just define any scale that you like and use it to generate texel coordinates in the shader.

It’s not quite so simple, though, because usually you can’t just switch textures based on slope. This will also look horrible because you will have very visible texture seams between the triangles which use the grass textures and the ones which use the cliff textures. So you’ll have to to use a weighting solution that gives you, say, 100% grass and 0% cliff at very flat slopes, and vice versa on very steep slopes, and will mix textures around the tipping point between cliff and grass.

So: Start learning about how to write custom vertex and fragment shaders. There are a few nice Youtube Tutorial videos for 2D and 3D that will get you started.

Thank you very much for your reply.

Weirdly I do want a blunt change between the cliff and grass textures as I’m remaking a game from 2000 for Quest VR that did exactly this. Working on the quest also means I’ve got to be very careful how much work I make the shaders do as the game I’m remaking had very long render distances so I’m heavily using LOD and object culling. I still want to leave some compute time to run some antialiasing on top (jaggies in VR aren’t fun). I’ve also got to be very careful with what I ask the shader to do as I’m limited by the hardware. The Terrain asset by Zylann on the asset store isn’t compatible with GLEIS 2.0 and crashes the hardware on Quest using GLEIS 3 with any of the rendering options which is why I had to use the geometry solution above rather than a shader solution for the heightmap terrain.

I’ll take a look at some 3D tutorials on vertex and fragment shaders. Currently the shader code is still a little beyond my current skill level but I’m willing to give anything a crack. Do you have any specific recommendations I should be looking at?

baq99 | 2021-03-25 22:23

I started out with a 2D/3D tutorial done by Nathan from GDQuest and Bastiaan Olij: Here’s the Youtube playlist: https://www.youtube.com/playlist?list=PLhqJJNjsQ7KHqNMYmTwtsYTeTrqrRP_fP

archeron | 2021-03-25 23:56

Thank you! Muchly appreciated!

baq99 | 2021-03-26 00:35