Optimized VoxelStreamNoise with finer height range and noise shaping

master
Marc Gilleron 2020-01-19 00:43:56 +00:00
parent c493aec343
commit 3263001c5f
1 changed files with 88 additions and 50 deletions

View File

@ -26,6 +26,9 @@ real_t VoxelStreamNoise::get_height_start() const {
}
void VoxelStreamNoise::set_height_range(real_t hrange) {
if (hrange < 0.1f) {
hrange = 0.1f;
}
_height_range = hrange;
}
@ -33,6 +36,40 @@ real_t VoxelStreamNoise::get_height_range() const {
return _height_range;
}
// For isosurface use cases, noise can be "shaped" by calculating only the first octave,
// and discarding the next ones if beyond some distance away from the isosurface,
// because then we assume next octaves won't change the sign (which crosses the surface).
// This might reduce accuracy in some areas, but it speeds up the results.
static inline float get_shaped_noise(OpenSimplexNoise &noise, float x, float y, float z, float threshold, float bias) {
x /= noise.get_period();
y /= noise.get_period();
z /= noise.get_period();
float sum = noise._get_octave_noise_3d(0, x, y, z);
// A default value for `threshold` would be `persistence`
if (sum + bias > threshold || sum + bias < -threshold) {
// Assume next octaves will not change sign of noise
return sum;
}
float amp = 1.0;
float max = 1.0;
int i = 0;
while (++i < noise.get_octaves()) {
x *= noise.get_lacunarity();
y *= noise.get_lacunarity();
z *= noise.get_lacunarity();
amp *= noise.get_persistence();
max += amp;
sum += noise._get_octave_noise_3d(i, x, y, z) * amp;
}
return sum / max;
}
void VoxelStreamNoise::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(out_buffer.is_null());
@ -41,77 +78,78 @@ void VoxelStreamNoise::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin
OpenSimplexNoise &noise = **_noise;
VoxelBuffer &buffer = **out_buffer;
if (origin_in_voxels.y > _height_start + _height_range) {
int isosurface_lower_bound = static_cast<int>(Math::floor(_height_start));
int isosurface_upper_bound = static_cast<int>(Math::ceil(_height_start + _height_range));
const int air_type = 0;
const int matter_type = 1;
if (origin_in_voxels.y >= isosurface_upper_bound) {
// Fill with air
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(_channel, 100.0);
}
else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, 0);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, air_type);
}
} else if (origin_in_voxels.y + (buffer.get_size().y << lod) < _height_start) {
} else if (origin_in_voxels.y + (buffer.get_size().y << lod) < isosurface_lower_bound) {
// Fill with matter
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.clear_channel_f(_channel, -100.0);
}
else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, 1);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.clear_channel(_channel, matter_type);
}
} else {
// TODO Proper noise optimization
// Prefetching was much faster, but it introduced LOD inconsistencies into the data itself, causing cracks.
// Need to implement it properly, or use a different noise library.
/*FloatBuffer3D &noise_buffer = _noise_buffer;
const int noise_buffer_step = 2;
Vector3i noise_buffer_size = buffer.get_size() / noise_buffer_step + Vector3i(1);
if (noise_buffer.get_size() != noise_buffer_size) {
noise_buffer.create(noise_buffer_size);
}
// Cache noise at lower grid resolution and interpolate after, much cheaper
for (int z = 0; z < noise_buffer.get_size().z; ++z) {
for (int x = 0; x < noise_buffer.get_size().x; ++x) {
for (int y = 0; y < noise_buffer.get_size().y; ++y) {
float lx = origin_in_voxels.x + (x << lod) * noise_buffer_step;
float ly = origin_in_voxels.y + (y << lod) * noise_buffer_step;
float lz = origin_in_voxels.z + (z << lod) * noise_buffer_step;
float n = noise.get_noise_3d(lx, ly, lz);
noise_buffer.set(x, y, z, n);
}
}
}*/
float iso_scale = noise.get_period() * 0.1;
//float noise_buffer_scale = 1.f / static_cast<float>(noise_buffer_step);
Vector3i size = buffer.get_size();
const float iso_scale = noise.get_period() * 0.1;
const Vector3i size = buffer.get_size();
const float height_range_inv = 1.f / _height_range;
const float one_minus_persistence = 1.f - noise.get_persistence();
for (int z = 0; z < size.z; ++z) {
int lz = origin_in_voxels.z + (z << lod);
for (int x = 0; x < size.x; ++x) {
int lx = origin_in_voxels.x + (x << lod);
for (int y = 0; y < size.y; ++y) {
float lx = origin_in_voxels.x + (x << lod);
float ly = origin_in_voxels.y + (y << lod);
float lz = origin_in_voxels.z + (z << lod);
int ly = origin_in_voxels.y + (y << lod);
//float n = noise_buffer.get_trilinear(x * noise_buffer_scale, y * noise_buffer_scale, z * noise_buffer_scale);
float n = noise.get_noise_3d(lx, ly, lz);
float t = (ly - _height_start) / _height_range;
float d = (n + 2.0 * t - 1.0) * iso_scale;
if (ly < isosurface_lower_bound) {
// Below is only matter
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(-1, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(matter_type, x, y, z, _channel);
}
continue;
} else if (ly >= isosurface_upper_bound) {
// Above is only air
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(1, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE) {
buffer.set_voxel(air_type, x, y, z, _channel);
}
continue;
}
// Bias is what makes noise become "matter" the lower we go, and "air" the higher we go
float t = (ly - _height_start) * height_range_inv;
float bias = 2.0 * t - 1.0;
// We are near the isosurface, need to calculate noise value
float n = get_shaped_noise(noise, lx, ly, lz, one_minus_persistence, bias);
float d = (n + bias) * iso_scale;
if (_channel == VoxelBuffer::CHANNEL_SDF) {
buffer.set_voxel_f(d, x, y, z, _channel);
}
else if (_channel == VoxelBuffer::CHANNEL_TYPE && d < 0) {
buffer.set_voxel(1, x, y, z, _channel);
} else if (_channel == VoxelBuffer::CHANNEL_TYPE && d < 0) {
buffer.set_voxel(matter_type, x, y, z, _channel);
}
}
}