Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions Assets.meta

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

13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
#1.1.12
- DCL_Toon: declared `_MetallicGlossMapArr` and `_MatCap_SamplerArr` in the Properties block (they existed only in HLSL), so the texture-array path can actually bind the stylized-metallic mask + matcap arrays via `material.SetTexture` (texture-array consumers like unity-explorer)

#1.1.11
- DCL_Toon: promoted `_MatCapColor` and `_BlurLevelMatcap` from compile-time constants to runtime per-material properties, so matcap tint and blur can be driven per material (e.g. from `MatcapPresets`)

#1.1.10
- DCL_Toon: added `MatcapPresets` ScriptableObject + bundled matcap library as the shared source of truth for stylized-metallic matcaps (consumed by aang-renderer, unity-explorer and future repos)

#1.1.9
- DCL_Toon: added normal map support (base/high-color/rim shading + DepthNormals for SSAO)
- DCL_Toon: added stylized metallic via matcap, driven by per-renderer `_IsStylizedMetallic` and an optional `_MetallicGlossMap` mask

#1.1.8
- Reverted gltfast version to 5.0.0

Expand Down
7 changes: 7 additions & 0 deletions Runtime/Shaders/Avatar/DCL_Toon/DCL_Toon.shader
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Shader "DCL/DCL_Toon"
[HideInInspector] [PerRendererData] _MatCap_SamplerArr_ID ("MatCap Array ID", Integer) = -1
[HideInInspector] [PerRendererData] _Emissive_TexArr_ID ("Emissive Array ID", Integer) = -1
[HideInInspector] [PerRendererData] _MetallicGlossMapArr_ID ("MetallicGlossMap Array ID", Integer) = -1
[HideInInspector] [PerRendererData] _IsStylizedMetallic ("Is Stylized Metallic (matcap)", Integer) = 0
[HideInInspector] [PerRendererData] _IsIridescent ("Is Iridescent (matcap)", Integer) = 0

[HideInInspector] [PerRendererData] _lastWearableVertCount ("Last wearable Vert Count", Integer) = -1
[HideInInspector] [PerRendererData] _lastAvatarVertCount ("Last avatar vert count", Integer) = -1
Expand All @@ -24,6 +26,8 @@ Shader "DCL/DCL_Toon"
[HideInInspector] _MainTexArr ("Main Texture Array", 2DArray) = "white" {}
[HideInInspector] _NormalMapArr ("Normal Texture Array", 2DArray) = "bump" {}
[HideInInspector] _Emissive_TexArr ("Emissive Texture Array", 2DArray) = "black" {}
[HideInInspector] _MetallicGlossMapArr ("MetallicGloss Texture Array", 2DArray) = "black" {}
[HideInInspector] _MatCap_SamplerArr ("MatCap Texture Array", 2DArray) = "black" {}

[HideInInspector] _simpleUI ("SimpleUI", Int ) = 0

