diff --git a/.gitignore b/.gitignore index b98dc456..d2de3dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ misc/file2c/file2cpp # Editor and OS things imgui.ini +.polyscope.ini .DS_Store .vscode *.swp diff --git a/include/polyscope/camera_view.h b/include/polyscope/camera_view.h index 61733109..743872d2 100644 --- a/include/polyscope/camera_view.h +++ b/include/polyscope/camera_view.h @@ -97,7 +97,6 @@ class CameraView : public QuantityStructure { // Rendering helpers used by quantities void setCameraViewUniforms(render::ShaderProgram& p); std::vector addCameraViewRules(std::vector initRules, bool withCameraView = true); - std::string getShaderNameForRenderMode(); // Get info related to how the frame is drawn (billboard center vector, center-to-top vector, center-to-right vector) std::tuple getFrameBillboardGeometry(); diff --git a/include/polyscope/internal.h b/include/polyscope/internal.h index e57ffa82..9327931e 100644 --- a/include/polyscope/internal.h +++ b/include/polyscope/internal.h @@ -5,6 +5,9 @@ #include #include +#include "polyscope/scaled_value.h" +#include "polyscope/types.h" + namespace polyscope { @@ -36,5 +39,20 @@ extern float lastRightSideFreeY; extern float leftWindowsWidth; extern float rightWindowsWidth; +// Cached versions of lazy properties used for updates +namespace lazy { +extern TransparencyMode transparencyMode; +extern ProjectionMode projectionMode; +extern int transparencyRenderPasses; +extern int ssaaFactor; +extern float uiScale; +extern bool groundPlaneEnabled; +extern GroundPlaneMode groundPlaneMode; +extern ScaledValue groundPlaneHeightFactor; +extern int shadowBlurIters; +extern float shadowDarkness; +} // namespace lazy + + } // namespace internal } // namespace polyscope diff --git a/include/polyscope/render/opengl/shaders/rules.h b/include/polyscope/render/opengl/shaders/rules.h index 221a678a..78979786 100644 --- a/include/polyscope/render/opengl/shaders/rules.h +++ b/include/polyscope/render/opengl/shaders/rules.h @@ -40,6 +40,8 @@ extern const ShaderReplacementRule PROJ_AND_INV_PROJ_MAT; extern const ShaderReplacementRule COMPUTE_SHADE_NORMAL_FROM_POSITION; extern const ShaderReplacementRule PREMULTIPLY_LIT_COLOR; extern const ShaderReplacementRule CULL_POS_FROM_VIEW; +extern const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE; +extern const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC; ShaderReplacementRule generateSlicePlaneRule(std::string uniquePostfix); ShaderReplacementRule generateVolumeGridSlicePlaneRule(std::string uniquePostfix); diff --git a/include/polyscope/vector_quantity.ipp b/include/polyscope/vector_quantity.ipp index e415123d..954c6e57 100644 --- a/include/polyscope/vector_quantity.ipp +++ b/include/polyscope/vector_quantity.ipp @@ -158,7 +158,8 @@ void VectorQuantity::drawVectors() { template void VectorQuantity::createProgram() { - std::vector rules = this->quantity.parent.addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + this->quantity.parent.addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (this->quantity.parent.wantsCullPosition()) { rules.push_back("VECTOR_CULLPOS_FROM_TAIL"); } diff --git a/include/polyscope/view.h b/include/polyscope/view.h index ae4591c2..8cfca87c 100644 --- a/include/polyscope/view.h +++ b/include/polyscope/view.h @@ -85,6 +85,8 @@ CameraParameters getCameraParametersForCurrentView(); // contains all of this in // (these friendly helpers to get the same info as ^^^) glm::mat4 getCameraViewMatrix(); void setCameraViewMatrix(glm::mat4 newMat); +ProjectionMode getProjectionMode(); +void setProjectionMode(ProjectionMode newMode); glm::mat4 getCameraPerspectiveMatrix(); glm::vec3 getCameraWorldPosition(); void getCameraFrame(glm::vec3& lookDir, glm::vec3& upDir, glm::vec3& rightDir); @@ -165,6 +167,7 @@ std::tuple screenCoordsToBufferInds(glm::vec2 screenCoords); glm::ivec2 screenCoordsToBufferIndsVec(glm::vec2 screenCoords); glm::vec2 bufferIndsToScreenCoords(int xPos, int yPos); glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds); +std::string getCurrentProjectionModeRaycastRule(); // == Internal helpers. Should probably not be called in user code. diff --git a/src/camera_view.cpp b/src/camera_view.cpp index e41cd416..7a3f2962 100644 --- a/src/camera_view.cpp +++ b/src/camera_view.cpp @@ -154,14 +154,16 @@ void CameraView::drawPickDelayed() { void CameraView::prepare() { { - std::vector rules = addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (wantsCullPosition()) rules.push_back("SPHERE_CULLPOS_FROM_CENTER"); rules = render::engine->addMaterialRules(material, rules); nodeProgram = render::engine->requestShader("RAYCAST_SPHERE", rules); } { - std::vector rules = addStructureRules({"SHADE_BASECOLOR"}); + std::vector rules = + addStructureRules({view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR"}); if (wantsCullPosition()) rules.push_back("CYLINDER_CULLPOS_FROM_MID"); rules = render::engine->addMaterialRules(material, rules); edgeProgram = render::engine->requestShader("RAYCAST_CYLINDER", rules); diff --git a/src/curve_network.cpp b/src/curve_network.cpp index 9a7d35d1..b3bfd800 100644 --- a/src/curve_network.cpp +++ b/src/curve_network.cpp @@ -221,6 +221,8 @@ void CurveNetwork::drawPickDelayed() { std::vector CurveNetwork::addCurveNetworkNodeRules(std::vector initRules) { initRules = addStructureRules(initRules); + + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); if (nodeRadiusQuantityName != "" || edgeRadiusQuantityName != "") { initRules.push_back("SPHERE_VARIABLE_SIZE"); @@ -233,6 +235,8 @@ std::vector CurveNetwork::addCurveNetworkNodeRules(std::vector CurveNetwork::addCurveNetworkEdgeRules(std::vector initRules) { initRules = addStructureRules(initRules); + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); + // use node radius to blend cylinder radius if (nodeRadiusQuantityName != "" || edgeRadiusQuantityName != "") { initRules.push_back("CYLINDER_VARIABLE_SIZE"); @@ -292,9 +296,10 @@ void CurveNetwork::preparePick() { size_t pickStart = pick::requestPickBufferRange(this, totalPickElements); { // Set up node picking program - nodePickProgram = - render::engine->requestShader("RAYCAST_SPHERE", addCurveNetworkNodeRules({"SPHERE_PROPAGATE_COLOR"}), - render::ShaderReplacementDefaults::Pick); + nodePickProgram = render::engine->requestShader( + "RAYCAST_SPHERE", + addCurveNetworkNodeRules({"SPHERE_PROPAGATE_COLOR"}), + render::ShaderReplacementDefaults::Pick); // Fill color buffer with packed point indices std::vector pickColors; diff --git a/src/internal.cpp b/src/internal.cpp index 29e9c9f8..86aa20ce 100644 --- a/src/internal.cpp +++ b/src/internal.cpp @@ -22,5 +22,21 @@ float lastRightSideFreeY = 10; float leftWindowsWidth = -1.; float rightWindowsWidth = -1.; + +namespace lazy { + +TransparencyMode transparencyMode = TransparencyMode::None; +ProjectionMode projectionMode = ProjectionMode::Perspective; +int transparencyRenderPasses = 8; +int ssaaFactor = 1; +float uiScale = -1.; +bool groundPlaneEnabled = true; +GroundPlaneMode groundPlaneMode = GroundPlaneMode::TileReflection; +ScaledValue groundPlaneHeightFactor = 0; +int shadowBlurIters = 2; +float shadowDarkness = .4; + +} // namespace lazy + } // namespace internal } // namespace polyscope diff --git a/src/point_cloud.cpp b/src/point_cloud.cpp index 25e24a05..be08eaa3 100644 --- a/src/point_cloud.cpp +++ b/src/point_cloud.cpp @@ -247,6 +247,9 @@ PointCloudPickResult PointCloud::interpretPickResult(const PickResult& rawResult std::vector PointCloud::addPointCloudRules(std::vector initRules, bool withPointCloud) { initRules = addStructureRules(initRules); if (withPointCloud) { + + initRules.push_back(view::getCurrentProjectionModeRaycastRule()); + if (pointRadiusQuantityName != "") { initRules.push_back("SPHERE_VARIABLE_SIZE"); } diff --git a/src/polyscope.cpp b/src/polyscope.cpp index b10dab50..fb7326a5 100644 --- a/src/polyscope.cpp +++ b/src/polyscope.cpp @@ -1421,19 +1421,6 @@ void refresh() { requestRedraw(); } -// Cached versions of lazy properties used for updates -namespace lazy { -TransparencyMode transparencyMode = TransparencyMode::None; -int transparencyRenderPasses = 8; -int ssaaFactor = 1; -float uiScale = -1.; -bool groundPlaneEnabled = true; -GroundPlaneMode groundPlaneMode = GroundPlaneMode::TileReflection; -ScaledValue groundPlaneHeightFactor = 0; -int shadowBlurIters = 2; -float shadowDarkness = .4; -} // namespace lazy - void processLazyProperties() { // Note: This function essentially represents lazy software design, and it's an ugly and error-prone part of the @@ -1448,45 +1435,52 @@ void processLazyProperties() { // There is a second function processLazyPropertiesOutsideOfImGui() which handles a few more that can only be set // at limited times when an ImGui frame is not active. + // projection mode + if (internal::lazy::projectionMode != view::projectionMode) { + internal::lazy::projectionMode = view::projectionMode; + view::setProjectionMode(view::projectionMode); + } + // transparency mode - if (lazy::transparencyMode != options::transparencyMode) { - lazy::transparencyMode = options::transparencyMode; + if (internal::lazy::transparencyMode != options::transparencyMode) { + internal::lazy::transparencyMode = options::transparencyMode; render::engine->setTransparencyMode(options::transparencyMode); } // transparency render passes - if (lazy::transparencyRenderPasses != options::transparencyRenderPasses) { - lazy::transparencyRenderPasses = options::transparencyRenderPasses; + if (internal::lazy::transparencyRenderPasses != options::transparencyRenderPasses) { + internal::lazy::transparencyRenderPasses = options::transparencyRenderPasses; requestRedraw(); } // ssaa - if (lazy::ssaaFactor != options::ssaaFactor) { - lazy::ssaaFactor = options::ssaaFactor; + if (internal::lazy::ssaaFactor != options::ssaaFactor) { + internal::lazy::ssaaFactor = options::ssaaFactor; render::engine->setSSAAFactor(options::ssaaFactor); } // ground plane - if (lazy::groundPlaneEnabled != options::groundPlaneEnabled || lazy::groundPlaneMode != options::groundPlaneMode) { - lazy::groundPlaneEnabled = options::groundPlaneEnabled; + if (internal::lazy::groundPlaneEnabled != options::groundPlaneEnabled || + internal::lazy::groundPlaneMode != options::groundPlaneMode) { + internal::lazy::groundPlaneEnabled = options::groundPlaneEnabled; if (!options::groundPlaneEnabled) { // if the (depecated) groundPlaneEnabled = false, set mode to None, so we only have one variable to check options::groundPlaneMode = GroundPlaneMode::None; } - lazy::groundPlaneMode = options::groundPlaneMode; + internal::lazy::groundPlaneMode = options::groundPlaneMode; requestRedraw(); } - if (lazy::groundPlaneHeightFactor.asAbsolute() != options::groundPlaneHeightFactor.asAbsolute() || - lazy::groundPlaneHeightFactor.isRelative() != options::groundPlaneHeightFactor.isRelative()) { - lazy::groundPlaneHeightFactor = options::groundPlaneHeightFactor; + if (internal::lazy::groundPlaneHeightFactor.asAbsolute() != options::groundPlaneHeightFactor.asAbsolute() || + internal::lazy::groundPlaneHeightFactor.isRelative() != options::groundPlaneHeightFactor.isRelative()) { + internal::lazy::groundPlaneHeightFactor = options::groundPlaneHeightFactor; requestRedraw(); } - if (lazy::shadowBlurIters != options::shadowBlurIters) { - lazy::shadowBlurIters = options::shadowBlurIters; + if (internal::lazy::shadowBlurIters != options::shadowBlurIters) { + internal::lazy::shadowBlurIters = options::shadowBlurIters; requestRedraw(); } - if (lazy::shadowDarkness != options::shadowDarkness) { - lazy::shadowDarkness = options::shadowDarkness; + if (internal::lazy::shadowDarkness != options::shadowDarkness) { + internal::lazy::shadowDarkness = options::shadowDarkness; requestRedraw(); } }; @@ -1495,8 +1489,8 @@ void processLazyPropertiesOutsideOfImGui() { // Like processLazyProperties, but this one handles properties which cannot be changed mid-ImGui frame // uiScale - if (lazy::uiScale != options::uiScale) { - lazy::uiScale = options::uiScale; + if (internal::lazy::uiScale != options::uiScale) { + internal::lazy::uiScale = options::uiScale; render::engine->configureImGui(); setInitialWindowWidths(); } diff --git a/src/render/ground_plane.cpp b/src/render/ground_plane.cpp index a8550e9f..c6f24981 100644 --- a/src/render/ground_plane.cpp +++ b/src/render/ground_plane.cpp @@ -252,7 +252,7 @@ void GroundPlane::draw(bool isRedraw) { } case ProjectionMode::Orthographic: { glm::vec4 lookDir = glm::vec4(0, 0, 1, 0) * viewMat; - groundPlaneProgram->setUniform("u_cameraHeight", (lookDir.y * state::lengthScale) + groundHeight); + groundPlaneProgram->setUniform("u_cameraHeight", (lookDir[iP] * state::lengthScale) + groundHeight); break; } } diff --git a/src/render/mock_opengl/mock_gl_engine.cpp b/src/render/mock_opengl/mock_gl_engine.cpp index 3fa63568..f6609d08 100644 --- a/src/render/mock_opengl/mock_gl_engine.cpp +++ b/src/render/mock_opengl/mock_gl_engine.cpp @@ -1964,6 +1964,8 @@ void MockGLEngine::populateDefaultShadersAndRules() { registerShaderRule("PREMULTIPLY_LIT_COLOR", PREMULTIPLY_LIT_COLOR); registerShaderRule("CULL_POS_FROM_VIEW", CULL_POS_FROM_VIEW); registerShaderRule("PROJ_AND_INV_PROJ_MAT", PROJ_AND_INV_PROJ_MAT); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC); // Lighting and shading things registerShaderRule("LIGHT_MATCAP", LIGHT_MATCAP); diff --git a/src/render/opengl/gl_engine.cpp b/src/render/opengl/gl_engine.cpp index 81f77676..779294a2 100644 --- a/src/render/opengl/gl_engine.cpp +++ b/src/render/opengl/gl_engine.cpp @@ -2500,6 +2500,8 @@ void GLEngine::populateDefaultShadersAndRules() { registerShaderRule("PREMULTIPLY_LIT_COLOR", PREMULTIPLY_LIT_COLOR); registerShaderRule("CULL_POS_FROM_VIEW", CULL_POS_FROM_VIEW); registerShaderRule("PROJ_AND_INV_PROJ_MAT", PROJ_AND_INV_PROJ_MAT); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE); + registerShaderRule("BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC); // Lighting and shading things registerShaderRule("LIGHT_MATCAP", LIGHT_MATCAP); diff --git a/src/render/opengl/shaders/common.cpp b/src/render/opengl/shaders/common.cpp index fd33d2f8..7b38a339 100644 --- a/src/render/opengl/shaders/common.cpp +++ b/src/render/opengl/shaders/common.cpp @@ -179,6 +179,39 @@ vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 return eyePos.xyz / eyePos.w; } +// Build ray start and ray direction for fragment-based raycasting. +// For perspective projection: rays originate from the camera (origin) and point towards the fragment view position. +void buildRayForFragmentPerspective(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, + out vec3 rayStart, out vec3 rayDir) { + vec3 viewPos = fragmentViewPosition(viewport, depthRange, invProjMat, fragCoord); + rayStart = vec3(0.0, 0.0, 0.0); + rayDir = normalize(viewPos); +} + +// Build ray start and ray direction for fragment-based raycasting. +// For orthographic projection: rays are parallel, all pointing in -Z direction in view space, +// starting from the fragment's XY position. +void buildRayForFragmentOrthographic(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, + out vec3 rayStart, out vec3 rayDir) { + + // Orthographic: parallel rays pointing in -Z direction + // Convert fragment screen position to NDC + vec2 ndcXY = ((2.0 * fragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1.0; + + // Unproject to view space at near plane (NDC z = -1) and far plane (NDC z = 1) + // For orthographic, we just need the XY in view space + vec4 ndcNear = vec4(ndcXY, -1.0, 1.0); + vec4 viewNear = invProjMat * ndcNear; + viewNear /= viewNear.w; + + vec4 ndcFar = vec4(ndcXY, 1.0, 1.0); + vec4 viewFar = invProjMat * ndcFar; + viewFar /= viewFar.w; + + rayStart = viewNear.xyz; + rayDir = normalize(viewFar.xyz - viewNear.xyz); +} + float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint) { vec4 clipPos = projMat * vec4(viewPoint, 1.); // only actually need one element of this result, could save work float z_ndc = clipPos.z / clipPos.w; @@ -206,6 +239,10 @@ bool raySphereIntersection(vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float } } +)" +// Split the raw string literal to avoid compiler string length limits +R"( + bool rayPlaneIntersection(vec3 rayStart, vec3 rayDir, vec3 planePos, vec3 planeDir, out float tHit, out vec3 pHit, out vec3 nHit) { float num = dot(planePos - rayStart, planeDir); diff --git a/src/render/opengl/shaders/cylinder_shaders.cpp b/src/render/opengl/shaders/cylinder_shaders.cpp index 601c7937..29b1faa1 100644 --- a/src/render/opengl/shaders/cylinder_shaders.cpp +++ b/src/render/opengl/shaders/cylinder_shaders.cpp @@ -163,7 +163,6 @@ R"( layout(location = 0) out vec4 outputF; float LARGE_FLOAT(); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); bool rayTaperedCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRadTail, float cylRadTip, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -173,7 +172,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + ${ BUILD_RAY_FOR_FRAGMENT }$ float tipRadius = u_radius; @@ -184,7 +184,7 @@ R"( float tHit; vec3 pHit; vec3 nHit; - rayTaperedCylinderIntersection(vec3(0., 0., 0), viewRay, tailView, tipView, tailRadius, tipRadius, tHit, pHit, nHit); + rayTaperedCylinderIntersection(rayStart, rayDir, tailView, tipView, tailRadius, tipRadius, tHit, pHit, nHit); if(tHit >= LARGE_FLOAT()) { discard; } diff --git a/src/render/opengl/shaders/rules.cpp b/src/render/opengl/shaders/rules.cpp index b260679c..c7a19719 100644 --- a/src/render/opengl/shaders/rules.cpp +++ b/src/render/opengl/shaders/rules.cpp @@ -465,6 +465,41 @@ const ShaderReplacementRule CULL_POS_FROM_VIEW ( /* textures */ {} ); +const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE( + /* rule name */ "BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + void buildRayForFragmentPerspective(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); + )"}, + {"BUILD_RAY_FOR_FRAGMENT", R"( + buildRayForFragmentPerspective(u_viewport, vec2(gl_DepthRange.near, gl_DepthRange.far), u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + )"}, + {"GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE", R"( + vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + )"} + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {} +); + +const ShaderReplacementRule BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC( + /* rule name */ "BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC", + { /* replacement sources */ + {"FRAG_DECLARATIONS", R"( + void buildRayForFragmentOrthographic(vec4 viewport, vec2 depthRange, mat4 projMat, mat4 invProjMat, vec4 fragCoord, out vec3 rayStart, out vec3 rayDir); + )"}, + {"BUILD_RAY_FOR_FRAGMENT", R"( + buildRayForFragmentOrthographic(u_viewport, vec2(gl_DepthRange.near, gl_DepthRange.far), u_projMatrix, u_invProjMatrix, gl_FragCoord, rayStart, rayDir); + )"}, + {"GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE", R"( + vec3 dirToCam = vec3(0.0, 0.0, 1.0); + )"} + }, + /* uniforms */ {}, + /* attributes */ {}, + /* textures */ {} +); ShaderReplacementRule generateSlicePlaneRule(std::string uniquePostfix) { diff --git a/src/render/opengl/shaders/sphere_shaders.cpp b/src/render/opengl/shaders/sphere_shaders.cpp index f4f1eb59..fdf55d01 100644 --- a/src/render/opengl/shaders/sphere_shaders.cpp +++ b/src/render/opengl/shaders/sphere_shaders.cpp @@ -82,7 +82,9 @@ R"( // Construct the 4 corners of a billboard quad, facing the camera // Quad is shifted pointRadius toward the camera, otherwise it doesn't actually necessarily // cover the full sphere due to perspective. - vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + // (this logic is different depending on perspective vs orthographic projection) + ${ GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE }$ + vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); @@ -141,7 +143,6 @@ R"( float LARGE_FLOAT(); vec3 lightSurfaceMat(vec3 normal, vec3 color, sampler2D t_mat_r, sampler2D t_mat_g, sampler2D t_mat_b, sampler2D t_mat_k); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); bool raySphereIntersection(vec3 rayStart, vec3 rayDir, vec3 sphereCenter, float sphereRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -151,7 +152,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + ${ BUILD_RAY_FOR_FRAGMENT }$ float pointRadius = u_pointRadius; ${ SPHERE_SET_POINT_RADIUS_FRAG }$ @@ -160,7 +162,7 @@ R"( float tHit; vec3 pHit; vec3 nHit; - bool hit = raySphereIntersection(vec3(0., 0., 0), viewRay, sphereCenterView, pointRadius, tHit, pHit, nHit); + bool hit = raySphereIntersection(rayStart, rayDir, sphereCenterView, pointRadius, tHit, pHit, nHit); if(tHit >= LARGE_FLOAT()) { discard; } @@ -265,7 +267,9 @@ R"( ${ SPHERE_SET_POINT_RADIUS_GEOM }$ // Construct the 4 corners of a billboard quad, facing the camera - vec3 dirToCam = normalize(-gl_in[0].gl_Position.xyz); + // (this logic is different depending on perspective vs orthographic projection) + ${ GEOM_CONSTRUCT_VECTOR_TO_CAMERA_IN_VIEW_SPACE }$ + vec3 basisX; vec3 basisY; buildTangentBasis(dirToCam, basisX, basisY); diff --git a/src/render/opengl/shaders/texture_draw_shaders.cpp b/src/render/opengl/shaders/texture_draw_shaders.cpp index 7a5b66cb..507a5fe2 100644 --- a/src/render/opengl/shaders/texture_draw_shaders.cpp +++ b/src/render/opengl/shaders/texture_draw_shaders.cpp @@ -174,6 +174,7 @@ R"( // Set the depth of the fragment from the stored texture data // TODO: this a wasteful way to convert ray depth to gl_FragDepth, I am sure it can be done with much less arithmetic... figure it out // WARNING this code is duplicated in other shaders + // WARNING this almost certainly does not work right for orthographic projections vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); viewRay = normalize(viewRay); diff --git a/src/render/opengl/shaders/vector_shaders.cpp b/src/render/opengl/shaders/vector_shaders.cpp index 61e0f3bc..98eb8b65 100644 --- a/src/render/opengl/shaders/vector_shaders.cpp +++ b/src/render/opengl/shaders/vector_shaders.cpp @@ -217,7 +217,6 @@ R"( layout(location = 0) out vec4 outputF; float LARGE_FLOAT(); - vec3 fragmentViewPosition(vec4 viewport, vec2 depthRange, mat4 invProjMat, vec4 fragCoord); bool rayCylinderIntersection(vec3 rayStart, vec3 rayDir, vec3 cylTail, vec3 cylTip, float cylRad, out float tHit, out vec3 pHit, out vec3 nHit); bool rayConeIntersection(vec3 rayStart, vec3 rayDir, vec3 coneBase, vec3 coneTip, float coneRad, out float tHit, out vec3 pHit, out vec3 nHit); float fragDepthFromView(mat4 projMat, vec2 depthRange, vec3 viewPoint); @@ -228,7 +227,8 @@ R"( { // Build a ray corresponding to this fragment vec2 depthRange = vec2(gl_DepthRange.near, gl_DepthRange.far); - vec3 viewRay = fragmentViewPosition(u_viewport, depthRange, u_invProjMatrix, gl_FragCoord); + vec3 rayStart, rayDir; + ${ BUILD_RAY_FOR_FRAGMENT }$ // geometric shape of hte vector float tipLengthFrac = 0.2; @@ -241,13 +241,13 @@ R"( vec3 nHit = vec3(777,777,777); vec3 cylEnd = tailView + (1. - tipLengthFrac) * (tipView - tailView); float cylRad = tipWidthFrac * adjRadius; - rayCylinderIntersection(vec3(0., 0., 0), viewRay, tailView, cylEnd, cylRad, tHit, pHit, nHit); + rayCylinderIntersection(rayStart, rayDir, tailView, cylEnd, cylRad, tHit, pHit, nHit); // Raycast to cone float tHitCone; vec3 pHitCone; vec3 nHitCone; - bool coneHit = rayConeIntersection(vec3(0., 0., 0), viewRay, cylEnd, tipView, adjRadius, tHitCone, pHitCone, nHitCone); + bool coneHit = rayConeIntersection(rayStart, rayDir, cylEnd, tipView, adjRadius, tHitCone, pHitCone, nHitCone); if(tHitCone < tHit) { tHit = tHitCone; pHit = pHitCone; diff --git a/src/transformation_gizmo.cpp b/src/transformation_gizmo.cpp index 25c25f88..ed66beca 100644 --- a/src/transformation_gizmo.cpp +++ b/src/transformation_gizmo.cpp @@ -50,6 +50,7 @@ void TransformationGizmo::prepare() { arrowProgram = render::engine->requestShader("RAYCAST_VECTOR", render::engine->addMaterialRules(material, { + view::getCurrentProjectionModeRaycastRule(), "VECTOR_PROPAGATE_COLOR", "TRANSFORMATION_GIZMO_VEC", "SHADE_COLOR", @@ -77,6 +78,7 @@ void TransformationGizmo::prepare() { sphereProgram = render::engine->requestShader("RAYCAST_SPHERE", render::engine->addMaterialRules(material, { + view::getCurrentProjectionModeRaycastRule(), "SHADE_BASECOLOR", "LIGHT_MATCAP" } diff --git a/src/view.cpp b/src/view.cpp index 1ceee52e..091216f5 100644 --- a/src/view.cpp +++ b/src/view.cpp @@ -142,6 +142,20 @@ glm::vec2 bufferIndsToScreenCoords(glm::ivec2 bufferInds) { return bufferIndsToScreenCoords(bufferInds.x, bufferInds.y); } +std::string getCurrentProjectionModeRaycastRule() { + // This is related to the render engine, it returns the name of the + // shader rule which constructs rays for raycasting-based rendering. + // See rules.h + + switch (view::projectionMode) { + case ProjectionMode::Perspective: + return "BUILD_RAY_FOR_FRAGMENT_PERSPECTIVE"; + case ProjectionMode::Orthographic: + return "BUILD_RAY_FOR_FRAGMENT_ORTHOGRAPHIC"; + } + return ""; +} + void processRotate(glm::vec2 startP, glm::vec2 endP) { if (startP == endP) { @@ -647,6 +661,15 @@ void setVerticalFieldOfViewDegrees(float newVal) { requestRedraw(); } +ProjectionMode getProjectionMode() { return projectionMode; } + +void setProjectionMode(ProjectionMode newMode) { + projectionMode = newMode; + internal::lazy::projectionMode = newMode; // update the lazy property right now, so we don't pay for a refresh twice + refresh(); + requestRedraw(); +} + float getVerticalFieldOfViewDegrees() { return view::fov; } float getAspectRatioWidthOverHeight() { return (float)bufferWidth / bufferHeight; } @@ -1129,13 +1152,11 @@ void buildViewGui() { std::string projectionModeStr = to_string(view::projectionMode); if (ImGui::BeginCombo("##ProjectionMode", projectionModeStr.c_str())) { if (ImGui::Selectable("Perspective", view::projectionMode == ProjectionMode::Perspective)) { - view::projectionMode = ProjectionMode::Perspective; - requestRedraw(); + setProjectionMode(ProjectionMode::Perspective); ImGui::SetItemDefaultFocus(); } if (ImGui::Selectable("Orthographic", view::projectionMode == ProjectionMode::Orthographic)) { - view::projectionMode = ProjectionMode::Orthographic; - requestRedraw(); + setProjectionMode(ProjectionMode::Orthographic); ImGui::SetItemDefaultFocus(); } ImGui::EndCombo(); diff --git a/test/src/camera_view_test.cpp b/test/src/camera_view_test.cpp index defc1749..0f0f91bd 100644 --- a/test/src/camera_view_test.cpp +++ b/test/src/camera_view_test.cpp @@ -66,6 +66,20 @@ TEST_F(PolyscopeTest, CameraViewUpdate) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, CameraViewOrthographicRendering) { + + polyscope::CameraView* cam1 = polyscope::registerCameraView( + "cam1", polyscope::CameraParameters(polyscope::CameraIntrinsics::fromFoVDegVerticalAndAspect(60, 2.), + polyscope::CameraExtrinsics::fromVectors( + glm::vec3{2., 2., 2.}, glm::vec3{-1., -1., -1.}, glm::vec3{0., 1., 0.}))); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, CameraViewPick) { polyscope::CameraView* cam1 = polyscope::registerCameraView( @@ -79,6 +93,11 @@ TEST_F(PolyscopeTest, CameraViewPick) { polyscope::show(3); + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::removeAllStructures(); } diff --git a/test/src/curve_network_test.cpp b/test/src/curve_network_test.cpp index 3225a9cf..26092ba7 100644 --- a/test/src/curve_network_test.cpp +++ b/test/src/curve_network_test.cpp @@ -37,12 +37,27 @@ TEST_F(PolyscopeTest, CurveNetworkAppearance) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, CurveNetworkOrthographicRendering) { + auto psCurve = registerCurveNetwork(); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + TEST_F(PolyscopeTest, CurveNetworkPick) { auto psCurve = registerCurveNetwork(); // Don't bother trying to actually click on anything, but make sure this doesn't crash polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::removeAllStructures(); } diff --git a/test/src/floating_test.cpp b/test/src/floating_test.cpp index bee1f692..27eeee67 100644 --- a/test/src/floating_test.cpp +++ b/test/src/floating_test.cpp @@ -36,6 +36,12 @@ TEST_F(PolyscopeTest, FloatingImageTest) { polyscope::show(3); im2->setShowFullscreen(true); polyscope::show(3); + + // try orthographic rendering + // (I think there are some bugs related to raycasting and depth projection, but at least make sure it doen't crash + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); } { // ColorImageQuantity diff --git a/test/src/point_cloud_test.cpp b/test/src/point_cloud_test.cpp index 5a10027f..574a9cf6 100644 --- a/test/src/point_cloud_test.cpp +++ b/test/src/point_cloud_test.cpp @@ -62,6 +62,21 @@ TEST_F(PolyscopeTest, PointCloudAppearance) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, PointCloudSphereOrthographicRendering) { + auto psPoints = registerPointCloud(); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + + psPoints->setPointRenderMode(polyscope::PointRenderMode::Quad); + polyscope::show(3); + + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); +} + + TEST_F(PolyscopeTest, PointCloudPick) { auto psPoints = registerPointCloud(); @@ -70,6 +85,11 @@ TEST_F(PolyscopeTest, PointCloudPick) { psPoints->setPointRenderMode(polyscope::PointRenderMode::Quad); polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + + // make sure it works in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::pickAtBufferInds(glm::ivec2(77, 88)); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); polyscope::removeAllStructures(); } @@ -148,6 +168,12 @@ TEST_F(PolyscopeTest, PointCloudVector) { q1->updateData(vals); polyscope::show(3); + // make sure vectors work in orthographic mode + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + + polyscope::removeAllStructures(); } diff --git a/test/src/volume_grid_test.cpp b/test/src/volume_grid_test.cpp index b05a5627..0c46c697 100644 --- a/test/src/volume_grid_test.cpp +++ b/test/src/volume_grid_test.cpp @@ -26,6 +26,7 @@ TEST_F(PolyscopeTest, ShowVolumeGrid) { EXPECT_FALSE(polyscope::hasVolumeGrid("test grid")); } + TEST_F(PolyscopeTest, VolumeGridBasicOptions) { // these are node dim @@ -56,6 +57,23 @@ TEST_F(PolyscopeTest, VolumeGridBasicOptions) { polyscope::removeAllStructures(); } +TEST_F(PolyscopeTest, VolumeGridOrthographicRendering) { + // clang-format off + uint32_t dimX = 8; + uint32_t dimY = 10; + uint32_t dimZ = 12; + glm::vec3 bound_low{-3., -3., -3.}; + glm::vec3 bound_high{3., 3., 3.}; + + polyscope::VolumeGrid* psGrid = polyscope::registerVolumeGrid("test grid", {dimX, dimY, dimZ}, bound_low, bound_high); + + // try orthographic rendering + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); + polyscope::show(3); +} + TEST_F(PolyscopeTest, VolumeGridSlicePlane) { // these are node dim diff --git a/test/src/volume_mesh_test.cpp b/test/src/volume_mesh_test.cpp index 1a81a786..f36a4e86 100644 --- a/test/src/volume_mesh_test.cpp +++ b/test/src/volume_mesh_test.cpp @@ -258,6 +258,11 @@ TEST_F(PolyscopeTest, VolumeMeshInspect) { // with a categorical quantity auto q1Cat = psVol->addVertexScalarQuantity("vals", vals, polyscope::DataType::CATEGORICAL); q1Cat->setEnabled(true); + + // try orthographic rendering + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Orthographic); + polyscope::show(3); + polyscope::view::setProjectionMode(polyscope::ProjectionMode::Perspective); polyscope::show(3); // clear it out