Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e5e0fbd
Add vision system baseline from PR #1491 for URP/tilemap rework.
Jul 7, 2026
84e71c2
Add grid-based vision occlusion and polar shadowcasting.
Jul 7, 2026
219ab66
Add Unity folder metadata for vision assets and scripts.
Jul 7, 2026
5619f7d
Port vision fog rendering to URP Render Graph.
Jul 7, 2026
baa5510
Fix vision build errors and keep tile occupancy in Tile namespace.
Jul 7, 2026
c96217b
Fix URP vision fog rendering and world-position reconstruction.
Jul 7, 2026
eed2417
feat(vision): drive FOV from client tilemap grid caster
Jul 7, 2026
0ca221d
fix(vision): DDA grid ray march for correct FOV
Jul 7, 2026
8740091
feat(vision): dynamic door tile state and harsher fog
Jul 7, 2026
8ad51be
Merge remote-tracking branch 'origin/develop' into claude/fov-shader-…
claude Jul 16, 2026
c7775aa
fix(vision): correct vertical UV flip in FOV depth reconstruction
claude Jul 16, 2026
533bc7b
fix(vision): bootstrap VisionSubSystem so FOV actually runs
claude Jul 17, 2026
d426e63
Add vision subsystem
henkhooft Jul 17, 2026
64895c5
fix(vision): make out-of-vision areas a hard black mask, not a soft fog
claude Jul 17, 2026
6204924
fix(vision): occlude against real GPU depth instead of the tile grid
claude Jul 17, 2026
b793842
fix(vision): compile errors in VisionOcclusionCapture
claude Jul 17, 2026
5f1f3e1
fix(vision): move LayerMask.GetMask out of the field initializer
claude Jul 17, 2026
92621bd
fix(vision): restore tile-grid polar FOV after GPU cubemap regression
henkhooft Jul 17, 2026
7eafed0
fix(vision): raycast FOV and ignore furniture occluders
henkhooft Jul 17, 2026
c3bdb7e
fix(vision): ignore trigger colliders so open airlocks do not block FOV
henkhooft Jul 17, 2026
015ef1f
fix(vision): skip preview cameras and reduce wall stripe jitter
henkhooft Jul 17, 2026
647c48d
fix(vision): keep wall occlusion when rays pass through dense props
henkhooft Jul 17, 2026
e5e5fd8
Merge remote-tracking branch 'origin/develop' into claude/fov-shader-…
claude Jul 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Assets/Content/Resources/Simple Toon/Shaders/STDefault.shader
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,105 @@
ENDHLSL
}

// Required for SSAO / _CameraDepthTexture consumers (Vision, Atmos). Without this,
// URP's depth prepass skips STDefault meshes (walls) while URP Lit floors/plenums
// still write depth — vision then reconstructs plenum positions "through" walls.
Pass
{
Name "DepthOnly"
Tags { "LightMode" = "DepthOnly" }

ColorMask 0
ZWrite On
Cull Back

HLSLPROGRAM
#pragma target 2.0
#pragma vertex DepthOnlyVertex
#pragma fragment DepthOnlyFragment
#pragma multi_compile_instancing

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float4 positionCS : SV_POSITION;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings DepthOnlyVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
return output;
}

half4 DepthOnlyFragment(Varyings input) : SV_Target
{
return 0.0;
}
ENDHLSL
}

Pass
{
Name "DepthNormals"
Tags { "LightMode" = "DepthNormals" }

ZWrite On
Cull Back

HLSLPROGRAM
#pragma target 2.0
#pragma vertex DepthNormalsVertex
#pragma fragment DepthNormalsFragment
#pragma multi_compile_instancing

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
float3 normalOS : NORMAL;
UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float3 normalWS : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};

Varyings DepthNormalsVertex(Attributes input)
{
Varyings output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
return output;
}

half4 DepthNormalsFragment(Varyings input) : SV_Target
{
return half4(NormalizeNormalPerPixel(input.normalWS), 0.0);
}
ENDHLSL
}

