Generated banner for Omniverse Realtime Viewer
Generated logo for Omniverse Realtime Viewer

Omniverse Realtime Viewer

OpenAIOfficialCreated by NVIDIA OmniverseDistributed by OpenAI

  • Skill
  • Meta (authoring, orchestration, planning, memory)
  • Software development
  • Apache-2.0
  • Medium risk

From the author

Use as the top-level router for Omniverse Realtime Viewer USD app requests and focused viewer reference documents.
— the description by NVIDIA Omniverse

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Omniverse Realtime Viewer skill acts as a central orchestrator for developing USD-based viewer applications. It provides a structured approach to selecting implementation recipes, enforcing architectural conventions, and managing dependencies. By utilizing a set of focused reference documents, it guides developers through the lifecycle of building, configuring, and validating viewer applications, ensuring consistent behavior across different delivery paths such as local workstations or streaming environments.

What it does

  • Routes development requests to specific implementation recipes based on the target delivery path.
  • Enforces architectural rules for USD rendering, stage management, and UI integration.
  • Provides guidance on camera selection, input handling, and viewport interaction.
  • Standardizes the integration of AOV switching and render product configuration.
  • Facilitates the capture of validation evidence for viewer readiness.
  • Maintains separation between user-provided USD files and application-specific session state.

When to use it

  • When initiating the development of a new USD viewer application within the Omniverse ecosystem.
  • When determining the appropriate delivery path for a viewer, such as local native or streaming.
  • When needing to implement consistent camera, selection, or UI behavior across viewer projects.
  • When integrating advanced rendering features like AOV switching into an existing viewer.

Advantages

  • Ensures architectural consistency across diverse viewer implementations.
  • Provides a clear, modular path for adding specific capabilities like input routing or data display.
  • Promotes clean separation of concerns between stage data and viewer-owned state.
  • Standardizes the validation process for consistent delivery.

Usage examples

“I need to build a browser-based viewer for USD files.”
The agent will route you to the streaming viewer recipe, providing guidance on WebRTC stream setup, UI integration, and viewport interaction.
“How do I implement AOV switching in my local USD viewer?”
The agent will provide the AOV switching reference, detailing how to map render vars and update the display state on the server.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/openai/omniverse-realtime-viewer/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip omniverse-realtime-viewer.zip -d .agents/skills

Documentation by NVIDIA Omniverse

<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->

<!-- SPDX-License-Identifier: Apache-2.0 -->

Omniverse Realtime Viewer

This is the top-level entry point for the Omniverse Realtime Viewer skill package. It is self-contained: all required routing, conventions, and validation guidance live in the selected references.

Use the focused reference documents as implementation recipes. This file chooses the right recipes and preserves the architectural rules that must hold across all generated viewer apps.

Instructions

Start by classifying the requested viewer, then read only the references needed for that delivery path and feature set. Implement the render path first, layer interaction and UI behavior on top of it, and finish by capturing validation evidence from references/validation.md.

Read Order

  1. Read references/routing.md to choose the delivery path and focused references.

  2. Read references/conventions.md before implementing camera, input, selection, viewport, streaming protocol, scene loading, or environment behavior.

  3. For broad viewer requests, read references/usd-viewer-app/README.md.

  4. If the delivery path is unclear, read references/streaming-vs-local/README.md.

  5. If the prompt includes layout, panels, controls, inspectors, status, or UX, read references/viewer-ux-workflow/README.md and then the focused viewer UI references. This applies to React/WebRTC, Tauri, Electron, ovui, ovwidgets, and Dear ImGui apps; "frontend" means user-facing UI, not only browser UI.

  6. For viewport interaction, read references/viewer-input-routing/README.md before references/camera-controls/README.md, references/native-picking-selection/README.md, or references/object-selection/README.md.

  7. Read only the focused capability references needed for the requested app.

  8. Use references/validation.md to capture review evidence before handoff.

