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_clear_color(LVector4(0));
211 _data_texture->clear_image();
219 void ShaderTerrainMesh::do_create_chunks() {
222 _base_chunk.clear_children();
225 _base_chunk.depth = 0;
228 _base_chunk.size = _size;
229 _base_chunk.edges.set(0, 0, 0, 0);
230 _base_chunk.avg_height = 0.5;
231 _base_chunk.min_height = 0.0;
232 _base_chunk.max_height = 1.0;
233 _base_chunk.last_clod = 0.0;
234 do_init_chunk(&_base_chunk);
250 void ShaderTerrainMesh::do_init_chunk(Chunk* chunk) {
251 if (chunk->size > _chunk_size) {
254 size_t child_chunk_size = chunk->size / 2;
257 for (
size_t y = 0; y < 2; ++y) {
258 for (
size_t x = 0; x < 2; ++x) {
259 Chunk* child =
new Chunk();
260 child->size = child_chunk_size;
261 child->depth = chunk->depth + 1;
262 child->x = chunk->x + x * child_chunk_size;
263 child->y = chunk->y + y * child_chunk_size;
264 do_init_chunk(child);
265 chunk->children[x + 2*y] = child;
270 for (
size_t i = 0; i < 4; ++i) {
271 chunk->children[i] =
nullptr;
292 void ShaderTerrainMesh::do_compute_bounds(Chunk* chunk) {
295 if (chunk->size == _chunk_size) {
304 #define get_xel(x, y) (data[(x) + (_size - 1 - (y)) * _size].b / (PN_stdfloat)PGM_MAXMAXVAL) 307 PN_stdfloat avg_height = 0.0, min_height = 1.0, max_height = 0.0;
308 for (
size_t x = 0; x < _chunk_size; ++x) {
309 for (
size_t y = 0; y < _chunk_size; ++y) {
312 PN_stdfloat height = get_xel(chunk->x + x, chunk->y + y);
313 avg_height += height;
314 min_height = min(min_height, height);
315 max_height = max(max_height, height);
320 avg_height /= _chunk_size * _chunk_size;
323 chunk->min_height = min_height;
324 chunk->max_height = max_height;
325 chunk->avg_height = avg_height;
328 for (
size_t y = 0; y < 2; ++y) {
329 for (
size_t x = 0; x < 2; ++x) {
330 chunk->edges.set_cell(x + 2 * y, get_xel(
331 chunk->x + x * (_chunk_size - 1),
332 chunk->y + y * (_chunk_size - 1)
342 chunk->avg_height = 0.0;
343 chunk->min_height = 1.0;
344 chunk->max_height = 0.0;
347 for (
size_t i = 0; i < 4; ++i) {
348 do_compute_bounds(chunk->children[i]);
349 chunk->avg_height += chunk->children[i]->avg_height / 4.0;
350 chunk->min_height = min(chunk->min_height, chunk->children[i]->min_height);
351 chunk->max_height = max(chunk->max_height, chunk->children[i]->max_height);
355 chunk->edges.set_x(chunk->children[0]->edges.get_x());
356 chunk->edges.set_y(chunk->children[1]->edges.get_y());
357 chunk->edges.set_z(chunk->children[2]->edges.get_z());
358 chunk->edges.set_w(chunk->children[3]->edges.get_w());
373 void ShaderTerrainMesh::do_create_chunk_geom() {
377 int size = (int)_chunk_size;
381 gvd->reserve_num_rows( (size + 3) * (size + 3) );
386 if (_generate_patches) {
393 for (
int y = -1; y <= size + 1; ++y) {
394 for (
int x = -1; x <= size + 1; ++x) {
395 LVector3 vtx_pos(x / (PN_stdfloat)size, y / (PN_stdfloat)size, 0.0f);
397 if (x == -1 || y == -1 || x == size + 1 || y == size + 1) {
398 vtx_pos.set_z(-1.0f / (PN_stdfloat)size);
399 vtx_pos.set_x(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_x())));
400 vtx_pos.set_y(max((PN_stdfloat)0, min((PN_stdfloat)1, vtx_pos.get_y())));
402 vertex_writer.add_data3(vtx_pos);
408 #define get_point_index(x, y) (((x) + 1) + (size + 3) * ((y) + 1)) 411 for (
int y = -1; y <= size; ++y) {
412 for (
int x = -1; x <= size; ++x) {
414 int tl = get_point_index(x, y);
415 int tr = get_point_index(x + 1, y);
416 int bl = get_point_index(x, y + 1);
417 int br = get_point_index(x + 1, y + 1);
420 if (stm_use_hexagonal_layout && (x + y) % 2 == 0 ) {
421 triangles->add_vertices(tl, tr, br);
422 triangles->add_vertices(tl, br, bl);
424 triangles->add_vertices(tl, tr, bl);
425 triangles->add_vertices(bl, tr, br);
430 #undef get_point_index 434 geom->add_primitive(triangles);
437 geom->clear_bounds();
471 nassertv(_data_texture !=
nullptr);
472 nassertv(_chunk_geom !=
nullptr);
474 _basic_collector.start();
479 if (_last_frame_count != frame_count) {
482 _last_frame_count = frame_count;
483 _current_view_index = 0;
487 CPT(
TransformState) modelview_transform = data.get_internal_transform(trav);
488 CPT(
RenderState) state = data._state->compose(get_state());
503 TraversalData traversal_data;
504 traversal_data.cam_bounds = cam_bounds;
505 traversal_data.model_mat = get_transform()->get_mat();
506 traversal_data.mvp_mat = modelview_transform->get_mat() * projection_mat;
507 traversal_data.emitted_chunks = 0;
508 traversal_data.storage_ptr = (ChunkDataEntry*)_data_texture->modify_ram_image().p();
512 traversal_data.storage_ptr += _data_texture->get_x_size() * _current_view_index;
514 if (_update_enabled) {
516 _lod_collector.start();
517 do_traverse(&_base_chunk, &traversal_data);
518 _lod_collector.stop();
523 traversal_data.emitted_chunks = _data_texture->get_x_size();
527 CPT(
RenderAttrib) current_shader_attrib = state->get_attrib_def(ShaderAttrib::get_class_slot());
530 if (!DCAST(
ShaderAttrib, current_shader_attrib)->has_shader()) {
531 shader_terrain_cat.warning() <<
"No shader set on the terrain! You need to set the appropriate shader!" << endl;
535 nassertv(current_shader_attrib !=
nullptr);
537 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
538 ShaderInput(
"ShaderTerrainMesh.terrain_size", LVecBase2i(_size)));
539 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
540 ShaderInput(
"ShaderTerrainMesh.chunk_size", LVecBase2i(_chunk_size)));
541 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
542 ShaderInput(
"ShaderTerrainMesh.view_index", LVecBase2i(_current_view_index)));
543 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
544 ShaderInput(
"ShaderTerrainMesh.data_texture", _data_texture));
545 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_shader_input(
546 ShaderInput(
"ShaderTerrainMesh.heightfield", _heightfield_tex));
547 current_shader_attrib = DCAST(
ShaderAttrib, current_shader_attrib)->set_instance_count(
548 traversal_data.emitted_chunks);
550 state = state->set_attrib(current_shader_attrib, 10000);
557 ++_current_view_index;
559 if (_current_view_index > (
size_t)stm_max_views) {
560 shader_terrain_cat.error() <<
"More views than supported! Increase the stm-max-views config variable!" << endl;
563 _basic_collector.stop();
580 void ShaderTerrainMesh::do_traverse(Chunk* chunk, TraversalData* data,
bool fully_visible) {
583 if (!fully_visible) {
586 PN_stdfloat scale = 1.0 / (PN_stdfloat)_size;
587 LPoint3 bb_min(chunk->x * scale, chunk->y * scale, chunk->min_height);
588 LPoint3 bb_max((chunk->x + chunk->size) * scale, (chunk->y + chunk->size) * scale, chunk->max_height);
592 int intersection = data->cam_bounds->contains(&bbox);
594 if (intersection == BoundingVolume::IF_no_intersection) {
602 fully_visible = (intersection & BoundingVolume::IF_all) != 0;
609 if (do_check_lod_matches(chunk, data) || chunk->size == _chunk_size) {
610 do_emit_chunk(chunk, data);
613 for (
size_t i = 0; i < 4; ++i) {
614 do_traverse(chunk->children[i], data, fully_visible);
632 bool ShaderTerrainMesh::do_check_lod_matches(Chunk* chunk, TraversalData* data) {
635 LVector2 projected_points[4];
636 for (
size_t y = 0; y < 2; ++y) {
637 for (
size_t x = 0; x < 2; ++x) {
640 LVector3 edge_pos = LVector3(
641 (PN_stdfloat)(chunk->x + x * (chunk->size - 1)) / (PN_stdfloat)_size,
642 (PN_stdfloat)(chunk->y + y * (chunk->size - 1)) / (PN_stdfloat)_size,
643 chunk->edges.get_cell(x + 2 * y)
645 LVector4 projected = data->mvp_mat.xform(LVector4(edge_pos, 1.0));
646 if (projected.get_w() == 0.0) {
647 projected.set(0.0, 0.0, -1.0, 1.0f);
649 projected *= 1.0 / projected.get_w();
650 projected_points[x + 2 * y].set(
651 projected.get_x() * data->screen_size.get_x(),
652 projected.get_y() * data->screen_size.get_y());
657 PN_stdfloat edge_top = (projected_points[1] - projected_points[3]).length_squared();
658 PN_stdfloat edge_right = (projected_points[0] - projected_points[2]).length_squared();
659 PN_stdfloat edge_bottom = (projected_points[2] - projected_points[3]).length_squared();
660 PN_stdfloat edge_left = (projected_points[0] - projected_points[1]).length_squared();
663 PN_stdfloat max_edge = max(edge_top, max(edge_right, max(edge_bottom, edge_left)));
667 max_edge = csqrt(max_edge);
669 PN_stdfloat tesselation_factor = (max_edge / _target_triangle_width) / (PN_stdfloat)_chunk_size;
670 PN_stdfloat clod_factor = max(0.0, min(1.0, 2.0 - tesselation_factor));
673 chunk->last_clod = clod_factor;
675 return tesselation_factor <= 2.0;
687 void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
688 if (data->emitted_chunks >= _data_texture->get_x_size()) {
691 if (data->emitted_chunks == _data_texture->get_x_size()) {
692 shader_terrain_cat.error() <<
"Too many chunks in the terrain! Consider lowering the desired LOD, or increase the stm-max-chunk-count variable." << endl;
693 data->emitted_chunks++;
698 ChunkDataEntry& data_entry = *data->storage_ptr;
699 data_entry.x = chunk->x;
700 data_entry.y = chunk->y;
701 data_entry.size = chunk->size / _chunk_size;
702 data_entry.clod = chunk->last_clod;
704 data->emitted_chunks++;
719 nassertr(_heightfield_tex !=
nullptr, LPoint3(0));
720 nassertr(_heightfield_tex->has_ram_image(), LPoint3(0));
723 nassertr(peeker !=
nullptr, LPoint3(0));
726 if (!peeker->lookup_bilinear(result, coord.get_x(), coord.get_y())) {
727 shader_terrain_cat.error() <<
"UV out of range, cant transform to world!" << endl;
730 LPoint3 unit_point(coord.get_x(), coord.get_y(), result.get_x());
731 return get_transform()->get_mat().xform_point_general(unit_point);
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
A basic node of the scene graph or data graph.
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
CullHandler * get_cull_handler() const
Returns the object that will receive the culled Geoms.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
A base class for any number of different kinds of lenses, linear and otherwise.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void set_bounds(const BoundingVolume *volume)
Resets the bounding volume so that it is the indicated volume.
This is a convenience class to specialize ConfigVariable as a boolean type.
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
virtual bool safe_to_combine() const
Returns true if it is generally safe to combine this particular kind of PandaNode with other kinds of...
virtual bool is_renderable() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This collects together the pieces of data that are accumulated for each node while walking the scene ...
const Lens * get_lens() const
Returns the particular Lens used for rendering.
xel * get_array()
Directly access the underlying PNMImage array.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const TransformState * get_camera_transform() const
Returns the position of the camera relative to the starting node.
int get_viewport_height() const
Returns the height of the viewport (display region) in pixels.
virtual bool safe_to_flatten() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
A lightweight class that represents a single element that may be timed and/or counted via stats.
This is another abstract class, for a general class of bounding volumes that actually enclose points ...
LPoint3 uv_to_world(const LTexCoord &coord) const
Transforms a texture coordinate to world space.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Defines a series of "patches", fixed-size groupings of vertices that must be processed by a tessellat...
The smallest atom of cull.
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void record_object(CullableObject *object, const CullTraverser *traverser)
This callback function is intended to be overridden by a derived class.
int get_viewport_width() const
Returns the width of the viewport (display region) in pixels.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
A container for geometry primitives.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class specializes ConfigVariable as an enumerated type.
bool generate()
Generates the terrain mesh.
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
An instance of this object is returned by Texture::peek().
virtual void add_for_draw(CullTraverser *trav, CullTraverserData &data)
Adds the node's contents to the CullResult we are building up during the cull traversal,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
const LMatrix4 & get_projection_mat(StereoChannel channel=SC_mono) const
Returns the complete transformation matrix from a 3-d point in space to a point on the film,...
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
SceneSetup * get_scene() const
Returns the SceneSetup object.
This is a convenience class to specialize ConfigVariable as an integer type.
int check_power_of_two(size_t x)
Helper function to check for a power of two.
Defines a series of disconnected triangles.
This is a special kind of GeometricBoundingVolume that fills all of space.
ShaderTerrainMesh()
Constructs a new Terrain Mesh.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
TypeHandle is the identifier used to differentiate C++ class types.
This object holds the camera position, etc., and other general setup information for rendering a part...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.