This is dumb as heck

This commit is contained in:
Joshua Barretto 2021-04-24 02:22:46 +01:00
parent 0f5d289ecb
commit d12f4e3aca
23 changed files with 1069 additions and 49 deletions

View File

@ -0,0 +1,191 @@
#version 330 core
#include <constants.glsl>
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
#define HAS_SHADOW_MAPS
#include <globals.glsl>
in vec3 f_pos;
flat in vec3 f_norm;
//flat in float f_select;
// flat in vec3 f_pos_norm;
in vec2 f_uv_pos;
in vec2 f_inst_light;
// flat in uint f_atlas_pos;
// in vec3 f_col;
// in float f_ao;
// in float f_light;
// in vec4 light_pos[2];
uniform sampler2D t_col_light;
//struct ShadowLocals {
// mat4 shadowMatrices;
// mat4 texture_mat;
//};
//
//layout (std140)
//uniform u_light_shadows {
// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192];
//};
out vec4 tgt_color;
#include <sky.glsl>
#include <light.glsl>
#include <lod.glsl>
const float FADE_DIST = 32.0;
void main() {
/* if (f_uv_pos.x < 757) {
discard;
} */
// vec2 f_uv_pos = vec2(768,1) + 0.5;
// vec2 f_uv_pos = vec2(760, 380);// + 0.5;
// vec2 f_uv_pos = vec2((uvec2(f_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu)) + 0.5;
/* if (f_uv_pos.x < 757) {
discard;
} */
// vec3 du = dFdx(f_pos);
// vec3 dv = dFdy(f_pos);
// vec3 f_norm = normalize(cross(du, dv));
float f_ao, f_glow;
vec3 f_col = greedy_extract_col_light_glow(t_col_light, f_uv_pos, f_ao, f_glow);
// vec3 my_chunk_pos = f_pos_norm;
// tgt_color = vec4(hash(floor(vec4(my_chunk_pos.x, 0, 0, 0))), hash(floor(vec4(0, my_chunk_pos.y, 0, 1))), hash(floor(vec4(0, 0, my_chunk_pos.z, 2))), 1.0);
// tgt_color = vec4(f_uv_pos / texSize, 0.0, 1.0);
// tgt_color = vec4(f_col.rgb, 1.0);
// return;
// vec4 light_pos[2];
//#if (SHADOW_MODE == SHADOW_MODE_MAP)
// // for (uint i = 0u; i < light_shadow_count.z; ++i) {
// // light_pos[i] = /*vec3(*/shadowMats[i].texture_mat * vec4(f_pos, 1.0)/*)*/;
// // }
// vec4 sun_pos = /*vec3(*/shadowMats[0].texture_mat * vec4(f_pos, 1.0)/*)*/;
//#elif (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_NONE)
// vec4 sun_pos = vec4(0.0);
//#endif
vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
// vec4 vert_pos4 = view_mat * vec4(f_pos, 1.0);
// vec3 view_dir = normalize(-vec3(vert_pos4)/* / vert_pos4.w*/);
vec3 view_dir = -cam_to_frag;
/* vec3 sun_dir = get_sun_dir(time_of_day.x);
vec3 moon_dir = get_moon_dir(time_of_day.x); */
// float sun_light = get_sun_brightness(sun_dir);
// float moon_light = get_moon_brightness(moon_dir);
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP || FLUID_MODE == FLUID_MODE_SHINY)
float f_alt = alt_at(f_pos.xy);
// float f_alt = f_pos.z;
#elif (SHADOW_MODE == SHADOW_MODE_NONE || FLUID_MODE == FLUID_MODE_CHEAP)
float f_alt = f_pos.z;
#endif
#if (SHADOW_MODE == SHADOW_MODE_CHEAP || SHADOW_MODE == SHADOW_MODE_MAP)
vec4 f_shadow = textureBicubic(t_horizon, pos_to_tex(f_pos.xy));
float sun_shade_frac = horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
// float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
#elif (SHADOW_MODE == SHADOW_MODE_NONE)
float sun_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, sun_dir);
#endif
float moon_shade_frac = 1.0;//horizon_at2(f_shadow, f_alt, f_pos, moon_dir);
// float sun_shade_frac = horizon_at(f_pos, sun_dir);
// float moon_shade_frac = horizon_at(f_pos, moon_dir);
// Globbal illumination "estimate" used to light the faces of voxels which are parallel to the sun or moon (which is a very common occurrence).
// Will be attenuated by k_d, which is assumed to carry any additional ambient occlusion information (e.g. about shadowing).
// float ambient_sides = clamp(mix(0.5, 0.0, abs(dot(-f_norm, sun_dir)) * 10000.0), 0.0, 0.5);
// NOTE: current assumption is that moon and sun shouldn't be out at the sae time.
// This assumption is (or can at least easily be) wrong, but if we pretend it's true we avoids having to explicitly pass in a separate shadow
// for the sun and moon (since they have different brightnesses / colors so the shadows shouldn't attenuate equally).
// float shade_frac = sun_shade_frac + moon_shade_frac;
// DirectionalLight sun_info = get_sun_info(sun_dir, sun_shade_frac, light_pos);
float point_shadow = shadow_at(f_pos, f_norm);
DirectionalLight sun_info = get_sun_info(sun_dir, point_shadow * sun_shade_frac, /*sun_pos*/f_pos);
DirectionalLight moon_info = get_moon_info(moon_dir, point_shadow * moon_shade_frac/*, light_pos*/);
vec3 surf_color = /*srgb_to_linear*//*linear_to_srgb*/(f_col);
float alpha = 1.0;
const float n2 = 1.5;
const float R_s2s0 = pow((1.0 - n2) / (1.0 + n2), 2);
const float R_s1s0 = pow((1.3325 - n2) / (1.3325 + n2), 2);
const float R_s2s1 = pow((1.0 - 1.3325) / (1.0 + 1.3325), 2);
const float R_s1s2 = pow((1.3325 - 1.0) / (1.3325 + 1.0), 2);
float R_s = (f_pos.z < f_alt) ? mix(R_s2s1 * R_s1s0, R_s1s0, medium.x) : mix(R_s2s0, R_s1s2 * R_s2s0, medium.x);
vec3 k_a = vec3(1.0);
vec3 k_d = vec3(1.0);
vec3 k_s = vec3(R_s);
vec3 emitted_light, reflected_light;
// Make voxel shadows block the sun and moon
sun_info.block = f_inst_light.x;
moon_info.block = f_inst_light.x;
// To account for prior saturation.
// float vert_light = pow(f_light, 1.5);
// vec3 light_frac = light_reflection_factor(f_norm/*vec3(0, 0, 1.0)*/, view_dir, vec3(0, 0, -1.0), vec3(1.0), vec3(R_s), alpha);
/* light_frac += light_reflection_factor(f_norm, view_dir, vec3(1.0, 0, 0.0), vec3(1.0), vec3(1.0), 2.0);
light_frac += light_reflection_factor(f_norm, view_dir, vec3(-1.0, 0, 0.0), vec3(1.0), vec3(1.0), 2.0);
light_frac += light_reflection_factor(f_norm, view_dir, vec3(0.0, -1.0, 0.0), vec3(1.0), vec3(1.0), 2.0);
light_frac += light_reflection_factor(f_norm, view_dir, vec3(0.0, 1.0, 0.0), vec3(1.0), vec3(1.0), 2.0); */
// vec3 light, diffuse_light, ambient_light;
// vec3 emitted_light, reflected_light;
// float point_shadow = shadow_at(f_pos,f_norm);
// vec3 point_light = light_at(f_pos, f_norm);
// vec3 surf_color = srgb_to_linear(vec3(0.2, 0.5, 1.0));
// vec3 cam_to_frag = normalize(f_pos - cam_pos.xyz);
float max_light = 0.0;
max_light += get_sun_diffuse2(sun_info, moon_info, f_norm, /*time_of_day.x, *//*cam_to_frag*/view_dir, k_a/* * (shade_frac * 0.5 + light_frac * 0.5)*/, k_d, k_s, alpha, emitted_light, reflected_light);
// reflected_light *= /*vert_light * */point_shadow * shade_frac;
// emitted_light *= /*vert_light * */point_shadow * max(shade_frac, MIN_SHADOW);
// max_light *= /*vert_light * */point_shadow * shade_frac;
// emitted_light *= point_shadow;
// reflected_light *= point_shadow;
// max_light *= point_shadow;
// get_sun_diffuse(f_norm, time_of_day.x, light, diffuse_light, ambient_light, 1.0);
// float point_shadow = shadow_at(f_pos, f_norm);
// diffuse_light *= f_light * point_shadow;
// ambient_light *= f_light * point_shadow;
// light += point_light;
// diffuse_light += point_light;
// reflected_light += point_light;
max_light += lights_at(f_pos, f_norm, view_dir, k_a, k_d, k_s, alpha, emitted_light, reflected_light);
/* vec3 point_light = light_at(f_pos, f_norm);
emitted_light += point_light;
reflected_light += point_light; */
// float ao = /*pow(f_ao, 0.5)*/f_ao * 0.85 + 0.15;
vec3 glow = pow(f_inst_light.y, 3) * 4 * glow_light(f_pos);
emitted_light += glow;
float ao = f_ao;
emitted_light *= ao;
reflected_light *= ao;
surf_color = illuminate(max_light, view_dir, surf_color * emitted_light, surf_color * reflected_light);
// vec3 surf_color = illuminate(f_col, light, diffuse_light, ambient_light);
//surf_color += f_select * (surf_color + 0.1) * vec3(0.15, 0.15, 0.15);
// tgt_color = vec4(color, 1.0);
tgt_color = vec4(surf_color, 1.0);
}