Non-Negotiables

  • Use ovrtx for all USD and 3D rendering.

  • Browser apps display an ovstream WebRTC video stream plus UI. The browser does not render USD geometry.

  • Do not substitute WebGL, Three.js, Babylon.js, PlayCanvas, A-Frame, model-viewer, react-three-fiber, glTF browser viewers, or other client-side 3D renderers.

  • If local validation cannot run because the GPU/runtime environment is absent, scaffold the ovrtx path and document the runtime requirement. Do not add a browser-renderer fallback.

  • Keep user USD files unmodified. Viewer cameras, render products, render vars, settings, selection metadata, and runtime state belong in session/composite layers or app state.

  • Keep one owner for renderer.step(), stage mutation, native picking, selection writes, and live attribute writes.

  • Keep dependency acquisition in references/dependencies/README.md and deployment choices in references/cloud-deployment/README.md; do not duplicate package locations or deployment setup.

Focused Reference Families

  • Entry points and recipes: references/usd-viewer-app/README.md, references/streaming-viewer-recipe/README.md, references/ovui-local-viewer-recipe/README.md, references/streaming-vs-local/README.md, references/electron-shm-viewer/README.md, references/ovwidgets-editor-shell/README.md.

  • Rendering and stage: references/ovrtx-rendering/README.md, references/stage-loading/README.md, references/stage-management/README.md, references/render-settings/README.md, references/aov-switching/README.md, references/stage-hierarchy/README.md, references/stage-queries/README.md, references/stage-attribute-reads/README.md, references/prim-transform-safety/README.md, references/usd-sample-data/README.md.

  • Delivery and runtime: references/streaming-server/README.md, references/streaming-client/README.md, references/streaming-messages/README.md, references/streaming-lifecycle/README.md, references/local-viewer/README.md, references/tauri-local-viewer/README.md, references/cpp-native-viewer/README.md, references/headless-shm-cli/README.md, references/viewer-backend-interface/README.md, references/webgl-shm-transport/README.md.

  • Viewer UI/UX: references/viewer-ux-workflow/README.md, references/viewer-layout-patterns/README.md, references/viewer-control-patterns/README.md, references/viewer-data-view-patterns/README.md, references/viewer-feedback-status/README.md.

  • Interaction: references/viewer-input-routing/README.md, references/camera-controls/README.md, references/object-selection/README.md, references/native-picking-selection/README.md, references/selection-feedback/README.md, references/selection-animation/README.md, references/transform-manipulator/README.md, references/gl-viewport-overlay/README.md, references/ovui-library/README.md, references/prim-pick-effects/README.md, references/prim-info-display/README.md, references/viewport-overlays/README.md.

  • Infrastructure: references/dependencies/README.md, references/windows-native-setup/README.md, references/cloud-assets/README.md, references/cloud-deployment/README.md, references/troubleshooting/README.md.

Build Workflow

  1. Classify the prompt by delivery path, target user, required capabilities, runtime environment, validation needs, and explicit constraints.

  2. Select a small reference set. Start with the recipe or routing reference, then add focused capabilities such as camera, picking, hierarchy, properties, render settings, transform tools, cloud assets, or deployment.

  3. Read selected references before writing app code. Follow their build order, import order, data-channel contracts, and renderer ownership rules.

  4. Implement the core render path first, then input routing and camera, then selection and data panels, then scene/settings features, then packaging or deployment.

  5. Treat the selected references as the behavior contract for API shape, compatibility, and generated project structure.

  6. Capture validation evidence before calling the viewer ready.

Examples

  • For a browser viewer request, use the streaming recipe references plus camera, picking, hierarchy, properties, render settings, and stream-status references.

  • For a local workstation viewer request, use the local or native delivery references plus renderer setup, stage loading, viewport input, and validation.

