Support for setting a heightfield handle directly on the ShaderTerrainMesh

This commit is contained in:
tobspr 2016-04-30 08:35:24 +02:00
parent 288452a861
commit dac2bfc7b9
4 changed files with 55 additions and 58 deletions

View File

@ -12,26 +12,28 @@
*/ */
/** /**
* @brief Sets the path to the heightfield * @brief Sets the heightfield texture
* @details This sets the path to the terrain heightfield. It should be 16bit * @details This sets the heightfield texture. It should be 16bit
* single channel, and have a power-of-two resolution greater than 32. * single channel, and have a power-of-two resolution greater than 32.
* Common sizes are 2048x2048 or 4096x4096. * 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) { INLINE void ShaderTerrainMesh::set_heightfield(Texture* heightfield) {
_heightfield_source = filename; _heightfield_tex = heightfield;
} }
/** /**
* @brief Returns the heightfield path * @brief Returns the heightfield
* @details This returns the path of the terrain heightfield, previously set with * @details This returns the terrain heightfield, previously set with
* set_heightfield() * set_heightfield()
* *
* @return Path to the heightfield * @return Path to the heightfield
*/ */
INLINE const Filename& ShaderTerrainMesh::get_heightfield_filename() const { INLINE Texture* ShaderTerrainMesh::get_heightfield() const {
return _heightfield_source; return _heightfield_tex;
} }
/** /**

View File

@ -35,7 +35,7 @@
#include "typeHandle.h" #include "typeHandle.h"
ConfigVariableBool stm_use_hexagonal_layout 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 " 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 " "the heightfield in a better way, however the CLOD transitions might be "
"visible due to the vertices not matching exactly.")); "visible due to the vertices not matching exactly."));
@ -81,7 +81,6 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
PandaNode("ShaderTerrainMesh"), PandaNode("ShaderTerrainMesh"),
_size(0), _size(0),
_chunk_size(32), _chunk_size(32),
_heightfield_source(""),
_generate_patches(false), _generate_patches(false),
_data_texture(NULL), _data_texture(NULL),
_chunk_geom(NULL), _chunk_geom(NULL),
@ -107,7 +106,7 @@ ShaderTerrainMesh::ShaderTerrainMesh() :
* @return true if the terrain was initialized, false if an error occured * @return true if the terrain was initialized, false if an error occured
*/ */
bool ShaderTerrainMesh::generate() { bool ShaderTerrainMesh::generate() {
if (!do_load_heightfield()) if (!do_check_heightfield())
return false; return false;
if (_chunk_size < 8 || !check_power_of_two(_chunk_size)) { if (_chunk_size < 8 || !check_power_of_two(_chunk_size)) {
@ -120,28 +119,29 @@ bool ShaderTerrainMesh::generate() {
return false; return false;
} }
do_extract_heightfield();
do_create_chunks(); do_create_chunks();
do_compute_bounds(&_base_chunk); do_compute_bounds(&_base_chunk);
do_create_chunk_geom(); do_create_chunk_geom();
do_init_data_texture(); 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; return true;
} }
/** /**
* @brief Converts the internal used PNMImage to a Texture * @brief Converts the internal used Texture to a PNMImage
* @details This converts the internal used PNMImage to a texture object. The * @details This converts the texture passed with set_heightfield to a PNMImage,
* reason for this is, that we need the PNMimage for computing the chunk * so we can read the pixels in a fast way. This is only used while generating
* bounds, but don't need it afterwards. However, since we have it in ram, * the chunks, and the PNMImage is destroyed afterwards.
* 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)
*/ */
void ShaderTerrainMesh::do_convert_heightfield() { void ShaderTerrainMesh::do_extract_heightfield() {
_heightfield_tex = new Texture(); nassertv(_heightfield_tex->has_ram_image()); // Heightfield not in RAM, extract ram image first
_heightfield_tex->load(_heightfield);
_heightfield_tex->set_keep_ram_image(true); _heightfield_tex->store(_heightfield);
if (_heightfield.get_maxval() != 65535) { if (_heightfield.get_maxval() != 65535) {
shader_terrain_cat.warning() << "Using non 16-bit heightfield!" << endl; 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_minfilter(SamplerState::FT_linear);
_heightfield_tex->set_magfilter(SamplerState::FT_linear); _heightfield_tex->set_magfilter(SamplerState::FT_linear);
_heightfield.clear();
} }
/** /**
* @brief Intermal method to load the heightfield * @brief Intermal method to check the heightfield
* @details This method loads the heightfield from the heightfield path, * @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 performs some basic checks, including a check for a power of two,
* and same width and height. * 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() { bool ShaderTerrainMesh::do_check_heightfield() {
if (_heightfield_tex->get_x_size() != _heightfield_tex->get_y_size()) {
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()) {
shader_terrain_cat.error() << "Only square heightfields are supported!"; shader_terrain_cat.error() << "Only square heightfields are supported!";
return false; return false;
} }
_size = _heightfield.get_x_size(); _size = _heightfield_tex->get_x_size();
if (_size < 32 || !check_power_of_two(_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: " shader_terrain_cat.error() << "Invalid heightfield! Needs to be >= 32 and a power of two (was: "
<< _size << ")!" << endl; << _size << ")!" << endl;
@ -687,8 +679,8 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
data_entry.size = chunk->size / _chunk_size; data_entry.size = chunk->size / _chunk_size;
data_entry.clod = chunk->last_clod; data_entry.clod = chunk->last_clod;
data->emitted_chunks ++; data->emitted_chunks++;
data->storage_ptr ++; data->storage_ptr++;
} }
/** /**
@ -701,7 +693,9 @@ void ShaderTerrainMesh::do_emit_chunk(Chunk* chunk, TraversalData* data) {
* @return World-Space point * @return World-Space point
*/ */
LPoint3 ShaderTerrainMesh::uv_to_world(const LTexCoord& coord) const { 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(); PT(TexturePeeker) peeker = _heightfield_tex->peek();
nassertr(peeker != NULL, LPoint3(0)); nassertr(peeker != NULL, LPoint3(0));

View File

@ -55,9 +55,9 @@ PUBLISHED:
ShaderTerrainMesh(); ShaderTerrainMesh();
INLINE void set_heightfield_filename(const Filename& filename); INLINE void set_heightfield(Texture* heightfield);
INLINE const Filename& get_heightfield_filename() const; INLINE Texture* get_heightfield() const;
MAKE_PROPERTY(heightfield_filename, get_heightfield_filename, set_heightfield_filename); MAKE_PROPERTY(heightfield, get_heightfield, set_heightfield);
INLINE void set_chunk_size(size_t chunk_size); INLINE void set_chunk_size(size_t chunk_size);
INLINE size_t get_chunk_size() const; INLINE size_t get_chunk_size() const;
@ -152,8 +152,8 @@ private:
ChunkDataEntry* storage_ptr; ChunkDataEntry* storage_ptr;
}; };
bool do_load_heightfield(); bool do_check_heightfield();
void do_convert_heightfield(); void do_extract_heightfield();
void do_init_data_texture(); void do_init_data_texture();
void do_create_chunks(); void do_create_chunks();
void do_init_chunk(Chunk* chunk); void do_init_chunk(Chunk* chunk);
@ -164,7 +164,6 @@ private:
bool do_check_lod_matches(Chunk* chunk, TraversalData* data); bool do_check_lod_matches(Chunk* chunk, TraversalData* data);
Chunk _base_chunk; Chunk _base_chunk;
Filename _heightfield_source;
size_t _size; size_t _size;
size_t _chunk_size; size_t _chunk_size;
bool _generate_patches; bool _generate_patches;

View File

@ -2,16 +2,15 @@
# Author: tobspr # Author: tobspr
# #
# Last Updated: 2016-02-13 # Last Updated: 2016-04-30
# #
# This tutorial provides an example of using the ShaderTerrainMesh class # This tutorial provides an example of using the ShaderTerrainMesh class
import os, sys, math, random
from direct.showbase.ShowBase import ShowBase from direct.showbase.ShowBase import ShowBase
from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data from panda3d.core import ShaderTerrainMesh, Shader, load_prc_file_data
from panda3d.core import SamplerState from panda3d.core import SamplerState
class ShaderTerrainDemo(ShowBase): class ShaderTerrainDemo(ShowBase):
def __init__(self): def __init__(self):
@ -19,14 +18,14 @@ class ShaderTerrainDemo(ShowBase):
# before the ShowBase is initialized # before the ShowBase is initialized
load_prc_file_data("", """ load_prc_file_data("", """
textures-power-2 none textures-power-2 none
window-title Panda3D Shader Terrain Demo
gl-coordinate-system default gl-coordinate-system default
window-title Panda3D ShaderTerrainMesh Demo
""") """)
# Initialize the showbase # Initialize the showbase
ShowBase.__init__(self) 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_fov(90)
self.camLens.set_near_far(0.1, 50000) 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 # Set a heightfield, the heightfield should be a 16-bit png and
# have a quadratic size of a power of two. # 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, # 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. # the terrain will attempt to make every triangle 10 pixels wide on screen.
@ -44,24 +43,28 @@ class ShaderTerrainDemo(ShowBase):
# Generate the terrain # Generate the terrain
self.terrain_node.generate() 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 = self.render.attach_new_node(self.terrain_node)
self.terrain.set_scale(1024, 1024, 100) self.terrain.set_scale(1024, 1024, 100)
self.terrain.set_pos(-512, -512, -70.0) self.terrain.set_pos(-512, -512, -70.0)
# Set a shader on the terrain. The ShaderTerrainMesh only works with # 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") terrain_shader = Shader.load(Shader.SL_GLSL, "terrain.vert.glsl", "terrain.frag.glsl")
self.terrain.set_shader(terrain_shader) self.terrain.set_shader(terrain_shader)
self.terrain.set_shader_input("camera", self.camera) 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 # Set some texture on the terrain
grass_tex = self.loader.loadTexture("textures/grass.png") grass_tex = self.loader.loadTexture("textures/grass.png")
grass_tex.set_minfilter(SamplerState.FT_linear_mipmap_linear) grass_tex.set_minfilter(SamplerState.FT_linear_mipmap_linear)
grass_tex.set_anisotropic_degree(16) grass_tex.set_anisotropic_degree(16)
self.terrain.set_texture(grass_tex) 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 = self.loader.loadModel("models/skybox.bam")
skybox.reparent_to(self.render) skybox.reparent_to(self.render)
skybox.set_scale(20000) 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_shader = Shader.load(Shader.SL_GLSL, "skybox.vert.glsl", "skybox.frag.glsl")
skybox.set_shader(skybox_shader) skybox.set_shader(skybox_shader)
demo = ShaderTerrainDemo() ShaderTerrainDemo().run()
demo.run()