Expand Down Expand Up @@ -133,6 +137,9 @@ Shader "DCL/DCL_Toon"
//
[Toggle(_)] _MatCap ("MatCap", Float ) = 0
_MatCap_Sampler ("MatCap_Sampler", 2D) = "black" {}
// Stylized-metallic mask (non-array path). glTF metallic-roughness map or a baked metallicFactor;
// the frag reads the metallic (.b) channel. Default black => no metal when no map is bound.
_MetallicGlossMap ("MetallicGlossMap (stylized metal mask)", 2D) = "black" {}
//v.2.0.6
_BlurLevelMatcap ("Blur Level of MatCap_Sampler", Range(0, 10)) = 0
_MatCapColor ("MatCapColor", Color) = (1,1,1,1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ float4 fragDoubleShadeFeather(VertexOutput i, half facing : VFACE) : SV_TARGET
float3x3 tangentTransform = float3x3( i.tangentDir, i.bitangentDir, i.normalDir);

int nNormalMapArrID = _NormalMapArr_ID;
float3 _NormalMap_var = UnpackNormalScale(SAMPLE_NORMALMAP(TRANSFORM_TEX(Set_UV0, _NormalMap), nNormalMapArrID), _BumpScale);

float3 normalLocal = _NormalMap_var.rgb;
float3 normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
float3 normalDirection;
// Uniform branch (per-material ID): materials without a normal map fall back to the
// geometric normal instead of sampling an invalid (-1) array slice.
if (nNormalMapArrID < 0)
{
normalDirection = i.normalDir;
}
else
{
float3 _NormalMap_var = UnpackNormalScale(SAMPLE_NORMALMAP(TRANSFORM_TEX(Set_UV0, _NormalMap), nNormalMapArrID), _BumpScale);
float3 normalLocal = _NormalMap_var.rgb;
normalDirection = normalize(mul( normalLocal, tangentTransform )); // Perturbed normals
}


// todo. not necessary to calc gi factor in shadowcaster pass.
Expand Down Expand Up @@ -182,8 +191,11 @@ float4 fragDoubleShadeFeather(VertexOutput i, half facing : VFACE) : SV_TARGET
half _Camera_Dir = _Camera_Right.y < 0 ? -1 : 1;
float _Rot_MatCapUV_var_ang = (fRotate_MatCapUV*3.141592654) - _Camera_Dir*_Camera_Roll*_CameraRolling_Stabilizer;
float2 _Rot_MatCapNmUV_var = RotateUV(Set_UV0, (_Rotate_NormalMapForMatCapUV*3.141592654), float2(0.5, 0.5), 1.0);
// MatCap with camera skew correction
float3 viewNormal = (mul(UNITY_MATRIX_V, float4(i.normalDir,0))).rgb;
// MatCap with camera skew correction.
// Use the normal-mapped world normal (normalDirection) so the matcap sheen catches normal-map
// detail. Falls back to the geometric normal automatically when no normal map is set, because
// normalDirection == i.normalDir in that case (see the _NormalMapArr_ID guard at the top).
float3 viewNormal = (mul(UNITY_MATRIX_V, float4(normalDirection,0))).rgb;
//float3 viewNormal = (mul(UNITY_MATRIX_V, float4(lerp( i.normalDir, mul( _NormalMapForMatCap_var.rgb, tangentTransform ).rgb, _Is_NormalMapForMatCap ),0))).rgb;
float3 NormalBlend_MatcapUV_Detail = viewNormal.rgb * float3(-1,-1,1);
float3 NormalBlend_MatcapUV_Base = (mul( UNITY_MATRIX_V, float4(viewDirection,0) ).rgb*float3(-1,-1,1)) + float3(0,0,1);
Expand All @@ -201,23 +213,73 @@ float4 fragDoubleShadeFeather(VertexOutput i, half facing : VFACE) : SV_TARGET
_Rot_MatCapUV_var = _Rot_MatCapUV_var;
}

int nMatCap_SamplerArrID = _MatCap_SamplerArr_ID;
float4 _MatCap_Sampler_var = SAMPLE_MATCAP(TRANSFORM_TEX(_Rot_MatCapUV_var, _MatCap_Sampler), nMatCap_SamplerArrID, _BlurLevelMatcap);

// MatcapMask
float _Tweak_MatcapMaskLevel_var = 1.0f;//saturate(lerp(_Set_MatcapMask_var.g, (1.0 - _Set_MatcapMask_var.g), _Inverse_MatcapMask) + _Tweak_MatcapMaskLevel);
float3 _Is_LightColor_MatCap_var = lerp( (_MatCap_Sampler_var.rgb*_MatCapColor.rgb), ((_MatCap_Sampler_var.rgb*_MatCapColor.rgb)*Set_LightColor), _Is_LightColor_MatCap );
// ShadowMask on Matcap in Blend mode : multiply
float3 Set_MatCap = lerp( _Is_LightColor_MatCap_var, (_Is_LightColor_MatCap_var*((1.0 - Set_FinalShadowMask)+(Set_FinalShadowMask*_TweakMatCapOnShadow)) + lerp(Set_HighColor*Set_FinalShadowMask*(1.0-_TweakMatCapOnShadow), float3(0.0, 0.0, 0.0), _Is_BlendAddToMatCap)), _Is_UseTweakMatCapOnShadow );

