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);
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An axis-aligned bounding box; that is, a minimum and maximum coordinate triple.
This is an abstract class for any volume in any sense which can be said to define the locality of ref...
get_frame_count
Returns the number of times tick() has been called since the ClockObject was created,...
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
This is a convenience class to specialize ConfigVariable as a boolean type.
This class specializes ConfigVariable as an enumerated type.
This is a convenience class to specialize ConfigVariable as an integer type.
virtual void record_object(CullableObject *object, const CullTraverser *traverser)
This callback function is intended to be overridden by a derived class.
This collects together the pieces of data that are accumulated for each node while walking the scene ...
This object performs a depth-first traversal of the scene graph, with optional view-frustum culling,...
CullHandler * get_cull_handler() const
Returns the object that will receive the culled Geoms.
SceneSetup * get_scene() const
Returns the SceneSetup object.
The smallest atom of cull.
Defines a series of "patches", fixed-size groupings of vertices that must be processed by a tessellat...
This is an abstract base class for a family of classes that represent the fundamental geometry primit...
Defines a series of disconnected triangles.
This defines the actual numeric vertex data stored in a Geom, in the structure defined by a particula...
This object provides a high-level interface for quickly writing a sequence of numeric values from a v...
A container for geometry primitives.
This is another abstract class, for a general class of bounding volumes that actually enclose points ...
A base class for any number of different kinds of lenses, linear and otherwise.
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,...
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
This is a special kind of GeometricBoundingVolume that fills all of space.
void clear()
Frees all memory allocated for the image, and clears all its parameters (size, color,...
xel * get_array()
Directly access the underlying PNMImage array.
A lightweight class that represents a single element that may be timed and/or counted via stats.
A basic node of the scene graph or data graph.
void set_bounds(const BoundingVolume *volume)
Resets the bounding volume so that it is the indicated volume.
This is the base class for a number of render attributes (other than transform) that may be set on sc...
This represents a unique collection of RenderAttrib objects that correspond to a particular renderabl...
This object holds the camera position, etc., and other general setup information for rendering a part...
const Lens * get_lens() const
Returns the particular Lens used for rendering.
int get_viewport_width() const
Returns the width of the viewport (display region) in pixels.
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.
LPoint3 uv_to_world(const LTexCoord &coord) const
Transforms a texture coordinate to world space.
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,...
virtual bool is_renderable() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
virtual bool safe_to_combine() const
Returns true if it is generally safe to combine this particular kind of PandaNode with other kinds of...
ShaderTerrainMesh()
Constructs a new Terrain Mesh.
bool generate()
Generates the terrain mesh.
virtual bool safe_to_flatten() const
Returns true if there is some value to visiting this particular node during the cull traversal for an...
An instance of this object is returned by Texture::peek().
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
TypeHandle is the identifier used to differentiate C++ class types.
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.
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.
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.
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.
int check_power_of_two(size_t x)
Helper function to check for a power of two.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.