# Oil Paint: Three.js game integration

Oil Paint turns a rendered game frame into directional color strokes, with dry-brush variation and a suggestion of raised paint. It runs on the GPU, uses no generative AI service, and does not change model geometry.

## Download and compatibility

Open **Oil Paint** in the Shader catalog, adjust its settings, then download the JavaScript file or copy the game module. Source viewing, copying and downloading share the same content. The exported `defaultOptions` contains your selected settings.

The compatibility baseline is **Three.js 0.187.0, WebGL2 and EffectComposer**. Use the game's existing copy of `three` and retain the exported MIT and Three.js notices. Unity, WebGPU, XR, masked post-processing and arbitrary older Three.js releases are outside first-version verification.

## Add it to an existing game

Save the downloaded `oil-paint.js` in your project. If you already use EffectComposer, add only the oil-paint pass. This minimal example uses your existing `renderer`, `scene` and `camera`:

```js
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
import createOilPaintPass from './oil-paint.js';

const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
const oilPaint = createOilPaintPass();
composer.addPass(oilPaint);
composer.addPass(new OutputPass());

// Call from your existing game loop instead of rendering the scene directly.
composer.render(deltaSeconds);
```

Use this order: scene and existing linear post-processing → Oil Paint → OutputPass → HUD. OutputPass performs the final tone mapping and display color conversion; do not add another gamma conversion. Oil Paint reads and writes linear color, retaining HDR highlights and the input alpha. Transparent objects and particles rendered into the input are included.

A DOM HUD may remain above the canvas. For a Three.js HUD scene, render it separately after the composer. Text already rendered into the input image cannot be identified and restored by this effect.

## Controls, sizing and cleanup

The five controls are `intensity`, `brushSize`, `edgePreservation`, `paintRelief` and `canvasStrength`. Read initial values from `defaultOptions` and a copy of the current effective values from `oilPaint.settings`.

Paint relief defaults to `2`, with a range of `0–3` and increments of `0.01`.
Effect strength defaults to `0.75`, with a range of `0–2`. Values above `1` amplify the difference between the painted and original image.

Edge preservation defaults to `-3`, with a range of `-10–1`. Negative values strengthen brush scratches and paint relief near edges; zero leaves painted edges unchanged, and positive values restore original-image edges. Moving from `0` to `-10` progressively strengthens brush texture and relief around contours while suppressing thin halos.

```js
oilPaint.setOptions({ intensity: 0.8, paintRelief: 0.35 });
oilPaint.enabled = false; // EffectComposer skips this pass.
oilPaint.enabled = true;
```

An intensity of zero copies the input. Disabling the pass lets EffectComposer skip its work entirely. Strokes are evaluated in screen space and change as the camera moves; they are not attached to model UVs. Raised paint is a visual approximation, without additional geometry or real cast shadows.

Keep your normal renderer, camera and composer resize handling. EffectComposer passes physical framebuffer dimensions to `oilPaint.setSize`; direct callers must also supply physical pixels, without applying DPR twice. Expensive internal stages run at half width and height, with full-size final output.

Remove the pass from the composer before calling `oilPaint.dispose()`. It releases only its own materials and intermediate render targets, never your renderer, composer, or input/output buffers. Your game remains responsible for disposing its other passes, composer and renderer.

## Performance and limits

The design target is an RTX 3060-class PC at 1920×1080 and 60 FPS. This requires measurement and is not a guarantee for arbitrary game scenes. High DPI and larger output sizes increase cost. Stroke size changes the sample footprint; the directional filter uses a fixed 29 samples regardless of stroke size. Small website thumbnails are not evidence of 1080p game performance.

Version one has no object-selection masks, motion vectors, temporal accumulation or model-specific strokes. Identical input and settings do not introduce time-based randomness, but camera and object motion can change stroke direction and color regions. Check distant silhouettes, combat cues, moving cameras and transparent particles in your own game.

References: [Anisotropic Kuwahara](https://www.kyprianidis.com/p/gpupro/) and [Three.js OutputPass](https://threejs.org/docs/pages/OutputPass.html).

Brush directions snap immediately to 15-degree steps without rotation interpolation; switching may be visible near angle boundaries.
