Provide a default material for VoxelMesherTransvoxel with variable-lod

master
Marc Gilleron 2022-06-26 17:21:05 +01:00
parent 5a05854fde
commit d9b74359f8
7 changed files with 149 additions and 3 deletions

View File

@ -0,0 +1,57 @@
#include "transvoxel_shader_minimal.h"
namespace zylann::voxel::transvoxel_shader_minimal {
// clang-format off
const char *GDSHADER_SOURCE =
"shader_type spatial;\n"
"\n"
"// From Voxel Tools API\n"
"uniform int u_transition_mask;\n"
"\n"
"float get_transvoxel_secondary_factor(int idata) {\n"
" int cell_border_mask = (idata >> 0) & 63; // Which sides the cell is touching\n"
" int vertex_border_mask = (idata >> 8) & 63; // Which sides the vertex is touching\n"
" // If the vertex is near a side where there is a low-resolution neighbor,\n"
" // move it to secondary position\n"
" int m = u_transition_mask & cell_border_mask;\n"
" float t = float(m != 0);\n"
" // If the vertex lies on one or more sides, and at least one side has no low-resolution neighbor,\n"
" // don't move the vertex.\n"
" t *= float((vertex_border_mask & ~u_transition_mask) == 0);\n"
" \n"
" // Debugging\n"
" //t *= 0.5 + 0.5 * sin(TIME * 4.0);\n"
" //t *= 2.0;\n"
"\n"
" return t;\n"
"}\n"
"\n"
"vec3 get_transvoxel_position(vec3 vertex_pos, vec4 fdata) {\n"
" int idata = floatBitsToInt(fdata.a);\n"
"\n"
" // Move vertices to smooth transitions\n"
" float secondary_factor = get_transvoxel_secondary_factor(idata);\n"
" vec3 secondary_position = fdata.xyz;\n"
" vec3 pos = mix(vertex_pos, secondary_position, secondary_factor);\n"
"\n"
" // If the mesh combines transitions and the vertex belongs to a transition,\n"
" // when that transition isn't active we change the position of the vertices so\n"
" // all triangles will be degenerate and won't be visible.\n"
" // This is an alternative to rendering them separately,\n"
" // which has less draw calls and less mesh resources to create in Godot.\n"
" // Ideally I would tweak the index buffer like LOD does but Godot does not\n"
" // expose anything to use it that way.\n"
" int itransition = (idata >> 16) & 0xff; // Is the vertex on a transition mesh?\n"
" float transition_cull = float(itransition == 0 || (itransition & u_transition_mask) != 0);\n"
" pos *= transition_cull;\n"
"\n"
" return pos;\n"
"}\n"
"\n"
"void vertex() {\n"
" VERTEX = get_transvoxel_position(VERTEX, CUSTOM0);\n"
"}\n"
"\n";
} // namespace zylann::voxel::transvoxel_shader_minimal

View File

@ -0,0 +1,10 @@
#ifndef VOXEL_TRANSVOXEL_SHADER_MINIMAL_H
#define VOXEL_TRANSVOXEL_SHADER_MINIMAL_H
namespace zylann::voxel::transvoxel_shader_minimal {
extern const char *GDSHADER_SOURCE;
} // namespace zylann::voxel::transvoxel_shader_minimal
#endif // VOXEL_TRANSVOXEL_SHADER_MINIMAL_H

View File

@ -5,10 +5,27 @@
#include "../../thirdparty/meshoptimizer/meshoptimizer.h"
#include "../../util/godot/funcs.h"
#include "../../util/profiling.h"
#include "transvoxel_shader_minimal.h"
#include "transvoxel_tables.cpp"
namespace zylann::voxel {
namespace {
Ref<ShaderMaterial> g_minimal_shader_material;
}
void VoxelMesherTransvoxel::load_static_resources() {
Ref<Shader> shader;
shader.instantiate();
shader->set_code(transvoxel_shader_minimal::GDSHADER_SOURCE);
g_minimal_shader_material.instantiate();
g_minimal_shader_material->set_shader(shader);
}
void VoxelMesherTransvoxel::free_static_resources() {
g_minimal_shader_material.unref();
}
VoxelMesherTransvoxel::VoxelMesherTransvoxel() {
set_padding(transvoxel::MIN_PADDING, transvoxel::MAX_PADDING);
}
@ -340,6 +357,10 @@ bool VoxelMesherTransvoxel::get_transitions_enabled() const {
return _transitions_enabled;
}
Ref<ShaderMaterial> VoxelMesherTransvoxel::get_default_lod_material() const {
return g_minimal_shader_material;
}
void VoxelMesherTransvoxel::_bind_methods() {
ClassDB::bind_method(D_METHOD("build_transition_mesh", "voxel_buffer", "direction"),
&VoxelMesherTransvoxel::build_transition_mesh);

View File

@ -50,6 +50,11 @@ public:
void set_transitions_enabled(bool enable);
bool get_transitions_enabled() const;
Ref<ShaderMaterial> get_default_lod_material() const override;
static void load_static_resources();
static void free_static_resources();
// Not sure if that's necessary, currently transitions are either combined or not generated
// enum TransitionMode {
// // No transition meshes will be generated

View File

@ -103,15 +103,25 @@ public:
virtual Ref<Material> get_material_by_index(unsigned int i) const;
#ifdef TOOLS_ENABLED
// If the mesher has problems, messages may be returned by this method so they can be shown to the user.
virtual void get_configuration_warnings(TypedArray<String> &out_warnings) const {}
#endif
// Returns `true` if the mesher generates a separate mesh for collisions.
// Returns `true` if the mesher generates specific data for mesh collisions, which will be found in
// `CollisionSurface`.
// If `false`, the rendering mesh may be used as collider.
virtual bool is_generating_collision_surface() const {
return false;
}
// Gets a special default material to be used to render meshes produced with this mesher, when variable level of
// detail is used. If null, standard materials or default Godot shaders can be used. This is mostly to provide a
// default shader that looks ok. Users are still expected to tweak them if need be.
// Such material is not meant to be modified.
virtual Ref<ShaderMaterial> get_default_lod_material() const {
return Ref<ShaderMaterial>();
}
protected:
static void _bind_methods();

View File

@ -136,6 +136,8 @@ void initialize_voxel_module(ModuleInitializationLevel p_level) {
VoxelMetadataFactory::get_singleton().add_constructor_by_type<gd::VoxelMetadataVariant>(
gd::METADATA_TYPE_VARIANT);
VoxelMesherTransvoxel::load_static_resources();
// TODO Can I prevent users from instancing it? is "register_virtual_class" correct for a class that's not
// abstract?
ClassDB::register_class<gd::VoxelServer>();
@ -266,6 +268,7 @@ void uninitialize_voxel_module(ModuleInitializationLevel p_level) {
// users can write custom generators, which run inside threads, and these threads are hosted in the server...
// See https://github.com/Zylann/godot_voxel/issues/189
VoxelMesherTransvoxel::free_static_resources();
VoxelStringNames::destroy_singleton();
VoxelGraphNodeDB::destroy_singleton();
gd::VoxelServer::destroy_singleton();

View File

@ -177,6 +177,24 @@ Ref<Material> VoxelLodTerrain::get_material() const {
void VoxelLodTerrain::set_material(Ref<Material> p_material) {
// TODO Update existing block surfaces
_material = p_material;
#ifdef TOOLS_ENABLED
// Create a fork of the default shader if a new empty ShaderMaterial is assigned
if (Engine::get_singleton()->is_editor_hint()) {
Ref<ShaderMaterial> sm = p_material;
if (sm.is_valid() && sm->get_shader().is_null() && _mesher.is_valid()) {
Ref<ShaderMaterial> default_sm = _mesher->get_default_lod_material();
if (default_sm.is_valid()) {
Ref<Shader> default_shader = default_sm->get_shader();
ZN_ASSERT_RETURN(default_shader.is_valid());
Ref<Shader> shader_copy = default_shader->duplicate();
sm->set_shader(shader_copy);
}
}
update_configuration_warnings();
}
#endif
}
unsigned int VoxelLodTerrain::get_data_block_size() const {
@ -1503,6 +1521,14 @@ void VoxelLodTerrain::apply_mesh_update(VoxelServer::BlockMeshOutput &ob) {
block->set_world(get_world_3d());
Ref<ShaderMaterial> shader_material = _material;
if (_material.is_null()) {
Ref<ShaderMaterial> default_material = _mesher->get_default_lod_material();
if (default_material.is_valid()) {
shader_material = default_material;
}
}
if (shader_material.is_valid() && block->get_shader_material().is_null()) {
ZN_PROFILE_SCOPE_NAMED("Add ShaderMaterial");
@ -1859,8 +1885,22 @@ TypedArray<String> VoxelLodTerrain::get_configuration_warnings() const {
return warnings;
}
Ref<VoxelMesher> mesher = get_mesher();
if (mesher.is_valid() && !mesher->supports_lod()) {
warnings.append(TTR("The assigned mesher does not support level of detail (LOD), results may be unexpected."));
if (mesher.is_valid()) {
if (!mesher->supports_lod()) {
warnings.append(
TTR("The assigned mesher ({0}) does not support level of detail (LOD), results may be unexpected.")
.format(varray(mesher->get_class())));
}
if (_material.is_valid() && mesher->get_default_lod_material().is_valid()) {
Ref<ShaderMaterial> sm = _material;
if (sm.is_null()) {
warnings.append(
TTR("The current mesher ({0}) requires custom shader code to render properly. The current "
"material might not be appropriate. Hint: you can assign a newly created {1} to fork the "
"default shader.")
.format(varray(mesher->get_class(), ShaderMaterial::get_class_static())));
}
}
}
return warnings;
}