// 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 } } }