// Composition: RimLight and MatCap as finalColor
// Broke down finalColor composition
float3 matCapColorOnAddMode = _RimLight_var+Set_MatCap*_Tweak_MatcapMaskLevel_var;
float _Tweak_MatcapMaskLevel_var_MultiplyMode = _Tweak_MatcapMaskLevel_var * lerp (1.0, (1.0 - (Set_FinalShadowMask)*(1.0 - _TweakMatCapOnShadow)), _Is_UseTweakMatCapOnShadow);
float3 matCapColorOnMultiplyMode = Set_HighColor*(1-_Tweak_MatcapMaskLevel_var_MultiplyMode) + Set_HighColor*Set_MatCap*_Tweak_MatcapMaskLevel_var_MultiplyMode + lerp(float3(0,0,0),Set_RimLight,_RimLight);
float3 matCapColorFinal = lerp(matCapColorOnMultiplyMode, matCapColorOnAddMode, _Is_BlendAddToMatCap);
float3 finalColor = lerp(_RimLight_var, matCapColorFinal, _MatCap);// Final Composition before Emissive
// Matcap - End
// The general UTS matcap feature is disabled (_MatCap == 0), so the base composition is RimLight.
float3 finalColor = _RimLight_var;// Final Composition before Emissive

// --- Stylized metallic (matcap-driven) ------------------------------------------------
// Uniform branch on per-material flags, so non-metallic materials pay no matcap fetch.
// Requires a matcap uploaded into _MatCap_SamplerArr; the optional _MetallicGlossMapArr
// slice acts as a per-pixel mask (r channel). No mask => fully metallic.
if (_IsStylizedMetallic > 0 && _MatCap_SamplerArr_ID >= 0)
{
int nMatCap_SamplerArrID = _MatCap_SamplerArr_ID;
float4 _MatCap_Sampler_var = SAMPLE_MATCAP(TRANSFORM_TEX(_Rot_MatCapUV_var, _MatCap_Sampler), nMatCap_SamplerArrID, _BlurLevelMatcap);

// Mask = "where do we put the matcap". Sampled from the glTF metallic-roughness map, whose
// metallic channel is B (glTF packs occlusion=R, roughness=G, metallic=B). A uniform
// metallicFactor is baked into a flat mask on the C# side, so it flows through here too.
// _MetallicGlossMapArr_ID < 0 => no mask => fully metallic (metalAmt = 1).
int nMetalMaskID = _MetallicGlossMapArr_ID;
float metalAmt = (nMetalMaskID < 0) ? 1.0 : SAMPLE_METALLICGLOSS(uv_maintex, nMetalMaskID).b;

// Strength of the metallic effect (0 = none, 1 = full). Tune to taste.
const float _StylizedMetalStrength = 1.0;

// Matcap reflection, tinted by scene light colour.
float3 matcapRefl = _MatCap_Sampler_var.rgb * _MatCapColor.rgb * Set_LightColor;

// --- Iridescence (view/fresnel thin-film, non-animated) -------------------------------
// Off by default; only tints the stylized-metal reflection. Reuses the grazing-angle
// fresnel (_RimArea_var = abs(1 - N·V)) so the hue shifts with camera angle like a real
// thin film. Cosine spectral palette (iq) — no time input, so nothing animates.
if (_IsIridescent > 0)
{
const float _IridescenceStrength = 1.0; // tint amount AT the grazing edge (0..1)
const float _IridescenceFrequency = 3.0; // spectral bands across the fresnel sweep
const float _IridescenceEdge = 1.0; // edge falloff: lower = wider mask, higher = hugs silhouette
float fresnel = saturate(_RimArea_var); // 0 head-on .. 1 grazing
float3 iri = 0.5 + 0.5 * cos(6.2831853 * (_IridescenceFrequency * fresnel + float3(0.0, 0.33, 0.67)));

// Multiplying by a [0,1] hue removes energy (darkens ~2 of 3 channels). Rescale the tinted
// reflection back to the original luminance so we shift hue WITHOUT losing brightness
// (energy-conserving, like real thin-film interference).
const float3 LUMA = float3(0.2126, 0.7152, 0.0722);
float3 tinted = matcapRefl * iri;
tinted *= dot(matcapRefl, LUMA) / max(dot(tinted, LUMA), 1e-4);

// Concentrate the effect at the fresnel/grazing edge so the surface stays plain chrome
// face-on and only the silhouette shimmers (real thin-film reads strongest at grazing angles).
float edge = pow(fresnel, _IridescenceEdge);
matcapRefl = lerp(matcapRefl, tinted, _IridescenceStrength * edge);
}

// REPLACE (active): the matcap reflection BECOMES the surface where metalAmt = 1, so metal
// areas read as bright chrome/silver instead of a darkened base. lerp so the mask/strength
// fade cleanly back to the lit toon colour where there's no metal.
finalColor = lerp(finalColor, matcapRefl, saturate(metalAmt) * _StylizedMetalStrength);

// --- Alternative looks (swap the line above for ONE of these) --------------------------
// Colored metal (replace, but tint the reflection by the base colour so hue is kept — gold etc.):
// finalColor = lerp(finalColor, matcapRefl * Set_FinalBaseColor, saturate(metalAmt) * _StylizedMetalStrength);
// Multiply (modulate base by matcap — subtle, can only darken, never full chrome):
// finalColor *= lerp(float3(1.0, 1.0, 1.0), matcapRefl, saturate(metalAmt) * _StylizedMetalStrength);
// Additive sheen (layer the reflection on top instead of replacing):
// finalColor += matcapRefl * saturate(metalAmt) * _StylizedMetalStrength;
// Screen blend (adds highlights with softer clipping than additive):
// float3 sheen = saturate(matcapRefl * saturate(metalAmt) * _StylizedMetalStrength);
// finalColor = 1.0 - (1.0 - finalColor) * (1.0 - sheen);
}
// --- Stylized metallic end ------------------------------------------------------------

