Optimized VoxelStreamNoise with finer height range and noise shaping
parent
c493aec343
commit
3263001c5f
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue