Better scattering and scatter (of both varieties)

This commit is contained in:
Joshua Barretto 2020-11-16 12:56:32 +00:00
parent ee65b4fb17
commit 49df604de0
10 changed files with 43 additions and 53 deletions

View File

@ -346,6 +346,7 @@ magically infused items?"#,
"hud.settings.cloud_rendering_mode.low": "Low", "hud.settings.cloud_rendering_mode.low": "Low",
"hud.settings.cloud_rendering_mode.medium": "Medium", "hud.settings.cloud_rendering_mode.medium": "Medium",
"hud.settings.cloud_rendering_mode.high": "High", "hud.settings.cloud_rendering_mode.high": "High",
"hud.settings.cloud_rendering_mode.ultra": "Ultra",
"hud.settings.fullscreen": "Fullscreen", "hud.settings.fullscreen": "Fullscreen",
"hud.settings.fullscreen_mode": "Fullscreen Mode", "hud.settings.fullscreen_mode": "Fullscreen Mode",
"hud.settings.fullscreen_mode.exclusive": "Exclusive", "hud.settings.fullscreen_mode.exclusive": "Exclusive",

View File

@ -40,13 +40,13 @@ vec4 cloud_at(vec3 pos, float dist) {
// Turbulence (small variations in clouds/mist) // Turbulence (small variations in clouds/mist)
const float turb_speed = -1.0; // Turbulence goes the opposite way const float turb_speed = -1.0; // Turbulence goes the opposite way
vec3 turb_offset = vec3(1, 1, 0) * time_of_day.x * turb_speed; vec3 turb_offset = vec3(1, 1, 0) * time_of_day.x * turb_speed;
#if (CLOUD_MODE != CLOUD_MODE_MINIMAL) #if (CLOUD_MODE >= CLOUD_MODE_MINIMAL)
turb_noise = noise_3d((wind_pos + turb_offset) * 0.001) - 0.5; turb_noise = noise_3d((wind_pos + turb_offset) * 0.001) - 0.5;
#endif #endif
#if (CLOUD_MODE == CLOUD_MODE_MEDIUM || CLOUD_MODE == CLOUD_MODE_HIGH) #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
turb_noise += (noise_3d((wind_pos + turb_offset * 0.3) * 0.004) - 0.5) * 0.35; turb_noise += (noise_3d((wind_pos + turb_offset * 0.3) * 0.004) - 0.5) * 0.35;
#endif #endif
#if (CLOUD_MODE == CLOUD_MODE_HIGH) #if (CLOUD_MODE >= CLOUD_MODE_HIGH)
turb_noise += (noise_3d((wind_pos + turb_offset * 0.3) * 0.01) - 0.5) * 0.125; turb_noise += (noise_3d((wind_pos + turb_offset * 0.3) * 0.01) - 0.5) * 0.125;
#endif #endif
mist *= (1.0 + turb_noise); mist *= (1.0 + turb_noise);
@ -62,14 +62,14 @@ vec4 cloud_at(vec3 pos, float dist) {
// Since we're assuming the sun/moon is always above (not always correct) it's the same for the moon // Since we're assuming the sun/moon is always above (not always correct) it's the same for the moon
float moon_access = sun_access; float moon_access = sun_access;
#if (CLOUD_MODE == CLOUD_MODE_HIGH) #if (CLOUD_MODE >= CLOUD_MODE_HIGH)
// Try to calculate a reasonable approximation of the cloud normal // Try to calculate a reasonable approximation of the cloud normal
float cloud_tendency_x = cloud_tendency_at(pos.xy + vec2(100, 0)); float cloud_tendency_x = cloud_tendency_at(pos.xy + vec2(100, 0));
float cloud_tendency_y = cloud_tendency_at(pos.xy + vec2(0, 100)); float cloud_tendency_y = cloud_tendency_at(pos.xy + vec2(0, 100));
vec3 cloud_norm = vec3( vec3 cloud_norm = vec3(
(cloud_tendency - cloud_tendency_x) * 7.5, (cloud_tendency - cloud_tendency_x) * 6,
(cloud_tendency - cloud_tendency_y) * 7.5, (cloud_tendency - cloud_tendency_y) * 6,
(pos.z - cloud_attr.x) / 250 + turb_noise * 1 (pos.z - cloud_attr.x) / 250 + turb_noise
); );
sun_access = mix(clamp(dot(-sun_dir.xyz, cloud_norm), 0.025, 1), sun_access, 0.25); sun_access = mix(clamp(dot(-sun_dir.xyz, cloud_norm), 0.025, 1), sun_access, 0.25);
moon_access = mix(clamp(dot(-moon_dir.xyz, cloud_norm), 0.025, 1), moon_access, 0.25); moon_access = mix(clamp(dot(-moon_dir.xyz, cloud_norm), 0.025, 1), moon_access, 0.25);
@ -96,14 +96,16 @@ float atan2(in float y, in float x) {
} }
const float DIST_CAP = 50000; const float DIST_CAP = 50000;
#if (CLOUD_MODE == CLOUD_MODE_HIGH) #if (CLOUD_MODE == CLOUD_MODE_ULTRA)
const uint QUALITY = 100u; const uint QUALITY = 200u;
#elif (CLOUD_MODE == CLOUD_MODE_HIGH)
const uint QUALITY = 50u;
#elif (CLOUD_MODE == CLOUD_MODE_MEDIUM) #elif (CLOUD_MODE == CLOUD_MODE_MEDIUM)
const uint QUALITY = 40u; const uint QUALITY = 30u;
#elif (CLOUD_MODE == CLOUD_MODE_LOW) #elif (CLOUD_MODE == CLOUD_MODE_LOW)
const uint QUALITY = 20u; const uint QUALITY = 16u;
#elif (CLOUD_MODE == CLOUD_MODE_MINIMAL) #elif (CLOUD_MODE == CLOUD_MODE_MINIMAL)
const uint QUALITY = 7u; const uint QUALITY = 5u;
#endif #endif
const float STEP_SCALE = DIST_CAP / (10.0 * float(QUALITY)); const float STEP_SCALE = DIST_CAP / (10.0 * float(QUALITY));
@ -155,11 +157,13 @@ vec3 get_cloud_color(vec3 surf_color, vec3 dir, vec3 origin, const float time_of
float moon_access = sample.y; float moon_access = sample.y;
float scatter_factor = 1.0 - 1.0 / (1.0 + density_integrals.x); float scatter_factor = 1.0 - 1.0 / (1.0 + density_integrals.x);
const float RAYLEIGH = 0.5;
surf_color = surf_color =
// Attenuate light passing through the clouds, removing light due to rayleigh scattering (transmission component) // Attenuate light passing through the clouds
surf_color * (1.0 - scatter_factor) - surf_color * density_integrals.y * sky_color + surf_color * (1.0 - scatter_factor) +
// This is not rayleigh scattering, but it's good enough for our purposes (only considers sun) // This is not rayleigh scattering, but it's good enough for our purposes (only considers sun)
sky_color * net_light * density_integrals.y + (1.0 - surf_color) * net_light * sky_color * density_integrals.y * RAYLEIGH +
// Add the directed light light scattered into the camera by the clouds // Add the directed light light scattered into the camera by the clouds
get_sun_color() * sun_scatter * sun_access * scatter_factor * get_sun_brightness() + get_sun_color() * sun_scatter * sun_access * scatter_factor * get_sun_brightness() +
// Really we should multiple by just moon_brightness here but this just looks better given that we lack HDR // Really we should multiple by just moon_brightness here but this just looks better given that we lack HDR

View File

@ -14,6 +14,7 @@
#define CLOUD_MODE_LOW 2 #define CLOUD_MODE_LOW 2
#define CLOUD_MODE_MEDIUM 3 #define CLOUD_MODE_MEDIUM 3
#define CLOUD_MODE_HIGH 4 #define CLOUD_MODE_HIGH 4
#define CLOUD_MODE_ULTRA 5
#define LIGHTING_ALGORITHM_LAMBERTIAN 0 #define LIGHTING_ALGORITHM_LAMBERTIAN 0
#define LIGHTING_ALGORITHM_BLINN_PHONG 1 #define LIGHTING_ALGORITHM_BLINN_PHONG 1

View File

@ -72,14 +72,14 @@ vec2 wind_offset = vec2(time_of_day.x * wind_speed);
float cloud_tendency_at(vec2 pos) { float cloud_tendency_at(vec2 pos) {
float nz = texture(t_noise, (pos + wind_offset) * 0.000075).x - 0.5; float nz = texture(t_noise, (pos + wind_offset) * 0.000075).x - 0.5;
nz = clamp(nz, 0, 1); nz = clamp(nz, 0, 1);
#if (CLOUD_MODE == CLOUD_MODE_MEDIUM || CLOUD_MODE == CLOUD_MODE_HIGH) #if (CLOUD_MODE >= CLOUD_MODE_MEDIUM)
nz += (texture(t_noise, (pos + wind_offset) * 0.00035).x - 0.5) * 0.15; nz += (texture(t_noise, (pos + wind_offset) * 0.00035).x - 0.5) * 0.15;
#endif #endif
return nz; return nz;
} }
float cloud_shadow(vec3 pos, vec3 light_dir) { float cloud_shadow(vec3 pos, vec3 light_dir) {
#if (CLOUD_MODE == CLOUD_MODE_NONE || CLOUD_MODE == CLOUD_MODE_MINIMAL) #if (CLOUD_MODE <= CLOUD_MODE_MINIMAL)
return 1.0; return 1.0;
#else #else
vec2 xy_offset = light_dir.xy * ((CLOUD_AVG_ALT - pos.z) / -light_dir.z); vec2 xy_offset = light_dir.xy * ((CLOUD_AVG_ALT - pos.z) / -light_dir.z);

View File

@ -342,7 +342,7 @@ void main() {
} else { } else {
f_ao *= mix(1.0, clamp(pow(fract(my_alt), 0.5), 0, 1), voxelize_factor); f_ao *= mix(1.0, clamp(pow(fract(my_alt), 0.5), 0, 1), voxelize_factor);
if (fract(f_pos.x) * abs(my_norm.x / cam_dir.x) < fract(f_pos.y) * abs(my_norm.y / cam_dir.y)) {//cam_dir.x / my_norm.x + clamp(dot(vec3(1, 0, 0), -cam_dir), 0, 1)) { if (fract(f_pos.x) * abs(my_norm.y / cam_dir.x) < fract(f_pos.y) * abs(my_norm.x / cam_dir.y)) {
voxel_norm = vec3(sign(cam_dir.x), 0, 0); voxel_norm = vec3(sign(cam_dir.x), 0, 0);
} else { } else {
voxel_norm = vec3(0, sign(cam_dir.y), 0); voxel_norm = vec3(0, sign(cam_dir.y), 0);

View File

@ -2125,6 +2125,7 @@ impl<'a> Widget for SettingsWindow<'a> {
CloudMode::Low, CloudMode::Low,
CloudMode::Medium, CloudMode::Medium,
CloudMode::High, CloudMode::High,
CloudMode::Ultra,
]; ];
let mode_label_list = [ let mode_label_list = [
&self.localized_strings.get("common.none"), &self.localized_strings.get("common.none"),
@ -2140,6 +2141,9 @@ impl<'a> Widget for SettingsWindow<'a> {
&self &self
.localized_strings .localized_strings
.get("hud.settings.cloud_rendering_mode.high"), .get("hud.settings.cloud_rendering_mode.high"),
&self
.localized_strings
.get("hud.settings.cloud_rendering_mode.ultra"),
]; ];
// Get which cloud rendering mode is currently active // Get which cloud rendering mode is currently active

View File

@ -110,45 +110,23 @@ impl Default for AaMode {
/// Cloud modes /// Cloud modes
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum CloudMode { pub enum CloudMode {
/// No clouds. On computers that can't handle loops well, have performance /// No clouds. As cheap as it gets.
/// issues in fragment shaders in general, or just have large
/// resolutions, this can be a *very* impactful performance difference.
/// Part of that is because of inefficiencies in how we implement
/// regular clouds. It is still not all that cheap on low-end machines, due
/// to many calculations being performed that use relatively expensive
/// functions, and at some point I'd like to both optimize the regular
/// sky shader further and create an even cheaper option.
None, None,
/// Volumetric clouds. This option can be *very* expensive on low-end /// Clouds, but barely. Ideally, any machine should be able to handle this just fine.
/// machines, to the point of making the game unusable, for several
/// reasons:
///
/// - The volumetric clouds use raymarching, which will cause catastrophic
/// performance degradation on GPUs without good support for loops. There
/// is an attempt to minimize the impact of this using a z-range check,
/// but on some low-end GPUs (such as some integrated graphics cards) this
/// test doesn't appear to be able to be predicted well at shader
/// invocation time.
/// - The cloud computations themselves are fairly involved, further
/// degrading performance.
/// - Although the sky shader is always drawn at the outer edges of the
/// skybox, the clouds themselves are supposed to be positioned much
/// lower, which means the depth check for the skybox incorrectly cuts off
/// clouds in some places. To compensate for these cases (e.g. where
/// terrain is occluded by clouds from above, and the camera is above the
/// clouds), we currently branch to see if we need to render the clouds in
/// *every* fragment shader. For machines that can't optimize the check,
/// this is absurdly expensive, so we should look at alternatives in the
/// future that player better with the GPU.
Minimal, Minimal,
/// Enough visual detail to be pleasing, but generally using poor-but-cheap approximations to derive parameters
Low, Low,
High, /// More detail. Enough to look good in most cases. For those that value looks but also high framerates.
#[serde(other)]
Medium, Medium,
/// High, but with extra compute power thrown at it to smooth out subtle imperfections
Ultra,
/// Lots of detail with good-but-costly derivation of parameters.
#[serde(other)]
High,
} }
impl Default for CloudMode { impl Default for CloudMode {
fn default() -> Self { CloudMode::Medium } fn default() -> Self { CloudMode::High }
} }
/// Fluid modes /// Fluid modes

View File

@ -1829,6 +1829,7 @@ fn create_pipelines(
CloudMode::Low => "CLOUD_MODE_LOW", CloudMode::Low => "CLOUD_MODE_LOW",
CloudMode::Medium => "CLOUD_MODE_MEDIUM", CloudMode::Medium => "CLOUD_MODE_MEDIUM",
CloudMode::High => "CLOUD_MODE_HIGH", CloudMode::High => "CLOUD_MODE_HIGH",
CloudMode::Ultra => "CLOUD_MODE_ULTRA",
}, },
match mode.lighting { match mode.lighting {
LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN", LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN",

View File

@ -1,6 +1,7 @@
use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, Canvas, CONFIG}; use crate::{column::ColumnSample, sim::SimChunk, util::RandomField, Canvas, CONFIG};
use common::terrain::SpriteKind; use common::terrain::SpriteKind;
use noise::NoiseFn; use noise::NoiseFn;
use rand::prelude::*;
use std::f32; use std::f32;
use vek::*; use vek::*;
@ -10,7 +11,7 @@ fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate const MUSH_FACT: f32 = 1.0e-4; // To balance everything around the mushroom spawning rate
const DEPTH_WATER_NORM: f32 = 15.0; // Water depth at which regular underwater sprites start spawning const DEPTH_WATER_NORM: f32 = 15.0; // Water depth at which regular underwater sprites start spawning
pub fn apply_scatter_to(canvas: &mut Canvas) { pub fn apply_scatter_to(canvas: &mut Canvas, rng: &mut impl Rng) {
use SpriteKind::*; use SpriteKind::*;
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
// TODO: Add back all sprites we had before // TODO: Add back all sprites we had before
@ -317,7 +318,7 @@ pub fn apply_scatter_to(canvas: &mut Canvas) {
.unwrap_or(true); .unwrap_or(true);
if density > 0.0 if density > 0.0
&& is_patch && is_patch
&& RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density) && rng.gen::<f32>() < density //RandomField::new(i as u32).chance(Vec3::new(wpos2d.x, wpos2d.y, 0), density)
&& underwater == *is_underwater && underwater == *is_underwater
{ {
Some(*kind) Some(*kind)

View File

@ -241,7 +241,7 @@ impl World {
}; };
layer::apply_trees_to(&mut canvas); layer::apply_trees_to(&mut canvas);
layer::apply_scatter_to(&mut canvas); layer::apply_scatter_to(&mut canvas, &mut dynamic_rng);
layer::apply_caves_to(&mut canvas); layer::apply_caves_to(&mut canvas);
layer::apply_paths_to(&mut canvas); layer::apply_paths_to(&mut canvas);