mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-04-22 02:48:25 -05:00
Add angular fisheye texture mapping for spheres (#3837)
* Add equirectangular to fisheye coordinate mapping for spheres * Code cleanup and some image examples * Add documentation and video sphere example * Fix renderable video sphere not updating on changed sphere settings * Increase example video sphere resolution and clarify orientation setting * Apply suggestions from code review Co-authored-by: Malin E <malin.ejdbo@gmail.com> * Address more code review comments * Update docs to match recent changes in #3835 * Apply suggestions from code review Co-authored-by: Alexander Bock <alexander.bock@liu.se> Co-authored-by: Andreas Engberg <48772850+engbergandreas@users.noreply.github.com> * Move texture mapping to fragment shader Fixes issues at edges due to interpolation of texture coordinates --------- Co-authored-by: Malin E <malin.ejdbo@gmail.com> Co-authored-by: Alexander Bock <alexander.bock@liu.se> Co-authored-by: Andreas Engberg <48772850+engbergandreas@users.noreply.github.com>
This commit is contained in:
+28
@@ -0,0 +1,28 @@
|
||||
-- Fisheye Mapping
|
||||
-- This example shows a sphere that is covered with an image which is retrieved from
|
||||
-- a local file path and mapped to the sphere using Angular Fisheye projection. The image
|
||||
-- will cover half of the sphere.
|
||||
|
||||
local Node = {
|
||||
Identifier = "RenderableSphereImageLocal_Example_FisheyeMapping",
|
||||
Renderable = {
|
||||
Type = "RenderableSphereImageLocal",
|
||||
Texture = openspace.absPath("${DATA}/test3.jpg"),
|
||||
TextureProjection = "Angular Fisheye",
|
||||
-- Set orientation to also render the inside of the sphere (which is the correct view
|
||||
-- for a fisheye/fulldome image)
|
||||
Orientation = "Both"
|
||||
},
|
||||
GUI = {
|
||||
Name = "RenderableSphereImageLocal - Fisheye Mapping",
|
||||
Path = "/Examples"
|
||||
}
|
||||
}
|
||||
|
||||
asset.onInitialize(function()
|
||||
openspace.addSceneGraphNode(Node)
|
||||
end)
|
||||
|
||||
asset.onDeinitialize(function()
|
||||
openspace.removeSceneGraphNode(Node)
|
||||
end)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
-- Fisheye Mapping
|
||||
-- This example shows a sphere that is covered with an image which is retrieved from an
|
||||
-- online URL and mapped to the sphere using Angular Fisheye projection. The image will
|
||||
-- cover half of the sphere.
|
||||
|
||||
local Node = {
|
||||
Identifier = "RenderableSphereImageOnline_Example_FisheyeMapping",
|
||||
Renderable = {
|
||||
Type = "RenderableSphereImageOnline",
|
||||
URL = "http://data.openspaceproject.com/examples/renderableplaneimageonline.jpg",
|
||||
TextureProjection = "Angular Fisheye",
|
||||
-- Set orientation to also render the inside of the sphere (which is the correct view
|
||||
-- for a fisheye/fulldome image)
|
||||
Orientation = "Both"
|
||||
},
|
||||
GUI = {
|
||||
Name = "RenderableSphereImageOnline - Fisheye Mapping",
|
||||
Path = "/Examples"
|
||||
}
|
||||
}
|
||||
|
||||
asset.onInitialize(function()
|
||||
openspace.addSceneGraphNode(Node)
|
||||
end)
|
||||
|
||||
asset.onDeinitialize(function()
|
||||
openspace.removeSceneGraphNode(Node)
|
||||
end)
|
||||
@@ -0,0 +1,42 @@
|
||||
-- Fulldome Fisheye Video
|
||||
-- Creates a 3D sphere with an angular fisheye video (fulldome) mapped onto its surface.
|
||||
-- The video will be shown on half of the sphere.
|
||||
|
||||
-- The video file is here downloaded from a URL. This code returns the path to a folder
|
||||
-- where the file is stored after download
|
||||
local data = asset.resource({
|
||||
Name = "Example Video Angular Fisheye",
|
||||
Type = "UrlSynchronization",
|
||||
Identifier = "example_video_angularfisheye",
|
||||
Url = "https://liu-se.cdn.openspaceproject.com/files/examples/video/examplevideo_fisheye.mp4"
|
||||
})
|
||||
|
||||
-- For a local file, use "asset.resource("path/to/local/video.mp4")" here instead
|
||||
local video = data .. "examplevideo_fisheye.mp4"
|
||||
|
||||
local Node = {
|
||||
Identifier = "RenderableVideoSphere_Example",
|
||||
Renderable = {
|
||||
Type = "RenderableVideoSphere",
|
||||
Video = video,
|
||||
TextureProjection = "Angular Fisheye",
|
||||
-- Set orientation to also render the inside of the sphere (which is the correct view
|
||||
-- for a fisheye/fulldome video)
|
||||
Orientation = "Both",
|
||||
-- Increasing the number of segments makes the sphere smoother and reduces distortion
|
||||
-- at the edge of the video
|
||||
Segments = 64
|
||||
},
|
||||
GUI = {
|
||||
Name = "RenderableVideoSphere - Fisheye Video",
|
||||
Path = "/Examples"
|
||||
}
|
||||
}
|
||||
|
||||
asset.onInitialize(function()
|
||||
openspace.addSceneGraphNode(Node)
|
||||
end)
|
||||
|
||||
asset.onDeinitialize(function()
|
||||
openspace.removeSceneGraphNode(Node)
|
||||
end)
|
||||
@@ -67,8 +67,8 @@ namespace {
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
enum class Orientation : int {
|
||||
Outside = 0,
|
||||
enum class Orientation {
|
||||
Outside,
|
||||
Inside,
|
||||
Both
|
||||
};
|
||||
@@ -88,6 +88,20 @@ namespace {
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
enum class TextureProjection {
|
||||
Equirectangular,
|
||||
AngularFisheye
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo TextureProjectionInfo = {
|
||||
"TextureProjection",
|
||||
"Texture Projection",
|
||||
"Specifies the projection mapping to use for any texture loaded onto the sphere "
|
||||
"(assumes Equirectangular per default). Note that for \"Angular Fisheye\" only "
|
||||
"half the sphere will be textured - the hemisphere centered around the z-axis.",
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
constexpr openspace::properties::Property::PropertyInfo DisableFadeInOutInfo = {
|
||||
"DisableFadeInOut",
|
||||
"Disable fade-in/fade-out effects",
|
||||
@@ -145,10 +159,11 @@ namespace {
|
||||
openspace::properties::Property::Visibility::AdvancedUser
|
||||
};
|
||||
|
||||
// This `Renderable` represents a simple sphere with an image. The image that is shown
|
||||
// should be in an equirectangular projection/spherical panoramic image or else
|
||||
// distortions will be introduced. The `Orientation` parameter determines whether the
|
||||
// provided image is shown on the inside, outside, or both sides of the sphere.
|
||||
// This `Renderable` represents a simple sphere with an image. Per default, the
|
||||
// sphere uses an equirectangular projection for the image mapping.
|
||||
//
|
||||
// The `Orientation` parameter determines whether the provided image is shown on
|
||||
// the inside, outside, or both sides of the sphere.
|
||||
struct [[codegen::Dictionary(RenderableSphere)]] Parameters {
|
||||
// [[codegen::verbatim(SizeInfo.description)]]
|
||||
std::optional<float> size [[codegen::greater(0.f)]];
|
||||
@@ -168,6 +183,14 @@ namespace {
|
||||
// [[codegen::verbatim(MirrorTextureInfo.description)]]
|
||||
std::optional<bool> mirrorTexture;
|
||||
|
||||
enum class [[codegen::map(TextureProjection)]] TextureProjection {
|
||||
Equirectangular,
|
||||
AngularFisheye [[codegen::key("Angular Fisheye")]]
|
||||
};
|
||||
|
||||
// [[codegen::verbatim(TextureProjectionInfo.description)]]
|
||||
std::optional<TextureProjection> textureProjection;
|
||||
|
||||
// [[codegen::verbatim(DisableFadeInOutInfo.description)]]
|
||||
std::optional<bool> disableFadeInOut;
|
||||
|
||||
@@ -204,6 +227,7 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary)
|
||||
, _segments(SegmentsInfo, 16, 4, 1000)
|
||||
, _orientation(OrientationInfo)
|
||||
, _mirrorTexture(MirrorTextureInfo, false)
|
||||
, _textureProjection(TextureProjectionInfo)
|
||||
, _disableFadeInDistance(DisableFadeInOutInfo, false)
|
||||
, _fadeInThreshold(FadeInThresholdInfo, 0.f, 0.f, 1.f, 0.001f)
|
||||
, _fadeOutThreshold(FadeOutThresholdInfo, 0.f, 0.f, 1.f, 0.001f)
|
||||
@@ -243,6 +267,15 @@ RenderableSphere::RenderableSphere(const ghoul::Dictionary& dictionary)
|
||||
_mirrorTexture = p.mirrorTexture.value_or(_mirrorTexture);
|
||||
addProperty(_mirrorTexture);
|
||||
|
||||
_textureProjection.addOptions({
|
||||
{ static_cast<int>(TextureProjection::Equirectangular), "Equirectangular" },
|
||||
{ static_cast<int>(TextureProjection::AngularFisheye), "Angular Fisheye" }
|
||||
});
|
||||
_textureProjection = p.textureProjection.has_value() ?
|
||||
static_cast<int>(codegen::map<TextureProjection>(*p.textureProjection)) :
|
||||
static_cast<int>(TextureProjection::Equirectangular);
|
||||
addProperty(_textureProjection);
|
||||
|
||||
_disableFadeInDistance = p.disableFadeInOut.value_or(_disableFadeInDistance);
|
||||
addProperty(_disableFadeInDistance);
|
||||
|
||||
@@ -439,6 +472,8 @@ void RenderableSphere::render(const RenderData& data, RendererTasks&) {
|
||||
defer{ unbindTexture(); };
|
||||
_shader->setUniform(_uniformCache.colorTexture, unit);
|
||||
|
||||
_shader->setUniform(_uniformCache.textureProjection, _textureProjection.value());
|
||||
|
||||
// Setting these states should not be necessary,
|
||||
// since they are the default state in OpenSpace.
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
@@ -65,6 +65,7 @@ protected:
|
||||
|
||||
properties::OptionProperty _orientation;
|
||||
properties::BoolProperty _mirrorTexture;
|
||||
properties::OptionProperty _textureProjection;
|
||||
|
||||
properties::BoolProperty _disableFadeInDistance;
|
||||
properties::FloatProperty _fadeInThreshold;
|
||||
@@ -84,7 +85,7 @@ private:
|
||||
std::unique_ptr<TransferFunction> _transferFunction;
|
||||
|
||||
UniformCache(opacity, modelViewProjection, modelViewTransform, modelViewRotation,
|
||||
colorTexture, mirrorTexture) _uniformCache;
|
||||
colorTexture, mirrorTexture, textureProjection) _uniformCache;
|
||||
};
|
||||
|
||||
} // namespace openspace
|
||||
|
||||
@@ -44,6 +44,10 @@ namespace {
|
||||
// This `Renderable` shows a sphere with an image provided by a local file on disk. To
|
||||
// show a sphere with an image from an online source, see
|
||||
// [RenderableSphereImageOnline](#base_screenspace_image_online).
|
||||
//
|
||||
// Per default, the sphere uses an equirectangular projection for the image mapping
|
||||
// and hence expects an equirectangular image. However, it can also be used to show
|
||||
// fisheye images by changing the `TextureProjection`.
|
||||
struct [[codegen::Dictionary(RenderableSphereImageLocal)]] Parameters {
|
||||
// [[codegen::verbatim(TextureInfo.description)]]
|
||||
std::filesystem::path texture;
|
||||
|
||||
@@ -68,6 +68,10 @@ namespace {
|
||||
// will be downloaded when the `Renderable` is added to a scene graph node. To show a
|
||||
// sphere with an image from a local file, see
|
||||
// [RenderableSphereImageLocal](#base_screenspace_image_local).
|
||||
//
|
||||
// Per default, the sphere uses an equirectangular projection for the image mapping
|
||||
// and hence expects an equirectangular image. However, it can also be used to show
|
||||
// fisheye images by changing the `TextureProjection`.
|
||||
struct [[codegen::Dictionary(RenderableSphereImageOnline)]] Parameters {
|
||||
// [[codegen::verbatim(TextureInfo.description)]]
|
||||
std::string url [[codegen::key("URL")]];
|
||||
|
||||
@@ -38,9 +38,48 @@ uniform vec2 dataMinMaxValues;
|
||||
uniform float opacity;
|
||||
uniform bool mirrorTexture;
|
||||
|
||||
const int Equirectangular = 0;
|
||||
const int AngularFisheye = 1;
|
||||
uniform int textureProjection;
|
||||
|
||||
const float M_PI = 3.14159265358979323846;
|
||||
|
||||
// Remap equirectangular texture coordinates into angular fisheye
|
||||
vec2 equiToAngularFisheye(vec2 textureCoords) {
|
||||
vec2 pos2 = textureCoords * 2.0 - 1.0; // Map [0,1] tex coords to [-1,1]
|
||||
|
||||
// 2D equi to 3D vector
|
||||
float lat = pos2.y * 0.5 * M_PI;
|
||||
float lon = pos2.x * M_PI;
|
||||
|
||||
// Map to 3D position, with Z being the north pole
|
||||
vec3 pos3 = vec3(
|
||||
cos(lat) * cos(lon),
|
||||
sin(lat),
|
||||
cos(lat) * sin(lon)
|
||||
);
|
||||
|
||||
float coverAngle = M_PI; // 180 degrees
|
||||
|
||||
// 3D vector to normalized 2D fisheye [-1,1]
|
||||
float r = 2.0 / coverAngle * atan(sqrt(dot(pos3.xz, pos3.xz)), pos3.y);
|
||||
float theta = atan(pos3.z, pos3.x);
|
||||
vec2 fisheye2D = vec2(r * cos(theta), r * sin(theta));
|
||||
|
||||
if (r > 1.0) {
|
||||
discard; // Invalid coordinates (outside fisheye frame)
|
||||
}
|
||||
|
||||
// Remap to [0,1]
|
||||
return 0.5 * fisheye2D + 0.5;
|
||||
}
|
||||
|
||||
Fragment getFragment() {
|
||||
vec2 texCoord = vs_textureCoords;
|
||||
vec2 texCoord = vs_textureCoords; // Equirectangular
|
||||
|
||||
if (textureProjection == AngularFisheye) {
|
||||
texCoord = equiToAngularFisheye(vs_textureCoords);
|
||||
}
|
||||
|
||||
Fragment frag;
|
||||
if (mirrorTexture) {
|
||||
|
||||
@@ -36,11 +36,10 @@ uniform mat4 modelViewProjection;
|
||||
uniform mat4 modelViewTransform;
|
||||
uniform mat3 modelViewRotation;
|
||||
|
||||
|
||||
void main() {
|
||||
vs_normal = modelViewRotation * normalize(in_position.xyz);
|
||||
vs_textureCoords = in_textureCoords;
|
||||
|
||||
vs_normal = modelViewRotation * normalize(in_position.xyz);
|
||||
vec4 position = modelViewProjection * vec4(in_position.xyz, 1.0);
|
||||
vs_position = modelViewTransform * vec4(in_position.xyz, 1.0);
|
||||
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
#include <openspace/util/sphere.h>
|
||||
|
||||
namespace {
|
||||
// This `Renderable` creates a textured 3D sphere where the texture is a video.
|
||||
// This `Renderable` creates a textured 3D sphere where the texture is a video. Per
|
||||
// default, the sphere uses an equirectangular projection for the image mapping
|
||||
// and hence expects a video in equirectangular format. However, it can also be used
|
||||
// to play fisheye videos by changing the `TextureProjection`.
|
||||
//
|
||||
// The video can either be played back based on a given simulation time
|
||||
// (`PlaybackMode` MapToSimulationTime) or through the user interface (for
|
||||
@@ -83,7 +86,8 @@ void RenderableVideoSphere::render(const RenderData& data, RendererTasks& render
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableVideoSphere::update(const UpdateData&) {
|
||||
void RenderableVideoSphere::update(const UpdateData& data) {
|
||||
RenderableSphere::update(data);
|
||||
if (!_videoPlayer.isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user