Pilot für eine generierte Figur: der Kohlenbeißer g01 kommt als Meshy-Ergebnis in den Port und ersetzt dort das Phase-4-Modell. Eine Figur genügt, um Pipeline, Bild und Leistung zu bewerten; Anatomie, Gesichter und Haare bleiben Künstler-Sculpts oder lizenzierte Packs (Vorgabe vom 1. Oktober). - Meshy-Pipeline unter tools/meshy/: API-Client (meshy.py, Ledger, Schlüssel nie geloggt) und Blender-Generator (build_figure.py) aus den unveränderten Downloads in assets-source/meshy/g01/. Export über den Manifest-Eintrag kohlenbeisser mit "replaces": "g01"; LuneUnitLibraryBuilder hängt die Figur zusätzlich unter der Godot-ID ein. - UV-Material AI_Kohlenbeisser (Familie authored): die Karten der Figur selbst, Lava-Glut aus der Albedo-Alpha (Meshy 7.1 ohne Emission-Map) mit _Glow 1,25. - Schatten ohne Normalen-Versatz (LuneShadowBias, _ShadowNormalScale 0): die UV-Naht-Kopien tragen geknickte Normalen; URP ApplyShadowBias zog helle Nähte durch den Schatten. Die Figur wirft nur mit Tiefen-Bias. - GPU-Batched-Skinning (PlayerSettings.meshDeformation, LuneProjectSetup): 35 Meshy-Kohlenbeißer kosteten mit CPU-Skinning rund 1 ms Haupt- und 1,7 ms Render-Thread je Bild. - LuneBenchmark: -luneBenchmarkRound und -luneBenchmarkArmy (Figuren-Stress). LuneAssetPreview: LUNE_ONLY für Vorher/Nachher, Trefferframe aus der Kontaktzeit des Manifests. FigureViewsTests (Explicit) für die vier Spielmomente der Figur. - Tests: EditMode 29/29, PlayMode 14/14 (6 Explicit), Verify 718/0, Player-Selbsttest 30/30, Kerntests 457/457, Fingerabdrücke unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5). - Leistung (A/B im selben Fenster, 35x g01): Meshy -21 % Mid (109,7 zu 138,8) und -13 % Karte (91,7 zu 105,6) gegenüber Phase-4. GPU-Skinning hebt Mid um 7-27 % und entlastet Haupt- und Render-Thread. Standard-Partie ab Runde 21 ohne g01 im Bild: drei Varianten bis 6 %. - Doku: U5-VISUAL (U5.5), PORT-QA, PORT-STATUS, KNOWN-BEHAVIOR-DIFFERENCES; Bilder screenshots/u5.5/ und u5.5-gpu/, Rohdaten logs/u5.5/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
574 lines
27 KiB
Text
574 lines
27 KiB
Text
// Shared surface shader of Champions of Luneraria in URP (U2B, PBR since U5). Ports the Godot
|
|
// world shaders (scripts/luneraria/view/lune_terrain.gdshader, scripts/presentation/sanctuary.gdshader,
|
|
// river.gdshader, materials/lune_vein.gdshader) and a simplified G2 building look: one
|
|
// shader, the look chosen per material with _Mode.
|
|
// 0 plain base colour (x vertex colour with _VertexColor), metal/emission from parameters
|
|
// 1 terrain zone colour from the vertex colour, rockiness in vertex alpha, triplanar limestone
|
|
// 2 stone Kluft stone: triplanar limestone relief
|
|
// 3 paving lane paving
|
|
// 4 soil packed earth
|
|
// 5 bark tree bark ridges
|
|
// 6 foliage leaves with wind
|
|
// 7 water river surface with moving waves
|
|
// 8 vein energy vein of the Ancient plaza (UV.x along, UV.y across)
|
|
// 9 crystal glowing crystal with rim light
|
|
// World-space patterns use Unity world coordinates (Godot x is mirrored); the look is the same.
|
|
// U5 PBR set (tools/blender/bake_pbr.py): albedo, tangent normal, ORM (R ao, G roughness,
|
|
// B metallic), triplanar like the stone map. _StoneMap stays as a cheap relief fallback.
|
|
// U5.3: the maps are detail around the Godot look. Albedo is divided by its mean colour
|
|
// (_AlbedoMean) and roughness by its mean (_OrmMean.g) where the material keeps its own
|
|
// roughness, so the Godot colours and roughness stay the average and the maps add variation.
|
|
// U5.4: _ObjectSpace samples the triplanar maps in the mesh's own space, so the detail of a unit
|
|
// stays on the moving, turning figure (world space would slide it over the body). _Rim adds a
|
|
// view-dependent rim light (Fresnel) that lifts the figures' silhouettes off the ground in the
|
|
// top-down view, the readability device of the reference MOBAs.
|
|
// U5.5: _Glow lets a UV figure glow where the alpha of its albedo map marks hot parts (the lava
|
|
// of the Meshy figures): emission = albedo x alpha x _Glow, added to _EmissionColor.
|
|
// _ShadowNormalScale scales URP's normal offset of the shadow caster (0 for the Meshy figures,
|
|
// whose UV-seam copies carry creased normals; see the ShadowCaster pass).
|
|
Shader "Luneraria/Surface"
|
|
{
|
|
Properties
|
|
{
|
|
_BaseColor("Base Colour", Color) = (1, 1, 1, 1)
|
|
[NoScaleOffset] _AlbedoMap("Albedo", 2D) = "white" {}
|
|
[NoScaleOffset] _NormalMap("Normal", 2D) = "bump" {}
|
|
[NoScaleOffset] _OrmMap("ORM (ao, rough, metal)", 2D) = "white" {}
|
|
[NoScaleOffset] _StoneMap("Stone Map", 2D) = "grey" {}
|
|
_StoneScale("Stone Scale", Float) = 0.23
|
|
_Mode("Mode", Float) = 0
|
|
_VertexColor("Multiply Vertex Colour", Float) = 0
|
|
_Metallic("Metallic", Range(0, 1)) = 0
|
|
_Roughness("Roughness", Range(0, 1)) = 0.88
|
|
[HDR] _EmissionColor("Emission", Color) = (0, 0, 0, 1)
|
|
_Relief("Relief Strength", Float) = 0.016
|
|
_NormalStrength("Normal Strength", Float) = 1
|
|
_Quality("Quality (1 lane PBR, 0 edge)", Float) = 1
|
|
_UvMaps("UV Maps (1 character, 0 world triplanar)", Float) = 0
|
|
_AlbedoMean("Albedo Mean (linear)", Vector) = (1, 1, 1, 1)
|
|
_OrmMean("ORM Mean (linear)", Vector) = (1, 1, 1, 1)
|
|
_ObjectSpace("Object Space Triplanar (1 units, 0 world)", Float) = 0
|
|
_Rim("Rim Light", Float) = 0
|
|
_RimColor("Rim Colour", Color) = (0.8, 0.86, 1, 1)
|
|
_Glow("Glow (UV albedo alpha x albedo)", Float) = 0
|
|
_ShadowNormalScale("Shadow Normal Bias Scale", Float) = 1
|
|
_Energy("Energy (veins)", Float) = 1
|
|
_Flicker("Flicker (veins)", Float) = 0
|
|
_Seed("Seed", Float) = 0
|
|
[Enum(UnityEngine.Rendering.CullMode)] _Cull("Cull", Float) = 2
|
|
}
|
|
|
|
SubShader
|
|
{
|
|
Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "Queue" = "Geometry" }
|
|
|
|
HLSLINCLUDE
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
|
|
|
|
CBUFFER_START(UnityPerMaterial)
|
|
half4 _BaseColor;
|
|
float _StoneScale;
|
|
float _Mode;
|
|
float _VertexColor;
|
|
half _Metallic;
|
|
half _Roughness;
|
|
half4 _EmissionColor;
|
|
float _Relief;
|
|
float _NormalStrength;
|
|
float _Quality;
|
|
float _UvMaps;
|
|
half4 _AlbedoMean;
|
|
half4 _OrmMean;
|
|
float _ObjectSpace;
|
|
half _Rim;
|
|
half4 _RimColor;
|
|
float _Energy;
|
|
float _Flicker;
|
|
float _Seed;
|
|
float _Cull;
|
|
half _Glow;
|
|
half _ShadowNormalScale;
|
|
CBUFFER_END
|
|
|
|
TEXTURE2D(_AlbedoMap);
|
|
SAMPLER(sampler_AlbedoMap);
|
|
TEXTURE2D(_NormalMap);
|
|
SAMPLER(sampler_NormalMap);
|
|
TEXTURE2D(_OrmMap);
|
|
SAMPLER(sampler_OrmMap);
|
|
TEXTURE2D(_StoneMap);
|
|
SAMPLER(sampler_StoneMap);
|
|
|
|
float LuneHash(float3 p)
|
|
{
|
|
p = frac(p * 0.1031);
|
|
p += dot(p, p.yzx + 33.33);
|
|
return frac((p.x + p.y) * p.z);
|
|
}
|
|
|
|
float LuneNoise(float3 p)
|
|
{
|
|
float3 i = floor(p);
|
|
float3 f = frac(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
return lerp(lerp(lerp(LuneHash(i), LuneHash(i + float3(1, 0, 0)), f.x), lerp(LuneHash(i + float3(0, 1, 0)), LuneHash(i + float3(1, 1, 0)), f.x), f.y),
|
|
lerp(lerp(LuneHash(i + float3(0, 0, 1)), LuneHash(i + float3(1, 0, 1)), f.x), lerp(LuneHash(i + float3(0, 1, 1)), LuneHash(i + float3(1, 1, 1)), f.x), f.y), f.z);
|
|
}
|
|
|
|
float LuneHash2(float2 p) { return frac(sin(dot(p, float2(127.1, 311.7))) * 43758.5453); }
|
|
|
|
float LuneNoise2(float2 p)
|
|
{
|
|
float2 i = floor(p);
|
|
float2 f = frac(p);
|
|
f = f * f * (3.0 - 2.0 * f);
|
|
return lerp(lerp(LuneHash2(i), LuneHash2(i + float2(1, 0)), f.x), lerp(LuneHash2(i + float2(0, 1)), LuneHash2(i + float2(1, 1)), f.x), f.y);
|
|
}
|
|
|
|
// Foliage sways a little with height (Godot: VERTEX.x += wind * 0.065 * clamp(y * 0.3)).
|
|
float3 LuneWind(float3 positionWS)
|
|
{
|
|
if (_Mode > 5.5 && _Mode < 6.5)
|
|
{
|
|
float t = _Time.y;
|
|
float wind = sin(t * 0.8 + positionWS.x * 0.55 + positionWS.z * 0.32) * sin(t * 0.37 + positionWS.z * 0.6);
|
|
positionWS.x += wind * 0.065 * clamp(positionWS.y * 0.3, 0.15, 1.0);
|
|
}
|
|
return positionWS;
|
|
}
|
|
ENDHLSL
|
|
|
|
Pass
|
|
{
|
|
Name "ForwardLit"
|
|
Tags { "LightMode" = "UniversalForward" }
|
|
Cull [_Cull]
|
|
|
|
HLSLPROGRAM
|
|
#pragma target 3.0
|
|
#pragma vertex Vert
|
|
#pragma fragment Frag
|
|
#pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
|
|
#pragma multi_compile _ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS
|
|
#pragma multi_compile _ _CLUSTER_LIGHT_LOOP
|
|
#pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
|
|
#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_fog
|
|
#pragma multi_compile_instancing
|
|
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
float3 normalOS : NORMAL;
|
|
float4 tangentOS : TANGENT;
|
|
float4 color : COLOR;
|
|
float2 uv : TEXCOORD0;
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
};
|
|
|
|
struct Varyings
|
|
{
|
|
float4 positionCS : SV_POSITION;
|
|
float3 positionWS : TEXCOORD0;
|
|
float3 normalWS : TEXCOORD1;
|
|
float4 color : TEXCOORD2;
|
|
float2 uv : TEXCOORD3;
|
|
float fogFactor : TEXCOORD4;
|
|
// Mesh tangent (xyz) and bitangent sign (w) for the authored UV normal maps (U5.3).
|
|
float4 tangentWS : TEXCOORD5;
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
UNITY_VERTEX_OUTPUT_STEREO
|
|
};
|
|
|
|
Varyings Vert(Attributes input)
|
|
{
|
|
Varyings output = (Varyings)0;
|
|
UNITY_SETUP_INSTANCE_ID(input);
|
|
UNITY_TRANSFER_INSTANCE_ID(input, output);
|
|
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
|
|
float3 positionWS = LuneWind(TransformObjectToWorld(input.positionOS.xyz));
|
|
output.positionWS = positionWS;
|
|
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
|
|
output.tangentWS = float4(TransformObjectToWorldDir(input.tangentOS.xyz), input.tangentOS.w * GetOddNegativeScale());
|
|
output.positionCS = TransformWorldToHClip(positionWS);
|
|
output.color = input.color;
|
|
output.uv = input.uv;
|
|
output.fogFactor = ComputeFogFactor(output.positionCS.z);
|
|
return output;
|
|
}
|
|
|
|
half4 Frag(Varyings input, bool frontFace : SV_IsFrontFace) : SV_Target
|
|
{
|
|
UNITY_SETUP_INSTANCE_ID(input);
|
|
float3 world = input.positionWS;
|
|
float3 n = normalize(input.normalWS);
|
|
if (!frontFace)
|
|
n = -n;
|
|
// Triplanar space: the world, or the mesh's own space for units (U5.4). Object space
|
|
// comes back from the world through the draw's matrix, so no extra interpolators.
|
|
bool objectSpace = _ObjectSpace > 0.5;
|
|
float3 triPosition = world;
|
|
float3 triNormal = n;
|
|
if (objectSpace)
|
|
{
|
|
triPosition = TransformWorldToObject(world);
|
|
triNormal = normalize(TransformWorldToObjectNormal(n));
|
|
}
|
|
float3 weights = pow(abs(triNormal), 5.0);
|
|
weights /= max(dot(weights, 1.0), 0.0001);
|
|
float3 p = triPosition * _StoneScale;
|
|
// U5 PBR set (lane tier): triplanar albedo, ORM and normal. Edge tier (Weg 2
|
|
// Stufen) skips the maps and keeps the flat U2 response of colour + stone relief.
|
|
// _UvMaps samples the authored UVs of a character (Ringborke bark) instead.
|
|
half3 texAlbedo = half3(1, 1, 1);
|
|
half3 texOrm = half3(1.0, _Roughness, _Metallic);
|
|
half3 nX = half3(0, 0, 1), nY = half3(0, 0, 1), nZ = half3(0, 0, 1);
|
|
// Variation of the map around its mean; 1 where no map is sampled (edge tier).
|
|
half roughDetail = 1.0;
|
|
half metalDetail = 1.0;
|
|
half glow = 0.0;
|
|
if (_Quality > 0.5)
|
|
{
|
|
if (_UvMaps > 0.5)
|
|
{
|
|
half4 albedoSample = SAMPLE_TEXTURE2D(_AlbedoMap, sampler_AlbedoMap, input.uv);
|
|
texAlbedo = albedoSample.rgb;
|
|
glow = albedoSample.a * _Glow;
|
|
texOrm = SAMPLE_TEXTURE2D(_OrmMap, sampler_OrmMap, input.uv).rgb;
|
|
// Authored UV normal (Ringborke bark) in the mesh's own tangent frame
|
|
// (Mikktspace tangents of the FBX import, skinned with the figure).
|
|
float3 tangent = normalize(input.tangentWS.xyz - n * dot(n, input.tangentWS.xyz));
|
|
float3 bitangent = cross(n, tangent) * (input.tangentWS.w < 0.0 ? -1.0 : 1.0);
|
|
half3 uvNormal = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv), _NormalStrength);
|
|
n = normalize(uvNormal.x * tangent + uvNormal.y * bitangent + uvNormal.z * n);
|
|
}
|
|
else
|
|
{
|
|
texAlbedo = SAMPLE_TEXTURE2D(_AlbedoMap, sampler_AlbedoMap, p.zy).rgb * weights.x
|
|
+ SAMPLE_TEXTURE2D(_AlbedoMap, sampler_AlbedoMap, p.xz).rgb * weights.y
|
|
+ SAMPLE_TEXTURE2D(_AlbedoMap, sampler_AlbedoMap, p.xy).rgb * weights.z;
|
|
texOrm = SAMPLE_TEXTURE2D(_OrmMap, sampler_OrmMap, p.zy).rgb * weights.x
|
|
+ SAMPLE_TEXTURE2D(_OrmMap, sampler_OrmMap, p.xz).rgb * weights.y
|
|
+ SAMPLE_TEXTURE2D(_OrmMap, sampler_OrmMap, p.xy).rgb * weights.z;
|
|
nX = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, p.zy), _NormalStrength);
|
|
nY = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, p.xz), _NormalStrength);
|
|
nZ = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, p.xy), _NormalStrength);
|
|
}
|
|
// Detail around the Godot colour: the map's mean becomes 1.
|
|
texAlbedo /= max(_AlbedoMean.rgb, half3(0.002, 0.002, 0.002));
|
|
roughDetail = texOrm.g / max(_OrmMean.g, 0.05);
|
|
metalDetail = texOrm.b / max(_OrmMean.b, 0.05);
|
|
}
|
|
int mode = (int)round(_Mode);
|
|
// The stone relief map only for the modes that use it (terrain, stone, paving, soil):
|
|
// plain surfaces (figures, building parts, props) save three samples (U5.4).
|
|
half3 stone = half3(0.5, 0.5, 0.5);
|
|
if (mode >= 1 && mode <= 4)
|
|
{
|
|
stone = SAMPLE_TEXTURE2D(_StoneMap, sampler_StoneMap, p.zy).rgb * weights.x
|
|
+ SAMPLE_TEXTURE2D(_StoneMap, sampler_StoneMap, p.xz).rgb * weights.y
|
|
+ SAMPLE_TEXTURE2D(_StoneMap, sampler_StoneMap, p.xy).rgb * weights.z;
|
|
}
|
|
float relief = dot(stone, 0.3333);
|
|
half3 albedo = _BaseColor.rgb * texAlbedo;
|
|
half roughness = texOrm.g;
|
|
half metallic = texOrm.b;
|
|
half ao = texOrm.r;
|
|
half3 emission = _EmissionColor.rgb + texAlbedo * glow;
|
|
float bump = 0.0;
|
|
// World-space modes keep their Godot look on top of the PBR set.
|
|
if (mode == 1)
|
|
{
|
|
float broad = LuneNoise(world * 0.35);
|
|
float grain = LuneNoise(world * 6.0);
|
|
float rock = input.color.a;
|
|
half3 soil = input.color.rgb * texAlbedo * lerp(0.84, 1.14, broad) * lerp(0.9, 1.07, grain);
|
|
half3 rocky = input.color.rgb * texAlbedo * lerp(0.72, 1.45, stone);
|
|
albedo = lerp(soil, rocky, rock);
|
|
roughness = saturate(lerp(0.93, 0.84, rock) * roughDetail);
|
|
metallic = texOrm.b;
|
|
bump = 0.006 + rock * 0.014;
|
|
}
|
|
else if (mode == 2)
|
|
{
|
|
albedo = _BaseColor.rgb * texAlbedo * lerp(0.65, 1.6, stone);
|
|
roughness = texOrm.g;
|
|
bump = _Relief;
|
|
}
|
|
else if (mode == 3)
|
|
{
|
|
albedo = _BaseColor.rgb * texAlbedo * lerp(0.82, 1.3, stone);
|
|
roughness = texOrm.g;
|
|
bump = _Relief;
|
|
}
|
|
else if (mode == 4)
|
|
{
|
|
float broad = LuneNoise(world * 0.8);
|
|
albedo = _BaseColor.rgb * texAlbedo * lerp(0.82, 1.12, broad);
|
|
albedo = lerp(albedo, half3(0.12, 0.17, 0.075) * texAlbedo, smoothstep(0.54, 0.8, broad) * 0.16);
|
|
roughness = texOrm.g;
|
|
bump = _Relief;
|
|
}
|
|
else if (mode == 5)
|
|
{
|
|
float ridges = LuneNoise(world * float3(13, 1.2, 13));
|
|
float grain = LuneNoise(world * 21.0);
|
|
albedo = _BaseColor.rgb * texAlbedo * lerp(0.45, 1.2, ridges) * lerp(0.8, 1.08, grain);
|
|
relief = ridges;
|
|
roughness = texOrm.g;
|
|
bump = 0.025;
|
|
}
|
|
else if (mode == 6)
|
|
{
|
|
float broad = LuneNoise(world * 0.8);
|
|
albedo = _BaseColor.rgb * texAlbedo * lerp(0.75, 1.2, broad);
|
|
roughness = texOrm.g;
|
|
// Godot BACKLIGHT (albedo * 0.32): light through thin leaves, approximated as a small glow.
|
|
emission += albedo * 0.32 * saturate(dot(-n, _MainLightPosition.xyz)) * _MainLightColor.rgb;
|
|
}
|
|
else if (mode == 7)
|
|
{
|
|
float2 flow = world.xz * 0.75 + float2(_Time.y * 0.037, -_Time.y * 0.075);
|
|
float waves = LuneNoise2(flow) * 0.6 + LuneNoise2(flow * 2.13 + float2(-_Time.y * 0.06, 0)) * 0.3 + LuneNoise2(flow * 4.17) * 0.1;
|
|
float fresnel = pow(1.0 - saturate(dot(n, GetWorldSpaceNormalizeViewDir(world))), 4.0);
|
|
// Godot river colours (U2/U3) with the water set as detail.
|
|
albedo = lerp(half3(0.022, 0.094, 0.098), half3(0.042, 0.145, 0.15), waves) * texAlbedo + half3(0.045, 0.07, 0.075) * fresnel;
|
|
roughness = lerp(texOrm.g, 0.22, fresnel * 0.5);
|
|
metallic = texOrm.b;
|
|
relief = waves;
|
|
bump = 0.055;
|
|
}
|
|
else if (mode == 8)
|
|
{
|
|
float across = abs(input.uv.y - 0.5) * 2.0;
|
|
float core = 1.0 - smoothstep(0.15, 0.7, across);
|
|
float lip = smoothstep(0.72, 0.95, across);
|
|
float flow = frac(input.uv.x * 0.35 + _Time.y * 0.45 + _Seed);
|
|
float pulse = smoothstep(0.0, 0.12, flow) * (1.0 - smoothstep(0.12, 0.45, flow));
|
|
float live = _Energy;
|
|
if (_Flicker > 0.0)
|
|
live *= lerp(1.0, step(0.4, LuneNoise(float3(_Time.y * 4.0, input.uv.x * 0.3, _Seed))), _Flicker);
|
|
half3 carved = half3(0.08, 0.08, 0.1) + half3(0.3, 0.29, 0.27) * lip;
|
|
albedo = lerp(carved, _BaseColor.rgb * 0.2, core * 0.6);
|
|
roughness = lerp(0.85, 0.3, core);
|
|
metallic = 0.0;
|
|
ao = 1.0;
|
|
emission = _BaseColor.rgb * core * live * (0.55 + pulse * 1.6);
|
|
}
|
|
else if (mode == 9)
|
|
{
|
|
float rim = pow(1.0 - saturate(dot(n, GetWorldSpaceNormalizeViewDir(world))), 2.5);
|
|
albedo = _BaseColor.rgb * 0.35;
|
|
roughness = 0.08;
|
|
metallic = 0.0;
|
|
ao = 1.0;
|
|
emission = _EmissionColor.rgb * (0.75 + rim * 1.2);
|
|
}
|
|
else
|
|
{
|
|
// plain: keep the material's own metal/roughness; the set adds variation around them.
|
|
roughness = saturate(_Roughness * roughDetail);
|
|
metallic = saturate(_Metallic * metalDetail);
|
|
}
|
|
if (_VertexColor > 0.5)
|
|
albedo *= input.color.rgb;
|
|
// Tangent-space normal of the PBR set (lane tier only), triplanar, on top of the
|
|
// geometric normal. UV characters (Ringborke) already blended their normal above.
|
|
if (_Quality > 0.5 && _UvMaps < 0.5)
|
|
{
|
|
// Whiteout blend of the three projections in the triplanar space, then to world.
|
|
float3 tn = triNormal;
|
|
nX = half3(nX.xy + tn.zy, abs(nX.z) * tn.x);
|
|
nY = half3(nY.xy + tn.xz, abs(nY.z) * tn.y);
|
|
nZ = half3(nZ.xy + tn.xy, abs(nZ.z) * tn.z);
|
|
float3 blended = normalize(nX.zyx * weights.x + nY.xzy * weights.y + nZ.xyz * weights.z);
|
|
n = objectSpace ? normalize(TransformObjectToWorldNormal(blended)) : blended;
|
|
}
|
|
if (bump > 0.0)
|
|
{
|
|
// Relief from the pattern's screen derivatives (surface gradient, as in Godot).
|
|
float3 dx = ddx(world);
|
|
float3 dy = ddy(world);
|
|
float3 rx = cross(dy, n);
|
|
float3 ry = cross(n, dx);
|
|
float det = dot(dx, rx);
|
|
float3 gradient = ddx(relief) * rx + ddy(relief) * ry;
|
|
n = normalize(abs(det) * n - sign(det) * gradient * bump);
|
|
}
|
|
|
|
if (_Rim > 0.0)
|
|
{
|
|
// Rim light: grazing view angles of the shaded normal, scaled by the main light so it stays out of shadowless night.
|
|
half rim = pow(1.0 - saturate(dot(n, GetWorldSpaceNormalizeViewDir(world))), 3.0);
|
|
emission += _RimColor.rgb * rim * _Rim * saturate(dot(_MainLightColor.rgb, half3(0.3333, 0.3333, 0.3333)));
|
|
}
|
|
|
|
InputData inputData = (InputData)0;
|
|
inputData.positionWS = world;
|
|
inputData.positionCS = input.positionCS;
|
|
inputData.normalWS = n;
|
|
inputData.viewDirectionWS = GetWorldSpaceNormalizeViewDir(world);
|
|
inputData.shadowCoord = TransformWorldToShadowCoord(world);
|
|
inputData.fogCoord = input.fogFactor;
|
|
inputData.bakedGI = SampleSH(n);
|
|
inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS);
|
|
inputData.shadowMask = half4(1, 1, 1, 1);
|
|
|
|
SurfaceData surface = (SurfaceData)0;
|
|
surface.albedo = albedo;
|
|
surface.metallic = metallic;
|
|
surface.specular = half3(0, 0, 0);
|
|
surface.smoothness = 1.0 - roughness;
|
|
surface.occlusion = ao;
|
|
surface.emission = emission;
|
|
surface.alpha = 1.0;
|
|
surface.normalTS = half3(0, 0, 1);
|
|
half4 color = UniversalFragmentPBR(inputData, surface);
|
|
color.rgb = MixFog(color.rgb, inputData.fogCoord);
|
|
return color;
|
|
}
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "ShadowCaster"
|
|
Tags { "LightMode" = "ShadowCaster" }
|
|
ZWrite On
|
|
ZTest LEqual
|
|
ColorMask 0
|
|
Cull [_Cull]
|
|
|
|
HLSLPROGRAM
|
|
#pragma target 3.0
|
|
#pragma vertex ShadowVert
|
|
#pragma fragment ShadowFrag
|
|
#pragma multi_compile_instancing
|
|
#pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
|
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
|
|
|
|
float3 _LightDirection;
|
|
float3 _LightPosition;
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
float3 normalOS : NORMAL;
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
};
|
|
|
|
// URP's ApplyShadowBias with the normal offset scaled per material (U5.5). The Meshy
|
|
// figures are split along their UV seams and the copies carry creased normals (up to
|
|
// 180 degrees apart); pushed along different normals the seams opened, and the sun
|
|
// drew thin bright lines through the figure's shadow. They cast with depth bias only.
|
|
float3 LuneShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection)
|
|
{
|
|
float invNdotL = 1.0 - saturate(dot(lightDirection, normalWS));
|
|
float scale = invNdotL * _ShadowBias.y * _ShadowNormalScale;
|
|
positionWS = lightDirection * _ShadowBias.xxx + positionWS;
|
|
positionWS = normalWS * scale.xxx + positionWS;
|
|
return positionWS;
|
|
}
|
|
|
|
float4 ShadowVert(Attributes input) : SV_POSITION
|
|
{
|
|
UNITY_SETUP_INSTANCE_ID(input);
|
|
float3 positionWS = LuneWind(TransformObjectToWorld(input.positionOS.xyz));
|
|
float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
|
|
#if _CASTING_PUNCTUAL_LIGHT_SHADOW
|
|
float3 lightDirectionWS = normalize(_LightPosition - positionWS);
|
|
#else
|
|
float3 lightDirectionWS = _LightDirection;
|
|
#endif
|
|
float4 positionCS = TransformWorldToHClip(LuneShadowBias(positionWS, normalWS, lightDirectionWS));
|
|
return ApplyShadowClamping(positionCS);
|
|
}
|
|
|
|
half4 ShadowFrag() : SV_Target { return 0; }
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "DepthOnly"
|
|
Tags { "LightMode" = "DepthOnly" }
|
|
ZWrite On
|
|
ColorMask R
|
|
Cull [_Cull]
|
|
|
|
HLSLPROGRAM
|
|
#pragma target 3.0
|
|
#pragma vertex DepthVert
|
|
#pragma fragment DepthFrag
|
|
#pragma multi_compile_instancing
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
};
|
|
|
|
float4 DepthVert(Attributes input) : SV_POSITION
|
|
{
|
|
UNITY_SETUP_INSTANCE_ID(input);
|
|
return TransformWorldToHClip(LuneWind(TransformObjectToWorld(input.positionOS.xyz)));
|
|
}
|
|
|
|
half DepthFrag() : SV_Target { return 0; }
|
|
ENDHLSL
|
|
}
|
|
|
|
Pass
|
|
{
|
|
Name "DepthNormals"
|
|
Tags { "LightMode" = "DepthNormals" }
|
|
ZWrite On
|
|
Cull [_Cull]
|
|
|
|
HLSLPROGRAM
|
|
#pragma target 3.0
|
|
#pragma vertex NormalsVert
|
|
#pragma fragment NormalsFrag
|
|
#pragma multi_compile_instancing
|
|
#pragma multi_compile_fragment _ _GBUFFER_NORMALS_OCT
|
|
|
|
struct Attributes
|
|
{
|
|
float4 positionOS : POSITION;
|
|
float3 normalOS : NORMAL;
|
|
UNITY_VERTEX_INPUT_INSTANCE_ID
|
|
};
|
|
|
|
struct Varyings
|
|
{
|
|
float4 positionCS : SV_POSITION;
|
|
float3 normalWS : TEXCOORD0;
|
|
};
|
|
|
|
Varyings NormalsVert(Attributes input)
|
|
{
|
|
UNITY_SETUP_INSTANCE_ID(input);
|
|
Varyings output;
|
|
output.positionCS = TransformWorldToHClip(LuneWind(TransformObjectToWorld(input.positionOS.xyz)));
|
|
output.normalWS = TransformObjectToWorldNormal(input.normalOS);
|
|
return output;
|
|
}
|
|
|
|
half4 NormalsFrag(Varyings input) : SV_Target
|
|
{
|
|
float3 normalWS = NormalizeNormalPerPixel(input.normalWS);
|
|
#if defined(_GBUFFER_NORMALS_OCT)
|
|
float2 octNormalWS = PackNormalOctQuadEncode(normalWS);
|
|
float2 remapped = saturate(octNormalWS * 0.5 + 0.5);
|
|
return half4(PackFloat2To888(remapped), 0.0);
|
|
#else
|
|
return half4(normalWS, 0.0);
|
|
#endif
|
|
}
|
|
ENDHLSL
|
|
}
|
|
}
|
|
}
|