Replicating shader texture function

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

Hi,

The biomes for my landscape are chosen by a shader depending on a value from a noise texture.

I want to be able to determine the biome of a point on my landscape outside of my shader as well, so I can place things like trees in the correct biomes.

My shader code is as follows:

shader_type spatial;

uniform sampler2D noise;
uniform sampler2D subnoise;
uniform sampler2D grass;
uniform sampler2D grassAlt;
uniform sampler2D dirt;
uniform float noiseZoom = 1000;
uniform float grassZoom = 10;


const float epsilon   = 1e-4;

varying vec3  world_pos;
varying vec3  normal;



void vertex(){
	// In vertex this position is local i think (not affected by camera)
	world_pos = VERTEX;
	normal = abs(NORMAL);
	normal /= normal.x + normal.y + normal.z;
}

float inverseLerp(float a, float b, float value){
	// Min, max, value
	float val = (value - a) / (b - a);
	return clamp(val, 0, 1);
}




void fragment() {
	vec3 grCol  = vec3(0.35, 0.63, 0.51);
	vec3 gr2Col = vec3(0, 0.5, 0.5);
	vec3 drCol  = vec3(0.6, 0.46, 0.32);
	

	
	
	float noiseValue = texture(noise, UV / noiseZoom).x;
	float noisePercent = inverseLerp(0.0, 1.0, noiseValue);
	
	
	float grassNoiseValue = texture(subnoise, UV / grassZoom).x;
	float grassPercent = inverseLerp(0.0, 1.0, grassNoiseValue);
	
	
	
	float g1 = clamp(sign(grassPercent - 0.0), 0.0, 1.0);
	float g2 = clamp(sign(grassPercent - 0.5), 0.0, 1.0);
	
	float grStrength = clamp(sign(noisePercent - 0.0), 0.0, 1.0);
	float drStrength = clamp(sign(noisePercent - 0.5), 0.0, 1.0);
	
	
	vec3 grassCol = (g1 * grCol) + (g2 * gr2Col);
	
	ALBEDO = vec3(0, 0, 0);	
	ALBEDO = ((1.0 - grStrength) * ALBEDO) + (grStrength * grassCol);
	ALBEDO = ((1.0 - drStrength) * ALBEDO) + (drStrength * drCol);

}

I have another function that is given an array of points that are on my mesh, it is as follows:

func interp(minimum, maximum, value):
	var inVal = minimum + (maximum - minimum) * value
	return clamp(minimum, maximum, inVal)

func generateTrees(gridPoints, biomeNoise, noiseZoom, minx, minz):
	var biomeImage = biomeNoise.get_data()
	var noise = biomeNoise.noise
	
	var index = randi() % gridPoints.size()
	for i in numberTrees:
		var positionGood = false
		var position
		
		while positionGood == false:
			# Pick a point from grid, if not used it is ok
			index = randi() % gridPoints.size()
			position = gridPoints[index]
			

			var tempX = position.x / noiseZoom
			var tempZ = position.z / noiseZoom
			
			var noiseVal = interp(0, 1, noise.get_noise_2d(tempX, tempZ))
		
		
			print("NoiseVal: ", noiseVal)
			var drStrength = clamp(sign(noiseVal - 0.5), 0.0, 1.0);
			print("drs: ", drStrength)
			
			
			if !treePositions.has(position) && drStrength < 0.5:
				positionGood = true
			else:
				fails += 1
				
			if fails >= 5000:
				setMultiMesh()
				makeColliders()
				return gridPoints
		# We have a position
		treePositions.push_back(position)
		position.y -= 1.5
		var type = randi() % treeMMs.size()
		var tree = treeObject.new()
		tree.init(type, position)
		treeList.push_back(tree)
		gridPoints.remove(index)
		
	
	setMultiMesh()
	makeColliders()
	return gridPoints

Both my function and my shader are given the same noiseTexture and noiseZoom factors - but the noise value seems to be different in both instances.

I want to determine the dirtStrength outside of my shader and only draw trees if it is below a certain value (so only draw trees if it is not dirt)

Any ideas on how I can replicate the results of my shader in this function?

Well, I’m still stuck on this.

So my shader uses UV to sample a position from my noiseTexture.
These values are in the range (0,0) → (1,1).
Then these values are divided my by noiseZoom to essentially zoom in on the noise.

So the values texture uses are very very small, but it is still able to sample noise.

In my function outside of my shader, the grid points are just some vertices on my landscape mesh. These are generally in the range of like -210 → 210 as the center of my landscape is (0, 0).

I have tried to replicate the texture function outside of my shader by normalizing the coords so that they are in a range of (0, 0) → (1, 1) like the UV coords.

But I cannot sample from an image using get_pixel with floats. The coords are too small without even dividing by noiseZoom.

So I’ve been scaling these by the width and height of my noiseTexture (512 x 512) to get values that can be sampled from the texture image.

But the values that I sample from my image do not match up with the values sampled by my shader.

Even if I remove noiseZoom entirely, in my function outside of my shader, sampling from the image, or using get_noise_2d, does not result in values that match what my shader produce.

So I’m not sure how I should go about adding biomes to my terrain.

Thummper | 2020-08-15 13:56

:bust_in_silhouette: Reply From: Thummper

So I finally figured this out.

It turns out the code that generates my actual terrain meshes was calculating the UVs incorrectly.

Once they were fixed, this code started to determine the biome correctly.

func generateTrees(gridPoints, noiseTexture, topLeftX, topLeftZ):

	var noiseImage  = noiseTexture.get_data()
	var noiseWidth  = noiseImage.get_width()
	var noiseHeight = noiseImage.get_height()
	
	

	var index = randi() % gridPoints.size()
	for i in numberTrees:
		var positionGood = false
		var position
		
		while positionGood == false:
			# Pick a point from grid, if not used it is ok
			index = randi() % gridPoints.size()
			position = gridPoints[index]
			
			var UV = Vector2( (position.x + topLeftX) / (topLeftX * 2), (position.z + topLeftZ) / (topLeftZ * 2))
			var noiseCoords = Vector2( UV.x * noiseWidth, UV.y * noiseHeight )

			
			noiseImage.lock()
			var noiseValue = noiseImage.get_pixel( noiseCoords.x, noiseCoords.y).r
			noiseImage.unlock()
	
			var grStrength = clamp(sign(noiseValue - 0.45), 0.0, 1.0);
			
			if !treePositions.has(position) && (grStrength == 1):
				positionGood = true
			else:
				fails += 1
			if fails >= 5000:
				setMultiMesh()
				makeColliders()
				return gridPoints
		# We have a position
		treePositions.push_back(position)
		position.y -= 1.5
		var type = randi() % treeMMs.size()
		var tree = treeObject.new()
		tree.init(type, position)
		treeList.push_back(tree)
		gridPoints.remove(index)
	setMultiMesh()
	makeColliders()
	return gridPoints