Completion Checklist

  • Selected references match the user's intent and delivery path.

  • No code path uses a browser-side 3D renderer for USD.

  • The generated app has one clear owner for render stepping and stage mutation.

  • User USD files remain untouched by viewer-owned session data.

  • Camera, input, selection, scene loading, and stream behavior follow references/conventions.md.

  • Setup/build/run results and visual interaction evidence are captured with references/validation.md.

Files

references/webgl-shm-transport/README.md

WebGL SHM Transport

Triggers

Use this skill for Electron SHM, SharedArrayBuffer, WebGL blitter, POSIX SHM pixels, BGRA texture upload, texSubImage2D, or blank canvas.

Use this with the parent electron-shm-viewer architecture skill when an Electron app displays ovrtx-rendered frames from a POSIX shared-memory ring buffer.

WebGL is only a 2D pixel blitter here. It uploads already-rendered pixels into one texture and draws a full-viewport quad. It must never load USD, render meshes, evaluate cameras, shade materials, or become a browser 3D renderer. All USD rendering remains in the process that owns ovrtx.Renderer.

Pipeline

SHM ring buffer slot (BGRA8, pitch-aligned)
  -> N-API addon: memcpy to SharedArrayBuffer on a libuv worker thread
  -> Electron IPC/preload: SAB handle crosses process boundary without copying
  -> React renderer: Uint8Array view of the SAB pixel payload
  -> WebGL: texImage2D first frame, texSubImage2D later frames
  -> full-viewport textured quad
  -> canvas sized with object-fit: contain for letterbox

Reference streaming-client for fixed-resolution letterbox conventions. The same rule applies here, adapted from <video> to <canvas>: keep the render resolution fixed, scale the presentation with containment, and map pointer events through the visible content rectangle.

Frame Contract

Each SAB snapshot begins with a 16-byte little-endian header:

byte 0..3    uint32 width
byte 4..7    uint32 height
byte 8..15   uint64 sequence
byte 16..N   pixel bytes

Pixels are BGRA8 unless the producer explicitly converts to RGBA8 before publishing. A packed frame has width * height * 4 pixel bytes after the header.

If the source SHM slot has pitch padding, prefer copying tight rows into the SAB. WebGL 1 cannot upload arbitrary row strides. WebGL 2 can use UNPACK_ROW_LENGTH, but tight SAB rows keep the first implementation simpler and more portable.

const HEADER_BYTES = 16;

interface FrameHeader { width: number; height: number; sequence: bigint }

function readFrameHeader(buffer: SharedArrayBuffer): FrameHeader {
  const view = new DataView(buffer, 0, HEADER_BYTES);
  return { width: view.getUint32(0, true), height: view.getUint32(4, true), sequence: view.getBigUint64(8, true) };
}

Electron Boundary

SharedArrayBuffer requires a cross-origin isolated renderer. Configure COOP/COEP in production and dev:

session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Cross-Origin-Opener-Policy': ['same-origin'],
      'Cross-Origin-Embedder-Policy': ['require-corp'],
    },
  });
});

const win = new BrowserWindow({
  webPreferences: { preload: PRELOAD_PATH, contextIsolation: true, nodeIntegration: false, sandbox: true },
});

For Vite dev, set the same two headers in server.headers so hot-reload resources do not break isolation.

Expose only explicit preload methods. Do not expose fs, child_process, arbitrary IPC, or unrestricted Node access to React.

contextBridge.exposeInMainWorld('shmFrames', {
  getFrameBuffer: () => ipcRenderer.sendSync('shm:get-frame-buffer'),
  getFrameMetadata: () => ipcRenderer.sendSync('shm:get-frame-metadata'),
  onFrameAvailable: (callback: () => void) => {
    const listener = () => callback();
    ipcRenderer.on('shm:frame-available', listener);
    return () => ipcRenderer.removeListener('shm:frame-available', listener);
  },
});

The renderer-side type should be limited to getFrameBuffer(), getFrameMetadata(), and onFrameAvailable(callback).

N-API Copy Rules

The native addon copies the newest complete ring slot into one stable SAB on a libuv worker thread:

