Add terrain shader and configuration files; update World class for shader integration

This commit is contained in:
ImBenji
2025-10-18 19:32:00 +01:00
parent f7de1c67af
commit 9e3cfb8d14
4 changed files with 2395 additions and 494 deletions

View File

@@ -1,12 +1,5 @@
using Godot;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Godot.Collections;
using Environment = Godot.Environment;
public enum DebugType
{
@@ -20,52 +13,21 @@ public enum DebugType
[Tool]
public partial class World : Node2D
{
private int _chunksX = 32;
[Export]
public int ChunkXCount
{
get => _chunksX;
set
{
_chunksX = value;
LoadTiles();
}
}
public Vector2I ChunksPerAxis
{
get
{
if (HeightMapImage == null)
{
return new Vector2I(_chunksX, _chunksX);
}
Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight());
int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX));
return new Vector2I(_chunksX, chunksY);
}
}
private int _tileSize = 96;
[Export]
public int TileSize
{
get => _tileSize;
set
{
_tileSize = value;
LoadTiles();
}
}
[Export]
public Texture2D HeightMapTexture;
private float _rockThreshold = 0.05f;
[Export]
public int MapWidth
{
get => _mapWidth;
set
{
_mapWidth = value;
SetupShader();
}
}
private int _mapWidth = 3072;
[Export]
public float RockThreshold
{
@@ -73,11 +35,11 @@ public partial class World : Node2D
set
{
_rockThreshold = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _snowHeightThreshold = 0.8f;
private float _rockThreshold = 0.05f;
[Export]
public float SnowHeightThreshold
{
@@ -85,11 +47,11 @@ public partial class World : Node2D
set
{
_snowHeightThreshold = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _waterThreshold = 0.0f;
private float _snowHeightThreshold = 0.8f;
[Export]
public float WaterThreshold
{
@@ -97,11 +59,11 @@ public partial class World : Node2D
set
{
_waterThreshold = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _desertTemperatureThreshold = 0.7f;
private float _waterThreshold = 0.0f;
[Export]
public float DesertTemperatureThreshold
{
@@ -109,11 +71,11 @@ public partial class World : Node2D
set
{
_desertTemperatureThreshold = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _desertMoistureThreshold = 0.3f;
private float _desertTemperatureThreshold = 0.7f;
[Export]
public float DesertMoistureThreshold
{
@@ -121,11 +83,11 @@ public partial class World : Node2D
set
{
_desertMoistureThreshold = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _temperatureNoiseFrequency = 0.01f;
private float _desertMoistureThreshold = 0.3f;
[Export]
public float TemperatureNoiseFrequency
{
@@ -133,11 +95,11 @@ public partial class World : Node2D
set
{
_temperatureNoiseFrequency = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _moistureNoiseFrequency = 0.01f;
private float _temperatureNoiseFrequency = 0.01f;
[Export]
public float MoistureNoiseFrequency
{
@@ -145,35 +107,35 @@ public partial class World : Node2D
set
{
_moistureNoiseFrequency = value;
LoadTiles();
UpdateShaderParameters();
}
}
private float _TemperatureNoiseAmplitude = 0.1f;
private float _moistureNoiseFrequency = 0.01f;
[Export]
public float TemperatureNoiseAmplitude
{
get => _TemperatureNoiseAmplitude;
get => _temperatureNoiseAmplitude;
set
{
_TemperatureNoiseAmplitude = value;
LoadTiles();
_temperatureNoiseAmplitude = value;
UpdateShaderParameters();
}
}
private float _MoistureNoiseAmplitude = 0.1f;
private float _temperatureNoiseAmplitude = 0.1f;
[Export]
public float MoistureNoiseAmplitude
public float MoistureNoiseAmplitude
{
get => _MoistureNoiseAmplitude;
get => _moistureNoiseAmplitude;
set
{
_MoistureNoiseAmplitude = value;
LoadTiles();
_moistureNoiseAmplitude = value;
UpdateShaderParameters();
}
}
private DebugType _debugType = DebugType.None;
private float _moistureNoiseAmplitude = 0.1f;
[Export]
public DebugType DebugType
{
@@ -181,136 +143,121 @@ public partial class World : Node2D
set
{
_debugType = value;
LoadTiles();
UpdateShaderParameters();
}
}
ConcurrentDictionary<Vector2I, Tile> _tiles = new ConcurrentDictionary<Vector2I, Tile>();
private CancellationTokenSource _cancellationTokenSource;
private static readonly int MaxDegreeOfParallelism = Math.Max(1, (int)(System.Environment.ProcessorCount * 0.5));
private DebugType _debugType = DebugType.None;
private Sprite2D _terrainSprite;
private ShaderMaterial _shaderMaterial;
public World()
{
}
public override void _Ready()
{
// If the height map image is not assigned, log an error and return
if (HeightMapTexture == null)
{
GD.PrintErr("Height map image is not assigned.");
GD.PrintErr("Height map texture is not assigned.");
return;
}
LoadTiles();
SetupShader();
}
private void LoadTiles()
private void SetupShader()
{
// Check if HeightMapImage is available
if (HeightMapImage == null)
// Don't run during initialization if not ready
if (HeightMapTexture == null) return;
// Clear any existing terrain sprite
if (_terrainSprite != null)
{
GD.PrintErr("Cannot load tiles: HeightMapImage is null");
_terrainSprite.QueueFree();
_terrainSprite = null;
}
// Load the shader
Shader terrainShader = GD.Load<Shader>("res://Shaders/terrain.gdshader");
if (terrainShader == null)
{
GD.PrintErr("Failed to load terrain shader.");
return;
}
// Cancel any existing generation
_cancellationTokenSource?.Cancel();
_cancellationTokenSource = new CancellationTokenSource();
var cancellationToken = _cancellationTokenSource.Token;
// Clear existing tiles
foreach (var tile in _tiles.Values)
// Create shader material
_shaderMaterial = new ShaderMaterial();
_shaderMaterial.Shader = terrainShader;
// Get heightmap dimensions
Image heightImage = HeightMapTexture.GetImage();
if (heightImage == null)
{
tile.Visible = false;
// Delete and delete its children
foreach (Node child in tile.GetChildren())
{
child.QueueFree();
}
tile.QueueFree();
GD.PrintErr("Failed to get image from HeightMapTexture.");
return;
}
_tiles.Clear();
int scale = 1;
// Store local copies of parameters to avoid them changing mid-generation
int tileSize = TileSize;
int chunksX = _chunksX;
// Get the resolution of the height map image
Vector2I heightMapResolution = new Vector2I(HeightMapImage.GetWidth(), HeightMapImage.GetHeight());
// Calculate the number of chunks in the Y direction based on the aspect ratio
int chunksY = (int)(heightMapResolution.Y / (heightMapResolution.X / _chunksX));
// Loop through each chunk position and create a Tile
Task.Run(() =>
{
try
{
for (int x = 0; x < chunksX; x++)
{
if (cancellationToken.IsCancellationRequested) return;
Vector2I heightMapSize = new Vector2I(heightImage.GetWidth(), heightImage.GetHeight());
for (int y = 0; y < chunksY; y++)
{
if (cancellationToken.IsCancellationRequested) return;
Vector2I chunkId = new Vector2I(x, y);
// Calculate output resolution
// Height is always calculated based on heightmap aspect ratio
int outputWidth = MapWidth;
float aspectRatio = (float)heightMapSize.Y / heightMapSize.X;
int outputHeight = (int)(outputWidth * aspectRatio);
Tile tile = new Tile(new Vector2I(tileSize, tileSize), chunkId, this);
tile.CancellationToken = cancellationToken;
tile.Position = new Vector2(chunkId.X * tileSize, chunkId.Y * tileSize) * scale;
tile.Scale = new Vector2(scale, scale);
if (!cancellationToken.IsCancellationRequested)
{
CallDeferred("add_child", tile);
_tiles[chunkId] = tile;
// GD.Print("Loaded tile at chunk " + chunkId);
}
}
}
}
catch (OperationCanceledException)
{
// GD.Print("Tile generation cancelled");
}
}, cancellationToken);
// Create a sprite to display the shader
_terrainSprite = new Sprite2D();
_terrainSprite.Material = _shaderMaterial;
_terrainSprite.TextureFilter = TextureFilterEnum.Nearest;
// Create a white texture with the desired output size
// The shader will render over this
Image whiteImage = Image.CreateEmpty(outputWidth, outputHeight, false, Image.Format.Rgba8);
whiteImage.Fill(Colors.White);
_terrainSprite.Texture = ImageTexture.CreateFromImage(whiteImage);
// Center the sprite
_terrainSprite.Centered = false;
AddChild(_terrainSprite);
// Update all shader parameters
UpdateShaderParameters();
}
private Image _heightMapCache;
public Image HeightMapImage
{
get
{
// If not cached, load and cache it
if (_heightMapCache == null && HeightMapTexture != null)
{
_heightMapCache = HeightMapTexture.GetImage();
// If GetImage() returns null, try loading directly from file
if (_heightMapCache == null && HeightMapTexture.ResourcePath != "")
{
_heightMapCache = Image.LoadFromFile(HeightMapTexture.ResourcePath);
if (_heightMapCache == null)
{
GD.PushError($"Could not load heightmap from {HeightMapTexture.ResourcePath}");
}
}
}
return _heightMapCache;
private void UpdateShaderParameters()
{
if (_shaderMaterial == null)
{
GD.PrintErr("ShaderMaterial is null, cannot update parameters");
return;
}
if (HeightMapTexture == null)
{
GD.PrintErr("HeightMapTexture is null, cannot update parameters");
return;
}
GD.Print($"Setting height_map texture: {HeightMapTexture.ResourcePath}");
// Set the heightmap texture
_shaderMaterial.SetShaderParameter("height_map", HeightMapTexture);
// Set all threshold parameters
_shaderMaterial.SetShaderParameter("water_threshold", WaterThreshold);
_shaderMaterial.SetShaderParameter("rock_threshold", RockThreshold);
_shaderMaterial.SetShaderParameter("snow_height_threshold", SnowHeightThreshold);
_shaderMaterial.SetShaderParameter("desert_temperature_threshold", DesertTemperatureThreshold);
_shaderMaterial.SetShaderParameter("desert_moisture_threshold", DesertMoistureThreshold);
// Set noise parameters
_shaderMaterial.SetShaderParameter("temperature_noise_frequency", TemperatureNoiseFrequency);
_shaderMaterial.SetShaderParameter("temperature_noise_amplitude", TemperatureNoiseAmplitude);
_shaderMaterial.SetShaderParameter("moisture_noise_frequency", MoistureNoiseFrequency);
_shaderMaterial.SetShaderParameter("moisture_noise_amplitude", MoistureNoiseAmplitude);
// Set debug mode
_shaderMaterial.SetShaderParameter("debug_type", (int)DebugType);
GD.Print("Shader parameters updated successfully");
}
}