champions-of-luneraria/Assets/Luneraria/Shaders/LuneSurface.shader
Martin Stickelbroeck 8a72cb165a U2B World Geometry
G1-Karte in Unity (Assets/Luneraria/Scenes/G1_Map.unity), reproduzierbar
gebaut aus dem Godot-Weltgenerator (gebacken), den FBX-Modellen aus U2A und der
Karte des C#-Kerns.

- tools/godot-golden/world_export.gd: backt Gelände (Raster, Farben, Lauf- und
  Standhöhen), Lanebeläge, Plattform, Brücke, Randsteine, Basen, Landmarken,
  Vegetation, Adern, Grottentreppen, Aufstellungsbereiche und Licht. Braucht
  einen echten Renderer (HEADLESS=0); prüft auf Headless-Einheitstransformationen.
- LuneWorldBuilder: Meshes (GUID-stabil), Szene, Licht, Himmel, Post-Processing,
  16 Türme, 2 Ancients und die Grotte aus LuneMap mit Teammaterialien.
- Materialsystem: Luneraria/Surface (Gelände, Kluft-Oberflächen, Fluss, Adern,
  Kristall, G2-Werte mit Teamvarianten und Vertex-AO) und Luneraria/Pad;
  LuneMaterialSet.Dress wie Godots dress.
- View-Assembly: LuneSpace (Unity = (-x, h, y)), LuneGround, LuneCameraMath,
  LuneBuildingView (Zustände intakt bis Ruine).
- Vergleichsbilder mit den Kameraeinstellungen der Godot-G2-Aufnahmen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 16:01:25 +02:00

420 lines
17 KiB
Text

// Shared surface shader of Champions of Luneraria in URP (U2B). 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.
Shader "Luneraria/Surface"
{
Properties
{
_BaseColor("Base Colour", Color) = (1, 1, 1, 1)
[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
_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 _Energy;
float _Flicker;
float _Seed;
float _Cull;
CBUFFER_END
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 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;
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.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;
float3 weights = pow(abs(n), 5.0);
weights /= max(dot(weights, 1.0), 0.0001);
float3 p = world * _StoneScale;
half3 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;
half roughness = _Roughness;
half metallic = _Metallic;
half3 emission = _EmissionColor.rgb;
float bump = 0.0;
int mode = (int)round(_Mode);
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 * lerp(0.84, 1.14, broad) * lerp(0.9, 1.07, grain);
half3 rocky = input.color.rgb * lerp(0.72, 1.45, stone);
albedo = lerp(soil, rocky, rock);
roughness = lerp(0.93, 0.84, rock);
bump = 0.006 + rock * 0.014;
}
else if (mode == 2)
{
albedo = _BaseColor.rgb * lerp(0.65, 1.6, stone);
bump = _Relief;
}
else if (mode == 3)
{
albedo = _BaseColor.rgb * lerp(0.82, 1.3, stone);
bump = _Relief;
}
else if (mode == 4)
{
float broad = LuneNoise(world * 0.8);
albedo = _BaseColor.rgb * lerp(0.82, 1.12, broad) * lerp(0.78, 1.2, relief);
albedo = lerp(albedo, half3(0.12, 0.17, 0.075), smoothstep(0.54, 0.8, broad) * 0.16);
bump = _Relief;
}
else if (mode == 5)
{
float ridges = LuneNoise(world * float3(13, 1.2, 13));
float grain = LuneNoise(world * 21.0);
albedo = _BaseColor.rgb * lerp(0.45, 1.2, ridges) * lerp(0.8, 1.08, grain);
relief = ridges;
bump = 0.025;
}
else if (mode == 6)
{
float broad = LuneNoise(world * 0.8);
albedo = _BaseColor.rgb * lerp(0.75, 1.2, broad);
roughness = 0.79;
// 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);
albedo = lerp(half3(0.022, 0.094, 0.098), half3(0.042, 0.145, 0.15), waves) + half3(0.045, 0.07, 0.075) * fresnel;
roughness = 0.32;
metallic = 0.18;
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);
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;
emission = _EmissionColor.rgb * (0.75 + rim * 1.2);
}
if (_VertexColor > 0.5)
albedo *= input.color.rgb;
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);
}
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 = 1.0;
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
};
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(ApplyShadowBias(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
}
}
}