slot = ring.newest_complete_slot()
copy header: width, height, sequence
for row in 0..height:
  memcpy(sab.pixels + row * width * 4, slot.pixels + row * slot.pitch_bytes, width * 4)
notify renderer: shm:frame-available

Use newest-frame-wins semantics. If the renderer is slower than the producer, skip old ring slots instead of building a queue. Publish the sequence only after the copied frame is coherent.

Shader Code

Vertex shader:

attribute vec2 a_position;
attribute vec2 a_texCoord;

varying vec2 v_texCoord;

void main() {
  v_texCoord = a_texCoord;
  gl_Position = vec4(a_position, 0.0, 1.0); // viewport clip-space position
}

Fragment shader:

precision mediump float;

uniform sampler2D u_frame;

varying vec2 v_texCoord;

void main() {
  gl_FragColor = texture2D(u_frame, v_texCoord);
}
const QUAD = new Float32Array([
  -1, -1, 0, 1,  1, -1, 1, 1,
  -1,  1, 0, 0,  1,  1, 1, 0,
]);

Texture Upload

Create the program, quad buffer, and texture once. Use texImage2D for first allocation or resolution changes; use texSubImage2D for normal frames.

function createTexture(gl: WebGLRenderingContext): WebGLTexture {
  const texture = gl.createTexture();
  if (!texture) throw new Error('Failed to create texture');
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  return texture;
}

function uploadFrame(
  gl: WebGLRenderingContext,
  texture: WebGLTexture,
  format: number,
  pixels: Uint8Array,
  width: number,
  height: number,
  previous: { width: number; height: number },
) {
  gl.bindTexture(gl.TEXTURE_2D, texture);
  if (previous.width !== width || previous.height !== height) {
    gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, gl.UNSIGNED_BYTE, pixels);
    previous.width = width;
    previous.height = height;
  } else {
    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, format, gl.UNSIGNED_BYTE, pixels);
  }
}

BGRA handling options:

  • Use EXT_texture_format_BGRA8888 when available: ext.BGRA_EXT is the upload format.

  • Convert BGRA to RGBA in JavaScript for simple viewers.

  • Convert BGRA to RGBA in a CUDA kernel before the SHM write for >60 fps or 4K targets.

const ext = gl.getExtension('EXT_texture_format_BGRA8888') as { BGRA_EXT: number } | null;
const uploadFormat = metadata.format === 'BGRA8' && ext ? ext.BGRA_EXT : gl.RGBA;

function bgraToRgba(source: Uint8Array, scratch: Uint8Array): Uint8Array {
  const src = new Uint32Array(source.buffer, source.byteOffset, source.byteLength / 4);
  const dst = new Uint32Array(scratch.buffer, scratch.byteOffset, scratch.byteLength / 4);
  for (let i = 0; i < src.length; i += 1) {
    const p = src[i];
    dst[i] = (p & 0xff00ff00) | ((p & 0xff) << 16) | ((p >>> 16) & 0xff);
  }
  return scratch;
}

The bit-shift path assumes little-endian desktop CPUs, which is the normal Electron target.

React Hook Pattern

The hook owns the WebGL context and RAF loop. Native notifications only mark that a frame may be ready; RAF decides when to draw.

import { useEffect, useRef, useState } from 'react';

declare global {
  interface Window {
    shmFrames: {
      getFrameBuffer(): SharedArrayBuffer | null;
      getFrameMetadata(): { pitchBytes?: number; format: 'BGRA8' | 'RGBA8' };
      onFrameAvailable(callback: () => void): () => void;
    };
  }
}