// GI_Intensity with Intensity Multiplier Filter
float3 envLightColor = envColor.rgb;
Expand Down
20 changes: 18 additions & 2 deletions Runtime/Shaders/Avatar/DCL_Toon/DCL_ToonDepthNormalsPass.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ struct Varyings
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD1;
float3 normalWS : TEXCOORD2;
// TBN carried so the normals buffer can match the lit surface's normal map (SSAO consistency).
float3 tangentWS : TEXCOORD3;
float3 bitangentWS : TEXCOORD4;

UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
Expand All @@ -54,6 +57,8 @@ Varyings DepthNormalsVertex(Attributes input)
#endif

output.normalWS = NormalizeNormalPerVertex(normalInput.normalWS);
output.tangentWS = normalInput.tangentWS;
output.bitangentWS = normalInput.bitangentWS;

return output;
}
Expand All @@ -76,14 +81,25 @@ void DepthNormalsFragment(
LODFadeCrossFade(input.positionCS);
#endif

// Perturb the surface normal by the normal map (ID-guarded; same TBN transform as the
// ForwardLit pass) so SSAO / the camera normals buffer match the shaded surface.
float3 surfaceNormalWS = input.normalWS;
int nNormalMapArrID = _NormalMapArr_ID;
if (nNormalMapArrID >= 0)
{
float3x3 tangentToWorld = float3x3(input.tangentWS, input.bitangentWS, input.normalWS);
float3 normalTS = UnpackNormalScale(SAMPLE_NORMALMAP(input.uv, nNormalMapArrID), _BumpScale);
surfaceNormalWS = mul(normalTS, tangentToWorld);
}

#if defined(_GBUFFER_NORMALS_OCT)
float3 normalWS = normalize(input.normalWS);
float3 normalWS = normalize(surfaceNormalWS);
float2 octNormalWS = PackNormalOctQuadEncode(normalWS); // values between [-1, +1], must use fp32 on some platforms.
float2 remappedOctNormalWS = saturate(octNormalWS * 0.5 + 0.5); // values between [ 0, 1]
half3 packedNormalWS = PackFloat2To888(remappedOctNormalWS); // values between [ 0, 1]
outNormalWS = half4(packedNormalWS, 0.0);
#else
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
float3 normalWS = NormalizeNormalPerPixel(surfaceNormalWS);
outNormalWS = half4(normalWS, 0.0);
#endif

Expand Down
Loading