From dac2bfc7b9fd4d937ca551c7aad9febe6414c088 Mon Sep 17 00:00:00 2001 From: tobspr Date: Sat, 30 Apr 2016 08:35:24 +0200 Subject: [PATCH] Support for setting a heightfield handle directly on the ShaderTerrainMesh --- panda/src/grutil/shaderTerrainMesh.I | 20 +++++---- panda/src/grutil/shaderTerrainMesh.cxx | 58 ++++++++++++-------------- panda/src/grutil/shaderTerrainMesh.h | 11 +++-- samples/shader-terrain/main.py | 24 ++++++----- 4 files changed, 55 insertions(+), 58 deletions(-) diff --git a/panda/src/grutil/shaderTerrainMesh.I b/panda/src/grutil/shaderTerrainMesh.I index 1f6e90f03c..da4471201f 100644 --- a/panda/src/grutil/shaderTerrainMesh.I +++ b/panda/src/grutil/shaderTerrainMesh.I @@ -12,26 +12,28 @@ */ /** - * @brief Sets the path to the heightfield - * @details This sets the path to the terrain heightfield. It should be 16bit + * @brief Sets the heightfield texture + * @details This sets the heightfield texture. It should be 16bit * single channel, and have a power-of-two resolution greater than 32. * Common sizes are 2048x2048 or 4096x4096. * - * @param filename Path to the heightfield + * You should call generate() after setting the heightfield. + * + * @param filename Heightfield texture */ -INLINE void ShaderTerrainMesh::set_heightfield_filename(const Filename& filename) { - _heightfield_source = filename; +INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) { + _heightfield_tex = heightfield; } /** - * @brief Returns the heightfield path - * @details This returns the path of the terrain heightfield, previously set with + * @brief Returns the heightfield + * @details This returns the terrain heightfield, previously set with * set_heightfield() * * @return Path to the heightfield */ -INLINE const Filename& ShaderTerrainMesh::get_heightfield_filename() const { - return _heightfield_source; +INLINE Texture* ShaderTerrainMesh::get_heightfield() const { + return _heightfield_tex; } /** diff --git a/panda/src/grutil/shaderTerrainMesh.cxx b/panda/src/grutil/shaderTerrainMesh.cxx index c8e247e24e..8e655de000 100644 --- a/panda/src/grutil/shaderTerrainMesh.cxx +++ b/panda/src/grutil/shaderTerrainMesh.cxx @@ -35,7 +35,7 @@ #include "typeHandle.h" ConfigVariableBool stm_use_hexagonal_layout -("stm-use-hexagonal-layout", true, +("stm-use-hexagonal-layout", false, PRC_DESC("Set this to true to use a hexagonal vertex layout. This approximates " "the heightfield in a better way, however the CLOD transitions might be " "visible due to the vertices not matching exactly.")); @@ -81,7 +81,6 @@ ShaderTerrainMesh::ShaderTerrainMesh() : PandaNode("ShaderTerrainMesh"), _size(0), _chunk_size(32), - _heightfield_source(""), _generate_patches(false), _data_texture(NULL), _chunk_geom(NULL), @@ -107,7 +106,7 @@ ShaderTerrainMesh::ShaderTerrainMesh() : * @return true if the terrain was initialized, false if an error occured */ bool ShaderTerrainMesh::generate() { - if (!do_load_heightfield()) + if (!do_check_heightfield()) return false; if (_chunk_size < 8 || !check_power_of_two(_chunk_size)) { @@ -120,28 +119,29 @@ bool ShaderTerrainMesh::generate() { return false; } + do_extract_heightfield(); do_create_chunks(); do_compute_bounds(&_base_chunk); do_create_chunk_geom(); do_init_data_texture(); - do_convert_heightfield(); + + // Clear image after using it, otherwise we have two copies of the heightfield + // in memory. + _heightfield.clear(); return true; } /** - * @brief Converts the internal used PNMImage to a Texture - * @details This converts the internal used PNMImage to a texture object. The - * reason for this is, that we need the PNMimage for computing the chunk - * bounds, but don't need it afterwards. However, since we have it in ram, - * we can just put its contents into a Texture object, which enables the - * user to call get_heightfield() instead of manually loading the texture - * from disk again to set it as shader input (Panda does not cache PNMImages) + * @brief Converts the internal used Texture to a PNMImage + * @details This converts the texture passed with set_heightfield to a PNMImage, + * so we can read the pixels in a fast way. This is only used while generating + * the chunks, and the PNMImage is destroyed afterwards. */ -void ShaderTerrainMesh::do_convert_heightfield() { - _heightfield_tex = new Texture(); - _heightfield_tex->load(_heightfield); - _heightfield_tex->set_keep_ram_image(true); +void ShaderTerrainMesh::do_extract_heightfield() { + nassertv(_heightfield_tex->has_ram_image()); // Heightfield not in RAM, extract ram image first + + _heightfield_tex->store(_heightfield); if (_heightfield.get_maxval() != 65535) { shader_terrain_cat.warning() << "Using non 16-bit heightfield!" << endl; @@ -150,31 +150,23 @@ void ShaderTerrainMesh::do_convert_heightfield() { } _heightfield_tex->set_minfilter(SamplerState::FT_linear); _heightfield_tex->set_magfilter(SamplerState::FT_linear); - _heightfield.clear(); } /** - * @brief Intermal method to load the heightfield - * @details This method loads the heightfield from the heightfield path, + * @brief Intermal method to check the heightfield + * @details This method cecks the heightfield generated from the heightfield texture, * and performs some basic checks, including a check for a power of two, * and same width and height. * - * @return true if the heightfield was loaded and meets the requirements + * @return true if the heightfield meets the requirements */ -bool ShaderTerrainMesh::do_load_heightfield() { - - if(!_heightfield.read(_heightfield_source)) { - shader_terrain_cat.error() << "Could not load heightfield from " << _heightfield_source << endl; - return false; - } - - if (_heightfield.get_x_size() != _heightfield.get_y_size()) { +bool ShaderTerrainMesh::do_check_heightfield() { + if (_heightfield_tex->get_x_size() != _heightfield_tex->get_y_size()) { shader_terrain_cat.error() << "Only square heightfields are supported!"; return false; } - _size = _heightfield.get_x_size(); - + _size = _heightfield_tex->get_x_size(); if (_size < 32 || !check_power_of_two(_size)) { shader_terrain_cat.error() << "Invalid heightfield! Needs to be >= 32 and a power of two (was: " << _size << ")!" << endl; @@ -687,8 +679,8 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) { data_entry.size = chunk->size / _chunk_size; data_entry.clod = chunk->last_clod; - data->emitted_chunks ++; - data->storage_ptr ++; + data->emitted_chunks++; + data->storage_ptr++; } /** @@ -701,7 +693,9 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) { * @return World-Space point */ LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const { - nassertr(_heightfield_tex != NULL, LPoint3(0)); + nassertr(_heightfield_tex != NULL, LPoint3(0)); // Heightfield not set yet + nassertr(_heightfield_tex->has_ram_image(), LPoint3(0)); // Heightfield not in memory + PT(TexturePeeker) peeker = _heightfield_tex->peek(); nassertr(peeker != NULL, LPoint3(0)); diff --git a/panda/src/grutil/shaderTerrainMesh.h b/panda/src/grutil/shaderTerrainMesh.h index f66caacf76..a24d116460 100644 --- a/panda/src/grutil/shaderTerrainMesh.h +++ b/panda/src/grutil/shaderTerrainMesh.h @@ -55,9 +55,9 @@ PUBLISHED: ShaderTerrainMesh(); - INLINE void set_heightfield_filename(const Filename& filename); - INLINE const Filename& get_heightfield_filename() const; - MAKE_PROPERTY(heightfield_filename, get_heightfield_filename, set_heightfield_filename); + INLINE void set_heightfield(Texture* heightfield); + INLINE Texture* get_heightfield() const; + MAKE_PROPERTY(heightfield, get_heightfield, set_heightfield); INLINE void set_chunk_size(size_t chunk_size); INLINE size_t get_chunk_size() const; @@ -152,8 +152,8 @@ private: ChunkDataEntry* storage_ptr; }; - bool do_load_heightfield(); - void do_convert_heightfield(); + bool do_check_heightfield(); + void do_extract_heightfield(); void do_init_data_texture(); void do_create_chunks(); void do_init_chunk(Chunk* chunk); @@ -164,7 +164,6 @@ private: bool do_check_lod_matches(Chunk* chunk, TraversalData* data); Chunk _base_chunk; - Filename _heightfield_source; size_t _size; size_t _chunk_size; bool _generate_patches; diff --git a/samples/shader-terrain/main.py b/samples/shader-terrain/main.py index 1fcea138f9..92ff61467a 100644 --- a/samples/shader-terrain/main.py +++ b/samples/shader-terrain/main.py @@ -2,16 +2,15 @@ # Author: tobspr # -# Last Updated: 2016-02-13 +# Last Updated: 2016-04-30 # # This tutorial provides an example of using the ShaderTerrainMesh class -import os, sys, math, random - from direct.showbase.ShowBase import ShowBase from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data from panda3d.core import SamplerState + class ShaderTerrainDemo(ShowBase): def __init__(self): @@ -19,14 +18,14 @@ class ShaderTerrainDemo(ShowBase): # before the ShowBase is initialized load_prc_file_data("", """ textures-power-2 none - window-title Panda3D Shader Terrain Demo gl-coordinate-system default + window-title Panda3D ShaderTerrainMesh Demo """) # Initialize the showbase ShowBase.__init__(self) - # Increase camera FOV aswell as the far plane + # Increase camera FOV as well as the far plane self.camLens.set_fov(90) self.camLens.set_near_far(0.1, 50000) @@ -35,7 +34,7 @@ class ShaderTerrainDemo(ShowBase): # Set a heightfield, the heightfield should be a 16-bit png and # have a quadratic size of a power of two. - self.terrain_node.heightfield_filename = "heightfield.png" + self.terrain_node.heightfield = self.loader.loadTexture("heightfield.png") # Set the target triangle width. For a value of 10.0 for example, # the terrain will attempt to make every triangle 10 pixels wide on screen. @@ -44,24 +43,28 @@ class ShaderTerrainDemo(ShowBase): # Generate the terrain self.terrain_node.generate() - # Attach the terrain to the main scene and set its scale + # Attach the terrain to the main scene and set its scale. With no scale + # set, the terrain ranges from (0, 0, 0) to (1, 1, 1) self.terrain = self.render.attach_new_node(self.terrain_node) self.terrain.set_scale(1024, 1024, 100) self.terrain.set_pos(-512, -512, -70.0) # Set a shader on the terrain. The ShaderTerrainMesh only works with - # an applied shader. You can use the shaders used here in your own shaders + # an applied shader. You can use the shaders used here in your own application terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl") self.terrain.set_shader(terrain_shader) self.terrain.set_shader_input("camera", self.camera) + # Shortcut to view the wireframe mesh + self.accept("f3", self.toggleWireframe) + # Set some texture on the terrain grass_tex = self.loader.loadTexture("textures/grass.png") grass_tex.set_minfilter(SamplerState.FT_linear_mipmap_linear) grass_tex.set_anisotropic_degree(16) self.terrain.set_texture(grass_tex) - # Load some skybox - you can safely ignore this code + # Load a skybox - you can safely ignore this code skybox = self.loader.loadModel("models/skybox.bam") skybox.reparent_to(self.render) skybox.set_scale(20000) @@ -77,5 +80,4 @@ class ShaderTerrainDemo(ShowBase): skybox_shader = Shader.load(Shader.SL_GLSL, "skybox.vert.glsl", "skybox.frag.glsl") skybox.set_shader(skybox_shader) -demo = ShaderTerrainDemo() -demo.run() +ShaderTerrainDemo().run()