export function useWebGLCanvas(canvasRef: React.RefObject<HTMLCanvasElement>) {
  const lastSequence = useRef<bigint>(-1n);
  const needsDraw = useRef(true);
  const scratch = useRef<Uint8Array | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const gl = canvas.getContext('webgl', {
      alpha: false,
      antialias: false,
      depth: false,
      stencil: false,
      preserveDrawingBuffer: false,
    });
    if (!gl) {
      setError('WebGL unavailable');
      return;
    }

    const metadata = window.shmFrames.getFrameMetadata();
    const ext = gl.getExtension('EXT_texture_format_BGRA8888') as { BGRA_EXT: number } | null;
    const nativeBgra = metadata.format === 'BGRA8' && !!ext;
    const uploadFormat = nativeBgra ? ext!.BGRA_EXT : gl.RGBA;
    const program = createProgram(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE);
    const quad = createQuadBuffer(gl, program, QUAD);
    const texture = createTexture(gl);
    const previous = { width: 0, height: 0 };

    const unsubscribe = window.shmFrames.onFrameAvailable(() => {
      needsDraw.current = true;
    });

    let raf = 0;
    const draw = () => {
      raf = requestAnimationFrame(draw);
      if (!needsDraw.current) return;

      const sab = window.shmFrames.getFrameBuffer();
      if (!sab) return;

      const header = readFrameHeader(sab);
      if (!header.width || !header.height || header.sequence === lastSequence.current) return;

      if (canvas.width !== header.width || canvas.height !== header.height) {
        canvas.width = header.width;
        canvas.height = header.height;
        gl.viewport(0, 0, header.width, header.height);
      }

      const byteLength = header.width * header.height * 4;
      const source = new Uint8Array(sab, HEADER_BYTES, byteLength);
      let pixels = source;
      if (metadata.format === 'BGRA8' && !nativeBgra) {
        if (!scratch.current || scratch.current.byteLength !== byteLength) {
          scratch.current = new Uint8Array(byteLength);
        }
        pixels = bgraToRgba(source, scratch.current);
      }

      gl.useProgram(program);
      gl.bindBuffer(gl.ARRAY_BUFFER, quad);
      uploadFrame(gl, texture, uploadFormat, pixels, header.width, header.height, previous);
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

      lastSequence.current = header.sequence;
      needsDraw.current = false;
    };

    const onLost = (event: Event) => { event.preventDefault(); setError('WebGL context lost'); };
    canvas.addEventListener('webglcontextlost', onLost);
    raf = requestAnimationFrame(draw);

    return () => {
      cancelAnimationFrame(raf);
      unsubscribe();
      canvas.removeEventListener('webglcontextlost', onLost);
      gl.deleteTexture(texture);
      gl.deleteBuffer(quad);
      gl.deleteProgram(program);
    };
  }, [canvasRef]);

  return { error };
}

createProgram compiles the two shaders above. createQuadBuffer binds a_position and a_texCoord with a stride of four floats and draws TRIANGLE_STRIP.

Canvas Containment

Keep the canvas backing size equal to the frame size. Let CSS contain the element inside its viewport shell:

