Add PictureTessellationLit shader (tessellation + height displacement)
Hull/domain tessellation with height-map displacement, occlusion map with strength/contrast controls, height-weighted brightness multiplier, standard PBR (base/normal/metallic/smoothness). ComplexLit pattern (UniversalForwardOnly) so custom shading survives Deferred+; ShadowCaster/DepthOnly/DepthNormalsOnly all run through the same displacement domain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a5f5fd832
commit
d0d7028ebb
2 files changed
+390
No files matched your search
@@ -0,0 +1,381 @@
|
||||
// Forward-only URP PBR shader with hardware tessellation + height-map displacement and an occlusion
|
||||
// map, built for the PictureFrameBuilder picture surface (works on any mesh with sane UVs).
|
||||
//
|
||||
// Deferred+ compatible via the ComplexLit pattern (same as PlayerCamoLit): "UniversalMaterialType" =
|
||||
// "ComplexLit" stencils these pixels out of the deferred lighting and URP shades them in the
|
||||
// UniversalForwardOnly pass instead - the only way a custom vertex pipeline (tessellation) can run
|
||||
// on the deferred path. ALL depth-relevant passes (ShadowCaster / DepthOnly / DepthNormalsOnly) run
|
||||
// the same hull/domain displacement so shadows, SSAO and depth match the displaced surface.
|
||||
//
|
||||
// Displacement = (height - 0.5) * _Displacement * _TessellationStrength along the vertex normal;
|
||||
// _Displacement is the authored amplitude in meters, _TessellationStrength is the runtime knob the
|
||||
// PictureFrameBuilder drives per frame (like _BumpScale / _OcclusionStrength).
|
||||
Shader "Chameleon/PictureTessellationLit"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_BaseMap("Albedo", 2D) = "white" {}
|
||||
_BaseColor("Color", Color) = (1, 1, 1, 1)
|
||||
_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
|
||||
_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
|
||||
_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
|
||||
_OcclusionContrast("Occlusion Contrast", Range(-1.0, 3.0)) = 0.0
|
||||
_Brightness("Brightness", Range(0.0, 3.0)) = 1.0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"RenderType" = "Opaque"
|
||||
"RenderPipeline" = "UniversalPipeline"
|
||||
"UniversalMaterialType" = "ComplexLit"
|
||||
"Queue" = "Geometry"
|
||||
}
|
||||
LOD 300
|
||||
|
||||
HLSLINCLUDE
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
||||
|
||||
TEXTURE2D(_HeightMap); SAMPLER(sampler_HeightMap);
|
||||
|
||||
CBUFFER_START(UnityPerMaterial)
|
||||
float4 _BaseMap_ST;
|
||||
half4 _BaseColor;
|
||||
half _Smoothness;
|
||||
half _Metallic;
|
||||
half _BumpScale;
|
||||
half _Displacement;
|
||||
half _TessellationStrength;
|
||||
half _TessellationFactor;
|
||||
half _OcclusionStrength;
|
||||
half _OcclusionContrast;
|
||||
half _Brightness;
|
||||
CBUFFER_END
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
float4 positionOS : POSITION;
|
||||
float3 normalOS : NORMAL;
|
||||
float4 tangentOS : TANGENT;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
// Hull/domain control point: everything stays in object space until the domain stage.
|
||||
struct ControlPoint
|
||||
{
|
||||
float4 positionOS : INTERNALTESSPOS;
|
||||
float3 normalOS : NORMAL;
|
||||
float4 tangentOS : TANGENT;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct TessFactors
|
||||
{
|
||||
float edge[3] : SV_TessFactor;
|
||||
float inside : SV_InsideTessFactor;
|
||||
};
|
||||
|
||||
ControlPoint TessVertex(Attributes input)
|
||||
{
|
||||
ControlPoint p;
|
||||
p.positionOS = input.positionOS;
|
||||
p.normalOS = input.normalOS;
|
||||
p.tangentOS = input.tangentOS;
|
||||
p.uv = input.uv;
|
||||
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;
|
||||
return f;
|
||||
}
|
||||
|
||||
[domain("tri")]
|
||||
[outputcontrolpoints(3)]
|
||||
[outputtopology("triangle_cw")]
|
||||
[partitioning("fractional_odd")]
|
||||
[patchconstantfunc("PatchConstants")]
|
||||
ControlPoint TessHull(InputPatch<ControlPoint, 3> patch, uint id : SV_OutputControlPointID)
|
||||
{
|
||||
return patch[id];
|
||||
}
|
||||
|
||||
#define TESS_INTERPOLATE(field) (patch[0].field * bary.x + patch[1].field * bary.y + patch[2].field * bary.z)
|
||||
|
||||
// Barycentric interpolation + height displacement along the vertex normal; each pass turns
|
||||
// the resulting object-space vertex into its own varyings in its domain function.
|
||||
Attributes TessInterpolate(OutputPatch<ControlPoint, 3> patch, float3 bary)
|
||||
{
|
||||
Attributes v;
|
||||
v.positionOS = TESS_INTERPOLATE(positionOS);
|
||||
v.normalOS = normalize(TESS_INTERPOLATE(normalOS));
|
||||
v.tangentOS = TESS_INTERPOLATE(tangentOS);
|
||||
v.uv = TESS_INTERPOLATE(uv);
|
||||
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;
|
||||
}
|
||||
ENDHLSL
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
Tags { "LightMode" = "UniversalForwardOnly" }
|
||||
|
||||
ZWrite On
|
||||
Cull Back
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma target 4.6
|
||||
#pragma vertex TessVertex
|
||||
#pragma hull TessHull
|
||||
#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.
|
||||
#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_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 _ _LIGHT_COOKIES
|
||||
#pragma multi_compile _ _LIGHT_LAYERS
|
||||
#pragma multi_compile _ _CLUSTER_LIGHT_LOOP
|
||||
#pragma multi_compile_fog
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
||||
|
||||
TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
|
||||
TEXTURE2D(_BumpMap); SAMPLER(sampler_BumpMap);
|
||||
TEXTURE2D(_OcclusionMap); SAMPLER(sampler_OcclusionMap);
|
||||
|
||||
struct Varyings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
float3 positionWS : TEXCOORD1;
|
||||
float3 normalWS : TEXCOORD2;
|
||||
float4 tangentWS : TEXCOORD3; // w = bitangent sign
|
||||
float fogCoord : TEXCOORD4;
|
||||
};
|
||||
|
||||
[domain("tri")]
|
||||
Varyings ForwardDomain(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);
|
||||
|
||||
Varyings o;
|
||||
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);
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 ForwardFragment(Varyings input) : SV_Target
|
||||
{
|
||||
half4 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor;
|
||||
// Height-weighted brightness: raised areas get the full multiplier, deep areas stay
|
||||
// untouched (height 0.5 = mid-gray default when no map is bound -> half effect).
|
||||
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;
|
||||
// Contrast around mid-gray: 0 = as sampled, -1 = flat (no occlusion detail), 3 = 4x.
|
||||
occ = saturate((occ - 0.5h) * (1.0h + _OcclusionContrast) + 0.5h);
|
||||
|
||||
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
|
||||
inputData.fogCoord = input.fogCoord;
|
||||
inputData.bakedGI = SampleSH(inputData.normalWS);
|
||||
inputData.shadowMask = half4(1, 1, 1, 1);
|
||||
|
||||
SurfaceData surfaceData = (SurfaceData)0;
|
||||
surfaceData.albedo = albedo.rgb;
|
||||
surfaceData.alpha = 1;
|
||||
surfaceData.metallic = _Metallic;
|
||||
surfaceData.smoothness = _Smoothness;
|
||||
surfaceData.normalTS = normalTS;
|
||||
// Strength > 1 keeps darkening past the authored map (saturate stops negatives).
|
||||
surfaceData.occlusion = saturate(LerpWhiteTo(occ, _OcclusionStrength));
|
||||
|
||||
half4 color = UniversalFragmentPBR(inputData, surfaceData);
|
||||
color.rgb = MixFog(color.rgb, input.fogCoord);
|
||||
return color;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "ShadowCaster"
|
||||
Tags { "LightMode" = "ShadowCaster" }
|
||||
|
||||
ZWrite On
|
||||
ZTest LEqual
|
||||
ColorMask 0
|
||||
Cull Back
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma target 4.6
|
||||
#pragma vertex TessVertex
|
||||
#pragma hull TessHull
|
||||
#pragma domain ShadowDomain
|
||||
#pragma fragment ShadowFragment
|
||||
|
||||
// NOT multi_compile_vertex: the keyword is consumed in the DOMAIN stage here.
|
||||
#pragma multi_compile _ _CASTING_PUNCTUAL_LIGHT_SHADOW
|
||||
|
||||
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
|
||||
|
||||
float3 _LightDirection;
|
||||
float3 _LightPosition;
|
||||
|
||||
struct ShadowVaryings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
};
|
||||
|
||||
[domain("tri")]
|
||||
ShadowVaryings ShadowDomain(TessFactors factors, OutputPatch<ControlPoint, 3> patch, float3 bary : SV_DomainLocation)
|
||||
{
|
||||
Attributes v = TessInterpolate(patch, bary);
|
||||
float3 positionWS = TransformObjectToWorld(v.positionOS.xyz);
|
||||
float3 normalWS = TransformObjectToWorldNormal(v.normalOS);
|
||||
#if defined(_CASTING_PUNCTUAL_LIGHT_SHADOW)
|
||||
float3 lightDirectionWS = normalize(_LightPosition - positionWS);
|
||||
#else
|
||||
float3 lightDirectionWS = _LightDirection;
|
||||
#endif
|
||||
float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, lightDirectionWS));
|
||||
#if UNITY_REVERSED_Z
|
||||
positionCS.z = min(positionCS.z, UNITY_NEAR_CLIP_VALUE);
|
||||
#else
|
||||
positionCS.z = max(positionCS.z, UNITY_NEAR_CLIP_VALUE);
|
||||
#endif
|
||||
ShadowVaryings o;
|
||||
o.positionCS = positionCS;
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 ShadowFragment() : SV_Target
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
Name "DepthOnly"
|
||||
Tags { "LightMode" = "DepthOnly" }
|
||||
|
||||
ZWrite On
|
||||
ColorMask R
|
||||
Cull Back
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma target 4.6
|
||||
#pragma vertex TessVertex
|
||||
#pragma hull TessHull
|
||||
#pragma domain DepthDomain
|
||||
#pragma fragment DepthFragment
|
||||
|
||||
struct DepthVaryings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
};
|
||||
|
||||
[domain("tri")]
|
||||
DepthVaryings DepthDomain(TessFactors factors, OutputPatch<ControlPoint, 3> patch, float3 bary : SV_DomainLocation)
|
||||
{
|
||||
Attributes v = TessInterpolate(patch, bary);
|
||||
DepthVaryings o;
|
||||
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
|
||||
return o;
|
||||
}
|
||||
|
||||
half DepthFragment(DepthVaryings input) : SV_Target
|
||||
{
|
||||
return input.positionCS.z;
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
|
||||
Pass
|
||||
{
|
||||
// Deferred+'s normals prepass draws ONLY this tag (the forward path draws both), see the
|
||||
// PlayerCamoLit fix - geometric (displaced) normal is enough for SSAO/HTrace.
|
||||
Name "DepthNormalsOnly"
|
||||
Tags { "LightMode" = "DepthNormalsOnly" }
|
||||
|
||||
ZWrite On
|
||||
Cull Back
|
||||
|
||||
HLSLPROGRAM
|
||||
#pragma target 4.6
|
||||
#pragma vertex TessVertex
|
||||
#pragma hull TessHull
|
||||
#pragma domain DepthNormalsDomain
|
||||
#pragma fragment DepthNormalsFragment
|
||||
|
||||
struct DepthNormalsVaryings
|
||||
{
|
||||
float4 positionCS : SV_POSITION;
|
||||
float3 normalWS : TEXCOORD0;
|
||||
};
|
||||
|
||||
[domain("tri")]
|
||||
DepthNormalsVaryings DepthNormalsDomain(TessFactors factors, OutputPatch<ControlPoint, 3> patch, float3 bary : SV_DomainLocation)
|
||||
{
|
||||
Attributes v = TessInterpolate(patch, bary);
|
||||
DepthNormalsVaryings o;
|
||||
o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
|
||||
o.normalWS = TransformObjectToWorldNormal(v.normalOS);
|
||||
return o;
|
||||
}
|
||||
|
||||
half4 DepthNormalsFragment(DepthNormalsVaryings input) : SV_Target
|
||||
{
|
||||
return half4(NormalizeNormalPerPixel(input.normalWS), 0);
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
|
||||
FallBack "Universal Render Pipeline/Lit"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19307ba9bc21d4949851f5ce569a86fe
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user