View File

@ -0,0 +1,230 @@
#version 330 core
#include <constants.glsl>
#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION
#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY
#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE
#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET
#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN
#include <globals.glsl>
#include <srgb.glsl>
#include <sky.glsl>
in vec3 v_pos;
in uint v_atlas_pos;
// in uint v_col;
in uint v_norm_ao;
in vec3 inst_pos;
struct LodStructureLocals {
uint nope;
};
layout (std140)
uniform u_locals {
uint nope;
// LodStructureLocals lod_structures[8];
};
// struct Instance {
// mat4 inst_mat;
// vec3 inst_col;
// float inst_wind_sway;
// };
//
// layout (std140)
// uniform u_ibuf {
// Instance sprite_instances[/*MAX_LAYER_FACES*/512];
// };
//struct ShadowLocals {
// mat4 shadowMatrices;
// mat4 texture_mat;
//};
//
//layout (std140)
//uniform u_light_shadows {
// ShadowLocals shadowMats[/*MAX_LAYER_FACES*/192];
//};
out vec3 f_pos;
flat out vec3 f_norm;
//flat out float f_select;
// flat out vec3 f_pos_norm;
// out vec3 f_col;
// out float f_ao;
out vec2 f_uv_pos;
out vec2 f_inst_light;
// flat out uint f_atlas_pos;
// out vec3 light_pos[2];
// out float f_light;
const float SCALE = 1.0 / 11.0;
const float SCALE_FACTOR = pow(SCALE, 1.3) * 0.2;
const int EXTRA_NEG_Z = 32768;
void main() {
// vec3 inst_chunk_pos = vec3(ivec3((uvec3(inst_pos_ori) >> uvec3(0, 6, 12)) & uvec3(0x3Fu, 0x3Fu, 0xFFFFu)) - ivec3(0, 0, EXTRA_NEG_Z));
// uint inst_ori = (inst_pos_ori >> 29) & 0x7u;
// SpriteLocals locals = sprites[inst_ori];
// SpriteLocals locals = sprites;
// mat4 inst_mat = locals.mat;
// float inst_wind_sway = locals.wind_sway.w;
// mat4 inst_mat = mat4(vec4(1, 0, 0, 0), vec4(0, 1, 0, 0), vec4(0, 0, 1, 0), vec4(5.5, 5.5, 0, 1));
// float inst_wind_sway = 0.0;
//mat4 inst_mat;
//inst_mat[0] = inst_mat0;
//inst_mat[1] = inst_mat1;
//inst_mat[2] = inst_mat2;
//inst_mat[3] = inst_mat3;
/* Instance instances = sprite_instances[gl_InstanceID & 1023];
mat4 inst_mat = instances.inst_mat;
vec3 inst_col = instances.inst_col;
float inst_wind_sway = instances.inst_wind_sway; */
//vec3 inst_offs = model_offs - focus_off.xyz;
// mat3 inst_mat;
// inst_mat[0] = inst_mat0.xyz;
// inst_mat[1] = inst_mat1.xyz;
// inst_mat[2] = inst_mat2.xyz;
// /* Instance instances = sprite_instances[gl_InstanceID & 1023];
// mat4 inst_mat = instances.inst_mat;
// vec3 inst_col = instances.inst_col;
// float inst_wind_sway = instances.inst_wind_sway; */
// float inst_wind_sway = wind_sway.w;
// vec3 inst_offs = model_offs - focus_off.xyz;
f_inst_light = vec2(1);//inst_light.xy;
// vec3 sprite_pos = floor(inst_mat3.xyz * SCALE) + inst_offs;
// f_pos_norm = v_pos;
// vec3 sprite_pos = (inst_mat * vec4(0, 0, 0, 1)).xyz;
// vec3 sprite_pos = floor((inst_mat * vec4(0, 0, 0, 1)).xyz * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs;
// vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(0, 0, 0, 1)).xyz - /* wind_sway.xyz * */offs.xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) - inst_offs;
// vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs;
// vec3 v_pos = vec3(gl_VertexID * 32, gl_VertexID % 32, 1.0);
// f_pos = v_pos + (model_offs - focus_off.xyz);
// vec3 v_pos = /*inst_mat*//*locals.*/wind_sway.xyz * v_pos;
//vec3 v_pos_ = /*inst_mat*//*locals.*//*sprites[0].*/wind_sway.xyz * v_pos;
// vec3 v_pos = (/*inst_mat*/locals.mat * vec4(v_pos, 1)).xyz + vec3(0.5, 0.5, 0.0);
// f_pos = v_pos * SCALE + (inst_chunk_pos + model_offs - focus_off.xyz);
// vec3 v_pos_ = (inst_mat * vec4(v_pos/* * SCALE*/, 1)).xyz;
// vec3 v_pos = (inst_mat * vec4(v_pos, 1)).xyz;
// f_pos = v_pos + (model_offs - focus_off.xyz);
f_pos = inst_pos - focus_off.xyz + (v_pos - vec3(5.5, 5.5, 0)) * 3.0;
float dist = distance(focus_pos.xy, f_pos.xy);
float pull_down = 0.2 / pow(dist / (view_distance.x * 0.9), 20.0);
f_pos.z -= pull_down;
// Terrain 'pop-in' effect
//f_pos.z -= 250.0 * (1.0 - min(1.0001 - 0.02 / pow(tick.x - load_time, 10.0), 1.0));
// f_pos = (inst_mat * v_pos_) * SCALE + sprite_pos;
// f_pos = (inst_mat * vec4(v_pos * SCALE, 1)).xyz + (model_offs - focus_off.xyz);
// f_pos = v_pos_ + (inst_chunk_pos + model_offs - focus_off.xyz + vec3(0.5, 0.5, 0.0));
// f_pos.z -= min(32.0, 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0));
// Wind waving
/* const float x_scale = sin(tick.x * 1.5 + f_pos.x * 0.1);
const float y_scale = sin(tick.x * 1.5 + f_pos.y * 0.1);
const float z_scale = pow(abs(v_pos_.z), 1.3) * SCALE_FACTOR;
const float xy_bias = sin(tick.x * 0.25);
const float z_bias = xy_bias * t_scale;
mat3 shear = mat4(
vec3(x_scale , 0.0, 0.0, 0.0),
vec3(0.0, y_scale, 0.0, 0.0),
vec3(0.0, 0.0, z_bias, 0.0),
vec3(0.0, 0.0, (1.0 / z_bias), 0.0)
); */
// const float x_scale = sin(tick.x * 1.5 + f_pos.x * 0.1);
// const float y_scale = sin(tick.x * 1.5 + f_pos.y * 0.1);
// const float z_scale = pow(abs(v_pos_.z), 1.3) * SCALE_FACTOR;
// const float xy_bias = sin(tick.x * 0.25);
// const float z_bias = xy_bias * t_scale;
// vec3 rotate = inst_wind_sway * vec3(
// sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35),
// sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25),
// 0.0
// ) * pow(abs(v_pos_.z/* + sprites[0].offs.z*/)/* * SCALE*/, 1.3) * /*0.2;*/SCALE_FACTOR;
//
// mat3 shear = mat4(
// vec3(x_scale * , 0.0, 0.0, 0.0),
// vec3(0.0, y_scale, 0.0, 0.0),
// vec3(0.0, 0.0, z_bias, 0.0),
// vec3(0.0, 0.0, (1.0 / z_bias), 0.0)
// );
/*if (wind_sway.w >= 0.4) */{
//f_pos += /*inst_wind_sway*/wind_sway.w * vec3(
// sin(tick.x * 1.5 + f_pos.y * 0.1) * sin(tick.x * 0.35),
// sin(tick.x * 1.5 + f_pos.x * 0.1) * sin(tick.x * 0.25),
// 0.0
// ) * 4 * v_pos_.z * /*0.2;*/SCALE_FACTOR;
}
// First 3 normals are negative, next 3 are positive
// vec3 normals[6] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1));
// uint norm_idx = (v_norm_ao >> 0) & 0x7u;
// f_norm = (inst_mat * vec4(normals[], 0)).xyz;
// TODO: Consider adding a second, already-normalized (i.e. unscaled) matrix.
// vec3 norm = /*normalize*/(inst_mat/*locals.mat*/[(v_norm_ao >> 1u) & 3u].xyz);
// vec3 norm = /*normalize*/(inst_mat/*locals.mat*/[(v_norm_ao >> 1u) & 3u]);
// vec3 norm = bone_data.normals_mat[axis_idx].xyz;
// norm = normalize(norm);
// norm = norm / SCALE_FACTOR / locals.wind_sway.xyz;
// norm = norm / (norm.x + norm.y + norm.z);
// vec3 norm = norm_mat * vec4(uvec3(1 << axis_idx) & uvec3(0x1u, 0x3u, 0x7u), 1);
// // Calculate normal here rather than for each pixel in the fragment shader
// f_norm = normalize((
// combined_mat *
// vec4(norm, 0)
// ).xyz);
const vec3 normals[8] = vec3[](vec3(-1,0,0), vec3(1,0,0), vec3(0,-1,0), vec3(0,1,0), vec3(0,0,-1), vec3(0,0,1), vec3(0,0,0), vec3(0,0,0));
vec3 norm = /*normalize*/normals[(v_norm_ao >> 1u) & 3u].xyz;
f_norm = mix(-norm, norm, v_norm_ao & 1u);
/* vec3 col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0;
f_col = srgb_to_linear(col) * srgb_to_linear(inst_col);
f_ao = float((v_norm_ao >> 3) & 0x3u) / 4.0; */
f_uv_pos = vec2((uvec2(v_atlas_pos) >> uvec2(0, 16)) & uvec2(0xFFFFu, 0xFFFFu));/* + 0.5*/;
// f_atlas_pos = v_atlas_pos;
/* for (uint i = 0u; i < light_shadow_count.z; ++i) {
light_pos[i] = vec3(shadowMats[i].texture_mat * vec4(f_pos, 1.0));
} */
// // Select glowing
// if (select_pos.w > 0 && select_pos.xyz == floor(sprite_pos)) {
// f_col *= 4.0;
// }
// f_light = 1.0;
// if (select_pos.w > 0) */{
//vec3 sprite_pos = /*round*/floor(((inst_mat * vec4(-offs.xyz, 1)).xyz) * SCALE/* - vec3(0.5, 0.5, 0.0)*/) + inst_offs;
//f_select = (select_pos.w > 0 && select_pos.xyz == sprite_pos/* - vec3(0.5, 0.5, 0.0) * SCALE*/) ? 1.0 : 0.0;
// }
gl_Position =
all_mat *
vec4(f_pos, 1);
// gl_Position.z = -gl_Position.z;
// gl_Position.z = -gl_Position.z / gl_Position.w;
// gl_Position.z = -gl_Position.z / 100.0;
// gl_Position.z = -gl_Position.z / 100.0;
// gl_Position.z = -1000.0 / (gl_Position.z + 10000.0);
}

BIN
assets/voxygen/voxel/mini_tree.vox (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/voxel/sprite/carrot/1.vox (Stored with Git LFS)

Binary file not shown.

View File

@ -31,9 +31,11 @@ use common::{
},
event::{EventBus, LocalEvent},
grid::Grid,
lod::LodZone,
outcome::Outcome,
recipe::RecipeBook,
resources::{DeltaTime, PlayerEntity, TimeOfDay},
spiral::Spiral2d,
terrain::{
block::Block, map::MapConfig, neighbors, BiomeKind, SitesKind, SpriteKind, TerrainChunk,
TerrainChunkSize,
@ -186,8 +188,10 @@ pub struct Client {
view_distance: Option<u32>,
// TODO: move into voxygen
loaded_distance: f32,
zones: HashMap<Vec2<i32>, Option<LodZone>>,
pending_chunks: HashMap<Vec2<i32>, Instant>,
pending_zones: HashMap<Vec2<i32>, Instant>,
}
/// Holds data related to the current players characters, as well as some
@ -685,8 +689,10 @@ impl Client {
state,
view_distance,
loaded_distance: 0.0,
zones: HashMap::new(),
pending_chunks: HashMap::new(),
pending_zones: HashMap::new(),
})
}
@ -790,7 +796,8 @@ impl Client {
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. } => &mut self.in_game_stream,
//Only in game, terrain
ClientGeneral::TerrainChunkRequest { .. } => &mut self.terrain_stream,
ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }=> &mut self.terrain_stream,
//Always possible
ClientGeneral::ChatMsg(_) | ClientGeneral::Terminate => {
&mut self.general_stream
@ -1334,6 +1341,7 @@ impl Client {
pub fn clear_terrain(&mut self) {
self.state.clear_terrain();
self.pending_chunks.clear();
self.pending_zones.clear();
}
pub fn place_block(&mut self, pos: Vec3<i32>, block: Block) {
@ -1486,6 +1494,8 @@ impl Client {
self.state.remove_chunk(key);
}
let now = Instant::now();
// Request chunks from the server.
self.loaded_distance = ((view_distance * TerrainChunkSize::RECT_SIZE.x) as f32).powi(2);
// +1 so we can find a chunk that's outside the vd for better fog
@ -1523,7 +1533,7 @@ impl Client {
self.send_msg_err(ClientGeneral::TerrainChunkRequest {
key: *key,
})?;
self.pending_chunks.insert(*key, Instant::now());
self.pending_chunks.insert(*key, now);
} else {
skip_mode = true;
}
@ -1546,10 +1556,29 @@ impl Client {
+ (TerrainChunkSize::RECT_SIZE.y as f32 / 2.0).powi(2))
.sqrt();
// Load nearby zones
if let Some(zones) = self.nearby_zones() {
for zone in zones {
if self.zones.get(&zone).is_none() {
if self.pending_zones.len() > 2 {
break;
} else {
self.send_msg_err(ClientGeneral::LodZoneRequest {
key: zone,
})?;
self.pending_zones.insert(zone, Instant::now());
}
}
}
}
// If chunks are taking too long, assume they're no longer pending.
let now = Instant::now();
self.pending_chunks
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3));
.retain(|_, created| now.saturating_duration_since(*created) < Duration::from_secs(3));
// If zones are taking too long, assume they're no longer pending.
self.pending_zones
.retain(|_, created| now.saturating_duration_since(*created) < Duration::from_secs(8));
}
// Send a ping to the server once every second
@ -1587,6 +1616,18 @@ impl Client {
Ok(frontend_events)
}
pub fn nearby_zones(&self) -> Option<impl Iterator<Item = Vec2<i32>>> {
const ZONE_RADIUS: u32 = 1;
let player_zone = self.position()?.xy().map2(TerrainChunkSize::RECT_SIZE, |e, sz| e as i32 / (sz * LodZone::SIZE) as i32);
Some(Spiral2d::new()
.take((ZONE_RADIUS * 2 + 1).pow(2) as usize)
.map(move |rpos| player_zone + rpos))
}
pub fn get_zone(&self, pos: Vec2<i32>) -> Option<&Option<LodZone>> {
self.zones.get(&pos)
}
/// Clean up the client after a tick.
pub fn cleanup(&mut self) {
// Cleanup the local state
@ -1935,6 +1976,9 @@ impl Client {
});
}
},
ServerGeneral::LodZone { key, zone } => {
self.zones.insert(key, zone);
},
_ => unreachable!("Not a terrain message"),
}
Ok(())

