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
* @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;
}
/**

View File

@ -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));

View File

@ -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;

View File

@ -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()