├── src ├── applications │ ├── CMakeLists.txt │ └── voe_globe │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ └── vsgpagedlod.cpp ├── CMakeLists.txt └── vsgEarth │ ├── Config.h.in │ ├── VoeLOD.h │ ├── VoeSim.cpp │ ├── reverse_depth.vert │ ├── VoeImageUtils.h │ ├── TileReaderVOE.h │ ├── VoeLOD.cpp │ ├── Export.h │ ├── VoeSim.h │ ├── VoeSDK.h │ ├── CMakeLists.txt │ ├── voeSDK.glsl │ ├── elevation_main.frag │ ├── VoeImageUtils.cpp │ ├── TerrainEngineVOE.h │ ├── TileReaderVOE.cpp │ └── TerrainEngineVOE.cpp ├── CMakeModules ├── vsgearthMacroUtils.cmake └── ConcatFiles.cmake ├── tests ├── readymap.earth ├── readymap-debug.earth └── viewpoints.xml ├── README.md ├── CMakeLists.txt └── LICENSE.txt /src/applications/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(voe_globe) 2 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(vsgEarth) 2 | add_subdirectory(applications) 3 | -------------------------------------------------------------------------------- /src/vsgEarth/Config.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define VSGEARTH_FULL_DATA_DIR "@VSGEARTH_FULL_DATA_DIR@" 4 | -------------------------------------------------------------------------------- /src/applications/voe_globe/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES 2 | vsgpagedlod.cpp 3 | ) 4 | 5 | SET(TARGET_SRC ${SOURCES}) 6 | 7 | INCLUDE_DIRECTORIES(${OSG_INCLUDE_DIRS} ${Vulkan_INCLUDE_DIR}) 8 | 9 | add_executable(voe_globe ${SOURCES}) 10 | 11 | target_link_libraries(voe_globe vsgEarth vsg::vsg osgEarth) 12 | 13 | target_link_libraries(voe_globe vsgXchange::vsgXchange) 14 | 15 | install(TARGETS voe_globe 16 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 17 | ) 18 | -------------------------------------------------------------------------------- /CMakeModules/vsgearthMacroUtils.cmake: -------------------------------------------------------------------------------- 1 | function(cat IN_FILES OUT_FILE) 2 | foreach(FILE IN_FILES) 3 | read(FILE CONTENT) 4 | write(APPEND OUT_FILE CONTENT) 5 | endforeach() 6 | endfunction() 7 | 8 | # https://stackoverflow.com/questions/4346412/how-to-prepend-all-filenames-on-the-list-with-common-path/31726284 9 | 10 | function(list_transform_prepend var prefix) 11 | set(temp "") 12 | foreach(f ${${var}}) 13 | list(APPEND temp "${prefix}${f}") 14 | endforeach() 15 | set(${var} "${temp}" PARENT_SCOPE) 16 | endfunction() 17 | -------------------------------------------------------------------------------- /CMakeModules/ConcatFiles.cmake: -------------------------------------------------------------------------------- 1 | # ConcatFiles.cmake 2 | # Define in_files, out_file 3 | 4 | math(EXPR out_arg_num "${CMAKE_ARGC} - 1") 5 | math(EXPR last_in_arg_num "${CMAKE_ARGC} - 2") 6 | 7 | set(out_file ${CMAKE_ARGV${out_arg_num}}) 8 | 9 | message("out_file: ${out_file}") 10 | 11 | foreach(_arg RANGE 1 ${out_arg_num}) 12 | message("${_arg}: ${CMAKE_ARGV${_arg}}") 13 | endforeach() 14 | 15 | file(REMOVE ${out_file}) 16 | foreach(_arg RANGE 3 ${last_in_arg_num}) 17 | file(READ ${CMAKE_ARGV${_arg}} contents) 18 | file(APPEND ${out_file} "${contents}") 19 | endforeach() 20 | 21 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeLOD.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A better behaved PagedLOD that deals with missing tiles and such. 4 | 5 | #include 6 | 7 | namespace osgEarth 8 | { 9 | enum TileStatus 10 | { 11 | Valid, 12 | NoSuchTile, 13 | Error 14 | }; 15 | 16 | TileStatus getTileStatus(const vsg::Node* node); 17 | void setTileStatus(vsg::Node* node, TileStatus status); 18 | 19 | class VoeLOD : public vsg::Inherit 20 | { 21 | public: 22 | VoeLOD() 23 | {} 24 | void accept(vsg::RecordTraversal& visitor) const override; 25 | }; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /tests/readymap.earth: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | http://readymap.org/readymap/tiles/1.0.0/7/ 18 | 19 | 20 | 21 | http://readymap.org/readymap/tiles/1.0.0/116/ 22 | egm96 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/readymap-debug.earth: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | http://readymap.org/readymap/tiles/1.0.0/7/ 18 | 19 | 20 | 21 | 22 | http://readymap.org/readymap/tiles/1.0.0/116/ 23 | egm96 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeSim.cpp: -------------------------------------------------------------------------------- 1 | #include "VoeSim.h" 2 | 3 | using namespace osgEarth; 4 | 5 | // Some "sensible" defaults; direction from sun over the Atlantic Ocean 6 | SimpleLight::SimpleLight() 7 | : worldDirection(-0.7071067811865476, 0.7071067811865476, 0.0) 8 | { 9 | _value = SimpleLightUniformValue::create(); 10 | // Eye-space direction just to have something initialized, but 11 | // should be updated with the view matrix. 12 | setDirection(vsg::vec3(worldDirection.x, worldDirection.y, worldDirection.z)); 13 | setColor(vsg::vec3(.8f, .8f, .8f)); 14 | setAmbient(vsg::vec3(.2f, .2f, .2f)); 15 | lightValues = vsg::DescriptorBuffer::create(_value, 0); 16 | } 17 | 18 | void SimpleLight::setEyeDirection(const vsg::dmat4& viewMatrix) 19 | { 20 | vsg::dvec4 dLightDirectionWorld(worldDirection.x, worldDirection.y, worldDirection.z, 0.0); 21 | vsg::dvec4 dLightDirectionEye = viewMatrix * dLightDirectionWorld; 22 | vsg::vec3 lightDirectionEye(dLightDirectionEye.x, dLightDirectionEye.y, dLightDirectionEye.z); 23 | setDirection(lightDirectionEye); 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## vsgEarth - osgEarth on the VSG Vulkan Scene Graph 2 | 3 | vsgEarth is a prototype of 4 | ![osgEarth](https://github.com/gwaldron/osgearth) that renders using 5 | Vulkan and the ![Vulkan Scene Graph](https://github.com/vsg-dev/VulkanSceneGraph) (VSG). 6 | Instead of than being a complete reimplementation of osgEarth, it uses the 7 | map layers of osgEarth as data sources for creating a VSG scene graph. 8 | 9 | ### Status: 10 | 11 | The vsgEarth library parses osgEarth ".earth" files. It can render 12 | multiple image layers and an elevation layer, using a simple lighting 13 | model, from the supported osgEarth sources. The canonical ReadyMap 14 | example works well. 15 | 16 | A sample program, voe_globe, provides a simple-yet-familiar interface. 17 | 18 | vsgEarth has been tested on Linux. 19 | 20 | ### Prerequisites: 21 | * The ![Vulkan SDK](https://www.lunarg.com/vulkan-sdk/) 22 | * ![osgEarth](https://github.com/gwaldron/osgearth) 23 | * ![VSG](https://github.com/vsg-dev/VulkanSceneGraph) 24 | * ![vsgXchange](https://github.com/vsg-dev/vsgXchange) 25 | * ![osg2vsg](https://github.com/vsg-dev/osg2vsg) 26 | 27 | These packages come with their own prerequisites, of course, so you will need to 28 | install them too. 29 | 30 | vsgEarth is Free Open Source Software distributed under the LGPL. 31 | 32 | Copyright 2022 [Pelican Mapping](http://web.pelicanmapping.com/). 33 | -------------------------------------------------------------------------------- /src/vsgEarth/reverse_depth.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "VoeSDK.h" 4 | 5 | layout(push_constant) uniform PushConstants { 6 | mat4 projection; 7 | mat4 modelview; 8 | } pc; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | layout(location = 3) in vec3 inEllipsoidNormal; 14 | 15 | layout(location = 0) out vec3 fragColor; 16 | layout(location = 1) out vec2 fragTexCoord; 17 | layout(location = 2) out vec3 oe_UpVectorView; 18 | layout(location = 3) out vec2 oe_normalMapCoords; 19 | layout(location = 4) out vec3 oe_normalMapBinormal; 20 | 21 | out gl_PerVertex { 22 | vec4 gl_Position; 23 | }; 24 | 25 | void main() { 26 | mat4 projection = pc.projection; 27 | 28 | if (reverseDepth != 0) 29 | { 30 | projection[2][2] *= -pc.projection[2][2] - 1.0; 31 | projection[3][2] *= -1.0; 32 | } 33 | float elevation = oe_terrain_getElevation(inTexCoord); 34 | vec3 position = inPosition + inEllipsoidNormal * elevation; 35 | gl_Position = (projection * pc.modelview) * vec4(position, 1.0); 36 | mat3 normalMatrix = mat3(transpose(inverse(pc.modelview))); 37 | fragColor = inColor; 38 | fragTexCoord = inTexCoord; 39 | // The normal map stuff 40 | oe_UpVectorView = normalMatrix * inEllipsoidNormal; 41 | oe_normalMapCoords = inTexCoord; 42 | oe_normalMapBinormal = normalize(normalMatrix * vec3(0.0, 1.0, 0.0)); 43 | } 44 | -------------------------------------------------------------------------------- /src/applications/voe_globe/README.md: -------------------------------------------------------------------------------- 1 | VOE - Prototype osgEarth with VSG scenegraph 2 | 3 | This is a proof-of-concept of delivering osgEarth content using Vulkan 4 | via the VSG scenegraph library. We use osgEarth libraries linked with 5 | Open Scene Graph and OpenGL as usual, along with VSG. At run time we 6 | don't create an OpenGL context, but we do create CPU-side OSG and 7 | OpenGL objects for the data obtained from osgEarth layers. We then use 8 | the vsgXchange library to translate these data into VSG scene graph 9 | nodes using a Paged LOD approach. 10 | 11 | Building VOE 12 | 13 | * [Prerequisites](#prerequisites) - list of project external 14 | dependencies 15 | 16 | * [Vulkan](https://vulkan.lunarg.com/) The Vulkan SDK. The most recent 17 | version should be fine. Do whatever is needed to get the SDK's bin 18 | and lib directories into your paths i.e., run the script in the SDK. 19 | 20 | * [VSG](https://github.com/vsg-dev/VulkanSceneGraph) 21 | * [vsgGIS](https://github.com/vsg-dev/vsgGIS) 22 | * [vsgXchange](https://github.com/vsg-dev/vsgGIS) 23 | 24 | vsgXchange can be built with the assimp interchange library, but this 25 | is currently broken and isn't necessary for VOE. 26 | 27 | * [Build VOE](#build-voe) 28 | Build osgEarth with the the VSGEARTH option set to ON. 29 | 30 | * [Run VOE](#run-voe) 31 | * Set the VSG_FILE_PATH environment variable to the osgEarth 32 | src/applications/voe_globe source directory so that the application 33 | can find the SPIR-V shader files. 34 | * Run an earth file e.g. voe_globe --window 1080 720 /home/moore/graphics/osgearth/voe/tests/readymap.earth 35 | 36 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeImageUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Wrapper around vsgXchange functions 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace osgEarth 11 | { 12 | vsg::ref_ptr convertToVsg(const osg::Image* image, bool mapRGBtoRGBAHint = false); 13 | 14 | template 15 | struct VsgType; 16 | 17 | template<> 18 | struct VsgType 19 | { 20 | using type = vsg::vec3; 21 | }; 22 | 23 | template<> 24 | struct VsgType 25 | { 26 | using type = vsg::vec2; 27 | }; 28 | 29 | template<> 30 | struct VsgType 31 | { 32 | using type = vsg::dvec3; 33 | }; 34 | 35 | template<> 36 | struct VsgType 37 | { 38 | using type = vsg::dvec2; 39 | }; 40 | 41 | template <> 42 | struct VsgType 43 | { 44 | using type = vsg::mat4; 45 | }; 46 | 47 | template <> 48 | struct VsgType 49 | { 50 | using type = vsg::dmat4; 51 | }; 52 | 53 | template 54 | const typename VsgType::type& toVsg(const T& val) 55 | { 56 | return *reinterpret_cast::type*>(&val); 57 | } 58 | 59 | template 60 | const typename VsgType::type* toVsg(const T* val) 61 | { 62 | return reinterpret_cast::type*>(val); 63 | } 64 | 65 | template 66 | typename VsgType::type* toVsg(T* val) 67 | { 68 | return reinterpret_cast::type*>(val); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/vsgEarth/TileReaderVOE.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "VoeSim.h" 12 | #include "Export.h" 13 | 14 | // Use osgEarth as a tile source 15 | 16 | namespace osgEarth 17 | { 18 | class TerrainEngineVOE; 19 | 20 | class VSGEARTH_EXPORT TileReaderVOE : public vsg::Inherit 21 | { 22 | friend class TerrainEngineVOE; 23 | public: 24 | TileReaderVOE(); 25 | // defaults for readymap / a globe 26 | vsg::dbox extents = {{-180.0, -90.0, 0.0}, {180.0, 90.0, 1.0}}; 27 | uint32_t maxLevel = 22; 28 | bool originTopLeft = true; 29 | double lodTransitionScreenHeightRatio = 0.25; 30 | 31 | std::string projection; 32 | vsg::ref_ptr ellipsoidModel; 33 | 34 | osg::ref_ptr mapNode; 35 | 36 | uint32_t mipmapLevelsHint = 16; 37 | 38 | void init(vsg::CommandLine& commandLine, vsg::ref_ptr options = {}); 39 | 40 | vsg::ref_ptr read(const vsg::Path& filename, vsg::ref_ptr options = {}) const override; 41 | // timing stats 42 | mutable std::mutex statsMutex; 43 | mutable uint64_t numTilesRead{0}; 44 | mutable double totalTimeReadingTiles{0.0}; 45 | SimpleLight simState; 46 | protected: 47 | vsg::ref_ptr read_root(vsg::ref_ptr options = {}) const; 48 | vsg::ref_ptr read_subtile(const osgEarth::TileKey& key, vsg::ref_ptr options = {}) const; 49 | vsg::observer_ptr terrainEngine; 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeLOD.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "VoeLOD.h" 4 | 5 | using namespace osgEarth; 6 | 7 | TileStatus osgEarth::getTileStatus(const vsg::Node* node) 8 | { 9 | TileStatus result = Valid; 10 | if (!node->getValue("voe_status", result)) 11 | { 12 | return Valid; 13 | } 14 | else 15 | { 16 | return result; 17 | } 18 | } 19 | 20 | void osgEarth::setTileStatus(vsg::Node* node, TileStatus status) 21 | { 22 | node->setValue("voe_status", status); 23 | } 24 | 25 | void VoeLOD::accept(vsg::RecordTraversal& visitor) const 26 | { 27 | const auto& child = children[0]; 28 | if (!child.node || getTileStatus(child.node) == Valid) 29 | { 30 | PagedLOD::accept(visitor); 31 | return; 32 | } 33 | // Simulate PagedLOD's traversal of child 1, which is actually 34 | // implemented in the RecordTraversal, yuck. 35 | auto &sphere = bound; 36 | 37 | // check if lod bounding sphere is in view frustum. 38 | if (!visitor.getState()->intersect(sphere)) 39 | { 40 | // Punt let PagedLOD do its bookkeeping 41 | PagedLOD::accept(visitor); 42 | return; 43 | } 44 | 45 | const auto& proj = visitor.getState()->projectionMatrixStack.top(); 46 | const auto& mv = visitor.getState()->modelviewMatrixStack.top(); 47 | auto f = -proj[1][1]; 48 | 49 | auto distance = std::abs(mv[0][2] * sphere.x + mv[1][2] * sphere.y + mv[2][2] * sphere.z + mv[3][2]); 50 | auto rf = sphere.r * f; 51 | 52 | // check the low res child to see if it's visible 53 | { 54 | const auto& loChild = children[1]; 55 | auto cutoff = loChild.minimumScreenHeightRatio * distance; 56 | bool child_visible = rf > cutoff; 57 | if (child_visible) 58 | { 59 | if (loChild.node) 60 | { 61 | loChild.node->accept(visitor); 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/vsgEarth/Export.h: -------------------------------------------------------------------------------- 1 | /* -*-c++-*- */ 2 | /* osgEarth - Geospatial SDK for OpenSceneGraph 3 | * Copyright 2020 Pelican Mapping 4 | * http://osgearth.org 5 | * 6 | * osgEarth is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU Lesser General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Lesser General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Lesser General Public License 17 | * along with this program. If not, see 18 | */ 19 | 20 | /* -*-c++-*- 21 | * Derived from osg/Export 22 | */ 23 | 24 | #ifndef VSGEARTH_EXPORT_H 25 | #define VSGEARTH_EXPORT_H 1 26 | 27 | #if defined(_MSC_VER) 28 | #pragma warning( disable : 4244 ) 29 | #pragma warning( disable : 4251 ) 30 | #pragma warning( disable : 4267 ) 31 | #pragma warning( disable : 4275 ) 32 | #pragma warning( disable : 4290 ) 33 | #pragma warning( disable : 4786 ) 34 | #pragma warning( disable : 4305 ) 35 | #pragma warning( disable : 4996 ) 36 | #endif 37 | 38 | #if defined(_MSC_VER) || defined(__CYGWIN__) || defined(__MINGW32__) || defined( __BCPLUSPLUS__) || defined( __MWERKS__) 39 | # if defined( VSGEARTH_LIBRARY_STATIC ) 40 | # define VSGEARTH_EXPORT 41 | # elif defined( VSGEARTH_LIBRARY ) 42 | # define VSGEARTH_EXPORT __declspec(dllexport) 43 | # else 44 | # define VSGEARTH_EXPORT __declspec(dllimport) 45 | # endif 46 | #else 47 | # define VSGEARTH_EXPORT 48 | #endif 49 | 50 | // set up define for whether member templates are supported by VisualStudio compilers. 51 | #ifdef _MSC_VER 52 | # if (_MSC_VER >= 1300) 53 | # define __STL_MEMBER_TEMPLATES 54 | # endif 55 | #endif 56 | 57 | #endif 58 | 59 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeSim.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Class that defines attributes driven by the voe "simulation," 4 | // e.g. lighting. 5 | #include 6 | 7 | #include "Export.h" 8 | 9 | namespace osgEarth 10 | { 11 | // The data structure passed to shaders. These values are mostly 12 | // vec3, but are declared vec4 to maintain alignment. 13 | 14 | struct SimpleLightUniform 15 | { 16 | vsg::vec4 direction; 17 | vsg::vec4 color; 18 | vsg::vec4 ambient; 19 | }; 20 | 21 | struct SimpleLightUniformValue : 22 | public vsg::Inherit, SimpleLightUniformValue> 23 | { 24 | }; 25 | 26 | class VSGEARTH_EXPORT SimpleLight 27 | { 28 | public: 29 | vsg::ref_ptr lightValues; 30 | SimpleLight(); 31 | 32 | vsg::ref_ptr& value() { return _value; } 33 | 34 | // Eye direction 35 | void setDirection(const vsg::vec3& direction) 36 | { 37 | _value->value().direction = vsg::vec4(direction.x, direction.y, direction.z, 0.0f); 38 | } 39 | 40 | const vsg::vec3& getDirection() const 41 | { 42 | return *reinterpret_cast(&_value->value().direction); 43 | } 44 | 45 | void setColor(const vsg::vec3& color) 46 | { 47 | _value->value().color = vsg::vec4(color.x, color.y, color.z, 1.0f); 48 | } 49 | 50 | const vsg::vec3& getColor() const 51 | { 52 | return *reinterpret_cast(&_value->value().color); 53 | } 54 | 55 | void setAmbient(const vsg::vec3& color) 56 | { 57 | _value->value().ambient = vsg::vec4(color.x, color.y, color.z, 1.0f); 58 | } 59 | 60 | const vsg::vec3& getAmbient() const 61 | { 62 | return *reinterpret_cast(&_value->value().ambient); 63 | } 64 | vsg::dvec3 worldDirection; 65 | 66 | void setEyeDirection(const vsg::dmat4& viewMatrix); 67 | protected: 68 | vsg::ref_ptr _value; 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeSDK.h: -------------------------------------------------------------------------------- 1 | // -*-GLSL-*- 2 | 3 | #ifndef VOESDK_H 4 | #define VOESDK_H 1 5 | 6 | layout(constant_id = 0) const uint reverseDepth = 0; 7 | layout(constant_id = 1) const uint maxImageLayers = 1; 8 | 9 | layout(binding = 0) uniform sampler2D texSampler[maxImageLayers]; // only available in fragment shader 10 | layout(binding = 1) uniform sampler2D elevationTex; 11 | layout(binding = 2) uniform sampler2D normalTex; 12 | 13 | struct TileImageLayer 14 | { 15 | mat4 imageTexMatrix; 16 | uint layerIndex; // Layer's index in voeLayers.imageLayerParams 17 | // 12 bytes padding 18 | }; 19 | 20 | // This declaration will not work unless the variable array (imageLayers) is put at the 21 | // end. Apparently the specialization constant cannot change the offsets of values in the structure, 22 | // even though no error or warning is produced. 23 | // 24 | // https://stackoverflow.com/questions/52191104/specialization-constant-used-for-array-size 25 | // describes the problem; although the context there is SPIRV in OpenGL, the issue is the same. 26 | 27 | layout(set = 0, binding = 3) uniform VOETile { 28 | mat4 elevationTexMatrix; 29 | vec4 elevTexelCoeff; 30 | uint numImageLayers; // Number of image layers in this tile 31 | // 12 bytes padding 32 | TileImageLayer imageLayers[maxImageLayers]; 33 | } voeTile; 34 | 35 | // VSG light data from ViewDependent state 36 | layout(set = 1, binding = 0) uniform LightData 37 | { 38 | vec4 values[64]; 39 | } lightData; 40 | 41 | // Wasteful, but preserves padding: 42 | // imageLayerParams[0] - enabled 43 | // imageLayerParams[1] - opacity 44 | // imageLayerParams[2] - blend mode: 0 - BLEND_INTERPOLATE, 1 - BLEND_MODULATE 45 | layout(set = 2, binding = 0) uniform VOELayers { 46 | vec4 imageLayerParams[maxImageLayers]; 47 | } voeLayers; 48 | 49 | #define OE_BLEND_INTERPOLATE 0 50 | #define OE_BLEND_MODULATE 1 51 | 52 | // layer parameter is index into the tile's layers i.e. the list in voeTile 53 | bool oe_layerEnabled(int layer); 54 | float oe_layerOpacity(int layer); 55 | int oe_layerColorBlending(int layer); 56 | vec4 oe_blendLayerColor(vec4 color, int layer); 57 | 58 | float oe_terrain_getElevation(in vec2 uv); 59 | vec4 oe_terrain_getNormalAndCurvatureScaled(in vec2 uv_scaledBiased); 60 | vec4 oe_terrain_getNormalAndCurvature(in vec2 st); 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project(vsgearth 4 | VERSION 0.0.1 5 | DESCRIPTION "osgEarth terrain with VSG backend" 6 | LANGUAGES CXX C 7 | ) 8 | set(VSGEARTH_SOVERSION 0) 9 | SET(VSGEARTH_RELEASE_CANDIDATE 0) 10 | 11 | # We have some custom .cmake scripts not in the official distribution. 12 | SET(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${vsgearth_SOURCE_DIR}/CMakeModules") 13 | 14 | # set the use of C++17 globally as all examples require it 15 | set(CMAKE_CXX_STANDARD 17) 16 | 17 | 18 | # Find Vulkan and the VSG 19 | if (VULKAN_SDK) 20 | set(ENV{VULKAN_SDK} ${VULKAN_SDK}) 21 | endif() 22 | 23 | set(VSG_MIN_VERSION 1.0.0) 24 | find_package(vsg ${VSG_MIN_VERSION} REQUIRED) 25 | 26 | # find osg2vsg package for loading image/nodes using OpenSceneGraph 27 | find_package(osg2vsg REQUIRED) 28 | set(EXTRA_LIBRARIES ${EXTRA_LIBRARIES} osg2vsg::osg2vsg) 29 | 30 | find_package(vsgXchange REQUIRED) 31 | 32 | # for generated cmake support files 33 | set(FIND_DEPENDENCY ${FIND_DEPENDENCY} "find_dependency(vsg ${VSG_MIN_VERSION} REQUIRED)") 34 | 35 | find_package(osgEarth REQUIRED) 36 | 37 | include(vsgearthMacroUtils) 38 | 39 | vsg_setup_build_vars() 40 | # This does include(GNUInstallDirs), which sets up all the correct install directories. 41 | vsg_setup_dir_vars() 42 | 43 | set(GENERATED_HEADERS_DIR "${PROJECT_BINARY_DIR}/include") 44 | 45 | set(VSGEARTH_DATA_DIR "${CMAKE_INSTALL_DATADIR}/vsgearth") 46 | set(VSGEARTH_FULL_DATA_DIR "${CMAKE_INSTALL_FULL_DATADIR}/vsgearth") 47 | 48 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/vsgEarth/Config.h.in" 49 | "${GENERATED_HEADERS_DIR}/vsgEarth/Config.h") 50 | 51 | vsg_add_target_clang_format( 52 | FILES 53 | ${CMAKE_SOURCE_DIR}/include/*/*.h 54 | ${CMAKE_SOURCE_DIR}/src/*/*.h 55 | ${CMAKE_SOURCE_DIR}/src/*/*.cpp 56 | ${CMAKE_SOURCE_DIR}/applications/*/*.h 57 | ${CMAKE_SOURCE_DIR}/applications/*/*.cpp 58 | ) 59 | 60 | vsg_add_target_clobber() 61 | vsg_add_target_docs( 62 | FILES 63 | ${CMAKE_SOURCE_DIR}/include 64 | ) 65 | vsg_add_target_uninstall() 66 | 67 | vsg_add_option_maintainer( 68 | PREFIX ${PROJECT_NAME} 69 | RCLEVEL ${VSGEARTH_RELEASE_CANDIDATE} 70 | ) 71 | 72 | # Make the headers visible to everything. This is not OSG / VSG style, 73 | # but we prefer that include files live next to their source files. 74 | include_directories(${vsgearth_SOURCE_DIR}/src ${GENERATED_HEADERS_DIR}) 75 | 76 | # source directory for main vsgXchange library 77 | add_subdirectory(src) 78 | 79 | vsg_add_feature_summary() 80 | -------------------------------------------------------------------------------- /src/vsgEarth/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | IF(VSGEARTH_BUILD_SHARED_LIBS) 2 | ADD_DEFINITIONS(-DVSGEARTH_LIBRARY) 3 | ELSE() 4 | ADD_DEFINITIONS(-DVSGEARTH_LIBRARY_STATIC) 5 | ENDIF() 6 | 7 | set(LIB_NAME vsgEarth) 8 | 9 | set(LIB_PUBLIC_HEADERS 10 | Export.h 11 | TerrainEngineVOE.h 12 | TileReaderVOE.h 13 | VoeSim.h) 14 | 15 | set(SOURCES 16 | ${LIB_PUBLIC_HEADERS} 17 | TerrainEngineVOE.cpp 18 | TileReaderVOE.cpp 19 | VoeLOD.cpp 20 | VoeLOD.h 21 | VoeSim.cpp 22 | VoeImageUtils.cpp 23 | VoeImageUtils.h 24 | ) 25 | 26 | ADD_LIBRARY( 27 | ${LIB_NAME} 28 | ${LIB_PUBLIC_HEADERS} 29 | ${SOURCES} 30 | ) 31 | 32 | set_target_properties(${LIB_NAME} PROPERTIES 33 | PUBLIC_HEADER "${LIB_PUBLIC_HEADERS}" 34 | ) 35 | 36 | target_link_libraries(${LIB_NAME} vsg::vsg osgEarth ${OpenSceneGraph_LIBRARIES}) 37 | 38 | target_compile_definitions(${LIB_NAME} PRIVATE vsgXchange_FOUND) 39 | target_link_libraries(${LIB_NAME} vsgXchange::vsgXchange) 40 | 41 | SET(CONCAT_COMMAND ${CMAKE_SOURCE_DIR}/CMakeModules/ConcatFiles.cmake) 42 | # Our GLSL "library" of functions that are made available in vertex and fragment shaders 43 | SET(GLSL_FILES voeSDK.glsl) 44 | list_transform_prepend(GLSL_FILES "${CMAKE_CURRENT_SOURCE_DIR}/") 45 | 46 | set(GLSLC $ENV{VULKAN_SDK}/bin/glslc) 47 | 48 | macro(make_shader SHADER_SOURCE SHADER_OUTPUT) 49 | add_custom_command(OUTPUT ${SHADER_OUTPUT} 50 | COMMAND "${CMAKE_COMMAND}" -P ${CONCAT_COMMAND} ${SHADER_SOURCE} ${GLSL_FILES} ${SHADER_OUTPUT} 51 | DEPENDS ${SHADER_SOURCE} ${GLSL_FILES}) 52 | 53 | add_custom_command(OUTPUT ${SHADER_OUTPUT}.spv 54 | COMMAND ${GLSLC} -I${CMAKE_CURRENT_SOURCE_DIR} -o ${SHADER_OUTPUT}.spv ${SHADER_OUTPUT} 55 | DEPENDS ${SHADER_OUTPUT} ${CMAKE_CURRENT_SOURCE_DIR}/VoeSDK.h) 56 | endmacro(make_shader) 57 | 58 | make_shader(${CMAKE_CURRENT_SOURCE_DIR}/reverse_depth.vert ${CMAKE_CURRENT_BINARY_DIR}/elevation.vert) 59 | make_shader(${CMAKE_CURRENT_SOURCE_DIR}/elevation_main.frag ${CMAKE_CURRENT_BINARY_DIR}/elevation.frag) 60 | 61 | set(TARGET_SPIRV 62 | ${CMAKE_CURRENT_BINARY_DIR}/elevation.vert.spv 63 | ${CMAKE_CURRENT_BINARY_DIR}/elevation.frag.spv 64 | ) 65 | 66 | add_custom_target(voe_shaders ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/elevation.vert.spv ${CMAKE_CURRENT_BINARY_DIR}/elevation.frag.spv) 67 | 68 | INCLUDE(ModuleInstall OPTIONAL) 69 | 70 | install(FILES ${TARGET_SPIRV} 71 | DESTINATION ${VSGEARTH_DATA_DIR}) 72 | 73 | install(TARGETS vsgEarth EXPORT vsgEarthTargets 74 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 75 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 76 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 77 | PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${LIB_NAME}" 78 | ) 79 | -------------------------------------------------------------------------------- /src/vsgEarth/voeSDK.glsl: -------------------------------------------------------------------------------- 1 | 2 | #include "VoeSDK.h" 3 | 4 | // Utility functions that should work in any stage 5 | 6 | /** 7 | * Sample the elevation data at a UV tile coordinate. 8 | */ 9 | float oe_terrain_getElevation(in vec2 uv) 10 | { 11 | // Texel-level scale and bias allow us to sample the elevation texture 12 | // on texel center instead of edge. 13 | vec2 elevc = uv 14 | * voeTile.elevTexelCoeff.x * voeTile.elevationTexMatrix[0][0] // scale 15 | + voeTile.elevTexelCoeff.x * voeTile.elevationTexMatrix[3].st // bias 16 | + voeTile.elevTexelCoeff.y; 17 | 18 | return texture(elevationTex, elevc).r; 19 | } 20 | 21 | /** 22 | * Read the normal vector and curvature at resolved UV tile coordinates. 23 | */ 24 | vec4 oe_terrain_getNormalAndCurvatureScaled(in vec2 uv_scaledBiased) 25 | { 26 | vec4 n = texture(normalTex, uv_scaledBiased); 27 | n.xyz = n.xyz*2.0-1.0; 28 | float curv = n.z; 29 | n.z = 1.0 - abs(n.x) - abs(n.y); 30 | // unnecessary since Z is never < 0: 31 | //float t = clamp(-n.z, 0, 1); 32 | //n.x += (n.x > 0)? -t : t; 33 | //n.y += (n.y > 0)? -t : t; 34 | return vec4(normalize(n.xyz), curv); 35 | } 36 | 37 | vec4 oe_terrain_getNormalAndCurvature(in vec2 st) 38 | { 39 | vec2 uv_scaledBiased = st 40 | * voeTile.elevTexelCoeff.x * voeTile.elevationTexMatrix[0][0] 41 | + voeTile.elevTexelCoeff.x * voeTile.elevationTexMatrix[3].st 42 | + voeTile.elevTexelCoeff.y; 43 | 44 | return oe_terrain_getNormalAndCurvatureScaled(uv_scaledBiased); 45 | } 46 | 47 | bool oe_layerEnabled(int layer) 48 | { 49 | uint mapLayer = voeTile.imageLayers[layer].layerIndex; 50 | return voeLayers.imageLayerParams[mapLayer].x == 0.0 ? false : true; 51 | } 52 | 53 | float oe_layerOpacity(int layer) 54 | { 55 | uint mapLayer = voeTile.imageLayers[layer].layerIndex; 56 | return voeLayers.imageLayerParams[mapLayer].y; 57 | } 58 | 59 | int oe_layerColorBlending(int layer) 60 | { 61 | uint mapLayer = voeTile.imageLayers[layer].layerIndex; 62 | return voeLayers.imageLayerParams[mapLayer].z == 0.0 ? 0 : 1; 63 | } 64 | 65 | vec4 oe_blendLayerColor(vec4 color, int layer) 66 | { 67 | vec4 layerColor; 68 | float oe_layer_opacity = oe_layerOpacity(layer); 69 | if (oe_layerColorBlending(layer) == OE_BLEND_INTERPOLATE) 70 | { 71 | layerColor.rgb = color.xyz; 72 | layerColor.a = color.a * oe_layer_opacity; 73 | } 74 | else 75 | { 76 | const float OE_MODULATION_EXPOSURE = 2.35; 77 | vec3 rgbHi = (oe_layer_opacity > 0.0 78 | ? color.rgb * OE_MODULATION_EXPOSURE / oe_layer_opacity 79 | : vec3(1.0)); 80 | layerColor.rgb = mix(vec3(1.0), rgbHi, oe_layer_opacity); 81 | layerColor.a = 1.0; 82 | } 83 | return layerColor; 84 | } 85 | -------------------------------------------------------------------------------- /src/vsgEarth/elevation_main.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "VoeSDK.h" 4 | 5 | layout(location = 0) in vec3 fragColor; 6 | layout(location = 1) in vec2 fragTexCoord; 7 | layout(location = 2) in vec3 oe_UpVectorView; 8 | layout(location = 3) in vec2 oe_normalMapCoords; 9 | layout(location = 4) in vec3 oe_normalMapBinormal; 10 | 11 | layout(location = 0) out vec4 outColor; 12 | 13 | void main() 14 | { 15 | vec3 tangent = normalize(cross(oe_normalMapBinormal, oe_UpVectorView)); 16 | mat3 oe_normalMapTBN = mat3(tangent, oe_normalMapBinormal, oe_UpVectorView); 17 | vec4 normalAndCurvature = oe_terrain_getNormalAndCurvature(oe_normalMapCoords); 18 | vec3 vp_Normal = normalize( oe_normalMapTBN*normalAndCurvature.rgb ); 19 | vec3 color = vec3(0.0, 0.0, 0.0); 20 | vec4 lightNums = lightData.values[0]; 21 | int numAmbientLights = int(lightNums[0]); 22 | int numDirectionalLights = int(lightNums[1]); 23 | int numPointLights = int(lightNums[2]); 24 | int numSpotLights = int(lightNums[3]); 25 | int index = 1; 26 | outColor = vec4(1.0, 1.0, 1.0, 1.0); 27 | 28 | vec4 diffuseColor = vec4(0.0, 0.0, 0.0, 0.0); 29 | for (int i = 0; i < voeTile.numImageLayers; ++i) 30 | { 31 | if (oe_layerEnabled(i)) 32 | { 33 | vec4 baseColor = texture(texSampler[i], fragTexCoord) * vec4(fragColor.rgb, 1.0); 34 | vec4 layerColor = oe_blendLayerColor(baseColor, i); 35 | // formulae for blending with destination alpha 36 | diffuseColor.a = layerColor.a + diffuseColor.a * (1 - layerColor.a); 37 | diffuseColor.rgb 38 | = (layerColor.rgb * layerColor.a + diffuseColor.rgb * diffuseColor.a * (1.0 - layerColor.a)) / diffuseColor.a; 39 | } 40 | } 41 | 42 | if (numAmbientLights>0) 43 | { 44 | // ambient lights 45 | for(int i = 0; i0) 53 | { 54 | // directional lights 55 | for(int i = 0; i 0.0) 64 | { 65 | vec3 halfDir = normalize(direction + vd); 66 | color.rgb += specularColor.rgb * (pow(max(dot(halfDir, nd), 0.0), shininess) * lightColor.a); 67 | } 68 | #endif 69 | } 70 | } 71 | outColor.rgb = color.rgb; 72 | } 73 | -------------------------------------------------------------------------------- /src/vsgEarth/VoeImageUtils.cpp: -------------------------------------------------------------------------------- 1 | #include "VoeImageUtils.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | /* 8 | 9 | Copyright(c) 2018 Robert Osfield 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | */ 18 | 19 | // vsgXchange internals 20 | 21 | namespace osgEarth 22 | { 23 | 24 | template 25 | vsg::ref_ptr create(osg::ref_ptr image, VkFormat format) 26 | { 27 | vsg::ref_ptr vsg_data; 28 | if (image->r() == 1) 29 | { 30 | vsg_data = vsg::Array2D::create(image->s(), image->t(), reinterpret_cast(image->data()), vsg::Data::Layout{format}); 31 | } 32 | else 33 | { 34 | vsg_data = vsg::Array3D::create(image->s(), image->t(), image->r(), reinterpret_cast(image->data()), vsg::Data::Layout{format}); 35 | } 36 | 37 | return vsg_data; 38 | } 39 | 40 | vsg::ref_ptr createExpandedData(const osg::Image* image) 41 | { 42 | if (image->getDataType() != GL_UNSIGNED_BYTE) 43 | return {}; 44 | unsigned sourceSize = image->getTotalSizeInBytesIncludingMipmaps(); 45 | unsigned sourceElements = sourceSize / 3; 46 | const unsigned char* sourceData = image->data(); 47 | vsg::ubvec4* destData = new vsg::ubvec4[sourceElements]; 48 | const unsigned char* srcPtr = sourceData; 49 | for (unsigned i = 0; i < sourceElements; ++i) 50 | { 51 | for (unsigned j = 0; j < 3; ++j) 52 | destData[i][j] = *srcPtr++; 53 | destData[i][3] = 255; 54 | } 55 | vsg::Data::Layout layout; 56 | layout.format = VK_FORMAT_R8G8B8A8_UNORM; 57 | layout.maxNumMipmaps = image->getNumMipmapLevels(); 58 | if (image->getOrigin() == osg::Image::BOTTOM_LEFT) 59 | layout.origin = vsg::BOTTOM_LEFT; 60 | else 61 | layout.origin = vsg::TOP_LEFT; 62 | return vsg::Array2D::create(image->s(), image->t(), destData, layout); 63 | } 64 | 65 | vsg::ref_ptr convertImage(const osg::Image* image, bool mapRGBtoRGBAHint) 66 | { 67 | if (mapRGBtoRGBAHint) 68 | { 69 | return osg2vsg::convert(*image); 70 | } 71 | else 72 | { 73 | vsg::ref_ptr options = vsg::Options::create(); 74 | options->mapRGBtoRGBAHint = false; 75 | return osg2vsg::convert(*image, options); 76 | } 77 | } 78 | 79 | vsg::ref_ptr convertToVsg(const osg::Image* image, bool mapRGBtoRGBAHint) 80 | { 81 | if (!image || image->isCompressed() 82 | || (image->getPixelFormat() != GL_RG && image->getPixelFormat() != GL_RGB)) 83 | return convertImage(image, mapRGBtoRGBAHint); 84 | 85 | if (image->getPixelFormat() == GL_RGB) 86 | { 87 | if (mapRGBtoRGBAHint) 88 | return createExpandedData(image); 89 | else 90 | return convertImage(image, mapRGBtoRGBAHint); 91 | } 92 | 93 | osg::Image* new_image = const_cast(image); 94 | 95 | // we want to pass ownership of the new_image data onto the vsg_image so reset the allocation 96 | // mode on the image to prevent deletion. 97 | new_image->setAllocationMode(osg::Image::NO_DELETE); 98 | 99 | vsg::ref_ptr vsg_data; 100 | if (image->getDataType() == GL_UNSIGNED_BYTE) 101 | vsg_data = create(new_image, VK_FORMAT_R8G8_UNORM); 102 | else if (image->getDataType() == GL_UNSIGNED_SHORT) 103 | vsg_data = create(new_image, VK_FORMAT_R16G16_UNORM); 104 | else if (image->getDataType() == GL_UNSIGNED_INT) 105 | vsg_data = create(new_image, VK_FORMAT_R32G32_UINT); 106 | else if (image->getDataType() == GL_FLOAT) 107 | vsg_data = create(new_image, VK_FORMAT_R32G32_SFLOAT); 108 | else if (image->getDataType() == GL_DOUBLE) 109 | vsg_data = create(new_image, VK_FORMAT_R64G64_SFLOAT); 110 | 111 | vsg::Data::Layout& layout = vsg_data->getLayout(); 112 | layout.maxNumMipmaps = image->getNumMipmapLevels(); 113 | layout.origin = (image->getOrigin() == osg::Image::BOTTOM_LEFT) ? vsg::BOTTOM_LEFT : vsg::TOP_LEFT; 114 | 115 | return vsg_data; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/vsgEarth/TerrainEngineVOE.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Class responsible for assembling a tile from the osgEarth map. In practice this means that it is 10 | // responsible for setting up the StateGroup nodes that hold the BindGraphicsPipeline objects that 11 | // are used to render the tiles. Management of the vsg::PagedLOD structure is delegated to 12 | // TileReaderVOE. 13 | 14 | #include "Export.h" 15 | #include "TileReaderVOE.h" 16 | #include "VoeSim.h" 17 | 18 | namespace osgEarth 19 | { 20 | struct TileParams 21 | { 22 | TileParams(int maxLayers) 23 | : maxLayers(maxLayers) 24 | { 25 | data = vsg::vec4Array::create(4 + 1 + 1 + 5 * maxLayers); 26 | } 27 | int maxLayers; 28 | 29 | vsg::mat4& elevationTexMatrix() 30 | { 31 | return *reinterpret_cast(&(*data)[0]); 32 | } 33 | 34 | 35 | vsg::vec2& elevTexelCoeff() 36 | { 37 | return *reinterpret_cast(&(*data)[4]); 38 | } 39 | 40 | uint32_t& numImageLayers() 41 | { 42 | return *reinterpret_cast(&(*data)[5]); 43 | } 44 | 45 | vsg::mat4& imageTexMatrix(int i) 46 | { 47 | return *reinterpret_cast(&(*data)[6 + i * 5]); 48 | } 49 | 50 | uint32_t& layerIndex(int i) 51 | { 52 | return *reinterpret_cast(&(*data)[6 + i * 5 + 4]); 53 | } 54 | vsg::ref_ptr data; 55 | }; 56 | 57 | // Layer parameters for the whole map 58 | struct LayerParams : public vsg::Inherit 59 | { 60 | LayerParams(int numLayers) 61 | : numLayers(numLayers), layerUIDs(numLayers, 0) 62 | { 63 | data = vsg::vec4Array::create(numLayers); 64 | layerParamsDescriptor = vsg::DescriptorBuffer::create(data, 0); 65 | } 66 | int numLayers; 67 | 68 | bool enabled(int layer) const 69 | { 70 | return (*data)[layer][0] == 1.0f; 71 | } 72 | 73 | void setEnabled(int layer, bool val) 74 | { 75 | (*data)[layer][0] = val; 76 | } 77 | 78 | float opacity(int layer) const 79 | { 80 | return (*data)[layer][1]; 81 | } 82 | 83 | void setOpacity(int layer, float opacity) 84 | { 85 | (*data)[layer][1] = opacity; 86 | } 87 | 88 | ColorBlending blendMode(int layer) const 89 | { 90 | return (*data)[layer][2] == 0.0 ? BLEND_INTERPOLATE : BLEND_MODULATE; 91 | } 92 | 93 | void setBlendMode(int layer, ColorBlending blendMode) 94 | { 95 | (*data)[layer][2] = blendMode; 96 | } 97 | std::vector layerUIDs; 98 | vsg::ref_ptr data; 99 | vsg::ref_ptr layerParamsDescriptor; 100 | }; 101 | 102 | struct VOELayerCallback : public VisibleLayerCallback 103 | { 104 | VOELayerCallback(vsg::ref_ptr layerParams, int layerIdx) 105 | : layerParams(layerParams), layerIdx(layerIdx) 106 | { 107 | } 108 | vsg::ref_ptr layerParams; 109 | void onVisibleChanged(class VisibleLayer* layer) override; 110 | void onOpacityChanged(class VisibleLayer* layer) override; 111 | 112 | const int layerIdx; 113 | }; 114 | 115 | class VSGEARTH_EXPORT TerrainEngineVOE : public vsg::Inherit 116 | { 117 | public: 118 | class WireframeInputHandler : public vsg::Inherit 119 | { 120 | vsg::observer_ptr switchNode; 121 | unsigned state; 122 | public: 123 | WireframeInputHandler(vsg::ref_ptr& switchNode); 124 | void apply(vsg::KeyPressEvent& keyPress) override; 125 | }; 126 | 127 | TerrainEngineVOE(); 128 | osgEarth::MapNode* init(vsg::ref_ptr options, vsg::CommandLine& arguments, 129 | vsg::ref_ptr traits); 130 | vsg::ref_ptr createScene(vsg::ref_ptr options); 131 | vsg::ref_ptr createTile(const osgEarth::TileKey& key, 132 | vsg::ref_ptr options) const; 133 | void update(vsg::ref_ptr viewer, vsg::ref_ptr camera); 134 | osgEarth::Map* getMap() { return mapNode->getMap(); } 135 | const osgEarth::Map* getMap() const { return mapNode->getMap(); } 136 | vsg::ref_ptr createWireframeHandler(); 137 | vsg::ref_ptr tileReader; 138 | std::string projection; 139 | vsg::ref_ptr ellipsoidModel; 140 | 141 | osg::ref_ptr mapNode; 142 | SimpleLight simState; 143 | uint32_t mipmapLevelsHint; 144 | uint32_t numImageLayers; 145 | VkSampleCountFlagBits samples; 146 | protected: 147 | void initLayers(); 148 | vsg::ref_ptr descriptorSetLayout; 149 | vsg::ref_ptr pipelineLayout; 150 | vsg::ref_ptr sampler; 151 | vsg::ref_ptr elevationSampler; 152 | vsg::ref_ptr normalSampler; 153 | vsg::ref_ptr rasterSwitch; 154 | // Not used, except as a demonstration of specialization constants 155 | bool reverseDepth = false; 156 | osg::ref_ptr modelFactory; 157 | vsg::ref_ptr layerParams; 158 | vsg::ref_ptr emptyElevationDescImage; 159 | vsg::ref_ptr emptyNormalDescImage; 160 | vsg::ref_ptr directionalLight; 161 | vsg::ref_ptr ambientLight; 162 | }; 163 | } 164 | -------------------------------------------------------------------------------- /tests/viewpoints.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8.806974516644791 5 | 4465.397310772911 6 | 37.55814749233749 7 | -122.334535784141 8 | San Francisco, California 9 | -34.79540299384382 10 | 78142.53643278375 11 | 12 | 13 | 17.33521725357022 14 | 2462.60273069609 15 | 46.82181702111031 16 | -121.7814936386096 17 | Mt Rainier, Washington 18 | -21.29241356548601 19 | 23926.75258864516 20 | 21 | 22 | -10.1722 23 | -30.6795 24 | 10034.5m 25 | -118.2936168720471 26 | 36.57474451155277 27 | 4234.804840335622 28 | +proj=longlat +datum=WGS84 +no_defs 29 | 30 | 31 | -72.70835146844568 32 | 6334.845537136309 33 | 27.94213038800919 34 | 86.9936567556778 35 | Nepal 36 | -18.63803872963365 37 | 13611.24948464565 38 | 39 | 40 | Grand Canyon, US 41 | -0.8051 42 | -21.5398 43 | 28203.1m 44 | -113.6095843647886 45 | 35.7698531293813 46 | 1345.386212403886 47 | +proj=longlat +datum=WGS84 +no_defs 48 | 49 | 50 | -121.443 51 | -11.05 52 | 131.491m 53 | 12.87702838363237 54 | 25.33350237523004 55 | 666.3695949940011 56 | +proj=longlat +datum=WGS84 +no_defs 57 | 58 | 59 | Hallstatt, Austria 60 | -21.36 61 | -35.0965 62 | 14786m 63 | 13.65444893405288 64 | 47.56470451831095 65 | 502.9998890347779 66 | 67 | 68 | Castel Gandolfo, Italy 69 | 99.4295 70 | -21.0981 71 | 2140.85m 72 | 12.66202808744844 73 | 41.75146324723777 74 | 340.4633435951546 75 | +proj=longlat +datum=WGS84 +no_defs 76 | 77 | 78 | Sion, Switzerland 79 | 77.5248 80 | -13.3381 81 | 1333.35m 82 | 7.275357552703459 83 | 46.19243759376418 84 | 683.0508383652195 85 | +proj=longlat +datum=WGS84 +no_defs 86 | 87 | 88 | 49.7937 89 | -26.6006 90 | 57.9473m 91 | -77.755348526353 92 | 38.04641758070872 93 | 42.46923910733312 94 | +proj=longlat +datum=WGS84 +no_defs 95 | 96 | 97 | 34.4312 98 | -16.2989 99 | 66.1984m 100 | -121.6193310135399 101 | 46.66096138267813 102 | 357.5176040958613 103 | +proj=longlat +datum=WGS84 +no_defs 104 | 105 | 106 | -65.3262 107 | -32.1928 108 | 84.6626m 109 | -75.76722964595007 110 | 36.2183042666146 111 | -40.40036714822054 112 | +proj=longlat +datum=WGS84 +no_defs 113 | 114 | 115 | 44.2873 116 | -27.5639 117 | 1731.74m 118 | -78.90770900060492 119 | -0.861917594231788 120 | 3529.603598460555 121 | +proj=longlat +datum=WGS84 +no_defs 122 | 123 | 124 | -19.7694 125 | -89.8951 126 | 1527.08m 127 | -122.4600185010713 128 | 38.28944457619371 129 | -9.02603252325207 130 | +proj=longlat +datum=WGS84 +no_defs 131 | 132 | 133 | -36.0105 134 | -44.2402 135 | 1117.34m 136 | 10.45728639794742 137 | 46.53056423352673 138 | 2686.409382589161 139 | +proj=longlat +datum=WGS84 +no_defs 140 | 141 | 142 | 169.243 143 | -28.2287 144 | 345.368m 145 | -78.66579949472357 146 | 38.94076285334965 147 | 822.951564357616 148 | +proj=longlat +datum=WGS84 +no_defs 149 | 150 | 151 | -1.18119 152 | -53.6318 153 | 1985.36m 154 | 131.0341405855624 155 | -25.34910343183637 156 | 614.0726152313873 157 | +proj=longlat +datum=WGS84 +no_defs 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /src/vsgEarth/TileReaderVOE.cpp: -------------------------------------------------------------------------------- 1 | #include "TileReaderVOE.h" 2 | #include "TerrainEngineVOE.h" 3 | #include "VoeImageUtils.h" 4 | #include "VoeLOD.h" 5 | 6 | #include 7 | #include 8 | 9 | using namespace osgEarth; 10 | 11 | osg::ArgumentParser convertArgs(vsg::CommandLine& commandLine) 12 | { 13 | return osg::ArgumentParser(&commandLine.argc(), commandLine.argv()); 14 | } 15 | 16 | // We're not rendering Mercator projections here, so skip this 17 | 18 | namespace 19 | { 20 | vsg::dvec3 computeLatitudeLongitudeAltitude(double lat, double lon, double alt) 21 | { 22 | return vsg::dvec3(lat, lon, alt); 23 | } 24 | } 25 | 26 | TileReaderVOE::TileReaderVOE() 27 | { 28 | } 29 | 30 | vsg::ref_ptr TileReaderVOE::read(const vsg::Path& filename, vsg::ref_ptr options) const 31 | { 32 | auto extension = vsg::lowerCaseFileExtension(filename); 33 | if (extension != ".tile") 34 | return {}; 35 | vsg::ref_ptr te(terrainEngine); 36 | if (!te) 37 | return {}; 38 | 39 | std::string tile_info = filename.substr(0, filename.length() - 5); 40 | if (tile_info == "root") 41 | { 42 | return read_root(options); 43 | } 44 | else 45 | { 46 | std::stringstream sstr(tile_info); 47 | 48 | uint32_t x, y, lod; 49 | char ch; // the '/' 50 | sstr >> lod >> ch >> x >> ch >> y; 51 | osgEarth::TileKey key(lod, x, y, te->getMap()->getProfile()); 52 | 53 | // std::cout<<"read("< tile_info = "<options = options; 83 | 84 | sceneRoot->addChild(plod); 85 | } 86 | } 87 | 88 | uint32_t estimatedNumOfTilesBelow = 0; 89 | uint32_t maxNumTilesBelow = 40000; 90 | 91 | uint32_t level = 0; 92 | for (uint32_t i = level; i < maxLevel; ++i) 93 | { 94 | estimatedNumOfTilesBelow += std::pow(4, i - level); 95 | } 96 | 97 | uint32_t tileMultiplier = std::min(estimatedNumOfTilesBelow, maxNumTilesBelow) + 1; 98 | 99 | // set up the ResourceHints required to make sure the VSG preallocates enough Vulkan resources for the paged database 100 | vsg::CollectResourceRequirements collectRequirements; 101 | sceneRoot->accept(collectRequirements); 102 | sceneRoot->setObject("ResourceHints", collectRequirements.createResourceHints(tileMultiplier)); 103 | 104 | return sceneRoot; 105 | } 106 | 107 | vsg::ref_ptr TileReaderVOE::read_subtile(const osgEarth::TileKey& key, 108 | vsg::ref_ptr options) const 109 | { 110 | vsg::ref_ptr te(terrainEngine); 111 | if (!te) 112 | return {}; 113 | 114 | // std::cout<<"Need to load subtile for "<> childTiles; 123 | for (int i = 0; i < 4; ++i) 124 | { 125 | auto childKey = key.createChildKey(i); 126 | auto childTile = te->createTile(childKey, options); 127 | if (childTile) 128 | { 129 | childTile->setValue("tileName", childKey.str()); 130 | childTiles.push_back(childTile); 131 | } 132 | else 133 | break; 134 | } 135 | 136 | if (childTiles.size()==4) 137 | { 138 | for(auto tile : childTiles) 139 | { 140 | vsg::ComputeBounds computeBound; 141 | tile->accept(computeBound); 142 | auto& bb = computeBound.bounds; 143 | vsg::dsphere newBound((bb.min.x + bb.max.x) * 0.5, (bb.min.y + bb.max.y) * 0.5, (bb.min.z + bb.max.z) * 0.5, vsg::length(bb.max - bb.min) * 0.5); 144 | 145 | if (local_lod < maxLevel) 146 | { 147 | auto plod = VoeLOD::create(); 148 | plod->bound = newBound; 149 | plod->children[0] = VoeLOD::Child{lodTransitionScreenHeightRatio, {}}; // external child visible when it's bound occupies more than 1/4 of the height of the window 150 | plod->children[1] = VoeLOD::Child{0.0, tile}; // visible always 151 | std::string tileName; 152 | tile->getValue("tileName", tileName); 153 | plod->filename = vsg::make_string(tileName, ".tile"); 154 | plod->options = options; 155 | 156 | //std::cout<<"plod->filename "<filename<addChild(plod); 159 | } 160 | else 161 | { 162 | auto cullGroup = vsg::CullGroup::create(); 163 | cullGroup->bound = newBound; 164 | cullGroup->addChild(tile); 165 | 166 | group->addChild(cullGroup); 167 | } 168 | } 169 | } 170 | 171 | vsg::time_point end_read = vsg::clock::now(); 172 | 173 | double time_to_read_tile = std::chrono::duration(end_read - start_read).count(); 174 | 175 | { 176 | std::scoped_lock lock(statsMutex); 177 | numTilesRead += 1; 178 | totalTimeReadingTiles += time_to_read_tile; 179 | } 180 | 181 | if (group->children.size() != 4) 182 | { 183 | OE_DEBUG << "Warning: could not load all 4 subtiles, loaded only " << group->children.size() << std::endl; 184 | setTileStatus(group, NoSuchTile); 185 | } 186 | 187 | return group; 188 | } 189 | 190 | void TileReaderVOE::init(vsg::CommandLine& commandLine, vsg::ref_ptr) 191 | { 192 | vsg::ref_ptr te(terrainEngine); 193 | if (!te) 194 | throw std::runtime_error("no terrain engine!"); 195 | commandLine.read("-t", lodTransitionScreenHeightRatio); 196 | commandLine.read("-m", maxLevel); 197 | 198 | } 199 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | VSGEARTH SOFTWARE LICENSE 2 | 3 | The vsgEarth library and included programs are provided under the terms 4 | of the GNU Library General Public License (LGPL - included below) with the 5 | following exceptions and stipulations: 6 | 7 | 1. Static linking of applications and modules to the osgEarth library does NOT 8 | constitute a derivative work and does not require the author to provide source 9 | code for the application or module. 10 | 11 | 2. If you link the application or module to a modified version of the osgEarth 12 | library, then the modifications to osgEarth must be provided under the terms of 13 | the LGPL. 14 | 15 | 3. The text herein comprises the definitive licensing information for the osgEarth 16 | library, and supersedes any licensing text found in osgEarth source files, 17 | header files, or documentation. 18 | 19 | 20 | ---- 21 | 22 | GNU LESSER GENERAL PUBLIC LICENSE 23 | Version 3, 29 June 2007 24 | 25 | Copyright (C) 2007 Free Software Foundation, Inc. 26 | Everyone is permitted to copy and distribute verbatim copies 27 | of this license document, but changing it is not allowed. 28 | 29 | 30 | This version of the GNU Lesser General Public License incorporates 31 | the terms and conditions of version 3 of the GNU General Public 32 | License, supplemented by the additional permissions listed below. 33 | 34 | 0. Additional Definitions. 35 | 36 | As used herein, "this License" refers to version 3 of the GNU Lesser 37 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 38 | General Public License. 39 | 40 | "The Library" refers to a covered work governed by this License, 41 | other than an Application or a Combined Work as defined below. 42 | 43 | An "Application" is any work that makes use of an interface provided 44 | by the Library, but which is not otherwise based on the Library. 45 | Defining a subclass of a class defined by the Library is deemed a mode 46 | of using an interface provided by the Library. 47 | 48 | A "Combined Work" is a work produced by combining or linking an 49 | Application with the Library. The particular version of the Library 50 | with which the Combined Work was made is also called the "Linked 51 | Version". 52 | 53 | The "Minimal Corresponding Source" for a Combined Work means the 54 | Corresponding Source for the Combined Work, excluding any source code 55 | for portions of the Combined Work that, considered in isolation, are 56 | based on the Application, and not on the Linked Version. 57 | 58 | The "Corresponding Application Code" for a Combined Work means the 59 | object code and/or source code for the Application, including any data 60 | and utility programs needed for reproducing the Combined Work from the 61 | Application, but excluding the System Libraries of the Combined Work. 62 | 63 | 1. Exception to Section 3 of the GNU GPL. 64 | 65 | You may convey a covered work under sections 3 and 4 of this License 66 | without being bound by section 3 of the GNU GPL. 67 | 68 | 2. Conveying Modified Versions. 69 | 70 | If you modify a copy of the Library, and, in your modifications, a 71 | facility refers to a function or data to be supplied by an Application 72 | that uses the facility (other than as an argument passed when the 73 | facility is invoked), then you may convey a copy of the modified 74 | version: 75 | 76 | a) under this License, provided that you make a good faith effort to 77 | ensure that, in the event an Application does not supply the 78 | function or data, the facility still operates, and performs 79 | whatever part of its purpose remains meaningful, or 80 | 81 | b) under the GNU GPL, with none of the additional permissions of 82 | this License applicable to that copy. 83 | 84 | 3. Object Code Incorporating Material from Library Header Files. 85 | 86 | The object code form of an Application may incorporate material from 87 | a header file that is part of the Library. You may convey such object 88 | code under terms of your choice, provided that, if the incorporated 89 | material is not limited to numerical parameters, data structure 90 | layouts and accessors, or small macros, inline functions and templates 91 | (ten or fewer lines in length), you do both of the following: 92 | 93 | a) Give prominent notice with each copy of the object code that the 94 | Library is used in it and that the Library and its use are 95 | covered by this License. 96 | 97 | b) Accompany the object code with a copy of the GNU GPL and this license 98 | document. 99 | 100 | 4. Combined Works. 101 | 102 | You may convey a Combined Work under terms of your choice that, 103 | taken together, effectively do not restrict modification of the 104 | portions of the Library contained in the Combined Work and reverse 105 | engineering for debugging such modifications, if you also do each of 106 | the following: 107 | 108 | a) Give prominent notice with each copy of the Combined Work that 109 | the Library is used in it and that the Library and its use are 110 | covered by this License. 111 | 112 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 113 | document. 114 | 115 | c) For a Combined Work that displays copyright notices during 116 | execution, include the copyright notice for the Library among 117 | these notices, as well as a reference directing the user to the 118 | copies of the GNU GPL and this license document. 119 | 120 | d) Do one of the following: 121 | 122 | 0) Convey the Minimal Corresponding Source under the terms of this 123 | License, and the Corresponding Application Code in a form 124 | suitable for, and under terms that permit, the user to 125 | recombine or relink the Application with a modified version of 126 | the Linked Version to produce a modified Combined Work, in the 127 | manner specified by section 6 of the GNU GPL for conveying 128 | Corresponding Source. 129 | 130 | 1) Use a suitable shared library mechanism for linking with the 131 | Library. A suitable mechanism is one that (a) uses at run time 132 | a copy of the Library already present on the user's computer 133 | system, and (b) will operate properly with a modified version 134 | of the Library that is interface-compatible with the Linked 135 | Version. 136 | 137 | e) Provide Installation Information, but only if you would otherwise 138 | be required to provide such information under section 6 of the 139 | GNU GPL, and only to the extent that such information is 140 | necessary to install and execute a modified version of the 141 | Combined Work produced by recombining or relinking the 142 | Application with a modified version of the Linked Version. (If 143 | you use option 4d0, the Installation Information must accompany 144 | the Minimal Corresponding Source and Corresponding Application 145 | Code. If you use option 4d1, you must provide the Installation 146 | Information in the manner specified by section 6 of the GNU GPL 147 | for conveying Corresponding Source.) 148 | 149 | 5. Combined Libraries. 150 | 151 | You may place library facilities that are a work based on the 152 | Library side by side in a single library together with other library 153 | facilities that are not Applications and are not covered by this 154 | License, and convey such a combined library under terms of your 155 | choice, if you do both of the following: 156 | 157 | a) Accompany the combined library with a copy of the same work based 158 | on the Library, uncombined with any other library facilities, 159 | conveyed under the terms of this License. 160 | 161 | b) Give prominent notice with the combined library that part of it 162 | is a work based on the Library, and explaining where to find the 163 | accompanying uncombined form of the same work. 164 | 165 | 6. Revised Versions of the GNU Lesser General Public License. 166 | 167 | The Free Software Foundation may publish revised and/or new versions 168 | of the GNU Lesser General Public License from time to time. Such new 169 | versions will be similar in spirit to the present version, but may 170 | differ in detail to address new problems or concerns. 171 | 172 | Each version is given a distinguishing version number. If the 173 | Library as you received it specifies that a certain numbered version 174 | of the GNU Lesser General Public License "or any later version" 175 | applies to it, you have the option of following the terms and 176 | conditions either of that published version or of any later version 177 | published by the Free Software Foundation. If the Library as you 178 | received it does not specify a version number of the GNU Lesser 179 | General Public License, you may choose any version of the GNU Lesser 180 | General Public License ever published by the Free Software Foundation. 181 | 182 | If the Library as you received it specifies that a proxy can decide 183 | whether future versions of the GNU Lesser General Public License shall 184 | apply, that proxy's public statement of acceptance of any version is 185 | permanent authorization for you to choose that version for the 186 | Library. 187 | -------------------------------------------------------------------------------- /src/applications/voe_globe/vsgpagedlod.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // #include "../../shared/AnimationPath.h" 11 | 12 | #include 13 | 14 | using namespace osgEarth; 15 | 16 | void usage(const char* name) 17 | { 18 | std::cout 19 | << "\nUsage: " << name << " file.earth\n\n" 20 | << "where options include:\n" 21 | << "--debug|-d\t load Vulkan debug layer\n" 22 | << "--api|-a\t load Vulkan dump layer\n" 23 | << "--IMMEDIATE\t set swapchain present mode to VK_PRESENT_MODE_IMMEDIATE_KHR\n" 24 | << "--fullscreen|--fs\t fullscreen window\n" 25 | << "--window|-w width height\t set window dimensions\n" 26 | << "--screen number\n" 27 | << "--display number\n" 28 | << "--samples n\t enable multisamples with n samples\n" 29 | << "-f numFrames\t run for numFrames and exit\n" 30 | << "--load-levels levels\t preload levels\n" 31 | << "--hmh height\t horizon mountain height for ellipsoid perspective viewing\n" 32 | << "--disble-EllipsoidPerspective|--dep\tdisable ellipsoid perspective\n" 33 | << "--file-cache path\t VSG file cache\n" 34 | << "--osgearth|-e\t osgEarth-style mouse buttons\n" 35 | << "--ot numThreads\t number of operation threads\n" 36 | << "--poi lat lon\t coordinates of initial point of interest\n" 37 | << "--distance dist\t distance from point of interest\n"; 38 | } 39 | 40 | int main(int argc, char** argv) 41 | { 42 | try 43 | { 44 | // set up defaults and read command line arguments to override them 45 | vsg::CommandLine arguments(&argc, argv); 46 | 47 | if (arguments.read({"--help", "-h", "-?"})) 48 | { 49 | usage(argv[0]); 50 | return 0; 51 | } 52 | // set up vsg::Options to pass in filepaths and ReaderWriter's and other IO realted options to use when reading and writing files. 53 | auto options = vsg::Options::create(); 54 | options->fileCache = vsg::getEnv("VSG_FILE_CACHE"); 55 | options->paths = vsg::getEnvPaths("VSG_FILE_PATH"); 56 | 57 | auto terrainEngine = TerrainEngineVOE::create(); 58 | options->add(terrainEngine->tileReader); 59 | // add vsgXchange's support for reading and writing 3rd party file formats 60 | options->add(vsgXchange::all::create()); 61 | 62 | arguments.read(options); 63 | 64 | auto windowTraits = vsg::WindowTraits::create(); 65 | windowTraits->windowTitle = "voe_globe"; 66 | windowTraits->debugLayer = arguments.read({"--debug", "-d"}); 67 | windowTraits->apiDumpLayer = arguments.read({"--api", "-a"}); 68 | if (arguments.read("--IMMEDIATE")) windowTraits->swapchainPreferences.presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR; 69 | if (arguments.read({"--fullscreen", "--fs"})) windowTraits->fullscreen = true; 70 | if (arguments.read({"--window", "-w"}, windowTraits->width, windowTraits->height)) { windowTraits->fullscreen = false; } 71 | arguments.read("--screen", windowTraits->screenNum); 72 | arguments.read("--display", windowTraits->display); 73 | arguments.read("--samples", windowTraits->samples); 74 | auto numFrames = arguments.value(-1, "-f"); 75 | auto loadLevels = arguments.value(0, "--load-levels"); 76 | auto horizonMountainHeight = arguments.value(0.0, "--hmh"); 77 | bool useEllipsoidPerspective = !arguments.read({"--disble-EllipsoidPerspective", "--dep"}); 78 | arguments.read("--file-cache", options->fileCache); 79 | bool osgEarthStyleMouseButtons = arguments.read({"--osgearth","-e"}); 80 | 81 | uint32_t numOperationThreads = 0; 82 | if (arguments.read("--ot", numOperationThreads)) options->operationThreads = vsg::OperationThreads::create(numOperationThreads); 83 | 84 | const double invalid_value = std::numeric_limits::max(); 85 | double poi_latitude = invalid_value; 86 | double poi_longitude = invalid_value; 87 | double poi_distance = invalid_value; 88 | while (arguments.read("--poi", poi_latitude, poi_longitude)) {}; 89 | while (arguments.read("--distance", poi_distance)) {}; 90 | 91 | if (arguments.errors()) return arguments.writeErrorMessages(std::cerr); 92 | terrainEngine->init(options, arguments, windowTraits); 93 | 94 | // create the viewer and assign window(s) to it 95 | bool multisampling = windowTraits->samples != VK_SAMPLE_COUNT_1_BIT; 96 | auto viewer = vsg::Viewer::create(); 97 | auto window = vsg::Window::create(windowTraits); 98 | if (!window) 99 | { 100 | std::cout << "Could not create windows." << std::endl; 101 | return 1; 102 | } 103 | 104 | if (multisampling) 105 | { 106 | // Initializing the device causes VSG to get the supported sample counts from Vulkan and 107 | // use the largest one that supports our request. 108 | window->getOrCreateDevice(); 109 | terrainEngine->samples = window->framebufferSamples(); 110 | } 111 | 112 | // load the root tile. 113 | auto vsg_scene = terrainEngine->createScene(options); 114 | if (!vsg_scene) return 1; 115 | 116 | viewer->addWindow(window); 117 | 118 | // compute the bounds of the scene graph to help position camera 119 | vsg::ComputeBounds computeBounds; 120 | vsg_scene->accept(computeBounds); 121 | vsg::dvec3 centre = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5; 122 | double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.6; 123 | double nearFarRatio = 0.0005; 124 | 125 | // set up the camera 126 | vsg::ref_ptr lookAt; 127 | vsg::ref_ptr perspective; 128 | vsg::ref_ptr ellipsoidModel(vsg_scene->getObject("EllipsoidModel")); 129 | if (ellipsoidModel) 130 | { 131 | if (poi_latitude != invalid_value && poi_longitude != invalid_value) 132 | { 133 | double height = (poi_distance != invalid_value) ? poi_distance : radius * 3.5; 134 | auto ecef = ellipsoidModel->convertLatLongAltitudeToECEF({poi_latitude, poi_longitude, 0.0}); 135 | auto ecef_normal = vsg::normalize(ecef); 136 | 137 | centre = ecef; 138 | vsg::dvec3 eye = centre + ecef_normal * height; 139 | vsg::dvec3 up = vsg::normalize(vsg::cross(ecef_normal, vsg::cross(vsg::dvec3(0.0, 0.0, 1.0), ecef_normal))); 140 | 141 | // set up the camera 142 | lookAt = vsg::LookAt::create(eye, centre, up); 143 | } 144 | else 145 | { 146 | lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0)); 147 | } 148 | 149 | if (useEllipsoidPerspective) 150 | { 151 | perspective = vsg::EllipsoidPerspective::create(lookAt, ellipsoidModel, 30.0, static_cast(window->extent2D().width) / static_cast(window->extent2D().height), nearFarRatio, horizonMountainHeight); 152 | } 153 | else 154 | { 155 | perspective = vsg::Perspective::create(30.0, static_cast(window->extent2D().width) / static_cast(window->extent2D().height), nearFarRatio * radius, radius * 4.5); 156 | } 157 | } 158 | else 159 | { 160 | perspective = vsg::Perspective::create(30.0, static_cast(window->extent2D().width) / static_cast(window->extent2D().height), nearFarRatio * radius, radius * 4.5); 161 | lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0)); 162 | } 163 | 164 | auto camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(window->extent2D())); 165 | 166 | // add close handler to respond the close window button and pressing escape 167 | viewer->addEventHandler(vsg::CloseHandler::create(viewer)); 168 | 169 | viewer->addEventHandler(terrainEngine->createWireframeHandler()); 170 | 171 | if (ellipsoidModel) 172 | { 173 | auto trackball = vsg::Trackball::create(camera, ellipsoidModel); 174 | trackball->addKeyViewpoint(vsg::KeySymbol('1'), 51.50151088842245, -0.14181489107549874, 2000.0, 2.0); // Grenwish Observatory 175 | trackball->addKeyViewpoint(vsg::KeySymbol('2'), 55.948642740309324, -3.199226855522667, 2000.0, 2.0); // Edinburgh Castle 176 | trackball->addKeyViewpoint(vsg::KeySymbol('3'), 48.858264952330764, 2.2945039609604665, 2000.0, 2.0); // Eiffel Town, Paris 177 | trackball->addKeyViewpoint(vsg::KeySymbol('4'), 52.5162603714634, 13.377684902745642, 2000.0, 2.0); // Brandenburg Gate, Berlin 178 | trackball->addKeyViewpoint(vsg::KeySymbol('5'), 30.047448591298807, 31.236319571791213, 10000.0, 2.0); // Cairo 179 | trackball->addKeyViewpoint(vsg::KeySymbol('6'), 35.653099536061156, 139.74704060056993, 10000.0, 2.0); // Tokyo 180 | trackball->addKeyViewpoint(vsg::KeySymbol('7'), 37.38701052699002, -122.08555895549424, 10000.0, 2.0); // Mountain View, California 181 | trackball->addKeyViewpoint(vsg::KeySymbol('8'), 40.689618207006355, -74.04465595488215, 10000.0, 2.0); // Empire State Building 182 | trackball->addKeyViewpoint(vsg::KeySymbol('9'), 25.997055873649554, -97.15543476551771, 1000.0, 2.0); // Boca Chica, Taxas 183 | 184 | if (osgEarthStyleMouseButtons) 185 | { 186 | trackball->panButtonMask = vsg::BUTTON_MASK_1; 187 | trackball->rotateButtonMask = vsg::BUTTON_MASK_2; 188 | trackball->zoomButtonMask = vsg::BUTTON_MASK_3; 189 | } 190 | 191 | viewer->addEventHandler(trackball); 192 | } 193 | else 194 | { 195 | viewer->addEventHandler(vsg::Trackball::create(camera)); 196 | } 197 | 198 | // if required pre load specific number of PagedLOD levels. 199 | if (loadLevels > 0) 200 | { 201 | vsg::LoadPagedLOD loadPagedLOD(camera, loadLevels); 202 | 203 | auto startTime = std::chrono::steady_clock::now(); 204 | 205 | vsg_scene->accept(loadPagedLOD); 206 | 207 | auto time = std::chrono::duration(std::chrono::steady_clock::now() - startTime).count(); 208 | std::cout << "No. of tiles loaed " << loadPagedLOD.numTiles << " in " << time << "ms." << std::endl; 209 | } 210 | 211 | auto commandGraph = vsg::createCommandGraphForView(window, camera, vsg_scene, VK_SUBPASS_CONTENTS_INLINE, false); 212 | viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph}); 213 | 214 | viewer->compile(); 215 | 216 | // rendering main loop 217 | while (viewer->advanceToNextFrame() && (numFrames < 0 || (numFrames--) > 0)) 218 | { 219 | // pass any events into EventHandlers assigned to the Viewer 220 | viewer->handleEvents(); 221 | 222 | viewer->update(); 223 | terrainEngine->update(viewer, camera); 224 | viewer->recordAndSubmit(); 225 | viewer->present(); 226 | } 227 | 228 | { 229 | std::scoped_lock lock(terrainEngine->tileReader->statsMutex); 230 | std::cout<<"numOperationThreads = "< 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace osgEarth; 16 | 17 | TerrainEngineVOE::WireframeInputHandler::WireframeInputHandler(vsg::ref_ptr& switchNode) 18 | : switchNode(switchNode), state(0) 19 | { 20 | } 21 | 22 | void TerrainEngineVOE::WireframeInputHandler::apply(vsg::KeyPressEvent& keyPress) 23 | { 24 | vsg::ref_ptr s(switchNode); 25 | if (!s) 26 | return; 27 | if (keyPress.keyBase == vsg::KEY_w) 28 | { 29 | state = (state + 1) % 3; 30 | for (unsigned i = 0; i < 3; ++i) 31 | { 32 | s->children[i].mask = i == state; 33 | } 34 | } 35 | } 36 | 37 | void VOELayerCallback::onVisibleChanged(class VisibleLayer *layer) 38 | { 39 | layerParams->setEnabled(layerIdx, layer->getVisible()); 40 | } 41 | 42 | void VOELayerCallback::onOpacityChanged(class VisibleLayer *layer) 43 | { 44 | layerParams->setOpacity(layerIdx, layer->getOpacity()); 45 | } 46 | 47 | TerrainEngineVOE::TerrainEngineVOE() 48 | : tileReader(TileReaderVOE::create()), mipmapLevelsHint(16), numImageLayers(0), 49 | samples(VK_SAMPLE_COUNT_1_BIT) 50 | { 51 | // XXX I'm not clear on what would happen if one tries to create an observer_ptr in an 52 | // unreferenced object, like TerrainEngineVOE is inside this constructor. Therefore, defer 53 | // initializing tileReader's observer_ptr to this. 54 | } 55 | 56 | namespace 57 | { 58 | osg::ArgumentParser convertArgs(vsg::CommandLine& commandLine) 59 | { 60 | return osg::ArgumentParser(&commandLine.argc(), commandLine.argv()); 61 | } 62 | } 63 | 64 | osgEarth::MapNode* 65 | TerrainEngineVOE::init(vsg::ref_ptr, vsg::CommandLine& commandLine, 66 | vsg::ref_ptr traits) 67 | { 68 | tileReader->terrainEngine = this; 69 | // Read the osgEarth arguments 70 | tileReader->init(commandLine); 71 | if (traits.valid()) 72 | { 73 | traits->depthFormat = VK_FORMAT_D32_SFLOAT; 74 | traits->deviceFeatures->get().fillModeNonSolid = VK_TRUE; 75 | } 76 | osg::ArgumentParser parser = convertArgs(commandLine); 77 | if (!(mapNode = osgEarth::MapNode::load(parser))) 78 | { 79 | return nullptr; 80 | } 81 | else 82 | { 83 | return mapNode.get(); 84 | } 85 | } 86 | 87 | vsg::ref_ptr TerrainEngineVOE::createScene(vsg::ref_ptr options) 88 | { 89 | if (!mapNode) 90 | throw std::runtime_error("no map"); 91 | const osgEarth::MapNode::Options& mapNodeOptions = const_cast(mapNode.get())->options(); 92 | modelFactory = new osgEarth::TerrainTileModelFactory(mapNodeOptions.terrain().get()); 93 | osgEarth::Map* map = mapNode->getMap(); 94 | auto const& em = map->getProfile()->getSRS()->getEllipsoid(); 95 | ellipsoidModel = vsg::EllipsoidModel::create(em.getRadiusEquator(), em.getRadiusPolar()); 96 | 97 | // set up graphics pipeline 98 | 99 | vsg::DescriptorSetLayoutBindings layerDescriptorBindings{ 100 | // layer parameters 101 | { 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, nullptr } 102 | }; 103 | 104 | auto layerDescriptorSetLayout = vsg::DescriptorSetLayout::create(layerDescriptorBindings); 105 | 106 | // Now the visible layers 107 | LayerVector layers; 108 | map->getLayers(layers); 109 | std::vector imageLayers; 110 | for (LayerVector::const_iterator i = layers.begin(); i != layers.end(); ++i) 111 | { 112 | Layer* layer = i->get(); 113 | 114 | if (!layer->isOpen()) 115 | continue; 116 | 117 | if (layer->getRenderType() != layer->RENDERTYPE_TERRAIN_SURFACE) 118 | continue; 119 | 120 | ImageLayer* imageLayer = dynamic_cast(layer); 121 | if (imageLayer) 122 | { 123 | imageLayers.push_back(imageLayer); 124 | } 125 | } 126 | numImageLayers= imageLayers.size(); 127 | layerParams = LayerParams::create(numImageLayers); 128 | for (int i = 0; i < numImageLayers; ++i) 129 | { 130 | layerParams->layerUIDs[i] = imageLayers[i]->getUID(); 131 | layerParams->setEnabled(i, imageLayers[i]->getVisible()); 132 | layerParams->setOpacity(i, imageLayers[i]->getOpacity()); 133 | layerParams->setBlendMode(i, imageLayers[i]->getColorBlending()); 134 | imageLayers[i]->Layer::addCallback(new VOELayerCallback(layerParams, i)); 135 | } 136 | 137 | vsg::DescriptorSetLayoutBindings elevationDescriptorBindings{ 138 | {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, numImageLayers, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, 139 | // Elevation texture 140 | {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT, nullptr}, 141 | // normal texture 142 | {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, nullptr}, 143 | {3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, nullptr } 144 | }; 145 | descriptorSetLayout = vsg::DescriptorSetLayout::create(elevationDescriptorBindings); 146 | 147 | vsg::PushConstantRanges pushConstantRanges{ 148 | {VK_SHADER_STAGE_VERTEX_BIT, 0, 128} // projection view, and model matrices, actual push constant calls autoaatically provided by the VSG's DispatchTraversal 149 | }; 150 | 151 | pipelineLayout 152 | = vsg::PipelineLayout::create(vsg::DescriptorSetLayouts{descriptorSetLayout, 153 | vsg::ViewDescriptorSetLayout::create(), 154 | layerDescriptorSetLayout}, 155 | pushConstantRanges); 156 | 157 | sampler = vsg::Sampler::create(); 158 | sampler->maxLod = mipmapLevelsHint; 159 | sampler->addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 160 | sampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 161 | sampler->addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 162 | sampler->anisotropyEnable = VK_TRUE; 163 | sampler->maxAnisotropy = 16.0f; 164 | 165 | elevationSampler = vsg::Sampler::create(); 166 | elevationSampler->maxLod = 0; 167 | elevationSampler->minFilter = VK_FILTER_NEAREST; 168 | elevationSampler->addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 169 | elevationSampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 170 | elevationSampler->addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 171 | 172 | normalSampler = vsg::Sampler::create(); 173 | normalSampler->maxLod = 0; 174 | normalSampler->addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 175 | normalSampler->addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 176 | normalSampler->addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; 177 | 178 | osg::Texture* emptyOsgElevation = createEmptyElevationTexture(); 179 | auto emptyElevData = convertToVsg(emptyOsgElevation->getImage(0)); 180 | emptyElevationDescImage = vsg::DescriptorImage::create(elevationSampler, emptyElevData, 1, 0, 181 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 182 | osg::Texture* emptyOsgNormal = createEmptyNormalMapTexture(); 183 | auto emptyNormalData = convertToVsg(emptyOsgNormal->getImage(0)); 184 | emptyNormalDescImage = vsg::DescriptorImage::create(normalSampler, emptyNormalData, 2, 0, 185 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 186 | // set up search paths to SPIRV shaders and textures 187 | vsg::Paths searchPaths = vsg::getEnvPaths("VSG_FILE_PATH"); 188 | auto dataDirPath = vsg::Path(VSGEARTH_FULL_DATA_DIR); 189 | searchPaths.push_back(dataDirPath); 190 | // load shaders 191 | vsg::ref_ptr vertexShader = vsg::ShaderStage::read(VK_SHADER_STAGE_VERTEX_BIT, "main", vsg::findFile("elevation.vert.spv", searchPaths)); 192 | vsg::ref_ptr fragmentShader = vsg::ShaderStage::read(VK_SHADER_STAGE_FRAGMENT_BIT, "main", vsg::findFile("elevation.frag.spv", searchPaths)); 193 | if (!vertexShader || !fragmentShader) 194 | { 195 | std::cout << "Could not create shaders." << std::endl; 196 | return {}; 197 | } 198 | 199 | vsg::VertexInputState::Bindings vertexBindingsDescriptions{ 200 | VkVertexInputBindingDescription{0, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX}, // vertex data 201 | VkVertexInputBindingDescription{1, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX}, // colour data 202 | VkVertexInputBindingDescription{2, sizeof(vsg::vec2), VK_VERTEX_INPUT_RATE_VERTEX}, // tex coord data 203 | VkVertexInputBindingDescription{3, sizeof(vsg::vec3), VK_VERTEX_INPUT_RATE_VERTEX} // normals 204 | }; 205 | 206 | vsg::VertexInputState::Attributes vertexAttributeDescriptions{ 207 | VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // vertex data 208 | VkVertexInputAttributeDescription{1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0}, // colour data 209 | VkVertexInputAttributeDescription{2, 2, VK_FORMAT_R32G32_SFLOAT, 0}, // tex coord data 210 | VkVertexInputAttributeDescription{3, 3, VK_FORMAT_R32G32B32_SFLOAT, 0} // vertex data 211 | }; 212 | 213 | auto depthStencilState = vsg::DepthStencilState::create(); 214 | vsg::ShaderStage::SpecializationConstants specializationConstants{ 215 | {0, vsg::uintValue::create(reverseDepth)}, 216 | {1, vsg::uintValue::create(numImageLayers)} 217 | }; 218 | vertexShader->specializationConstants = specializationConstants; 219 | fragmentShader->specializationConstants = specializationConstants; 220 | vsg::GraphicsPipelineStates fillPipelineStates{ 221 | vsg::RasterizationState::create(), 222 | vsg::VertexInputState::create(vertexBindingsDescriptions, vertexAttributeDescriptions), 223 | vsg::InputAssemblyState::create(), 224 | vsg::MultisampleState::create(samples), 225 | vsg::ColorBlendState::create(), 226 | depthStencilState}; 227 | 228 | auto wireRasterState = vsg::RasterizationState::create(); 229 | wireRasterState->polygonMode = VK_POLYGON_MODE_LINE; 230 | vsg::GraphicsPipelineStates wirePipelineStates(fillPipelineStates); 231 | wirePipelineStates[0] = wireRasterState; 232 | auto pointRasterState = vsg::RasterizationState::create(); 233 | pointRasterState->polygonMode = VK_POLYGON_MODE_POINT; 234 | vsg::GraphicsPipelineStates pointPipelineStates(fillPipelineStates); 235 | pointPipelineStates[0] = pointRasterState; 236 | 237 | vsg::ShaderStages shaderStages{vertexShader, fragmentShader}; 238 | auto lightStateGroup = vsg::StateGroup::create(); 239 | lightStateGroup->add(vsg::BindViewDescriptorSets::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 240 | 1)); 241 | auto layerDescriptorSet 242 | = vsg::DescriptorSet::create(layerDescriptorSetLayout, 243 | vsg::Descriptors{layerParams->layerParamsDescriptor}); 244 | auto bindLayerDescriptorSet = vsg::BindDescriptorSet::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 245 | 2, layerDescriptorSet); 246 | lightStateGroup->add(bindLayerDescriptorSet); 247 | // StateSwitch experiments 248 | auto rasterStateGroup = vsg::StateGroup::create(); 249 | rasterSwitch = vsg::StateSwitch::create(); 250 | vsg::GraphicsPipelineStates* pipelineStates[] 251 | = {&fillPipelineStates, &wirePipelineStates, &pointPipelineStates}; 252 | for (int i = 0; i < 3; ++i) 253 | { 254 | auto graphicsPipeline 255 | = vsg::GraphicsPipeline::create(pipelineLayout, shaderStages, *pipelineStates[i]); 256 | auto bindGraphicsPipeline = vsg::BindGraphicsPipeline::create(graphicsPipeline); 257 | rasterSwitch->add(i == 0, bindGraphicsPipeline); 258 | } 259 | rasterStateGroup->add(rasterSwitch); 260 | rasterStateGroup->addChild(lightStateGroup); 261 | auto plodRoot = vsg::read_cast("root.tile", options); 262 | lightStateGroup->addChild(plodRoot); 263 | // Put everything under a group node in order to have a place to hang the lights. 264 | auto sceneGroup = vsg::Group::create(); 265 | sceneGroup->addChild(rasterStateGroup); 266 | directionalLight = vsg::DirectionalLight::create(); 267 | directionalLight->name = "directional"; 268 | directionalLight->color = simState.getColor(); 269 | directionalLight->intensity = 1.0f; 270 | directionalLight->direction = simState.worldDirection; 271 | sceneGroup->addChild(directionalLight); 272 | ambientLight = vsg::AmbientLight::create(); 273 | ambientLight->name = "ambient"; 274 | ambientLight->color = simState.getAmbient(); 275 | ambientLight->intensity = 1.0f; 276 | sceneGroup->addChild(ambientLight); 277 | // assign the EllipsoidModel so that the overall geometry of the database can be used as guide 278 | // for clipping and navigation. 279 | sceneGroup->setObject("EllipsoidModel", ellipsoidModel); 280 | return sceneGroup; 281 | } 282 | 283 | vsg::ref_ptr createTileGeometry(const osgEarth::TileKey& tileKey, uint32_t tileSize, 284 | uint8_t textureOrigin) 285 | { 286 | const uint32_t numRows = tileSize; 287 | const uint32_t numCols = tileSize; 288 | const uint32_t numVertices = numRows * numCols; 289 | const uint32_t numTriangles = (numRows - 1) * (numCols - 1) * 2; 290 | osgEarth::GeoLocator locator(tileKey.getExtent()); 291 | osgEarth::GeoPoint centroid = tileKey.getExtent().getCentroid(); 292 | osg::Matrix world2local, local2world; 293 | centroid.createWorldToLocal(world2local); 294 | local2world.invert(world2local); 295 | 296 | float sCoordScale = 1.0f / float(numCols - 1); 297 | float tCoordScale = 1.0f / float(numRows - 1); 298 | float tCoordOrigin = 0.0; 299 | 300 | if (textureOrigin == vsg::TOP_LEFT) 301 | { 302 | tCoordScale = -tCoordScale; 303 | tCoordOrigin = 1.0f; 304 | } 305 | 306 | vsg::vec3 color(1.0f, 1.0f, 1.0f); 307 | 308 | // set up vertex coords 309 | auto vertices = vsg::vec3Array::create(numVertices); 310 | auto colors = vsg::vec3Array::create(numVertices); 311 | auto texcoords = vsg::vec2Array::create(numVertices); 312 | auto ellipsoidNormals = vsg::vec3Array::create(numVertices); 313 | for (uint32_t r = 0; r < numRows; ++r) 314 | { 315 | double ny = r / static_cast(numRows - 1); 316 | for (uint32_t c = 0; c < numCols; ++c) 317 | { 318 | double nx = c / static_cast(numCols - 1); 319 | uint32_t vi = c + r * numCols; 320 | osg::Vec3d unit(nx, ny, 0.0); 321 | osg::Vec3d model; 322 | locator.unitToWorld(unit, model); 323 | // OSG multiplication order 324 | osg::Vec3d modelLTP(model * world2local); 325 | vertices->set(vi, vsg::vec3(toVsg(modelLTP))); 326 | unit.z() = 1.0f; 327 | osg::Vec3d modelPlusOne; 328 | locator.unitToWorld(unit, modelPlusOne); 329 | osg::Vec3d normal; 330 | normal = (modelPlusOne*world2local) - modelLTP; 331 | normal.normalize(); 332 | ellipsoidNormals->set(vi, vsg::vec3(toVsg(normal))); 333 | vsg::vec2 texcoord(c * sCoordScale, tCoordOrigin + r * tCoordScale); 334 | texcoords->set(vi, texcoord); 335 | colors->set(vi, color); 336 | } 337 | } 338 | 339 | // set up indices 340 | auto indices = vsg::ushortArray::create(numTriangles * 3); 341 | auto itr = indices->begin(); 342 | for (uint32_t r = 0; r < numRows - 1; ++r) 343 | { 344 | for (uint32_t c = 0; c < numCols - 1; ++c) 345 | { 346 | uint32_t vi = c + r * numCols; 347 | (*itr++) = vi; 348 | (*itr++) = vi + 1; 349 | (*itr++) = vi + numCols; 350 | (*itr++) = vi + numCols; 351 | (*itr++) = vi + 1; 352 | (*itr++) = vi + numCols + 1; 353 | } 354 | } 355 | 356 | // setup geometry 357 | auto drawCommands = vsg::Commands::create(); 358 | drawCommands->addChild(vsg::BindVertexBuffers::create(0, vsg::DataList{vertices, colors, texcoords, 359 | ellipsoidNormals})); 360 | drawCommands->addChild(vsg::BindIndexBuffer::create(indices)); 361 | drawCommands->addChild(vsg::DrawIndexed::create(indices->size(), 1, 0, 0, 0)); 362 | return drawCommands; 363 | } 364 | 365 | // per-tile image layer data 366 | struct ImageLayerTileData 367 | { 368 | ImageLayerTileData(vsg::ref_ptr info, 369 | osg::Matrixf& texMatrix, 370 | UID uid) 371 | : info(info), texMatrix(texMatrix), uid(uid) 372 | {} 373 | vsg::ref_ptr info; 374 | osg::Matrixf& texMatrix; 375 | UID uid; 376 | }; 377 | 378 | vsg::ref_ptr 379 | TerrainEngineVOE::createTile(const osgEarth::TileKey& key, vsg::ref_ptr options) const 380 | { 381 | osg::ref_ptr tileModel 382 | = modelFactory->createTileModel(getMap(), key, osgEarth::CreateTileManifest(), 383 | nullptr, nullptr); 384 | std::vector tileData; 385 | uint8_t imageOrigin = vsg::BOTTOM_LEFT; // XXX huge hack; should probably just decide on 386 | // OpenGL order 387 | for (auto& colorLayer : tileModel->colorLayers()) 388 | { 389 | auto& layer = colorLayer.layer(); 390 | if (!layer.valid()) 391 | continue; 392 | ImageLayer* imageLayer = dynamic_cast(layer.get()); 393 | if (imageLayer && colorLayer.texture()->osgTexture().valid()) 394 | { 395 | auto texture = colorLayer.texture()->osgTexture().get(); 396 | osg::Matrixf& textureMatrix = colorLayer.matrix(); 397 | auto data = convertToVsg(texture->getImage(0), true); 398 | imageOrigin = data->getLayout().origin; 399 | tileData.push_back(ImageLayerTileData(vsg::ImageInfo::create(sampler, data), textureMatrix, 400 | imageLayer->getUID())); 401 | } 402 | } 403 | // XXX Should do something smarter as we exhaust some of the image layers. Go back to the 404 | // modelFactory? On the other hand, layers like debug layer are effectively infinite, so we do 405 | // need a scheme for using lower resolution data to fill in for that which is unavailable. 406 | if (tileData.size() < numImageLayers) 407 | return {}; 408 | // create StateGroup to bind any texture state 409 | auto scenegraph = vsg::StateGroup::create(); 410 | osgEarth::GeoPoint centroid = key.getExtent().getCentroid(); 411 | auto localToWorld = ellipsoidModel->computeLocalToWorldTransform(vsg::dvec3(centroid.y(), centroid.x(), 0.0)); 412 | 413 | // create texture image and associated DescriptorSets and binding 414 | vsg::ImageInfoList imageTextures(tileData.size()); 415 | std::transform(tileData.begin(), tileData.end(), imageTextures.begin(), 416 | [](const ImageLayerTileData& data) { return data.info; }); 417 | auto texDescriptor = vsg::DescriptorImage::create(imageTextures, 0, 0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 418 | 419 | vsg::ref_ptr descriptorSet; 420 | osg::ref_ptr elevationTexture; 421 | vsg::ref_ptr elevationTexDescriptor = emptyElevationDescImage; 422 | vsg::ref_ptr normalTexDescriptor = emptyNormalDescImage; 423 | const float bias = .5; 424 | float tileWidth = 1.0f; 425 | if (tileModel->elevation().texture()) 426 | { 427 | auto osgElevationTexture = tileModel->elevation().texture()->osgTexture(); 428 | auto elevData = convertToVsg(osgElevationTexture->getImage(0)); 429 | auto osgNormalTexture = tileModel->normalMap().texture()->osgTexture(); 430 | auto normalData = convertToVsg(osgNormalTexture->getImage(0)); 431 | elevationTexDescriptor = vsg::DescriptorImage::create(elevationSampler, elevData, 1, 0, 432 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 433 | normalTexDescriptor = vsg::DescriptorImage::create(normalSampler, normalData, 2, 0, 434 | VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); 435 | tileWidth = static_cast(elevData->width()); 436 | } 437 | TileParams tileParams(numImageLayers); 438 | for (int i = 0; i < numImageLayers; ++i) 439 | { 440 | UID uid = tileData[i].uid; 441 | tileParams.imageTexMatrix(i) = toVsg(tileData[i].texMatrix); 442 | auto layerItr = std::find(layerParams->layerUIDs.begin(), layerParams->layerUIDs.end(), uid); 443 | if (layerItr != layerParams->layerUIDs.end()) 444 | { 445 | tileParams.layerIndex(i) = std::distance(layerParams->layerUIDs.begin(),layerItr); 446 | } 447 | else 448 | { 449 | tileParams.layerIndex(i) = 0; // XXX Index into layerParams 450 | } 451 | } 452 | if (tileModel->elevation().texture()) 453 | { 454 | tileParams.elevationTexMatrix() 455 | = toVsg((tileModel->elevation().matrix())); 456 | } 457 | { 458 | tileParams.elevationTexMatrix() = vsg::mat4(); 459 | } 460 | tileParams.elevTexelCoeff()[0] = (tileWidth - (2.0*bias)) / tileWidth; 461 | tileParams.elevTexelCoeff()[1] = bias / tileWidth; 462 | tileParams.numImageLayers() = numImageLayers; 463 | 464 | // XXX Sucks that you need to specify the "binding" (location) in the descriptor buffer itself. 465 | auto tileParamsBuffer = vsg::DescriptorBuffer::create(tileParams.data, 3); 466 | descriptorSet = vsg::DescriptorSet::create(descriptorSetLayout, 467 | vsg::Descriptors{texDescriptor, elevationTexDescriptor, 468 | normalTexDescriptor, tileParamsBuffer}); 469 | auto bindDescriptorSets = vsg::BindDescriptorSets::create(VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, vsg::DescriptorSets{descriptorSet}); 470 | scenegraph->add(bindDescriptorSets); 471 | 472 | // set up model transformation node 473 | auto transform = vsg::MatrixTransform::create(localToWorld); // VK_SHADER_STAGE_VERTEX_BIT 474 | 475 | // add transform to root of the scene graph 476 | scenegraph->addChild(transform); 477 | auto drawCommands = createTileGeometry(key, 32, imageOrigin); 478 | // add drawCommands to transform 479 | transform->addChild(drawCommands); 480 | return scenegraph; 481 | } 482 | 483 | 484 | vsg::ref_ptr TerrainEngineVOE::createWireframeHandler() 485 | { 486 | return WireframeInputHandler::create(rasterSwitch); 487 | } 488 | 489 | void TerrainEngineVOE::update(vsg::ref_ptr viewer, vsg::ref_ptr camera) 490 | { 491 | // XXX Check if this is still needed 492 | viewer->waitForFences(1, 50000000); 493 | directionalLight->color = simState.getColor(); 494 | directionalLight->direction = simState.worldDirection; 495 | ambientLight->color = simState.getAmbient(); 496 | layerParams->layerParamsDescriptor->copyDataListToBuffers(); 497 | } 498 | --------------------------------------------------------------------------------