View File

@ -74,10 +74,13 @@ pub enum ClientGeneral {
RefundSkill(Skill),
UnlockSkillGroup(SkillGroupKind),
RequestSiteInfo(SiteId),
//Only in Game, via terrain stream
// Only in Game, via terrain stream
TerrainChunkRequest {
key: Vec2<i32>,
},
LodZoneRequest {
key: Vec2<i32>,
},
//Always possible
ChatMsg(String),
Terminate,
@ -117,6 +120,7 @@ impl ClientMsg {
| ClientGeneral::ExitInGame
| ClientGeneral::PlayerPhysics { .. }
| ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::UnlockSkill(_)
| ClientGeneral::RefundSkill(_)
| ClientGeneral::RequestSiteInfo(_)

View File

@ -3,6 +3,7 @@ use crate::sync;
use common::{
character::{self, CharacterItem},
comp::{self, invite::InviteKind, item::MaterialStatManifest},
lod::LodZone,
outcome::Outcome,
recipe::RecipeBook,
resources::TimeOfDay,
@ -109,6 +110,10 @@ pub enum ServerGeneral {
chunk: Result<CompressedData<TerrainChunk>, ()>,
},
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
LodZone {
key: Vec2<i32>,
zone: Option<LodZone>,
},
// Always possible
PlayerListUpdate(PlayerListUpdate),
/// A message to go into the client chat box. The client is responsible for
@ -231,6 +236,7 @@ impl ServerMsg {
| ServerGeneral::InventoryUpdate(_, _)
| ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_)
| ServerGeneral::LodZone { .. }
| ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_)
| ServerGeneral::Knockback(_)

View File

@ -46,6 +46,7 @@ pub mod figure;
#[cfg(not(target_arch = "wasm32"))]
pub mod generation;
#[cfg(not(target_arch = "wasm32"))] pub mod grid;
#[cfg(not(target_arch = "wasm32"))] pub mod lod;
#[cfg(not(target_arch = "wasm32"))]
pub mod lottery;
#[cfg(not(target_arch = "wasm32"))]

18
common/src/lod.rs Normal file
View File

@ -0,0 +1,18 @@
use vek::*;
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LodZone {
pub trees: Vec<LodTree>,
}
impl LodZone {
/// Size in chunks
pub const SIZE: u32 = 64;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct LodTree {
pub pos: Vec2<u16>,
pub alt: u16,
}

View File

@ -116,7 +116,8 @@ impl Client {
},
//Ingame related, terrain
ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) => {
| ServerGeneral::TerrainBlockUpdates(_)
| ServerGeneral::LodZone { .. } => {
self.terrain_stream.lock().unwrap().send(g)
},
// Always possible
@ -187,7 +188,8 @@ impl Client {
},
//Ingame related, terrain
ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) => {
| ServerGeneral::TerrainBlockUpdates(_)
| ServerGeneral::LodZone { .. } => {
PreparedMsg::new(5, &g, &self.terrain_stream_params)
},
// Always possible

View File

@ -10,6 +10,7 @@ use common_net::msg::{ClientGeneral, CompressedData, ServerGeneral};
use rayon::iter::ParallelIterator;
use specs::{Entities, Join, ParJoin, Read, ReadExpect, ReadStorage};
use tracing::{debug, trace};
use std::sync::Arc;
/// This system will handle new messages from clients
#[derive(Default)]
@ -19,6 +20,7 @@ impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, Arc<world::World>>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, NetworkRequestMetrics>,
ReadStorage<'a, Pos>,
@ -35,6 +37,7 @@ impl<'a> System<'a> for Sys {
(
entities,
server_event_bus,
world,
terrain,
network_metrics,
positions,
@ -91,6 +94,12 @@ impl<'a> System<'a> for Sys {
network_metrics.chunks_request_dropped.inc();
}
},
ClientGeneral::LodZoneRequest { key } => {
client.send(ServerGeneral::LodZone {
key,
zone: world.lod().get_zone(key).cloned(),
})?;
},
_ => tracing::error!("not a client_terrain msg"),
}
Ok(())

View File

@ -5,7 +5,7 @@ use crate::{
},
render::{
self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline,
TerrainPipeline,
TerrainPipeline, LodStructurePipeline,
},
scene::math,
};
@ -267,6 +267,143 @@ where
}
}
impl<'a: 'b, 'b, V: 'a> Meshable<LodStructurePipeline, &'b mut GreedyMesh<'a>> for V
where
V: BaseVol<Vox = Cell> + ReadVol + SizedVol,
/* TODO: Use VolIterator instead of manually iterating
* &'a V: IntoVolIterator<'a> + IntoFullVolIterator<'a>,
* &'a V: BaseVol<Vox=Cell>, */
{
type Pipeline = LodStructurePipeline;
type Result = ();
type ShadowPipeline = ShadowPipeline;
type Supplement = (&'b mut GreedyMesh<'a>, &'b mut Mesh<Self::Pipeline>, bool);
type TranslucentPipeline = LodStructurePipeline;
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn generate_mesh(
self,
(greedy, opaque_mesh, vertical_stripes): Self::Supplement,
) -> MeshGen<LodStructurePipeline, &'b mut GreedyMesh<'a>, Self> {
let max_size = greedy.max_size();
// NOTE: Required because we steal two bits from the normal in the shadow uint
// in order to store the bone index. The two bits are instead taken out
// of the atlas coordinates, which is why we "only" allow 1 << 15 per
// coordinate instead of 1 << 16.
assert!(max_size.width.max(max_size.height) < 1 << 16);
let lower_bound = self.lower_bound();
let upper_bound = self.upper_bound();
assert!(
lower_bound.x <= upper_bound.x
&& lower_bound.y <= upper_bound.y
&& lower_bound.z <= upper_bound.z
);
// Lower bound coordinates must fit in an i16 (which means upper bound
// coordinates fit as integers in a f23).
assert!(
i16::try_from(lower_bound.x).is_ok()
&& i16::try_from(lower_bound.y).is_ok()
&& i16::try_from(lower_bound.z).is_ok(),
"Sprite offsets should fit in i16",
);
let greedy_size = upper_bound - lower_bound + 1;
// TODO: Should this be 16, 16, 64?
assert!(
greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 64,
"Sprite size out of bounds: {:?} ≤ (31, 31, 63)",
greedy_size - 1
);
let (flat, flat_get) = {
let (w, h, d) = (greedy_size + 2).into_tuple();
let flat = {
let vol = self;
let mut flat = vec![Cell::empty(); (w * h * d) as usize];
let mut i = 0;
for x in -1..greedy_size.x + 1 {
for y in -1..greedy_size.y + 1 {
for z in -1..greedy_size.z + 1 {
let wpos = lower_bound + Vec3::new(x, y, z);
let block = vol.get(wpos).map(|b| *b).unwrap_or(Cell::empty());
flat[i] = block;
i += 1;
}
}
}
flat
};
let flat_get = move |flat: &Vec<Cell>, Vec3 { x, y, z }| match flat
.get((x * h * d + y * d + z) as usize)
.copied()
{
Some(b) => b,
None => panic!("x {} y {} z {} d {} h {}", x, y, z, d, h),
};
(flat, flat_get)
};
// NOTE: Cast to usize is safe because of previous check, since all values fit
// into u16 which is safe to cast to usize.
let greedy_size = greedy_size.as_::<usize>();
let greedy_size_cross = greedy_size;
let draw_delta = Vec3::new(1, 1, 1);
let get_light = move |flat: &mut _, pos: Vec3<i32>| {
if flat_get(flat, pos).is_empty() {
1.0
} else {
0.0
}
};
let get_glow = |_flat: &mut _, _pos: Vec3<i32>| 0.0;
let get_color = move |flat: &mut _, pos: Vec3<i32>| {
flat_get(flat, pos).get_color().unwrap_or(Rgb::zero())
};
let get_opacity = move |flat: &mut _, pos: Vec3<i32>| flat_get(flat, pos).is_empty();
let should_draw = move |flat: &mut _, pos: Vec3<i32>, delta: Vec3<i32>, uv| {
should_draw_greedy_ao(vertical_stripes, pos, delta, uv, |vox| flat_get(flat, vox))
};
// NOTE: Fits in i16 (much lower actually) so f32 is no problem (and the final
// position, pos + mesh_delta, is guaranteed to fit in an f32).
let mesh_delta = lower_bound.as_::<f32>();
let create_opaque = |atlas_pos, pos: Vec3<f32>, norm, _meta| {
SpriteVertex::new(atlas_pos, pos + mesh_delta, norm)
};
greedy.push(GreedyConfig {
data: flat,
draw_delta,
greedy_size,
greedy_size_cross,
get_light,
get_glow,
get_opacity,
should_draw,
push_quad: |atlas_origin, dim, origin, draw_dim, norm, meta: &bool| {
opaque_mesh.push_quad(greedy::create_quad(
atlas_origin,
dim,
origin,
draw_dim,
norm,
meta,
|atlas_pos, pos, norm, &meta| create_opaque(atlas_pos, pos, norm, meta),
));
},
make_face_texel: move |flat: &mut _, pos, light, glow| {
TerrainVertex::make_col_light(light, glow, get_color(flat, pos))
},
});
(Mesh::new(), Mesh::new(), Mesh::new(), ())
}
}
impl<'a: 'b, 'b, V: 'a> Meshable<ParticlePipeline, &'b mut GreedyMesh<'a>> for V
where
V: BaseVol<Vox = Cell> + ReadVol + SizedVol,

View File

@ -23,6 +23,7 @@ pub use self::{
},
fluid::FluidPipeline,
lod_terrain::{Locals as LodTerrainLocals, LodData, LodTerrainPipeline},
lod_structure::{Instance as LodStructureInstance, Locals as LodStructureLocals, LodStructurePipeline},
particle::{Instance as ParticleInstance, ParticlePipeline},
postprocess::{
create_mesh as create_pp_mesh, Locals as PostProcessLocals, PostProcessPipeline,

View File

@ -0,0 +1,139 @@
use super::{
super::{Pipeline, TgtColorFmt, TgtDepthStencilFmt},
shadow, terrain, Globals, Light, Shadow,
};
use core::fmt;
use gfx::{
self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline,
gfx_pipeline_inner, gfx_vertex_struct_meta, state::ColorMask,
};
use vek::*;
pub use super::sprite::Vertex;
gfx_defines! {
/*
vertex Vertex {
pos: [f32; 3] = "v_pos",
// Because we try to restrict terrain sprite data to a 128×128 block
// we need an offset into the texture atlas.
atlas_pos: u32 = "v_atlas_pos",
// ____BBBBBBBBGGGGGGGGRRRRRRRR
// col: u32 = "v_col",
// ...AANNN
// A = AO
// N = Normal
norm_ao: u32 = "v_norm_ao",
}
*/
constant Locals {
nope: u32 = "nope",
}
vertex/*constant*/ Instance {
pos: [f32; 3] = "inst_pos",
}
pipeline pipe {
vbuf: gfx::VertexBuffer<Vertex> = (),
ibuf: gfx::InstanceBuffer<Instance> = (),
col_lights: gfx::TextureSampler<[f32; 4]> = "t_col_light",
locals: gfx::ConstantBuffer<Locals> = "u_locals",
globals: gfx::ConstantBuffer<Globals> = "u_globals",
lights: gfx::ConstantBuffer<Light> = "u_lights",
shadows: gfx::ConstantBuffer<Shadow> = "u_shadows",
point_shadow_maps: gfx::TextureSampler<f32> = "t_point_shadow_maps",
directed_shadow_maps: gfx::TextureSampler<f32> = "t_directed_shadow_maps",
alt: gfx::TextureSampler<[f32; 2]> = "t_alt",
horizon: gfx::TextureSampler<[f32; 4]> = "t_horizon",
noise: gfx::TextureSampler<f32> = "t_noise",
// Shadow stuff
light_shadows: gfx::ConstantBuffer<shadow::Locals> = "u_light_shadows",
tgt_color: gfx::BlendTarget<TgtColorFmt> = ("tgt_color", ColorMask::all(), gfx::preset::blend::ALPHA),
tgt_depth_stencil: gfx::DepthTarget<TgtDepthStencilFmt> = gfx::preset::depth::LESS_EQUAL_WRITE,
// tgt_depth_stencil: gfx::DepthStencilTarget<TgtDepthStencilFmt> = (gfx::preset::depth::LESS_EQUAL_WRITE,Stencil::new(Comparison::Always,0xff,(StencilOp::Keep,StencilOp::Keep,StencilOp::Keep))),
}
}
/*
impl fmt::Display for Vertex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Vertex")
.field("pos", &Vec3::<f32>::from(self.pos))
.field(
"atlas_pos",
&Vec2::new(self.atlas_pos & 0xFFFF, (self.atlas_pos >> 16) & 0xFFFF),
)
.field("norm_ao", &self.norm_ao)
.finish()
}
}
impl Vertex {
// NOTE: Limit to 16 (x) × 16 (y) × 32 (z).
#[allow(clippy::collapsible_else_if)]
pub fn new(
atlas_pos: Vec2<u16>,
pos: Vec3<f32>,
norm: Vec3<f32>, /* , col: Rgb<f32>, ao: f32 */
) -> Self {
let norm_bits = if norm.x != 0.0 {
if norm.x < 0.0 { 0 } else { 1 }
} else if norm.y != 0.0 {
if norm.y < 0.0 { 2 } else { 3 }
} else {
if norm.z < 0.0 { 4 } else { 5 }
};
Self {
// pos_norm: ((pos.x as u32) & 0x003F)
// | ((pos.y as u32) & 0x003F) << 6
// | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12
// | if meta { 1 } else { 0 } << 28
// | (norm_bits & 0x7) << 29,
pos: pos.into_array(),
atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | ((atlas_pos.y as u32) & 0xFFFF) << 16,
norm_ao: norm_bits,
}
}
}
*/
impl Instance {
pub fn new(
pos: Vec3<f32>,
) -> Self {
Self {
pos: pos.into_array(),
}
}
}
impl Default for Instance {
fn default() -> Self { Self::new(Vec3::zero()) }
}
impl Default for Locals {
fn default() -> Self { Self::new() }
}
impl Locals {
pub fn new() -> Self {
Self {
nope: 0,
}
}
}
pub struct LodStructurePipeline;
impl Pipeline for LodStructurePipeline {
type Vertex = Vertex;
}