Pass
{
Name "ShadowCaster"
Expand Down
8 changes: 8 additions & 0 deletions Assets/Content/Resources/Vision.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions Assets/Content/Resources/Vision/SpacePlane.shader
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
Shader "Custom/SpacePlane"
{
Properties
{
_BaseMap ("Albedo", 2D) = "black" {}
_BaseColor ("Color", Color) = (1, 1, 1, 1)
}

SubShader
{
Tags
{
"RenderType" = "Transparent"
"Queue" = "Transparent"
"RenderPipeline" = "UniversalPipeline"
}

Pass
{
Name "SpacePlane"
Tags { "LightMode" = "UniversalForward" }

ZWrite On
Blend SrcAlpha OneMinusSrcAlpha
Cull Back

HLSLPROGRAM
#pragma target 3.0
#pragma vertex Vert
#pragma fragment Frag

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Vision.hlsl"

TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
float4 _BaseColor;

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
};

Varyings Vert(Attributes input)
{
Varyings output;
VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
output.positionCS = vertexInput.positionCS;
output.positionWS = vertexInput.positionWS;
output.uv = input.uv;
return output;
}

half4 Frag(Varyings input) : SV_Target
{
if (VisionIsVisibleWorld(input.positionWS))
discard;

half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
return albedo;
}
ENDHLSL
}
}
}
10 changes: 10 additions & 0 deletions Assets/Content/Resources/Vision/SpacePlane.shader.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions Assets/Content/Resources/Vision/Spaceplane.mat
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
serializedVersion: 8
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Spaceplane
m_Shader: {fileID: 4800000, guid: d7a3002ca6faa3b588f87113808c11bd, type: 3}
m_ValidKeywords: []
m_InvalidKeywords: []
m_LightmapFlags: 4
m_EnableInstancingVariants: 0
m_DoubleSidedGI: 0
m_CustomRenderQueue: 3000
stringTagMap:
RenderType: Transparent
disabledShaderPasses: []
m_SavedProperties:
serializedVersion: 3
m_TexEnvs:
- _BaseMap:
m_Texture: {fileID: 0}
m_Scale: {x: 1, y: 1}
m_Offset: {x: 0, y: 0}
m_Ints: []
m_Floats: []
m_Colors:
- _BaseColor: {r: 0, g: 0, b: 0, a: 1}
m_BuildTextureStacks: []
8 changes: 8 additions & 0 deletions Assets/Content/Resources/Vision/Spaceplane.mat.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

135 changes: 135 additions & 0 deletions Assets/Content/Resources/Vision/Vision.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#ifndef SS3D_VISION_INCLUDED
#define SS3D_VISION_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

TEXTURE2D(_VisionMap);
SAMPLER(sampler_VisionMap);

float4x4 _PlayerCameraInvViewProj;
float4 _PlayerPos;
float _PlayerAngle;
float _ViewConeWidth;
float _ViewRange;

float VisionModulo(float x, float y)
{
return x - y * floor(x / y);
}

bool VisionDepthIsSky(float rawDepth)
{
#if defined(UNITY_REVERSED_Z)
return rawDepth <= 0.0;
#else
return rawDepth >= 1.0;
#endif
}

float2 VisionWorldToFov(float3 posWorld)
{
float2 posFov = posWorld.xz - _PlayerPos.xz;
float rawAngle = (posFov.x == 0.0) ? 0.0 : atan2(posFov.x, -posFov.y);
float adjAngle = -rawAngle;
float clampedAngle = VisionModulo(adjAngle, TWO_PI);
return float2(length(posFov), clampedAngle);
}