.viewportShell { width: 100%; height: 100%; overflow: hidden; background: #0b0d10; }
.viewportCanvas { display: block; width: 100%; height: 100%; object-fit: contain; }

Map pointer input through the letterboxed content rectangle before sending coordinates to electron-shm-viewer:

function clientToFramePoint(event: React.PointerEvent<HTMLElement>, frameWidth: number, frameHeight: number) {
  const rect = event.currentTarget.getBoundingClientRect();
  const scale = Math.min(rect.width / frameWidth, rect.height / frameHeight);
  const contentWidth = frameWidth * scale;
  const contentHeight = frameHeight * scale;
  const offsetX = (rect.width - contentWidth) / 2;
  const offsetY = (rect.height - contentHeight) / 2;
  const x = event.clientX - rect.left - offsetX;
  const y = event.clientY - rect.top - offsetY;
  if (x < 0 || y < 0 || x >= contentWidth || y >= contentHeight) return null;
  return { x: Math.floor((x / contentWidth) * frameWidth), y: Math.floor((y / contentHeight) * frameHeight) };
}

Frame Pacing And Backpressure

Use requestAnimationFrame, compare sequence numbers, and upload only new frames. Keep showing the previous texture when no new sequence is available. If several frames arrive before the next RAF, draw only the newest SAB contents.

Do not store pixel data in React state. Keep SAB views, scratch buffers, WebGL handles, and sequence numbers in refs.

At 1920x1080 RGBA8, one frame is about 8.3 MB and 60 fps uploads about 497 MB/s. At 3840x2160, one frame is about 33.2 MB and 60 fps uploads about 2.0 GB/s. The main bottlenecks are SHM-to-SAB copy, JavaScript BGRA conversion, texture upload bandwidth, and main-thread scheduling.

Performance guidance:

  • reuse the SAB allocation and WebGL texture;

  • repack pitch-aligned SHM rows to tight SAB rows;

  • prefer native BGRA upload or server-side CUDA conversion for high frame rates;

  • disable alpha, antialias, depth, and stencil on the WebGL context;

  • throttle UI overlay updates separately from pixel upload;

  • remove per-frame logs and avoid draining every skipped ring slot.

Troubleshooting

Blank canvas:

  • verify window.crossOriginIsolated === true and the preload returns a SharedArrayBuffer;

  • decode and log the first header once: width, height, sequence;

  • check shader compile and program link logs;

  • check that the canvas has a nonzero CSS size and call gl.getError() after first texImage2D.

Wrong colors:

  • red and blue swapped means BGRA bytes were uploaded as RGBA;

  • use EXT_texture_format_BGRA8888, JavaScript conversion, or server-side CUDA conversion;

  • confirm the producer format after AOV changes.

Stride artifacts or diagonal tearing:

  • WebGL 1 cannot upload padded rows; copy tight rows into the SAB or use WebGL 2 UNPACK_ROW_LENGTH;

  • publish only complete ring slots;

  • use native completion flags or atomics before the addon copies a slot.

Stale frames:

  • confirm the sequence increments and the addon chooses the newest complete slot;

  • use RAF instead of rendering directly from every IPC notification;

  • check whether DevTools or long React work is blocking the renderer main thread.

Context loss:

  • listen for webglcontextlost; on restore, recreate the program, buffer, texture, and upload state;

  • keep the SAB transport independent from WebGL resource lifetime.

SharedArrayBuffer unavailable:

  • set COOP/COEP in Electron responses and the dev server;

  • make subresources compatible with require-corp;

  • keep contextIsolation: true, nodeIntegration: false, and a narrow preload API.

Checklist

  1. Read electron-shm-viewer first for process topology and input protocol ownership.

  2. Keep ovrtx as the only USD/3D renderer; WebGL only blits pixels.

  3. Copy newest complete SHM slots into a stable SAB on a libuv worker.

  4. Parse [u32 width][u32 height][u64 sequence] from the first 16 bytes.

  5. Reuse one texture and use texSubImage2D after first allocation.

  6. Handle BGRA/RGBA explicitly.

  7. Pace display with RAF and skip stale sequences.

  8. Present the fixed render resolution with object-fit: contain.

  9. Keep Electron security explicit: COOP/COEP, context isolation, no unrestricted Node.

Requirements and compatibility

Written for: OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

The author says: Orchestrator skill. Downstream focused references may require NVIDIA GPUs, ovrtx, ovstream, ovui, OpenUSD, Python, Node/React, Tauri, Electron, C++, or cloud GPU deployment access depending on the selected viewer path.

Security

What our scanner noticed· Medium risk

  • Large base64-encoded blobs
  • Instructions that use the network

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
NVIDIA Omniverse
Publisher
OpenAI (Official)
Distributed by
OpenAI
Source
openai/plugins · plugins/nvidia/skills/omniverse-realtime-viewer · commit d420d13
Licence
Apache License 2.0 (redistribution allowed)
  • LICENSE (written by AutoGineer)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by NVIDIA Omniverse or OpenAI.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download omniverse-realtime-viewer.zip