# Adding Post-Processing Effects to the Pascal Editor 3D Viewer

> Learn how to add post-processing effects like SSGI, outlines, and TRAA to the Pascal Editor 3D viewer. Configure and integrate these WebGPU render pipeline effects into your React app.

- Repository: [Pascal/editor](https://github.com/pascalorg/editor)
- Tags: deep-dive
- Published: 2026-03-25

---

**The Pascal Editor implements post-processing effects through an isolated WebGPU render pipeline in the viewer package, using Three.js TSL nodes for SSGI, outlines, TRAA, and denoising that developers can configure via exported parameters and integrate into any React application.**

The **Pascal Editor** (`pascalorg/editor`) renders 3D scenes using a dedicated **viewer** package that exposes a WebGPU-based post-processing stack. This architecture deliberately separates rendering logic from the editor UI, allowing developers to add visual effects using **Three Shader Language (TSL)** nodes without modifying core application code.

## How the WebGPU RenderPipeline Works

Inside [`packages/viewer/src/components/viewer/post-processing.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/post-processing.tsx), the system constructs a **WebGPU RenderPipeline** that processes frames through a node-based shader graph. The pipeline rebuilds automatically whenever the project ID changes or when the application explicitly requests a retry, ensuring post-processing effects stay synchronized with the current 3D scene.

The implementation imports specialized TSL display nodes—**`outline`**, **`ssgi`**, **`traa`**, and **`denoise`**—and connects them through standard TSL operations like `add`, `mix`, and `output` to produce the final composed image. This node graph approach allows you to chain effects declaratively without writing raw WGSL shaders.

## Configuring SSGI and Effect Parameters

Global illumination settings are tunable through the exported **`SSGI_PARAMS`** constant defined in the post-processing module. These parameters control screen-space global illumination quality, radius, and intensity without requiring pipeline recompilation.

Key configuration options include:

- **`enabled`** – Toggle SSGI on or off
- **`radius`** – Sampling radius for indirect lighting (default is 2.0 for softer GI)
- **`giIntensity`** – Boost factor for indirect lighting contribution
- **Slice count** – Determines ray marching quality

```typescript
// Enable SSGI and customise parameters
import { SSGI_PARAMS } from '@pascal-app/viewer/src/components/viewer/post-processing';

SSGI_PARAMS.enabled = true;
SSGI_PARAMS.radius = 2.0;        // Larger radius for softer GI
SSGI_PARAMS.giIntensity = 0.8;   // Boost indirect lighting

```

## Layer Masking for Selective Effects

The pipeline uses strict **layer masking** to ensure post-processing affects only specific geometry. While the main scene renders to **`SCENE_LAYER`**, the post-processing pass targets **`ZONE_LAYER`** exclusively. This separation—configured in [`packages/viewer/src/lib/layers.ts`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/lib/layers.ts)—prevents UI elements or gizmos from receiving unwanted blur, bloom, or ambient occlusion.

When setting up custom geometry that requires post-processing, ensure you assign it to `ZONE_LAYER` so the RenderPipeline includes it in the effect chain:

```typescript
import { ZONE_LAYER } from '@pascal-app/viewer/src/lib/layers';

mesh.layers.set(ZONE_LAYER);

```

## Handling Background Colors and Theme Transitions

To prevent visual flashes on first render, the component maintains a **`bgUniform`** that stores the current scene background color. Each frame, this uniform lerps toward the active theme color (dark or light), creating smooth transitions when users switch themes. The `PostProcessing` component reads the theme from the viewer store and updates `bgUniform` automatically, ensuring the clear color matches the UI without manual intervention.

## Retry Logic and Error Resilience

Pipeline creation includes defensive retry mechanics. If WebGPU context loss or shader compilation fails, the system attempts recreation up to **`MAX_PIPELINE_RETRIES`** with a delay of **`RETRY_DELAY_MS`** between attempts. Errors are logged to the console, but rendering continues in a degraded state rather than crashing the application, maintaining user agency even on unsupported hardware.

## Integrating Post-Processing in Your Application

The `PostProcessing` component is imported and rendered inside the main `Viewer` component at [`packages/viewer/src/components/viewer/index.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/index.tsx). This encapsulation keeps effect logic completely separate from the editor app, allowing you to drop the viewer into any React project with full post-processing support.

**Basic implementation** requires only the `Viewer` component and a project ID:

```tsx
import Viewer from '@pascal-app/viewer/src/components/viewer';
import { useTheme } from 'some-ui-lib';

export default function ProjectViewer({ projectId }) {
  const theme = useTheme(); // 'dark' | 'light'

  return (
    <Viewer
      projectId={projectId}
      theme={theme} // Background colour updates automatically
    />
  );
}

```

**Triggering pipeline rebuilds** (e.g., after changing settings) uses the viewer store:

```tsx
import useViewer from '@pascal-app/viewer/src/store/use-viewer';

function RefreshButton() {
  const requestRebuild = useViewer(state => state.requestPipelineRebuild);
  return <button onClick={requestRebuild}>Rebuild Post‑FX</button>;
}

```

## Summary

- **Pipeline location**: [`packages/viewer/src/components/viewer/post-processing.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/post-processing.tsx) contains the WebGPU RenderPipeline and TSL node graph.
- **Effect nodes**: Available TSL nodes include `outline`, `ssgi`, `traa`, and `denoise`, chained via `add`, `mix`, and `output`.
- **Configuration**: Modify **`SSGI_PARAMS`** to adjust global illumination without rebuilding the pipeline.
- **Layer isolation**: Only geometry on **`ZONE_LAYER`** receives post-processing; the main scene uses `SCENE_LAYER`.
- **Theme support**: Background colors transition smoothly via `bgUniform` lerping to prevent flash on load.
- **Error handling**: Automatic retry with `MAX_PIPELINE_RETRIES` ensures robustness against WebGPU context loss.
- **Integration**: Import the `Viewer` component from [`packages/viewer/src/components/viewer/index.tsx`](https://github.com/pascalorg/editor/blob/main/packages/viewer/src/components/viewer/index.tsx) and use `useViewer` for state management.

## Frequently Asked Questions

### What TSL nodes are available for post-processing in the Pascal Editor?

The viewer provides four primary TSL display nodes: **`outline`** for edge detection, **`ssgi`** for screen-space global illumination, **`traa`** for temporal anti-aliasing, and **`denoise`** for noise reduction. These are imported into [`post-processing.tsx`](https://github.com/pascalorg/editor/blob/main/post-processing.tsx) and connected through a node graph using standard TSL operations like `add` and `mix` to create the final composite output.

### How do I enable or disable SSGI in the 3D viewer?

Import the **`SSGI_PARAMS`** constant from `@pascal-app/viewer/src/components/viewer/post-processing` and toggle the `enabled` boolean property. You can also adjust `radius` and `giIntensity` numerically to control the softness and strength of indirect lighting. Changes take effect immediately without requiring a pipeline rebuild.

### Why are post-processing effects applied only to certain layers?

The architecture uses **layer masking** to maintain UI clarity. The pipeline renders only objects on **`ZONE_LAYER`** through the post-processing stack, while **`SCENE_LAYER`** handles standard rendering. This ensures that helper objects, gizmos, or overlay UI remain sharp and unaffected by blur or glow effects applied to the main 3D content.

### How does the viewer handle theme changes without flashing?

The component creates a **`bgUniform`** that stores the current background color. On each frame, it lerps this uniform toward the active theme color (provided via the `theme` prop on the `Viewer` component). This gradual interpolation prevents the jarring color jump that would otherwise occur on the first render or during theme switches.