// Same contract as AtmosUnproject: `uv` is the raw VisionVert UV (not
// GetFullScreenTriangleTexCoord). Depth is sampled with the UNITY_UV_STARTS_AT_TOP flip;
// NDC Y is flipped for the inverse VP multiply. Mirrors AtmosScatter + AtmosCommon.hlsl —
// the fix that stopped volumetric gas bleeding through walls.
float3 VisionUnproject(float2 uv, float deviceDepth)
{
float4 positionCS = float4(uv * 2.0 - 1.0, deviceDepth, 1.0);
#if UNITY_UV_STARTS_AT_TOP
positionCS.y = -positionCS.y;
#endif
float4 positionWS = mul(_PlayerCameraInvViewProj, positionCS);
return positionWS.xyz / max(positionWS.w, 1e-6);
}

// Prefer this entry from fullscreen passes that use VisionVert (raw 0..1 UV).
float3 VisionWorldFromScreenUV(float2 uvScreen)
{
float2 depthUV = uvScreen;
#if UNITY_UV_STARTS_AT_TOP
depthUV.y = 1.0 - depthUV.y;
#endif

float rawDepth = SampleSceneDepth(depthUV);

#if !defined(UNITY_REVERSED_Z)
rawDepth = lerp(UNITY_NEAR_CLIP_VALUE, 1.0, rawDepth);
#endif

if (VisionDepthIsSky(rawDepth))
return _PlayerPos.xyz;

return VisionUnproject(uvScreen, rawDepth);
}

float3 VisionClipToWorld(float2 posClip)
{
return VisionWorldFromScreenUV(posClip * 0.5 + 0.5);
}

float4 _VisionMap_TexelSize;

float VisionSampleWallDepth(float2 viewUV)
{
// Center sample (bilinear via texture filterMode).
float center = SAMPLE_TEXTURE2D(_VisionMap, sampler_VisionMap, viewUV).r;

// Fill small angular holes on continuous surfaces (flat walls) without pulling in a
// distant corridor ray: only promote neighbors within ~2.5m of the center hit.
float texel = _VisionMap_TexelSize.x;
float left = SAMPLE_TEXTURE2D(_VisionMap, sampler_VisionMap, viewUV + float2(-texel, 0.0)).r;
float right = SAMPLE_TEXTURE2D(_VisionMap, sampler_VisionMap, viewUV + float2(texel, 0.0)).r;
float left2 = SAMPLE_TEXTURE2D(_VisionMap, sampler_VisionMap, viewUV + float2(-texel * 2.0, 0.0)).r;
float right2 = SAMPLE_TEXTURE2D(_VisionMap, sampler_VisionMap, viewUV + float2(texel * 2.0, 0.0)).r;

float maxNeighbor = max(max(left, right), max(left2, right2));
float centerWorld = center * _ViewRange;
float neighborWorld = maxNeighbor * _ViewRange;
if (abs(neighborWorld - centerWorld) < 2.5)
center = max(center, maxNeighbor);

// Extra slack so wall meshes that sit slightly past the collider / polar sample stay lit
// (especially at glancing angles where XZ depth and ray depth disagree a little).
return center * _ViewRange + 0.4;
}

bool VisionIsVisibleWorld(float3 posWorld)
{
float2 polarCoords = VisionWorldToFov(posWorld);

float centerAngle = _ViewConeWidth * 0.5;
float centerAngleWorld = _PlayerAngle;
float rawOffset = polarCoords.y - centerAngleWorld;
float clampedOffset = VisionModulo(rawOffset, TWO_PI) - PI;

if (abs(clampedOffset) > centerAngle && polarCoords.x > 1.0)
return false;

float currentAngle = centerAngle + clampedOffset;
float2 viewUV = float2(currentAngle / _ViewConeWidth, 0.0);

float currentDepth = polarCoords.x;
float wallDepth = VisionSampleWallDepth(viewUV);

return currentDepth <= wallDepth;
}

bool VisionIsVisibleScreenUV(float2 screenUV)
{
return VisionIsVisibleWorld(VisionWorldFromScreenUV(screenUV));
}

bool VisionIsVisibleClip(float2 posClip)
{
return VisionIsVisibleScreenUV(posClip * 0.5 + 0.5);
}

#endif
10 changes: 10 additions & 0 deletions Assets/Content/Resources/Vision/Vision.hlsl.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading