PictureFrameBuilder/Shader: lighting parity, HTrace GBuffer pass, absolute frame metrics

Shader (PictureTessellationLit):
- Lighting parity with URP Lit: lightmaps, shadowmask/mixed, APV, DBuffer
  decals, vertex additional lights, rendering layers, fragment fog
- UniversalGBuffer pass (with tessellation + graded albedo) so HTrace WSGI
  composes correct GI instead of a cyan veil over pictures
- Skip GPU tessellation entirely when strength/displacement is 0
- Inspector ranges matched to builder (-3..3 bump, -10..10 tessellation)

Builder:
- Frame Thickness & Padding are now absolute meters (size-independent)
  instead of fractions of the picture size
- Tooltip updates for the new default/range semantics

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Fable 5 committed 2026-07-11 12:30:19 +02:00
1 parent 0c7ac11023
commit 57a4627b81
2 files changed
+308 -42

No files matched your search

@@ -6,10 +6,10 @@ namespace Chameleon.Components
// Procedural picture frame, generated live in the editor (and at runtime) from the inspector
// parameters. Sizing is inside-out: the picture keeps the texture's aspect ratio EXACTLY and the
// outer frame grows around it by padding + thickness (Size = longer outer side) - insetting a
// uniform border from an aspect-scaled outer rect would distort the picture. All shape parameters
// are relative fractions (thickness/padding of the picture's longer side, radius of the maximum
// possible rounding, depths of the frame depth), so a frame keeps its proportions when resized via
// Size - resize via Size, NOT via transform scale. Four submeshes with their own
// uniform border from an aspect-scaled outer rect would distort the picture. Thickness and padding
// are absolute meters (independent of Size - the picture shrinks as they grow); corner radius is a
// fraction of the maximum possible rounding and the depths are fractions of the frame depth.
// Resize via Size, NOT via transform scale. Four submeshes with their own
// material slots: 0 picture, 1 inner cavity (recess walls + padding ring), 2 frame (front ring, outer
// sides, edge rounding), 3 back plate. Front faces -Z like Unity's built-in Quad. An assigned
// PictureTexture becomes the picture material's base map via a per-frame DontSave material
@@ -46,8 +46,8 @@ namespace Chameleon.Components
[SerializeField] Vector2 _aspect = Vector2.one;
[Header("Frame")]
[Tooltip("Border width of the frame as a fraction of the picture's longer side.")]
[SerializeField, Range(0f, 1f)] float _frameThickness = 0.05f;
[Tooltip("Border width of the frame in meters (independent of Size).")]
[SerializeField, Range(0.001f, 0.5f)] float _frameThickness = 0.03f;
[Tooltip("Depth of the frame, front face to back face.")]
[SerializeField, Range(0.002f, 0.500f)] float _frameDepth = 0.05f;
[Tooltip("Rounding of the outer frame edges relative to the maximum possible radius (thickness / half depth); 0 = sharp box, 1 = fully rounded.")]
@@ -58,27 +58,27 @@ namespace Chameleon.Components
[SerializeField, Range(-1f, 1f)] float _frameInnerDepth = -0.1f;
[Header("Picture")]
[Tooltip("Gap between the frame's inner edge and the picture as a fraction of the picture's longer side.")]
[SerializeField, Range(0f, 1f)] float _padding = 0.01f;
[Tooltip("Gap between the frame's inner edge and the picture in meters (independent of Size).")]
[SerializeField, Range(0f, 0.5f)] float _padding = 0.005f;
[Tooltip("Picture plane: -1 = fully inside (on the back plate), 0 = flush with the inner floor, 1 = a full frame depth in front of the frame.")]
[SerializeField, Range(-1f, 1f)] float _pictureDepth = 0.1f;
[Tooltip("Photo for this frame: applied as the picture material's base map via a per-frame material instance (the material asset itself is never modified) and drives the aspect ratio. Empty = the material's own base map is shown.")]
[SerializeField] Texture _pictureTexture;
[Tooltip("Optional normal map for the picture: applied as the picture material's bump map via the same per-frame material instance.")]
[SerializeField] Texture _pictureNormalMap;
[Tooltip("Intensity of the picture normal map (URP _BumpScale). 1 = as authored.")]
[SerializeField, Range(0f, 3f)] float _normalMapStrength = 1f;
[Tooltip("Intensity of the picture normal map (URP _BumpScale). 0 = flat, negative inverts the relief.")]
[SerializeField, Range(-3f, 3f)] float _normalMapStrength = 0;
[Tooltip("Optional height map for the picture: set as _HeightMap on the per-frame material instance (tessellation shader); on plain URP Lit it falls back to the parallax map.")]
[SerializeField] Texture _pictureHeightMap;
[Tooltip("Scales the material's occlusion (_OcclusionStrength). 1 = as authored.")]
[SerializeField, Range(0f, 3f)] float _occlusionStrength = 1f;
[Tooltip("Scales the material's occlusion (_OcclusionStrength). 0 = no occlusion darkening.")]
[SerializeField, Range(0f, 3f)] float _occlusionStrength = 0;
[Tooltip("Contrast of the occlusion map around mid-gray (_OcclusionContrast). 0 = unchanged, -1 = flat, 3 = strongly exaggerated.")]
[SerializeField, Range(-1f, 3f)] float _occlusionContrast;
[Tooltip("Brightness multiplier on the picture base map (_Brightness) to compensate the darkening from occlusion. 1 = unchanged.")]
[SerializeField, Range(0f, 3f)] float _brightness = 1f;
[Tooltip("Scales the material's tessellation displacement (_TessellationStrength). 1 = as authored.")]
[SerializeField, Range(0f, 10f)] float _tessellationStrength = 1f;
[Tooltip("Triangle grid resolution of the picture plane (cells along its longer side); raise it so displacement/tessellation has vertices to work with. 1 = plain quad.")]
[SerializeField, Range(0f, 3f)] float _brightness = 1;
[Tooltip("Scales the material's tessellation displacement (_TessellationStrength). 0 = flat (GPU tessellation is skipped entirely), negative inverts the relief.")]
[SerializeField, Range(-10f, 10f)] float _tessellationStrength = 0;
[Tooltip("Triangle grid resolution of the picture plane mesh (cells along its longer side). 1 = plain quad; extra triangles visible in wireframe beyond this come from the shader's GPU Tessellation Factor, not this mesh.")]
[SerializeField, Range(1, 10)] int _pictureSubdivisions = 1;
[Header("Picture Crop")]
@@ -162,13 +162,13 @@ namespace Chameleon.Components
ResolveAspect();
#endif
// Inside-out sizing: the picture keeps the texture aspect exactly; thickness and padding
// are fractions of the picture's longer side, and Size stays the longer OUTER side:
// size = pLong + 2*(t + pad) = pLong * (1 + thickness + padding). No clamp dead zones -
// every 0..1 combination yields valid geometry.
// are absolute meters (they do NOT scale with Size), and Size stays the longer OUTER
// side: pLong = size - 2*(t + pad), clamped so the picture never degenerates (the outer
// side then exceeds Size instead).
float d = Mathf.Max(_frameDepth, 0.002f);
float pLong = Mathf.Max(_size, 0.01f) / (1f + _frameThickness + _padding);
float t = Mathf.Max(_frameThickness * pLong * 0.5f, 0.001f);
float pad = _padding * pLong * 0.5f;
float t = Mathf.Max(_frameThickness, 0.001f);
float pad = Mathf.Max(_padding, 0f);
float pLong = Mathf.Max(Mathf.Max(_size, 0.01f) - 2f * (t + pad), 0.01f);
float pw = Mathf.Max(pLong * _aspect.x, 0.001f);
float ph = Mathf.Max(pLong * _aspect.y, 0.001f);
float ow = pw + 2f * pad;
+286 -20
View File
@@ -19,10 +19,10 @@ Shader "Chameleon/PictureTessellationLit"
_Smoothness("Smoothness", Range(0.0, 1.0)) = 0.5
_Metallic("Metallic", Range(0.0, 1.0)) = 0.0
[Normal] _BumpMap("Normal Map", 2D) = "bump" {}
_BumpScale("Normal Strength", Range(0.0, 3.0)) = 1.0
_BumpScale("Normal Strength", Range(-3.0, 3.0)) = 1.0
_HeightMap("Height Map", 2D) = "gray" {}
_Displacement("Displacement (m)", Range(0.0, 0.2)) = 0.02
_TessellationStrength("Tessellation Strength", Range(0.0, 3.0)) = 1.0
_TessellationStrength("Tessellation Strength", Range(-10.0, 10.0)) = 1.0
_TessellationFactor("Tessellation Factor", Range(1.0, 32.0)) = 8.0
_OcclusionMap("Occlusion Map", 2D) = "white" {}
_OcclusionStrength("Occlusion Strength", Range(0.0, 3.0)) = 1.0
@@ -66,6 +66,8 @@ Shader "Chameleon/PictureTessellationLit"
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
float2 staticLightmapUV : TEXCOORD1;
float2 dynamicLightmapUV : TEXCOORD2;
};
// Hull/domain control point: everything stays in object space until the domain stage.
@@ -75,6 +77,8 @@ Shader "Chameleon/PictureTessellationLit"
float3 normalOS : NORMAL;
float4 tangentOS : TANGENT;
float2 uv : TEXCOORD0;
float2 staticLightmapUV : TEXCOORD1;
float2 dynamicLightmapUV : TEXCOORD2;
};
struct TessFactors
@@ -90,16 +94,20 @@ Shader "Chameleon/PictureTessellationLit"
p.normalOS = input.normalOS;
p.tangentOS = input.tangentOS;
p.uv = input.uv;
p.staticLightmapUV = input.staticLightmapUV;
p.dynamicLightmapUV = input.dynamicLightmapUV;
return p;
}
TessFactors PatchConstants(InputPatch<ControlPoint, 3> patch)
{
TessFactors f;
f.edge[0] = _TessellationFactor;
f.edge[1] = _TessellationFactor;
f.edge[2] = _TessellationFactor;
f.inside = _TessellationFactor;
// No displacement -> no reason to subdivide (keeps the wireframe a plain grid).
half factor = (_TessellationStrength == 0.0h || _Displacement == 0.0h) ? 1.0h : _TessellationFactor;
f.edge[0] = factor;
f.edge[1] = factor;
f.edge[2] = factor;
f.inside = factor;
return f;
}
@@ -124,6 +132,8 @@ Shader "Chameleon/PictureTessellationLit"
v.normalOS = normalize(TESS_INTERPOLATE(normalOS));
v.tangentOS = TESS_INTERPOLATE(tangentOS);
v.uv = TESS_INTERPOLATE(uv);
v.staticLightmapUV = TESS_INTERPOLATE(staticLightmapUV);
v.dynamicLightmapUV = TESS_INTERPOLATE(dynamicLightmapUV);
float height = SAMPLE_TEXTURE2D_LOD(_HeightMap, sampler_HeightMap, TRANSFORM_TEX(v.uv, _BaseMap), 0).r;
v.positionOS.xyz += v.normalOS * ((height - 0.5) * _Displacement * _TessellationStrength);
return v;
@@ -145,22 +155,39 @@ Shader "Chameleon/PictureTessellationLit"
#pragma domain ForwardDomain
#pragma fragment ForwardFragment
// Keyword set mirrored from PlayerCamoLit's working Deferred+ forward-only pass,
// minus lightmap/detail/emission features this shader does not have.
// Keyword set mirrored from URP Lit.shader's ForwardLit pass (Universal Pipeline +
// Unity defined keywords) so every lighting feature the standard Lit supports works
// here too: lightmaps, shadowmask/mixed lighting, APV probe volumes, decals (DBuffer),
// vertex additional lights, rendering-layer output, reflection probe rotation.
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
#pragma multi_compile _ EVALUATE_SH_MIXED EVALUATE_SH_VERTEX
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION
#pragma multi_compile_fragment _ _REFLECTION_PROBE_ATLAS
#pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
#pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
#pragma multi_compile_fragment _ _SCREEN_SPACE_IRRADIANCE
#pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3
#pragma multi_compile_fragment _ _LIGHT_COOKIES
#pragma multi_compile _ _LIGHT_LAYERS
#pragma multi_compile _ _CLUSTER_LIGHT_LOOP
#pragma multi_compile_fog
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl"
#pragma multi_compile _ LIGHTMAP_SHADOW_MIXING
#pragma multi_compile _ SHADOWS_SHADOWMASK
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile_fragment _ LIGHTMAP_BICUBIC_SAMPLING
#pragma multi_compile_fragment _ REFLECTION_PROBE_ROTATION
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile _ USE_LEGACY_LIGHTMAPS
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Fog.hlsl"
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ProbeVolumeVariants.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DBuffer.hlsl"
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);
@@ -173,7 +200,18 @@ Shader "Chameleon/PictureTessellationLit"
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 tangentWS : TEXCOORD3; // w = bitangent sign
float fogCoord : TEXCOORD4;
#ifdef _ADDITIONAL_LIGHTS_VERTEX
half4 fogFactorAndVertexLight : TEXCOORD4; // x: fog, yzw: vertex light
#else
half fogFactor : TEXCOORD4;
#endif
DECLARE_LIGHTMAP_OR_SH(staticLightmapUV, vertexSH, 5);
#ifdef DYNAMICLIGHTMAP_ON
float2 dynamicLightmapUV : TEXCOORD6;
#endif
#ifdef USE_APV_PROBE_OCCLUSION
float4 probeOcclusion : TEXCOORD7;
#endif
};
[domain("tri")]
@@ -183,17 +221,38 @@ Shader "Chameleon/PictureTessellationLit"
VertexPositionInputs pos = GetVertexPositionInputs(v.positionOS.xyz);
VertexNormalInputs nrm = GetVertexNormalInputs(v.normalOS, v.tangentOS);
Varyings o;
Varyings o = (Varyings)0;
o.positionCS = pos.positionCS;
o.positionWS = pos.positionWS;
o.normalWS = nrm.normalWS;
o.tangentWS = float4(nrm.tangentWS, v.tangentOS.w * GetOddNegativeScale());
o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
o.fogCoord = ComputeFogFactor(pos.positionCS.z);
half fogFactor = 0;
#if !defined(_FOG_FRAGMENT)
fogFactor = ComputeFogFactor(pos.positionCS.z);
#endif
#ifdef _ADDITIONAL_LIGHTS_VERTEX
o.fogFactorAndVertexLight = half4(fogFactor, VertexLighting(pos.positionWS, nrm.normalWS));
#else
o.fogFactor = fogFactor;
#endif
OUTPUT_LIGHTMAP_UV(v.staticLightmapUV, unity_LightmapST, o.staticLightmapUV);
#ifdef DYNAMICLIGHTMAP_ON
o.dynamicLightmapUV = v.dynamicLightmapUV * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
OUTPUT_SH4(pos.positionWS, o.normalWS, GetWorldSpaceNormalizeViewDir(pos.positionWS), o.vertexSH, o.probeOcclusion);
return o;
}
half4 ForwardFragment(Varyings input) : SV_Target
void ForwardFragment(
Varyings input
, out half4 outColor : SV_Target0
#ifdef _WRITE_RENDERING_LAYERS
, out uint outRenderingLayers : SV_Target1
#endif
)
{
half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
// Height-weighted brightness: raised areas get the full multiplier, deep areas stay
@@ -220,9 +279,32 @@ Shader "Chameleon/PictureTessellationLit"
#else
inputData.shadowCoord = TransformWorldToShadowCoord(input.positionWS);
#endif
inputData.fogCoord = input.fogCoord;
inputData.bakedGI = SampleSH(inputData.normalWS);
inputData.shadowMask = half4(1, 1, 1, 1);
#ifdef _ADDITIONAL_LIGHTS_VERTEX
inputData.fogCoord = InitializeInputDataFog(float4(input.positionWS, 1.0), input.fogFactorAndVertexLight.x);
inputData.vertexLighting = input.fogFactorAndVertexLight.yzw;
#else
inputData.fogCoord = InitializeInputDataFog(float4(input.positionWS, 1.0), input.fogFactor);
#endif
// Baked GI exactly like LitForwardPass.InitializeBakedGIData: lightmaps (static/
// dynamic), APV probe volumes, screen-space irradiance, shadowmask - else SH.
#if defined(_SCREEN_SPACE_IRRADIANCE)
inputData.bakedGI = SAMPLE_GI(_ScreenSpaceIrradiance, input.positionCS.xy);
#elif defined(DYNAMICLIGHTMAP_ON)
inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.dynamicLightmapUV, input.vertexSH, inputData.normalWS);
inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
#elif !defined(LIGHTMAP_ON) && (defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2))
inputData.bakedGI = SAMPLE_GI(input.vertexSH,
GetAbsolutePositionWS(inputData.positionWS),
inputData.normalWS,
inputData.viewDirectionWS,
input.positionCS.xy,
input.probeOcclusion,
inputData.shadowMask);
#else
inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.vertexSH, inputData.normalWS);
inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
#endif
SurfaceData surfaceData = (SurfaceData)0;
surfaceData.albedo = albedo.rgb;
@@ -233,9 +315,16 @@ Shader "Chameleon/PictureTessellationLit"
// Strength > 1 keeps darkening past the authored map (saturate stops negatives).
surfaceData.occlusion = saturate(LerpWhiteTo(occ, _OcclusionStrength));
#if defined(_DBUFFER)
ApplyDecalToSurfaceData(input.positionCS, surfaceData, inputData);
#endif
half4 color = UniversalFragmentPBR(inputData, surfaceData);
color.rgb = MixFog(color.rgb, input.fogCoord);
return color;
color.rgb = MixFog(color.rgb, inputData.fogCoord);
outColor = color;
#ifdef _WRITE_RENDERING_LAYERS
outRenderingLayers = EncodeMeshRenderingLayer();
#endif
}
ENDHLSL
}
@@ -353,6 +442,9 @@ Shader "Chameleon/PictureTessellationLit"
#pragma domain DepthNormalsDomain
#pragma fragment DepthNormalsFragment
// Same as URP Lit's DepthNormals pass: decals need the rendering-layers target.
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl"
struct DepthNormalsVaryings
{
float4 positionCS : SV_POSITION;
@@ -369,9 +461,183 @@ Shader "Chameleon/PictureTessellationLit"
return o;
}
half4 DepthNormalsFragment(DepthNormalsVaryings input) : SV_Target
void DepthNormalsFragment(
DepthNormalsVaryings input
, out half4 outNormalWS : SV_Target0
#ifdef _WRITE_RENDERING_LAYERS
, out uint outRenderingLayers : SV_Target1
#endif
)
{
return half4(NormalizeNormalPerPixel(input.normalWS), 0);
outNormalWS = half4(NormalizeNormalPerPixel(input.normalWS), 0);
#ifdef _WRITE_RENDERING_LAYERS
outRenderingLayers = EncodeMeshRenderingLayer();
#endif
}
ENDHLSL
}
Pass
{
// The project renders forward(+); the ONLY consumer of this pass is HTrace WSGI, which
// renders its thin GBuffer through the "UniversalGBuffer" tag and composites its traced
// irradiance from GBuffer0.rgb (albedo) / GBuffer1.a (occlusion) AFTER post-processing.
// Without it these pixels keep stale GBuffer data and get wrong injected GI (the cyan
// sky tint) - same failure class as the HeroCutout/PlayerCamo GBuffer fixes. Albedo is
// graded exactly like the forward pass (brightness/occlusion/contrast) so the injected
// indirect light matches the visible picture.
Name "GBuffer"
Tags { "LightMode" = "UniversalGBuffer" }
ZWrite On
ZTest LEqual
Cull Back
HLSLPROGRAM
#pragma target 4.6
#pragma exclude_renderers gles3 glcore
#pragma vertex TessVertex
#pragma hull TessHull
#pragma domain GBufferDomain
#pragma fragment GBufferFragment
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE _MAIN_LIGHT_SHADOWS_SCREEN
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING
#pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION
#pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
#pragma multi_compile_fragment _ _DBUFFER_MRT1 _DBUFFER_MRT2 _DBUFFER_MRT3
#pragma multi_compile_fragment _ _RENDER_PASS_ENABLED
#pragma multi_compile _ _CLUSTER_LIGHT_LOOP
#pragma multi_compile _ EVALUATE_SH_MIXED EVALUATE_SH_VERTEX
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl"
#pragma multi_compile _ LIGHTMAP_SHADOW_MIXING
#pragma multi_compile _ SHADOWS_SHADOWMASK
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile_fragment _ LIGHTMAP_BICUBIC_SAMPLING
#pragma multi_compile_fragment _ REFLECTION_PROBE_ROTATION
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile _ USE_LEGACY_LIGHTMAPS
#pragma multi_compile_fragment _ _GBUFFER_NORMALS_OCT
#pragma multi_compile_fragment _ _SCREEN_SPACE_IRRADIANCE
#include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ProbeVolumeVariants.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/GBufferOutput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DBuffer.hlsl"
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);
TEXTURE2D(_OcclusionMap); SAMPLER(sampler_OcclusionMap);
struct GBufferVaryings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 normalWS : TEXCOORD2;
float4 tangentWS : TEXCOORD3; // w = bitangent sign
DECLARE_LIGHTMAP_OR_SH(staticLightmapUV, vertexSH, 4);
#ifdef DYNAMICLIGHTMAP_ON
float2 dynamicLightmapUV : TEXCOORD5;
#endif
#ifdef USE_APV_PROBE_OCCLUSION
float4 probeOcclusion : TEXCOORD6;
#endif
};
[domain("tri")]
GBufferVaryings GBufferDomain(TessFactors factors, OutputPatch<ControlPoint, 3> patch, float3 bary : SV_DomainLocation)
{
Attributes v = TessInterpolate(patch, bary);
VertexPositionInputs pos = GetVertexPositionInputs(v.positionOS.xyz);
VertexNormalInputs nrm = GetVertexNormalInputs(v.normalOS, v.tangentOS);
GBufferVaryings o = (GBufferVaryings)0;
o.positionCS = pos.positionCS;
o.positionWS = pos.positionWS;
o.normalWS = nrm.normalWS;
o.tangentWS = float4(nrm.tangentWS, v.tangentOS.w * GetOddNegativeScale());
o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
OUTPUT_LIGHTMAP_UV(v.staticLightmapUV, unity_LightmapST, o.staticLightmapUV);
#ifdef DYNAMICLIGHTMAP_ON
o.dynamicLightmapUV = v.dynamicLightmapUV * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
OUTPUT_SH4(pos.positionWS, o.normalWS, GetWorldSpaceNormalizeViewDir(pos.positionWS), o.vertexSH, o.probeOcclusion);
return o;
}
GBufferFragOutput GBufferFragment(GBufferVaryings input)
{
// Surface build mirrors ForwardFragment 1:1 (graded albedo, contrast'd occlusion).
half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
half heightBr = SAMPLE_TEXTURE2D(_HeightMap, sampler_HeightMap, input.uv).r;
albedo.rgb *= lerp(1.0h, _Brightness, heightBr);
half3 normalTS = UnpackNormalScale(SAMPLE_TEXTURE2D(_BumpMap, sampler_BumpMap, input.uv), _BumpScale);
half occ = SAMPLE_TEXTURE2D(_OcclusionMap, sampler_OcclusionMap, input.uv).g;
occ = saturate((occ - 0.5h) * (1.0h + _OcclusionContrast) + 0.5h);
half occlusion = saturate(LerpWhiteTo(occ, _OcclusionStrength));
float sgn = input.tangentWS.w;
float3 bitangentWS = sgn * cross(input.normalWS, input.tangentWS.xyz);
half3x3 tbn = half3x3(input.tangentWS.xyz, bitangentWS, input.normalWS);
InputData inputData = (InputData)0;
inputData.positionWS = input.positionWS;
inputData.positionCS = input.positionCS;
inputData.normalWS = NormalizeNormalPerPixel(TransformTangentToWorld(normalTS, tbn));
inputData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(input.positionWS);
inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
#if defined(_MAIN_LIGHT_SHADOWS_SCREEN)
inputData.shadowCoord = float4(inputData.normalizedScreenSpaceUV, 0, 1);
#else
inputData.shadowCoord = TransformWorldToShadowCoord(input.positionWS);
#endif
#if defined(_SCREEN_SPACE_IRRADIANCE)
inputData.bakedGI = SAMPLE_GI(_ScreenSpaceIrradiance, input.positionCS.xy);
#elif defined(DYNAMICLIGHTMAP_ON)
inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.dynamicLightmapUV, input.vertexSH, inputData.normalWS);
inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
#elif !defined(LIGHTMAP_ON) && (defined(PROBE_VOLUMES_L1) || defined(PROBE_VOLUMES_L2))
inputData.bakedGI = SAMPLE_GI(input.vertexSH,
GetAbsolutePositionWS(inputData.positionWS),
inputData.normalWS,
inputData.viewDirectionWS,
input.positionCS.xy,
input.probeOcclusion,
inputData.shadowMask);
#else
inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.vertexSH, inputData.normalWS);
inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV);
#endif
SurfaceData surfaceData = (SurfaceData)0;
surfaceData.albedo = saturate(albedo.rgb); // GBuffer0 is UNORM; HTrace multiplies GI by it
surfaceData.alpha = 1;
surfaceData.metallic = _Metallic;
surfaceData.smoothness = _Smoothness;
surfaceData.normalTS = normalTS;
surfaceData.occlusion = occlusion;
#if defined(_DBUFFER)
ApplyDecalToSurfaceData(input.positionCS, surfaceData, inputData);
#endif
BRDFData brdfData;
InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData);
Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, inputData.shadowMask);
MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI, inputData.shadowMask);
half3 color = GlobalIllumination(brdfData, (BRDFData)0, 0,
inputData.bakedGI, surfaceData.occlusion, inputData.positionWS,
inputData.normalWS, inputData.viewDirectionWS, inputData.normalizedScreenSpaceUV);
return PackGBuffersBRDFData(brdfData, inputData, surfaceData.smoothness, color, surfaceData.occlusion);
}
ENDHLSL
}