[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1305":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":15,"stars7d":16,"stars30d":17,"stars90d":14,"forks30d":14,"starsTrendScore":16,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":19,"archived":20,"fork":20,"defaultBranch":21,"hasWiki":22,"hasPages":20,"topics":23,"createdAt":9,"pushedAt":9,"updatedAt":24,"readmeContent":25,"aiSummary":26,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":27,"discoverSource":28},1305,"unity-isometric-pixel-pipeline","bababuyyy\u002Funity-isometric-pixel-pipeline","bababuyyy","Pixel art isometric render pipeline for Unity 6 URP — toon shading, GPU grass, outline, sharp upscale",null,"ShaderLab",274,26,258,0,2,6,16,4.29,"MIT License",false,"main",true,[],"2026-06-12 02:00:26","# Unity Isometric Pixel Art Pipeline\n\n**A complete pixel art render pipeline for Unity 6 URP** — toon shading, GPU-instanced grass, adaptive outline, and sharp upscale with pixel-perfect panning.\n\nBuilt for isometric 3D games targeting a hand-crafted pixel art aesthetic, inspired by the work of [t3ssel8r](https:\u002F\u002Fwww.youtube.com\u002F@t3ssel8r).\n\nhttps:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F2ce64edf-1cb1-42fd-8b4b-039c94fd1b00\n\n---\n\n## Table of Contents\n- [How It Works](#how-it-works)\n- [The 5-Pass Pipeline](#the-5-pass-pipeline)\n- [Shaders](#shaders)\n- [Pixel-Perfect Panning](#pixel-perfect-panning)\n- [Why Not PBR?](#why-not-pbr)\n- [Requirements](#requirements)\n- [Project Structure](#project-structure)\n- [Scene Hierarchy Reference](#scene-hierarchy-reference)\n- [Setup From Scratch](#setup-from-scratch)\n- [Inspector Parameters Reference](#inspector-parameters-reference)\n- [Known Limitations](#known-limitations)\n- [References & Credits](#references--credits)\n- [License](#license)\n\n---\n\n## How It Works\n\nThe core idea is simple: render a 3D scene at a very low internal resolution (640×360), apply a 1-pixel outline shader at that resolution, then upscale to the screen with a sharp filter. Because the outline is computed at the internal resolution, every edge is guaranteed to be exactly 1 pixel — this is what makes it read as pixel art.\n\n```\n┌─────────────┐     ┌──────────────┐     ┌──────────────┐     ┌───────────┐     ┌──────────────┐\n│ Scene        │────▶│ Downsample   │────▶│ Outline 1px  │────▶│ Composite │────▶│ Sharp        │\n│ (full res)   │     │ (640×360)    │     │ (640×360)    │     │ (640×360) │     │ Upscale      │\n└─────────────┘     └──────────────┘     └──────────────┘     └───────────┘     └──────────────┘\n```\n\nThe outline shader samples Unity's depth and normals buffers to detect two types of edges:\n- **Silhouette** — where objects meet the background (depth discontinuity)\n- **Crease** — where two faces of the same object meet at a sharp angle (normal discontinuity)\n\nBoth are combined into a single RGBA mask (color + alpha) that gets composited over the scene color.\n\n---\n\n## The 5-Pass Pipeline\n\nThe `PixelRendererFeature` implements a 5-pass pipeline using Unity 6's Render Graph API:\n\n### Pass 0 — CopyColor\nCopies the full-resolution camera output to a safe buffer. This is necessary because the camera target can't be read and written simultaneously in the Render Graph.\n\n### Pass 1 — Downsample Color\nDownscales the scene from full resolution to the internal resolution (default 640×360) using bilinear filtering. This is the low-res scene that will become the final pixel art image.\n\n### Pass 2 — Outline at Internal Resolution\n**This is the most important architectural decision.** The outline shader runs at 640×360, not at full resolution. Because `_BlitTexture_TexelSize` is `(1\u002F640, 1\u002F360)`, each kernel step equals exactly 1 pixel in the final image. This guarantees 1px outlines regardless of screen resolution.\n\nThe shader samples `SampleSceneDepth` and `SampleSceneNormals` (which are still full-res buffers from the engine) but at UV coordinates spaced 1 internal pixel apart.\n\nThe outline is written to a separate texture with `FilterMode.Point` and `GraphicsFormat.R8G8B8A8_UNorm` — Point filter prevents bleed between pixels, and RGBA format is critical because the alpha channel carries the outline intensity.\n\n### Pass 3 — Composite\nBlends the outline mask over the low-res scene color. Silhouette darkens using the neighboring pixel's color. Crease brightens the center pixel's color. Both use alpha blending from the outline mask.\n\n### Pass 4 — Sharp Upscale\nUpscales from 640×360 to the screen resolution using a sharp interpolation method (fwidth + smoothstep). This avoids both the blurriness of bilinear filtering and the harshness of nearest-neighbor, producing clean pixel edges with mathematically smooth transitions at texel boundaries.\n\nThis pass also applies the **pixel-perfect panning offset** (`_PixelPanOffset`) to compensate for sub-texel camera movement.\n\n---\n\n## Shaders\n\n### ToonLit (`Custom\u002FToonLit`)\nStandard opaque material shader with toon stepping.\n\n- **Toon Stepping** — Quantizes diffuse lighting into discrete bands (`_Cuts` parameter). 3 cuts = 3 visible color bands per material.\n- **Cloud Shadows** — Global scrolling noise texture that modulates lighting. Controlled by `CloudShadowManager.cs`.\n- **Bayer Dithering** — Optional 4×4 ordered dithering to break up toon band boundaries.\n- **Patch System** — World-space noise-driven color variation. Two noise layers (`_Color2`, `_Color3`) overlay the base color based on world XZ position. Used for terrain color variation.\n- **Palette System** (toggle) — Replaces the standard `albedo × lighting` with `albedo × lerp(ShadowColor, HighlightColor, lit)`. Allows artistic control over shadow\u002Fhighlight tint per material. Disabled by default.\n- **4 passes** — ForwardLit, ShadowCaster, DepthOnly, DepthNormals. All share identical CBUFFER for SRP Batcher compatibility.\n\n### GrassBlade (`Custom\u002FGrassBlade`)\nBillboard grass with GPU Instancing.\n\n- **GPU Instancing** — Each blade is a billboard quad. ~35k instances in the demo scene.\n- **Color Inheritance** — Sprites carry only alpha\u002Fshape. Color comes from the same patch system as the terrain, so grass blends seamlessly with the ground.\n- **Wind** — Dual-layer noise-driven sway. Each blade samples wind noise at its world position for organic variation.\n- **Cloud Shadows** — Same global cloud system as ToonLit, applied per-blade at the instance root position.\n- **Accent Sprites** — Random replacement of grass blades with flower\u002Fdecoration sprites at configurable frequency.\n- **Fake Perspective** — UV distortion based on wind and camera direction for depth illusion.\n- **2 passes only** — ForwardLit and ShadowCaster. Intentionally excluded from DepthOnly\u002FDepthNormals to avoid outline artifacts (each sprite would generate its own outline, creating visual noise).\n\n### OutlineShader (`Hidden\u002FOutlineShader`)\nScreen-space edge detection at internal resolution.\n\n- **Silhouette Detection** — Samples 8 neighbors in a 3×3 grid. Detects depth discontinuities with an adaptive threshold that scales based on surface angle relative to the camera (`_AngleZScale`). Uses the color of the nearest neighbor (closest to camera) darkened by `_LineDarken`.\n- **Crease Detection** — Uses **directional contrast** method on 4 cardinal neighbors. Instead of summing `1 - dot(normal_center, normal_neighbor)` (which falsely triggers on curved faces), it computes `abs(d_top - d_bottom)` and `abs(d_left - d_right)` and takes the max. On a curved face, opposite neighbors vary equally so contrast ≈ 0. On a real edge, one side varies much more than the other so contrast is high. Brightens the center pixel's color by `_CreaseBrighten`.\n- **Output** — RGBA mask where RGB = edge color and A = edge intensity. Background is `float4(0,0,0,0)`.\n\n### CompositeShader\nSimple alpha blend of the outline mask over the scene color. Samples `_OutlineTexture` (set as global texture via `SetGlobalTextureAfterPass`) and blends it over `_BlitTexture` (the low-res scene).\n\n### SharpUpscaleShader (`Hidden\u002FSharpUpscale`)\nUpscales from internal resolution to screen resolution.\n\n- Uses `fwidth()` to calculate the ratio between source texels and screen pixels\n- Applies `smoothstep` at texel boundaries for mathematically sharp transitions\n- Adds `_PixelPanOffset` UV compensation for sub-texel camera movement\n- Samples with `sampler_LinearClamp` at LOD 0\n\n---\n\n## Pixel-Perfect Panning\n\nMoving a camera through a 3D scene at low resolution causes **pixel creep** — pixels appear to swim and jitter because the camera position doesn't align with the texel grid.\n\nThe fix is a two-step process:\n\n1. **Snap** the camera position to the nearest texel-sized grid point in view space. This eliminates creep but makes movement choppy.\n2. **Compensate** the snap error as a UV offset in the upscale shader. This recovers smooth movement while keeping the pixel grid stable.\n\nIn `IsometricCameraController.cs`:\n```\ntrue position → convert to camera local space → snap XY to texel grid → compute snap error → \nconvert error to UV space → apply snapped position to camera → send UV offset to shader\n```\n\nIn `SharpUpscaleShader.shader`:\n```hlsl\nfloat2 uv = input.texcoord;\nuv += _PixelPanOffset.xy;  \u002F\u002F sub-texel compensation\n\u002F\u002F ... sharp sample as normal\n```\n\n**References:**\n- [aarthifical — Pixel Perfect 2D (YouTube)](https:\u002F\u002Fyoutu.be\u002FjguyR4yJb1M) — explains the technique in 2D\n- [David Holland — 3D Pixel Art Rendering](https:\u002F\u002Fwww.davidhol.land\u002Farticles\u002F3d-pixel-art-rendering\u002F) — 3D adaptation with orthographic camera\n\n---\n\n## Why Not PBR?\n\nThe pipeline is designed for **flat toon shading with discrete color bands**. PBR textures (roughness, metallic, normal maps) produce smooth gradients that conflict with toon stepping:\n\n- At 640×360, PBR gradients become visual noise between the discrete bands\n- Raising resolution to accommodate PBR (1280×720+) loses the pixel art aesthetic\n- There's no resolution sweet spot that satisfies both PBR and pixel art\n\nThe intended workflow is hand-picked flat colors per material — think color palettes, not painted textures.\n\n---\n\n## Requirements\n- Unity 6 (6000.x)\n- Universal Render Pipeline (URP)\n- SSAO **must be disabled** (causes artifacts with GPU-instanced grass)\n\n---\n\n## Project Structure\n```\nAssets\u002F\n├── Materials\u002F\n│   ├── Pipeline\u002F\n│   │   ├── MAT_Outline.mat          ← Shader: Hidden\u002FOutlineShader\n│   │   ├── MAT_Composite.mat        ← Shader: Hidden\u002FCompositeShader\n│   │   └── MAT_SharpU.mat           ← Shader: Hidden\u002FSharpUpscale\n│   ├── Toon\u002F\n│   │   ├── MAT_ToonGreen.mat        ← Shader: Custom\u002FToonLit (example terrain material)\n│   │   ├── MAT_ToonGray.mat         ← Shader: Custom\u002FToonLit (example stone material)\n│   │   └── ...\n│   └── Grass\u002F\n│       └── MAT_Grass.mat            ← Shader: Custom\u002FGrassBlade\n├── Rendering\u002F\n│   ├── PC_Renderer.asset            ← URP Renderer Data with PixelRendererFeature added\n│   └── RenderFeatures\u002F\n│       ├── PixelRendererFeature.cs\n│       └── OutlineRendererFeature.cs ← Standalone version (not used in 5-pass pipeline)\n├── Scripts\u002F\n│   └── Systems\u002F\n│       ├── CloudShadowManager.cs\n│       ├── GrassSpawner.cs\n│       ├── IsometricCameraController.cs\n│       └── PlayerPlaceholder.cs\n├── Shaders\u002F\n│   ├── ToonLighting\u002F\n│   │   ├── ToonLit.shader\n│   │   └── GrassBlade.shader\n│   └── PostProcess\u002F\n│       ├── OutlineShader.shader\n│       ├── CompositeShader.shader\n│       └── SharpUpscaleShader.shader\n├── Textures\u002F\n│   ├── CloudNoise_v5.png            ← Seamless noise for cloud shadows\n│   ├── WindNoise.png                ← Seamless noise for grass wind\n│   └── GrassSprite.png             ← Alpha cutout grass blade sprite\n└── Scenes\u002F\n    └── DemoScene.unity              ← Ready-to-play demo scene\n```\n\n---\n\n## Scene Hierarchy Reference\n\nHow the demo scene is organized. Use this as a guide when building your own scene:\n\n```\nScene\n├── Directional Light             ← Main sun light\n├── Global Volume                 ← URP post-processing (SSAO disabled)\n├── EnvironmentManager            ← CloudShadowManager.cs\n├── GrassManager                  ← GrassSpawner.cs\n├── Plane                         ← Terrain with ToonLit material\n├── PlayerPlaceholder             ← Follow target for the camera\n├── Camera Pivot                  ← IsometricCameraController.cs\n│   └── Main Camera               ← Camera component (Orthographic, child of pivot)\n├── [scene objects]               ← Cubes, rocks, etc. with ToonLit materials\n└── Wall \u002F Wall (1) \u002F ...         ← Invisible walls (Box Collider only, Mesh Renderer disabled)\n```\n\n### Component Placement\n\n| Script | GameObject | Fields to Assign |\n|--------|-----------|------------------|\n| IsometricCameraController | Camera Pivot | Target → PlayerPlaceholder, Pixel Renderer Feature → PixelRendererFeature asset |\n| CloudShadowManager | EnvironmentManager | Light → Directional Light, Cloud Noise → CloudNoise_v5.png |\n| GrassSpawner | GrassManager | Grass Material → MAT_Grass, terrain reference, density settings |\n\n### Notes\n- The **Camera Pivot** is an empty GameObject. The actual Camera is a **child** of it — this separation is what allows pixel-perfect snapping without jitter.\n- **PlayerPlaceholder** can be any GameObject with a Transform. The camera will follow its position.\n- **Invisible walls** are regular cubes with Mesh Renderer disabled but Box Collider kept active. They prevent the player from falling off the map.\n- All scene objects use **Custom\u002FToonLit** materials. The terrain uses the patch system (`_Color2`, `_Color3`) for color variation.\n\n---\n\n## Setup From Scratch\n\nIf you want to integrate this pipeline into an existing project instead of using the demo scene:\n\n### Step 1 — Import Files\nCopy the `Shaders\u002F`, `Scripts\u002F`, and `Rendering\u002FRenderFeatures\u002F` folders into your URP project.\n\n### Step 2 — Create Pipeline Materials\nCreate 3 materials and assign the correct shader to each:\n\n| Material | Shader |\n|----------|--------|\n| MAT_Outline | Hidden\u002FOutlineShader |\n| MAT_Composite | Your composite shader |\n| MAT_SharpU | Hidden\u002FSharpUpscale |\n\nThese names are for your reference only — the important thing is assigning them in the PixelRendererFeature inspector.\n\n### Step 3 — Configure URP Renderer\n1. Select your **URP Renderer Data** asset\n2. Click **Add Renderer Feature** → **PixelRendererFeature**\n3. Assign the 3 materials in the inspector slots\n4. Set internal resolution (default: 640 width, 360 height)\n5. **Disable SSAO** if enabled\n\n### Step 4 — Camera Setup\n1. Create an empty GameObject named `CameraPivot`\n2. Add `IsometricCameraController` component to it\n3. Create a Camera as a **child** of CameraPivot\n4. Set the Camera to **Orthographic**\n5. Drag the `PixelRendererFeature` asset into the `Pixel Renderer Feature` field on the controller\n6. Set Fixed Rotation to `(20, 45, 0)` for standard isometric angle\n\n### Step 5 — Cloud Shadows\n1. Add `CloudShadowManager` component to any GameObject\n2. Assign your Directional Light\n3. Assign `CloudNoise_v5.png` (or any seamless noise texture)\n4. Recommended starting values: Scale 60, Contrast 3, Threshold 0.4, ShadowMin 0.3\n\n### Step 6 — Create Toon Materials\nFor each object material:\n1. Create a material with shader `Custom\u002FToonLit`\n2. Set `Base Color` to your desired flat color\n3. Set `Cuts` to 3 (three visible light bands)\n4. Adjust `Steepness` and `Wrap` to taste\n\n### Step 7 — Grass (Optional)\n1. Create a material with shader `Custom\u002FGrassBlade`\n2. Assign a grass sprite texture (alpha cutout PNG)\n3. Add `GrassSpawner` to your terrain\n4. Configure density, scale, and color patches to match your terrain material\n\n---\n\n## Inspector Parameters Reference\n\n### PixelRendererFeature — Outline Parameters\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| **Internal Resolution** | | |\n| Width | 640 | Internal render width in pixels |\n| Height | 360 | Internal render height in pixels |\n| **Silhouette** | | |\n| Line Darken | 0.0 | 0 = black outline, >0 = darkened neighbor color |\n| Line Alpha | 1.0 | Silhouette opacity |\n| Z Delta Cutoff | 0.15 | Depth difference threshold for silhouette detection |\n| Angle Z Cutoff | 0.3 | Surface angle where adaptive threshold kicks in |\n| Angle Z Scale | 4 | How much the threshold scales for angled surfaces |\n| Kernel Radius | 1.0 | Sampling distance. Keep at 1.0 for 1px lines at internal res |\n| **Crease** | | |\n| Crease Brighten | 1.0 | How much to brighten crease edges |\n| Crease Alpha | 1.0 | Crease opacity |\n| Depth Diff Low | 0.0 | Lower bound of depth smoothstep for crease suppression |\n| Depth Diff High | 0.05 | Upper bound of depth smoothstep for crease suppression |\n| Normal Smooth Low | 0.05 | Minimum directional contrast to start showing crease |\n| Normal Smooth High | 0.3 | Directional contrast where crease is fully visible |\n\n### IsometricCameraController\n\n| Parameter | Default | Description |\n|-----------|---------|-------------|\n| Fixed Rotation | (20, 45, 0) | Initial camera angle (pitch, yaw, roll) |\n| Smoothing | 5 | Follow target smoothing speed |\n| Use Pixel Snap | true | Enable pixel-perfect panning |\n| Pixel Renderer Feature | — | Drag your PixelRendererFeature asset here |\n\n---\n\n## Known Limitations\n\n1. **Crease at object bases** — Where objects meet the terrain, a crease line may appear at the base. This is because grass doesn't write to the depth\u002Fnormals buffers. Less noticeable with dense grass and detailed assets.\n\n2. **Cloud shadow cuts** — Under heavy cloud shadow, toon stepping bands can collapse because the cloud shadow clamps the diffuse value before toon stepping occurs.\n\n3. **Grass excluded from depth\u002Fnormals** — Adding DepthOnly\u002FDepthNormals passes to the grass shader causes each blade to generate its own outline, creating visual noise. The grass intentionally remains invisible to the outline shader.\n\n4. **Not designed for PBR** — The pipeline assumes flat toon shading. PBR textures will produce noisy results at the internal resolution.\n\n---\n\n## References & Credits\n\nThis project was built by studying and adapting techniques from multiple sources:\n\n- **[t3ssel8r](https:\u002F\u002Fwww.youtube.com\u002F@t3ssel8r)** — Original inspiration. Pixel art 3D in realtime, outline technique, cloud shadows on grass, palette-driven iteration.\n- **[David Holland](https:\u002F\u002Fwww.davidhol.land\u002Farticles\u002F3d-pixel-art-rendering\u002F)** — Pixel aligned panning (snap + UV compensation), volumetric god rays via shell texturing, grass LIGHT_VERTEX technique. Implemented in Godot.\n- **[KodyKing](https:\u002F\u002Fgithub.com\u002FKodyJKing\u002Fhello-threejs)** — Crease detection method using depth and normal differences. hello-threejs repository.\n- **[Roystan](https:\u002F\u002Froystan.net\u002Farticles\u002Foutline-shader\u002F)** — Roberts Cross outline technique (depth + normals), depth threshold modulation. Unity tutorial.\n- **[keijiro\u002FKino](https:\u002F\u002Fgithub.com\u002Fkeijiro\u002FKino)** — Post-processing effects collection for Unity. Referenced for Recolor edge detection.\n- **[aarthifical](https:\u002F\u002Fyoutu.be\u002FjguyR4yJb1M)** — Pixel-perfect camera movement explanation in 2D.\n- **[Dylearn](https:\u002F\u002Fwww.youtube.com\u002F@Dylearn)** — Grass shader techniques, crease detection approach.\n\n---\n\n## Support\n\nIf this project helped you, consider:\n- ⭐ Starring the repository\n- 🎮 Checking out the [itch.io demo](https:\u002F\u002Fbababuyyyy.itch.io\u002Funity-isometric-pixel-art-shader-demo) (pay-what-you-want)\n- ☕ [Buy me a coffee](https:\u002F\u002Fwww.paypal.com\u002Fcgi-bin\u002Fwebscr?cmd=_donations&business=danilolima.se@hotmail.com)\n\n---\n\n## License\n\nMIT — free for personal and commercial use.\n\n---\n\nMade by [Bababuyyy](https:\u002F\u002Fgithub.com\u002Fbababuyyy)\n","这是一个为Unity 6 URP设计的像素艺术等距渲染管线，支持卡通着色、GPU实例化草、自适应轮廓线和锐利放大。项目的核心功能包括五阶段渲染流程，其中关键在于在低分辨率（如640×360）下进行轮廓线处理，确保无论屏幕分辨率如何，轮廓线始终精确为1像素宽，从而保持清晰的像素艺术效果。此外，该管线还实现了像素完美平移，非常适合追求手绘风格像素艺术视觉效果的等距视角3D游戏开发场景。","2026-06-11 02:42:57","CREATED_QUERY"]