42 (
"stm-use-hexagonal-layout",
false,
43 PRC_DESC(
"Set this to true to use a hexagonal vertex layout. This approximates "
44 "the heightfield in a better way, however the CLOD transitions might be "
45 "visible due to the vertices not matching exactly."));
48 (
"stm-max-chunk-count", 2048,
49 PRC_DESC(
"Controls the maximum amount of chunks the Terrain can display. If you use "
50 "a high LOD, you might have to increment this value. The lower this value is "
51 "the less data has to be transferred to the GPU."));
55 PRC_DESC(
"Controls the maximum amount of different views the Terrain can be rendered "
56 "with. Each camera rendering the terrain corresponds to a view. Lowering this "
57 "value will reduce the data that has to be transferred to the GPU."));
60 (
"stm-heightfield-minfilter", SamplerState::FT_linear,
61 PRC_DESC(
"This specifies the minfilter that is applied for a heightfield texture. This "
62 "can be used to create heightfield that is visual correct with collision "
63 "geometry (for example bullet terrain mesh) by changing it to nearest"));
66 (
"stm-heightfield-magfilter", SamplerState::FT_linear,
67 PRC_DESC(
"This specifies the magfilter that is applied for a heightfield texture. This "
68 "can be used to create heightfield that is visual correct with collision "
69 "geometry (for example bullet terrain mesh) by changing it to nearest"));
71 PStatCollector ShaderTerrainMesh::_basic_collector(
"Cull:ShaderTerrainMesh:Setup");
72 PStatCollector ShaderTerrainMesh::_lod_collector(
"Cull:ShaderTerrainMesh:CollectLOD");
74 NotifyCategoryDef(shader_terrain,
"");
87 return ((x != 0) && ((x & (~x + 1)) == x));
100 _generate_patches(false),
101 _data_texture(nullptr),
102 _chunk_geom(nullptr),
103 _current_view_index(0),
104 _last_frame_count(-1),
105 _target_triangle_width(10.0f),
106 _update_enabled(true),
107 _heightfield_tex(nullptr)
126 if (!do_check_heightfield())
130 shader_terrain_cat.error() <<
"Invalid chunk size! Has to be >= 8 and a power of two!" << endl;
134 if (_chunk_size > _size / 4) {
135 shader_terrain_cat.error() <<
"Chunk size too close or greater than the actual terrain size!" << endl;
139 do_extract_heightfield();
141 do_compute_bounds(&_base_chunk);
142 do_create_chunk_geom();
143 do_init_data_texture();
147 _heightfield.
clear();
158 void ShaderTerrainMesh::do_extract_heightfield() {
159 if (!_heightfield_tex->has_ram_image()) {
160 _heightfield_tex->reload();
163 _heightfield_tex->store(_heightfield);
166 shader_terrain_cat.warning() <<
"Using non 16-bit heightfield!" << endl;
168 _heightfield_tex->set_format(Texture::F_r16);
170 _heightfield_tex->set_minfilter(stm_heightfield_minfilter);
171 _heightfield_tex->set_magfilter(stm_heightfield_magfilter);
172 _heightfield_tex->set_wrap_u(SamplerState::WM_clamp);
173 _heightfield_tex->set_wrap_v(SamplerState::WM_clamp);
184 bool ShaderTerrainMesh::do_check_heightfield() {
185 if (_heightfield_tex->get_x_size() != _heightfield_tex->get_y_size()) {
186 shader_terrain_cat.error() <<
"Only square heightfields are supported!";
190 _size = _heightfield_tex->get_x_size();
192 shader_terrain_cat.error() <<
"Invalid heightfield! Needs to be >= 32 and a power of two (was: "
193 << _size <<
")!" << endl;
207 void ShaderTerrainMesh::do_init_data_texture() {
208 _data_texture =
new Texture(
"TerrainDataTexture");
209 _data_texture->setup_2d_texture(stm_max_chunk_count, stm_max_views, Texture::T_float, Texture::F_rgba32);
210 _data_texture->set_compression(Texture::CM_off);
211 _data_texture->set_clear_color(LVector4(0));
212 _data_texture->clear_image();
220 void ShaderTerrainMesh::do_create_chunks() {
223 _base_chunk.clear_children();
226 _base_chunk.depth = 0;
229 _base_chunk.size = _size;
230 _base_chunk.edges.set(0, 0, 0, 0);
231 _base_chunk.avg_height = 0.5;
232 _base_chunk.min_height = 0.0;
233 _base_chunk.max_height = 1.0;
234 _base_chunk.last_clod = 0.0;
235 do_init_chunk(&_base_chunk);
251 void ShaderTerrainMesh::do_init_chunk(Chunk* chunk) {
252 if (chunk->size > _chunk_size) {
255 size_t child_chunk_size = chunk->size / 2;
258 for (
size_t y = 0; y < 2; ++y) {
259 for (
size_t x = 0; x < 2; ++x) {
260 Chunk* child =
new Chunk();
261 child->size = child_chunk_size;
262 child->depth = chunk->depth + 1;
263 child->x = chunk->x + x * child_chunk_size;
264 child->y = chunk->y + y * child_chunk_size;
265 do_init_chunk(child);
266 chunk->children[x + 2*y] = child;
271 for (
size_t i = 0; i < 4; ++i) {
272 chunk->children[i] =
nullptr;
293 void ShaderTerrainMesh::do_compute_bounds(Chunk* chunk) {
296 if (chunk->size == _chunk_size) {
305 #define get_xel(x, y) (data[(x) + (_size - 1 - (y)) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL)
308 PN_stdfloat avg_height = 0.0, min_height = 1.0, max_height = 0.0;
309 for (
size_t x = 0; x < _chunk_size; ++x) {
310 for (
size_t y = 0; y < _chunk_size; ++y) {
313 PN_stdfloat height = get_xel(chunk->x + x, chunk->y + y);
314 avg_height += height;
315 min_height = min(min_height, height);
316 max_height = max(max_height, height);
321 avg_height /= _chunk_size * _chunk_size;
324 chunk->min_height = min_height;
325 chunk->max_height = max_height;
326 chunk->avg_height = avg_height;
329 for (
size_t y = 0; y < 2; ++y) {
330 for (
size_t x = 0; x < 2; ++x) {
331 chunk->edges.set_cell(x + 2 * y, get_xel(
332 chunk->x + x * (_chunk_size - 1),
333 chunk->y + y * (_chunk_size - 1)
343 chunk->avg_height = 0.0;
344 chunk->min_height = 1.0;
345 chunk->max_height = 0.0;
348 for (
size_t i = 0; i < 4; ++i) {
349 do_compute_bounds(chunk->children[i]);
350 chunk->avg_height += chunk->children[i]->avg_height / 4.0;
351 chunk->min_height = min(chunk->min_height, chunk->children[i]->min_height);
352 chunk->max_height = max(chunk->max_height, chunk->children[i]->max_height);
356 chunk->edges.set_x(chunk->children[0]->edges.get_x());
357 chunk->edges.set_y(chunk->children[1]->edges.get_y());
358 chunk->edges.set_z(chunk->children[2]->edges.get_z());
359 chunk->edges.set_w(chunk->children[3]->edges.get_w());
374 void ShaderTerrainMesh::do_create_chunk_geom() {
378 int size = (int)_chunk_size;
382 gvd->reserve_num_rows( (size + 3) * (size + 3) );
387 if (_generate_patches) {
394 for (
int y = -1; y <= size + 1; ++y) {
395 for (
int x = -1; x <= size + 1; ++x) {
396 LVector3 vtx_pos(x / (PN_stdfloat)size, y / (PN_stdfloat)size, 0.0f);
398 if (x == -1 || y == -1 || x == size + 1 || y == size + 1) {
399 vtx_pos.set_z(-1.0f / (PN_stdfloat)size);
400 vtx_pos.set_x(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_x())));
401 vtx_pos.set_y(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_y())));
403 vertex_writer.add_data3(vtx_pos);
409 #define get_point_index(x, y) (((x) + 1) + (size + 3) * ((y) + 1))
412 for (
int y = -1; y <= size; ++y) {
413 for (
int x = -1; x <= size; ++x) {
415 int tl = get_point_index(x, y);
416 int tr = get_point_index(x + 1, y);
417 int bl = get_point_index(x, y + 1);
418 int br = get_point_index(x + 1, y + 1);
421 if (stm_use_hexagonal_layout && (x + y) % 2 == 0 ) {
422 triangles->add_vertices(tl, tr, br);
423 triangles->add_vertices(tl, br, bl);
425 triangles->add_vertices(tl, tr, bl);
426 triangles->add_vertices(bl, tr, br);
431 #undef get_point_index
435 geom->add_primitive(triangles);
438 geom->clear_bounds();
472 nassertv(_data_texture !=
nullptr);
473 nassertv(_chunk_geom !=
nullptr);
475 _basic_collector.start();
480 if (_last_frame_count != frame_count) {
483 _last_frame_count = frame_count;
484 _current_view_index = 0;
488 CPT(
TransformState) modelview_transform = data.get_internal_transform(trav);
489 CPT(
RenderState) state = data._state->compose(get_state());
504 TraversalData traversal_data;
505 traversal_data.cam_bounds = cam_bounds;
506 traversal_data.model_mat = get_transform()->get_mat();
507 traversal_data.mvp_mat = modelview_transform->get_mat() * projection_mat;
508 traversal_data.emitted_chunks = 0;
509 traversal_data.storage_ptr = (ChunkDataEntry*)_data_texture->modify_ram_image().p();
513 traversal_data.storage_ptr += _data_texture->get_x_size() * _current_view_index;
515 if (_update_enabled) {
517 _lod_collector.start();
518 do_traverse(&_base_chunk, &traversal_data);
519 _lod_collector.stop();
524 traversal_data.emitted_chunks = _data_texture->get_x_size();
528 CPT(
RenderAttrib) current_shader_attrib = state->get_attrib_def(ShaderAttrib::get_class_slot());
531 if (!DCAST(
ShaderAttrib, current_shader_attrib)->has_shader()) {
532 shader_terrain_cat.warning() <<
"No shader set on the terrain! You need to set the appropriate shader!" << endl;
536 nassertv(current_shader_attrib !=
nullptr);
538 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
539 ShaderInput(
"ShaderTerrainMesh.terrain_size", LVecBase2i(_size)));
540 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
541 ShaderInput(
"ShaderTerrainMesh.chunk_size", LVecBase2i(_chunk_size)));
542 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
543 ShaderInput(
"ShaderTerrainMesh.view_index", LVecBase2i(_current_view_index)));
544 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
545 ShaderInput(
"ShaderTerrainMesh.data_texture", _data_texture));
546 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
547 ShaderInput(
"ShaderTerrainMesh.heightfield", _heightfield_tex));
548 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_instance_count(
549 traversal_data.emitted_chunks);
551 state = state->set_attrib(current_shader_attrib, 10000);
558 ++_current_view_index;
560 if (_current_view_index > (
size_t)stm_max_views) {
561 shader_terrain_cat.error() <<
"More views than supported! Increase the stm-max-views config variable!" << endl;
564 _basic_collector.stop();
581 void ShaderTerrainMesh::do_traverse(Chunk* chunk, TraversalData* data,
bool fully_visible) {
584 if (!fully_visible) {
587 PN_stdfloat scale = 1.0 / (PN_stdfloat)_size;
588 LPoint3 bb_min(chunk->x * scale, chunk->y * scale, chunk->min_height);
589 LPoint3 bb_max((chunk->x + chunk->size) * scale, (chunk->y + chunk->size) * scale, chunk->max_height);
593 int intersection = data->cam_bounds->contains(&bbox);
595 if (intersection == BoundingVolume::IF_no_intersection) {
603 fully_visible = (intersection & BoundingVolume::IF_all) != 0;
610 if (do_check_lod_matches(chunk, data) || chunk->size == _chunk_size) {
611 do_emit_chunk(chunk, data);
614 for (
size_t i = 0; i < 4; ++i) {
615 do_traverse(chunk->children[i], data, fully_visible);
633 bool ShaderTerrainMesh::do_check_lod_matches(Chunk* chunk, TraversalData* data) {
636 LVector2 projected_points[4];
637 for (
size_t y = 0; y < 2; ++y) {
638 for (
size_t x = 0; x < 2; ++x) {
641 LVector3 edge_pos = LVector3(
642 (PN_stdfloat)(chunk->x + x * (chunk->size - 1)) / (PN_stdfloat)_size,
643 (PN_stdfloat)(chunk->y + y * (chunk->size - 1)) / (PN_stdfloat)_size,
644 chunk->edges.get_cell(x + 2 * y)
646 LVector4 projected = data->mvp_mat.xform(LVector4(edge_pos, 1.0));
647 if (projected.get_w() == 0.0) {
648 projected.set(0.0, 0.0, -1.0, 1.0f);
650 projected *= 1.0 / projected.get_w();
651 projected_points[x + 2 * y].set(
652 projected.get_x() * data->screen_size.get_x(),
653 projected.get_y() * data->screen_size.get_y());
658 PN_stdfloat edge_top = (projected_points[1] - projected_points[3]).length_squared();
659 PN_stdfloat edge_right = (projected_points[0] - projected_points[2]).length_squared();
660 PN_stdfloat edge_bottom = (projected_points[2] - projected_points[3]).length_squared();
661 PN_stdfloat edge_left = (projected_points[0] - projected_points[1]).length_squared();
664 PN_stdfloat max_edge = max(edge_top, max(edge_right, max(edge_bottom, edge_left)));
668 max_edge = csqrt(max_edge);
670 PN_stdfloat tesselation_factor = (max_edge / _target_triangle_width) / (PN_stdfloat)_chunk_size;
671 PN_stdfloat clod_factor = max(0.0, min(1.0, 2.0 - tesselation_factor));
674 chunk->last_clod = clod_factor;
676 return tesselation_factor <= 2.0;
688 void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
689 if (data->emitted_chunks >= _data_texture->get_x_size()) {
692 if (data->emitted_chunks == _data_texture->get_x_size()) {
693 shader_terrain_cat.error() <<
"Too many chunks in the terrain! Consider lowering the desired LOD, or increase the stm-max-chunk-count variable." << endl;
694 data->emitted_chunks++;
699 ChunkDataEntry& data_entry = *data->storage_ptr;
700 data_entry.x = chunk->x;
701 data_entry.y = chunk->y;
702 data_entry.size = chunk->size / _chunk_size;
703 data_entry.clod = chunk->last_clod;
705 data->emitted_chunks++;
720 nassertr(_heightfield_tex !=
nullptr, LPoint3(0));
721 nassertr(_heightfield_tex->has_ram_image(), LPoint3(0));
724 nassertr(peeker !=
nullptr, LPoint3(0));
727 if (!peeker->lookup_bilinear(result, coord.get_x(), coord.get_y())) {
728 shader_terrain_cat.error() <<
"UV out of range, cant transform to world!" << endl;
731 LPoint3 unit_point(coord.get_x(), coord.get_y(), result.get_x());
732 return get_transform()->get_mat().xform_point_general(unit_point);