Add light shape wireframe display in viewport
Draw a type-appropriate wireframe gizmo for every UsdLux light in the stage, mirroring the existing camera-wireframe overlay (dedicated VAO/VBO reusing the bbox shader, drawn on top with depth-test off). Shapes, built in the light's local space and pushed through its local-to-world transform: - SphereLight 3 orthogonal circles (radius); axis-cross when treatAsPoint - RectLight width x height rectangle + emission direction line - DiskLight circle + direction line - CylinderLight two end-cap circles + connectors (radius/length) - DistantLight sun disc + parallel rays - DomeLight 3 large display-scaled circles Selected lights draw accent orange, others warm yellow. Verified by creating a SphereLight (3-circle sphere) and a RectLight (rectangle) in-app: distinct shapes, correct selected/deselected colours, gizmos visible over geometry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,14 @@
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdGeom/bboxCache.h>
|
||||
#include <pxr/usd/usdGeom/camera.h>
|
||||
#include <pxr/usd/usdGeom/xformCache.h>
|
||||
#include <pxr/usd/usdLux/lightAPI.h>
|
||||
#include <pxr/usd/usdLux/sphereLight.h>
|
||||
#include <pxr/usd/usdLux/rectLight.h>
|
||||
#include <pxr/usd/usdLux/diskLight.h>
|
||||
#include <pxr/usd/usdLux/distantLight.h>
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
#include <pxr/usd/usdLux/cylinderLight.h>
|
||||
#include <pxr/imaging/glf/contextCaps.h>
|
||||
#include <pxr/imaging/cameraUtil/conformWindow.h>
|
||||
#include <pxr/imaging/cameraUtil/framing.h>
|
||||
@@ -160,6 +168,7 @@ UsdSceneRenderer::~UsdSceneRenderer() {
|
||||
DestroyAxisResources();
|
||||
DestroyBBoxResources();
|
||||
DestroyCamWireResources();
|
||||
DestroyLightWireResources();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -177,6 +186,7 @@ void UsdSceneRenderer::SetStage(pxr::UsdStageRefPtr stage) {
|
||||
DestroyAxisResources();
|
||||
DestroyBBoxResources();
|
||||
DestroyCamWireResources();
|
||||
DestroyLightWireResources();
|
||||
|
||||
// Determine stage up-axis for dome light rotation (mirrors stageView._stageIsZup)
|
||||
if (stage) {
|
||||
@@ -252,6 +262,7 @@ void UsdSceneRenderer::InitRenderer() {
|
||||
InitAxisResources();
|
||||
InitBBoxResources();
|
||||
InitCamWireResources();
|
||||
InitLightWireResources();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -1046,6 +1057,30 @@ void UsdSceneRenderer::DestroyCamWireResources()
|
||||
if (m_camWireVBO) { glDeleteBuffers(1, &m_camWireVBO); m_camWireVBO = 0; }
|
||||
}
|
||||
|
||||
void UsdSceneRenderer::InitLightWireResources()
|
||||
{
|
||||
if (m_lightWireVAO != 0) return;
|
||||
if (!m_bboxProgram) return; // reuse the already-compiled bbox shader program
|
||||
|
||||
glGenVertexArrays(1, &m_lightWireVAO);
|
||||
glGenBuffers(1, &m_lightWireVBO);
|
||||
|
||||
glBindVertexArray(m_lightWireVAO);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_lightWireVBO);
|
||||
// Sphere/cylinder gizmos use several circles; budget more than the camera VBO.
|
||||
glBufferData(GL_ARRAY_BUFFER, 1024 * 3 * sizeof(float), nullptr, GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
||||
glBindVertexArray(0);
|
||||
LOG_INFO("Light wireframe GL resources initialized.");
|
||||
}
|
||||
|
||||
void UsdSceneRenderer::DestroyLightWireResources()
|
||||
{
|
||||
if (m_lightWireVAO) { glDeleteVertexArrays(1, &m_lightWireVAO); m_lightWireVAO = 0; }
|
||||
if (m_lightWireVBO) { glDeleteBuffers(1, &m_lightWireVBO); m_lightWireVBO = 0; }
|
||||
}
|
||||
|
||||
void UsdSceneRenderer::DrawBox(
|
||||
const pxr::GfRange3d& range,
|
||||
const pxr::GfMatrix4f& mvp)
|
||||
@@ -1392,6 +1427,191 @@ void UsdSceneRenderer::DrawCameraWireframes(
|
||||
m_drawTarget->Unbind();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// BuildLightWireframeLines
|
||||
//
|
||||
// Geometry is built in the light's LOCAL space (using its schema attributes in
|
||||
// local units) and every vertex is pushed through localToWorld, so position,
|
||||
// orientation and scale are all handled uniformly. USD lights emit along local
|
||||
// -Z (rect/disk/distant); cylinder length runs along local X.
|
||||
// ===========================================================================
|
||||
|
||||
void UsdSceneRenderer::BuildLightWireframeLines(const pxr::UsdPrim& lightPrim,
|
||||
const pxr::GfMatrix4d& l2w,
|
||||
double scale,
|
||||
std::vector<float>& outVerts)
|
||||
{
|
||||
using pxr::GfVec3d;
|
||||
static constexpr int kSeg = 24;
|
||||
static constexpr double kTwoPi = 6.283185307179586;
|
||||
|
||||
auto pushLine = [&](const GfVec3d& a, const GfVec3d& b) {
|
||||
GfVec3d wa = l2w.Transform(a);
|
||||
GfVec3d wb = l2w.Transform(b);
|
||||
outVerts.push_back((float)wa[0]); outVerts.push_back((float)wa[1]); outVerts.push_back((float)wa[2]);
|
||||
outVerts.push_back((float)wb[0]); outVerts.push_back((float)wb[1]); outVerts.push_back((float)wb[2]);
|
||||
};
|
||||
// Circle centred at c, spanned by unit axes u,v, radius r (local space).
|
||||
auto circle = [&](const GfVec3d& c, const GfVec3d& u, const GfVec3d& v, double r) {
|
||||
GfVec3d prev;
|
||||
for (int i = 0; i <= kSeg; ++i) {
|
||||
double a = kTwoPi * double(i) / double(kSeg);
|
||||
GfVec3d p = c + u * (r * std::cos(a)) + v * (r * std::sin(a));
|
||||
if (i > 0) pushLine(prev, p);
|
||||
prev = p;
|
||||
}
|
||||
};
|
||||
|
||||
const GfVec3d X(1,0,0), Y(0,1,0), Z(0,0,1), O(0,0,0);
|
||||
const double marker = scale * 0.5; // display-scaled size for direction lines / markers
|
||||
|
||||
if (lightPrim.IsA<pxr::UsdLuxRectLight>()) {
|
||||
float w = 1.0f, h = 1.0f;
|
||||
pxr::UsdLuxRectLight rl(lightPrim);
|
||||
rl.GetWidthAttr().Get(&w, m_currentTime);
|
||||
rl.GetHeightAttr().Get(&h, m_currentTime);
|
||||
double hw = w * 0.5, hh = h * 0.5;
|
||||
GfVec3d bl(-hw,-hh,0), br(hw,-hh,0), tr(hw,hh,0), tl(-hw,hh,0);
|
||||
pushLine(bl,br); pushLine(br,tr); pushLine(tr,tl); pushLine(tl,bl);
|
||||
pushLine(O, GfVec3d(0,0,-marker)); // emission direction (-Z)
|
||||
}
|
||||
else if (lightPrim.IsA<pxr::UsdLuxDiskLight>()) {
|
||||
float r = 0.5f; pxr::UsdLuxDiskLight(lightPrim).GetRadiusAttr().Get(&r, m_currentTime);
|
||||
circle(O, X, Y, r);
|
||||
pushLine(O, GfVec3d(0,0,-marker));
|
||||
}
|
||||
else if (lightPrim.IsA<pxr::UsdLuxCylinderLight>()) {
|
||||
float r = 0.5f, len = 1.0f;
|
||||
pxr::UsdLuxCylinderLight cl(lightPrim);
|
||||
cl.GetRadiusAttr().Get(&r, m_currentTime);
|
||||
cl.GetLengthAttr().Get(&len, m_currentTime);
|
||||
double hx = len * 0.5;
|
||||
circle(GfVec3d( hx,0,0), Y, Z, r); // end caps (in local YZ)
|
||||
circle(GfVec3d(-hx,0,0), Y, Z, r);
|
||||
pushLine(GfVec3d(-hx, r,0), GfVec3d(hx, r,0)); // connecting lines
|
||||
pushLine(GfVec3d(-hx,-r,0), GfVec3d(hx,-r,0));
|
||||
pushLine(GfVec3d(-hx,0, r), GfVec3d(hx,0, r));
|
||||
pushLine(GfVec3d(-hx,0,-r), GfVec3d(hx,0,-r));
|
||||
}
|
||||
else if (lightPrim.IsA<pxr::UsdLuxDistantLight>()) {
|
||||
// Sun: small disc facing -Z plus parallel rays along -Z.
|
||||
double r = marker;
|
||||
circle(O, X, Y, r);
|
||||
const GfVec3d off[5] = { O, GfVec3d(r,0,0), GfVec3d(-r,0,0), GfVec3d(0,r,0), GfVec3d(0,-r,0) };
|
||||
for (const auto& o : off) pushLine(o, o + GfVec3d(0,0,-marker*2.0));
|
||||
}
|
||||
else if (lightPrim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||
// Environment dome: 3 large display-scaled circles around the origin.
|
||||
double r = std::max(scale, 1.0);
|
||||
circle(O, X, Y, r); circle(O, X, Z, r); circle(O, Y, Z, r);
|
||||
}
|
||||
else if (lightPrim.IsA<pxr::UsdLuxSphereLight>()) {
|
||||
float r = 0.5f; pxr::UsdLuxSphereLight sl(lightPrim);
|
||||
sl.GetRadiusAttr().Get(&r, m_currentTime);
|
||||
bool pointish = false; sl.GetTreatAsPointAttr().Get(&pointish, m_currentTime);
|
||||
if (pointish || r < 1e-4f) {
|
||||
double s = marker; // point light: axis cross + tiny disc
|
||||
pushLine(GfVec3d(-s,0,0), GfVec3d(s,0,0));
|
||||
pushLine(GfVec3d(0,-s,0), GfVec3d(0,s,0));
|
||||
pushLine(GfVec3d(0,0,-s), GfVec3d(0,0,s));
|
||||
circle(O, X, Y, s * 0.4);
|
||||
} else {
|
||||
circle(O, X, Y, r); circle(O, X, Z, r); circle(O, Y, Z, r);
|
||||
}
|
||||
}
|
||||
else {
|
||||
double s = marker; // unknown light: generic point marker
|
||||
pushLine(GfVec3d(-s,0,0), GfVec3d(s,0,0));
|
||||
pushLine(GfVec3d(0,-s,0), GfVec3d(0,s,0));
|
||||
pushLine(GfVec3d(0,0,-s), GfVec3d(0,0,s));
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// DrawLightWireframes (mirrors DrawCameraWireframes)
|
||||
// ===========================================================================
|
||||
|
||||
void UsdSceneRenderer::DrawLightWireframes(
|
||||
pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPathVector& selectedPaths,
|
||||
const pxr::GfMatrix4d& viewProjMatrix,
|
||||
double viewportCameraDist)
|
||||
{
|
||||
if (!stage) return;
|
||||
if (!m_bboxProgram || !m_lightWireVAO || !m_lightWireVBO) return;
|
||||
if (!m_drawTarget) return;
|
||||
|
||||
// Collect every light prim (anything carrying UsdLuxLightAPI).
|
||||
pxr::SdfPathVector lightPaths;
|
||||
for (const pxr::UsdPrim& prim : stage->Traverse()) {
|
||||
if (prim.HasAPI<pxr::UsdLuxLightAPI>())
|
||||
lightPaths.push_back(prim.GetPath());
|
||||
}
|
||||
if (lightPaths.empty()) return;
|
||||
|
||||
double rawScale = viewportCameraDist * 0.12;
|
||||
double scale = std::min(rawScale, 50.0);
|
||||
scale = std::max(scale, 0.5);
|
||||
|
||||
std::unordered_set<std::string> selectedSet;
|
||||
for (const auto& p : selectedPaths) selectedSet.insert(p.GetString());
|
||||
|
||||
pxr::GfVec2i sz = m_drawTarget->GetSize();
|
||||
m_drawTarget->Bind();
|
||||
glViewport(0, 0, sz[0], sz[1]);
|
||||
|
||||
GLboolean prevDepthTest = glIsEnabled(GL_DEPTH_TEST);
|
||||
GLboolean prevDepthMask; glGetBooleanv(GL_DEPTH_WRITEMASK, &prevDepthMask);
|
||||
GLboolean prevBlend = glIsEnabled(GL_BLEND);
|
||||
|
||||
// Draw on top of everything (like camera wireframes) so lights stay visible.
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
if (m_aaEnabled) glEnable(GL_LINE_SMOOTH);
|
||||
|
||||
glUseProgram(m_bboxProgram);
|
||||
pxr::GfMatrix4f vp = pxr::GfMatrix4f(viewProjMatrix);
|
||||
|
||||
pxr::UsdGeomXformCache xformCache(m_currentTime);
|
||||
|
||||
for (const auto& lpath : lightPaths) {
|
||||
pxr::UsdPrim prim = stage->GetPrimAtPath(lpath);
|
||||
if (!prim) continue;
|
||||
|
||||
pxr::GfMatrix4d l2w = xformCache.GetLocalToWorldTransform(prim);
|
||||
|
||||
bool isSelected = (selectedSet.count(lpath.GetString()) > 0);
|
||||
pxr::GfVec4f col = isSelected
|
||||
? pxr::GfVec4f(1.00f, 0.75f, 0.10f, 1.00f) // accent orange
|
||||
: pxr::GfVec4f(1.00f, 0.90f, 0.35f, 0.90f); // warm yellow
|
||||
glUniform4f(m_bboxUniformColor, col[0], col[1], col[2], col[3]);
|
||||
|
||||
std::vector<float> verts;
|
||||
BuildLightWireframeLines(prim, l2w, scale, verts);
|
||||
if (verts.empty()) continue;
|
||||
|
||||
int vertCount = static_cast<int>(verts.size() / 3);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, m_lightWireVBO);
|
||||
glBufferData(GL_ARRAY_BUFFER,
|
||||
static_cast<GLsizeiptr>(verts.size() * sizeof(float)),
|
||||
verts.data(), GL_DYNAMIC_DRAW);
|
||||
glUniformMatrix4fv(m_bboxUniformMVP, 1, GL_TRUE, vp.GetArray());
|
||||
glBindVertexArray(m_lightWireVAO);
|
||||
glDrawArrays(GL_LINES, 0, vertCount);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
if (m_aaEnabled) glDisable(GL_LINE_SMOOTH);
|
||||
if (prevDepthTest) glEnable(GL_DEPTH_TEST); else glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(prevDepthMask);
|
||||
if (!prevBlend) glDisable(GL_BLEND);
|
||||
|
||||
glUseProgram(0);
|
||||
m_drawTarget->Unbind();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// PickCameraAtPoint
|
||||
// ===========================================================================
|
||||
|
||||
@@ -108,6 +108,16 @@ public:
|
||||
const pxr::GfMatrix4d& viewProjMatrix,
|
||||
double viewportCameraDist);
|
||||
|
||||
/// Draw wireframe gizmos for all UsdLux light prims in the stage.
|
||||
/// The shape reflects each light's type (sphere/rect/disk/distant/dome/
|
||||
/// cylinder); selected lights get an accent colour. viewportCameraDist
|
||||
/// scales direction lines and point/distant/dome markers.
|
||||
void DrawLightWireframes(
|
||||
pxr::UsdStageRefPtr stage,
|
||||
const pxr::SdfPathVector& selectedPaths,
|
||||
const pxr::GfMatrix4d& viewProjMatrix,
|
||||
double viewportCameraDist);
|
||||
|
||||
/// Test a screen-space mouse position against all camera wireframe segments.
|
||||
/// Returns true and sets *outCameraPath if a camera wireframe is within
|
||||
/// kCameraPickRadius (10 px) of mousePos. Prioritises the closest hit.
|
||||
@@ -217,6 +227,8 @@ private:
|
||||
void DestroyBBoxResources();
|
||||
void InitCamWireResources();
|
||||
void DestroyCamWireResources();
|
||||
void InitLightWireResources();
|
||||
void DestroyLightWireResources();
|
||||
/// Draw a single axis-aligned box from a GfRange3d.
|
||||
void DrawBox(const pxr::GfRange3d& range, const pxr::GfMatrix4f& mvp);
|
||||
|
||||
@@ -227,6 +239,14 @@ private:
|
||||
double scale,
|
||||
std::vector<float>& outVerts);
|
||||
|
||||
/// Build light-gizmo line segments (GL_LINES vertex pairs) in world space for
|
||||
/// a single light prim. localToWorld is the light's transform at m_currentTime;
|
||||
/// scale is the display-size factor (for direction lines / point markers).
|
||||
void BuildLightWireframeLines(const pxr::UsdPrim& lightPrim,
|
||||
const pxr::GfMatrix4d& localToWorld,
|
||||
double scale,
|
||||
std::vector<float>& outVerts);
|
||||
|
||||
/// Project a world-space point to absolute screen coordinates (x=imagePosX+pixelX, etc.).
|
||||
/// Returns false when the point is behind the camera.
|
||||
static bool WorldToScreen(const pxr::GfVec3d& world,
|
||||
@@ -307,6 +327,10 @@ private:
|
||||
GLuint m_camWireVAO = 0;
|
||||
GLuint m_camWireVBO = 0; ///< dynamic VBO; reallocated per frame as needed
|
||||
|
||||
// --- Light wireframe GL resources (mirrors camera wireframe) ---
|
||||
GLuint m_lightWireVAO = 0;
|
||||
GLuint m_lightWireVBO = 0; ///< dynamic VBO; reallocated per frame as needed
|
||||
|
||||
// --- Camera wireframe cache ---
|
||||
pxr::SdfPathVector m_cachedCameraPaths;
|
||||
bool m_cameraCacheDirty = true;
|
||||
|
||||
@@ -1104,6 +1104,9 @@ void ViewportTile::Render(int tileIndex, ImVec2 pos, ImVec2 size,
|
||||
m_stage, m_selectedSdfPaths,
|
||||
m_camera.GetUsdCameraPath(),
|
||||
viewProj, m_camera.GetDist());
|
||||
m_renderer.DrawLightWireframes(
|
||||
m_stage, m_selectedSdfPaths,
|
||||
viewProj, m_camera.GetDist());
|
||||
}
|
||||
|
||||
// Display FBO texture (flip UV Y: OpenGL is bottom-up)
|
||||
|
||||
Reference in New Issue
Block a user