View File

@ -2,6 +2,7 @@ pub mod clouds;
pub mod figure;
pub mod fluid;
pub mod lod_terrain;
pub mod lod_structure;
pub mod particle;
pub mod postprocess;
pub mod shadow;

View File

@ -5,7 +5,7 @@ use super::{
mesh::Mesh,
model::{DynamicModel, Model},
pipelines::{
clouds, figure, fluid, lod_terrain, particle, postprocess, shadow, skybox, sprite, terrain,
clouds, figure, fluid, lod_terrain, lod_structure, particle, postprocess, shadow, skybox, sprite, terrain,
ui, GlobalModel, Globals,
},
texture::Texture,
@ -143,6 +143,8 @@ struct Shaders {
fluid_frag_shiny: AssetHandle<Glsl>,
sprite_vert: AssetHandle<Glsl>,
sprite_frag: AssetHandle<Glsl>,
lod_structure_vert: AssetHandle<Glsl>,
lod_structure_frag: AssetHandle<Glsl>,
particle_vert: AssetHandle<Glsl>,
particle_frag: AssetHandle<Glsl>,
ui_vert: AssetHandle<Glsl>,
@ -203,6 +205,8 @@ impl assets::Compound for Shaders {
fluid_frag_shiny: AssetExt::load("voxygen.shaders.fluid-frag.shiny")?,
sprite_vert: AssetExt::load("voxygen.shaders.sprite-vert")?,
sprite_frag: AssetExt::load("voxygen.shaders.sprite-frag")?,
lod_structure_vert: AssetExt::load("voxygen.shaders.lod-structure-vert")?,
lod_structure_frag: AssetExt::load("voxygen.shaders.lod-structure-frag")?,
particle_vert: AssetExt::load("voxygen.shaders.particle-vert")?,
particle_frag: AssetExt::load("voxygen.shaders.particle-frag")?,
ui_vert: AssetExt::load("voxygen.shaders.ui-vert")?,
@ -267,6 +271,7 @@ pub struct Renderer {
terrain_pipeline: GfxPipeline<terrain::pipe::Init<'static>>,
fluid_pipeline: GfxPipeline<fluid::pipe::Init<'static>>,
sprite_pipeline: GfxPipeline<sprite::pipe::Init<'static>>,
lod_structure_pipeline: GfxPipeline<lod_structure::pipe::Init<'static>>,
particle_pipeline: GfxPipeline<particle::pipe::Init<'static>>,
ui_pipeline: GfxPipeline<ui::pipe::Init<'static>>,
lod_terrain_pipeline: GfxPipeline<lod_terrain::pipe::Init<'static>>,
@ -319,6 +324,7 @@ impl Renderer {
terrain_pipeline,
fluid_pipeline,
sprite_pipeline,
lod_structure_pipeline,
particle_pipeline,
ui_pipeline,
lod_terrain_pipeline,
@ -415,6 +421,7 @@ impl Renderer {
terrain_pipeline,
fluid_pipeline,
sprite_pipeline,
lod_structure_pipeline,
particle_pipeline,
ui_pipeline,
lod_terrain_pipeline,
@ -907,6 +914,7 @@ impl Renderer {
terrain_pipeline,
fluid_pipeline,
sprite_pipeline,
lod_structure_pipeline,
particle_pipeline,
ui_pipeline,
lod_terrain_pipeline,
@ -922,6 +930,7 @@ impl Renderer {
self.terrain_pipeline = terrain_pipeline;
self.fluid_pipeline = fluid_pipeline;
self.sprite_pipeline = sprite_pipeline;
self.lod_structure_pipeline = lod_structure_pipeline;
self.particle_pipeline = particle_pipeline;
self.ui_pipeline = ui_pipeline;
self.lod_terrain_pipeline = lod_terrain_pipeline;
@ -1673,6 +1682,69 @@ impl Renderer {
);
}
/// Queue the rendering of the provided lod structures in the upcoming
/// frame.
pub fn render_lod_structures(
&mut self,
model: &Model<lod_structure::LodStructurePipeline>,
col_lights: &Texture<ColLightFmt>,
global: &GlobalModel,
locals: &Consts<lod_structure::Locals>,
instances: &Instances<lod_structure::Instance>,
lod: &lod_terrain::LodData,
) {
let (point_shadow_maps, directed_shadow_maps) =
if let Some(shadow_map) = &mut self.shadow_map {
(
(
shadow_map.point_res.clone(),
shadow_map.point_sampler.clone(),
),
(
shadow_map.directed_res.clone(),
shadow_map.directed_sampler.clone(),
),
)
} else {
(
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
)
};
self.encoder.draw(
&gfx::Slice {
start: model.vertex_range().start,
end: model.vertex_range().end,
base_vertex: 0,
instances: Some((instances.count() as u32, 0)),
buffer: gfx::IndexBuffer::Auto,
},
&self.lod_structure_pipeline.pso,
&lod_structure::pipe::Data {
vbuf: model.vbuf.clone(),
ibuf: instances.ibuf.clone(),
col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()),
// NOTE: It would be nice if this wasn't needed and we could use a constant buffer
// offset into the sprite data. Hopefully, when we switch to wgpu we can do this,
// as it offers the exact API we want (the equivalent can be done in OpenGL using
// glBindBufferOffset).
locals: locals.buf.clone(),
globals: global.globals.buf.clone(),
lights: global.lights.buf.clone(),
shadows: global.shadows.buf.clone(),
light_shadows: global.shadow_mats.buf.clone(),
point_shadow_maps,
directed_shadow_maps,
noise: (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
alt: (lod.alt.srv.clone(), lod.alt.sampler.clone()),
horizon: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()),
tgt_color: self.tgt_color_view.clone(),
tgt_depth_stencil: (self.tgt_depth_stencil_view.clone()/* , (1, 1) */),
},
);
}
/// Queue the rendering of the provided LoD terrain model in the upcoming
/// frame.
pub fn render_lod_terrain(
@ -1880,6 +1952,7 @@ fn create_pipelines(
GfxPipeline<terrain::pipe::Init<'static>>,
GfxPipeline<fluid::pipe::Init<'static>>,
GfxPipeline<sprite::pipe::Init<'static>>,
GfxPipeline<lod_structure::pipe::Init<'static>>,
GfxPipeline<particle::pipe::Init<'static>>,
GfxPipeline<ui::pipe::Init<'static>>,
GfxPipeline<lod_terrain::pipe::Init<'static>>,
@ -2011,6 +2084,16 @@ fn create_pipelines(
gfx::state::CullFace::Back,
)?;
// Construct a pipeline for rendering LoD structures
let lod_structure_pipeline = create_pipeline(
factory,
lod_structure::pipe::new(),
&shaders.lod_structure_vert.read().0,
&shaders.lod_structure_frag.read().0,
&include_ctx,
gfx::state::CullFace::Back,
)?;
// Construct a pipeline for rendering particles
let particle_pipeline = create_pipeline(
factory,
@ -2145,6 +2228,7 @@ fn create_pipelines(
terrain_pipeline,
fluid_pipeline,
sprite_pipeline,
lod_structure_pipeline,
particle_pipeline,
ui_pipeline,
lod_terrain_pipeline,

View File

@ -2,17 +2,31 @@ use crate::{
render::{
pipelines::lod_terrain::{Locals, LodData, Vertex},
Consts, GlobalModel, LodTerrainPipeline, Mesh, Model, Quad, Renderer,
LodStructurePipeline, LodStructureLocals, LodStructureInstance, Instances, Texture, ColLightFmt, ShadowPipeline,
},
settings::Settings,
scene::SceneData,
};
use client::Client;
use common::{spiral::Spiral2d, util::srgba_to_linear};
use common::{
lod::LodZone,
terrain::TerrainChunkSize,
spiral::Spiral2d,
util::srgba_to_linear,
vol::RectVolSize,
};
use vek::*;
use hashbrown::HashMap;
pub struct Lod {
model: Option<(u32, Model<LodTerrainPipeline>)>,
locals: Consts<Locals>,
data: LodData,
structure_col_lights: Texture<ColLightFmt>,
tree_locals: Consts<LodStructureLocals>,
tree_model: Model<LodStructurePipeline>,
zones: HashMap<Vec2<i32>, Zone>,
}
// TODO: Make constant when possible.
@ -23,6 +37,25 @@ pub fn water_color() -> Rgba<f32> {
impl Lod {
pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self {
let (tree_model, structure_col_lights) = {
use crate::{mesh::{Meshable, greedy::GreedyMesh}, render::SpritePipeline};
use common::{figure::Segment, assets::{DotVoxAsset, AssetExt}};
let max_texture_size = renderer.max_texture_size();
let max_size = guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
let mut greedy = GreedyMesh::new(max_size);
let mut mesh = Mesh::new();
Meshable::<LodStructurePipeline, &mut GreedyMesh>::generate_mesh(
Segment::from(&DotVoxAsset::load_expect("voxygen.voxel.mini_tree").read().0),
(&mut greedy, &mut mesh, false),
);
(
renderer.create_model(&mesh).expect("Failed to upload sprite model data to the GPU!"),
ShadowPipeline::create_col_lights(renderer, &greedy.finalize())
.expect("Failed to upload sprite color and light data to the GPU!"),
)
};
Self {
model: None,
locals: renderer.create_consts(&[Locals::default()]).unwrap(),
@ -35,6 +68,10 @@ impl Lod {
settings.graphics.lod_detail.max(100).min(2500),
water_color().into_array().into(),
),
tree_model,
structure_col_lights,
tree_locals: renderer.create_consts(&[LodStructureLocals::default()]).unwrap(),
zones: HashMap::default(),
}
}
@ -45,7 +82,11 @@ impl Lod {
self.data.tgt_detail = (detail - detail % 2).max(100).min(2500);
}
pub fn maintain(&mut self, renderer: &mut Renderer) {
pub fn maintain(
&mut self,
renderer: &mut Renderer,
scene_data: &SceneData,
) {
if self
.model
.as_ref()
@ -59,12 +100,56 @@ impl Lod {
.unwrap(),
));
}
if let Some(zones) = scene_data.client.nearby_zones() {
for zone_pos in zones {
if let Some(Some(zone)) = scene_data.client.get_zone(zone_pos) {
self.zones
.entry(zone_pos)
.or_insert_with(|| {
let zone_wpos = zone_pos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e * (sz * LodZone::SIZE) as i32);
println!("Zone {:?} ({:?}) has {} trees", zone_pos, zone_wpos, zone.trees.len());
Zone {
tree_instances: renderer
.create_instances(&zone.trees
.iter()
.map(|tree| LodStructureInstance::new(
(zone_wpos + tree.pos.map(|e| e as i32)).map(|e| e as f32).with_z(tree.alt as f32)
))
.collect::<Vec<_>>())
.expect("Failed to upload lod tree instances to the GPU!"),
}
});
}
}
}
}
pub fn render(&self, renderer: &mut Renderer, global: &GlobalModel) {
pub fn render(
&self,
renderer: &mut Renderer,
global: &GlobalModel,
lod: &LodData,
scene_data: &SceneData,
) {
if let Some((_, model)) = self.model.as_ref() {
renderer.render_lod_terrain(&model, global, &self.locals, &self.data);
}
if let Some(zones) = scene_data.client.nearby_zones() {
for zone_pos in zones {
if let Some(zone) = self.zones.get(&zone_pos) {
renderer.render_lod_structures(
&self.tree_model,
&self.structure_col_lights,
global,
&self.tree_locals,
&zone.tree_instances,
lod,
);
}
}
}
}
}
@ -95,3 +180,7 @@ fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainPipeline> {
})
.collect()
}
struct Zone {
tree_instances: Instances<LodStructureInstance>,
}

View File

@ -686,7 +686,7 @@ impl Scene {
.expect("Failed to update post-process locals");
// Maintain LoD.
self.lod.maintain(renderer);
self.lod.maintain(renderer, &scene_data);
// Maintain the terrain.
let (_visible_bounds, visible_light_volume, visible_psr_bounds) = self.terrain.maintain(
@ -1076,7 +1076,7 @@ impl Scene {
lod,
camera_data,
);
self.lod.render(renderer, global);
self.lod.render(renderer, global, lod, scene_data);
// Render the skybox.
renderer.render_skybox(&self.skybox.model, global, &self.skybox.locals, lod);

View File

@ -86,6 +86,7 @@ impl ForestKind {
pub struct TreeAttr {
pub pos: Vec2<i32>,
pub alt_approx: f32,
pub seed: u32,
pub scale: f32,
pub forest_kind: ForestKind,

View File

@ -66,6 +66,7 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
scale,
forest_kind,
inhabited,
..
} in trees
{
let tree = if let Some(tree) = tree_cache.entry(pos).or_insert_with(|| {
@ -75,19 +76,12 @@ pub fn apply_trees_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
// Ensure that it's valid to place a *thing* here
if col.alt < col.water_level
|| col.spawn_rate < 0.9
|| col.water_dist.map(|d| d < 8.0).unwrap_or(false)
|| col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false)
{
return None;
}
// Ensure that it's valid to place a tree here
if !is_quirky && ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density
{
return None;
}
Some(Tree {
pos: Vec3::new(pos.x, pos.y, col.alt as i32),
model: 'model: {

View File

@ -25,6 +25,7 @@ pub mod config;
pub mod index;
pub mod land;
pub mod layer;
pub mod lod;
pub mod pathfinding;
pub mod sim;
pub mod sim2;
@ -37,6 +38,7 @@ pub use crate::{
canvas::{Canvas, CanvasInfo},
config::CONFIG,
land::Land,
lod::LodInfo,
};
pub use block::BlockGen;
pub use column::ColumnSample;
@ -67,6 +69,7 @@ pub enum Error {
pub struct World {
sim: sim::WorldSim,
civs: civ::Civs,
lod: LodInfo,
}
#[derive(Deserialize)]
@ -94,13 +97,17 @@ impl World {
sim2::simulate(&mut index, &mut sim);
(Self { sim, civs }, IndexOwned::new(index))
let lod = LodInfo::new(&sim);
(Self { sim, civs, lod }, IndexOwned::new(index))
}
pub fn sim(&self) -> &sim::WorldSim { &self.sim }
pub fn civs(&self) -> &civ::Civs { &self.civs }
pub fn lod(&self) -> &LodInfo { &self.lod }
pub fn tick(&self, _dt: Duration) {
// TODO
}

43
world/src/lod.rs Normal file
View File

@ -0,0 +1,43 @@
use crate::{
sim::WorldSim,
util::Grid,
};
use common::{
terrain::TerrainChunkSize,
vol::RectVolSize,
lod::{LodZone, LodTree},
};
use vek::*;
pub struct LodInfo {
zones: Grid<LodZone>,
}
impl LodInfo {
pub fn new(sim: &WorldSim) -> Self {
Self {
zones: Grid::populate_from(
sim.get_size().map(|e| (e.next_power_of_two() / LodZone::SIZE).max(1) as i32),
|pos| {
let to_wpos = |pos| pos * (TerrainChunkSize::RECT_SIZE * LodZone::SIZE).map(|e| e as i32);
let zone_wpos = to_wpos(pos);
let trees = sim
.get_trees_area(zone_wpos, to_wpos(pos + 1))
.map(|attr| LodTree {
pos: (attr.pos - zone_wpos).map(|e| e as u16),
alt: attr.alt_approx as u16,
})
// TODO: filter those outside zone bounds
.collect();
LodZone { trees }
},
),
}
}
pub fn get_zone(&self, pos: Vec2<i32>) -> Option<&LodZone> {
self.zones.get(pos)
}
}

View File

@ -2044,12 +2044,17 @@ impl WorldSim {
self.get_nearest_way(wpos, |chunk| Some(chunk.cave))
}
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
self.get_trees_area(wpos, wpos + 1)
}
/// Return an iterator over candidate tree positions (note that only some of
/// these will become trees since environmental parameters may forbid
/// them spawning).
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
pub fn get_trees_area(&self, min: Vec2<i32>, max: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
// Deterministic based on wpos
let normal_trees = std::array::IntoIter::new(self.gen_ctx.structure_gen.get(wpos))
let normal_trees = self.gen_ctx.structure_gen
.par_iter(min, max)
.filter_map(move |(pos, seed)| {
let chunk = self.get_wpos(pos)?;
let env = Environment {
@ -2061,29 +2066,40 @@ impl WorldSim {
0.0
},
};
Some(TreeAttr {
pos,
seed,
scale: 1.0,
forest_kind: *Lottery::from(
ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37)
.get(pos.map(|e| e as f64) / CLUSTER_SIZE)
+ 1.0)
/ 2.0;
(fk.proclivity(&env) * nz, Some(fk))
})
.chain(std::iter::once((0.001, None)))
.collect::<Vec<_>>(),
)
.choose_seeded(seed)
.as_ref()?,
inhabited: false,
})
});
if !chunk.is_underwater()
&& ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 < chunk.tree_density
&& chunk.spawn_rate > 0.5
{
Some(TreeAttr {
pos,
alt_approx: chunk.alt,
seed,
scale: 1.0,
forest_kind: *Lottery::from(
ForestKind::into_enum_iter()
.enumerate()
.map(|(i, fk)| {
const CLUSTER_SIZE: f64 = 48.0;
let nz = (FastNoise2d::new(i as u32 * 37)
.get(pos.map(|e| e as f64) / CLUSTER_SIZE)
+ 1.0)
/ 2.0;
(fk.proclivity(&env) * nz, Some(fk))
})
.chain(std::iter::once((0.001, None)))
.collect::<Vec<_>>(),
)
.choose_seeded(seed)
.as_ref()?,
inhabited: false,
})
} else {
None
}
})
// TODO: don't do this, it's horrible and slow
.collect::<Vec<_>>()
.into_iter();
// // For testing
// let giant_trees =