From c29c6f5bacb0820b9bf0f6a6d96c9ce18925ca1f Mon Sep 17 00:00:00 2001 From: ud18010 Date: Wed, 15 Jul 2026 10:23:46 +0800 Subject: [PATCH] Add XPbr shader: base map (RGB albedo + A alpha/AO) + mix map (RG normal, B metallic, A roughness) Co-Authored-By: Claude Opus 4.8 --- Client/Assets/Game/shader/XShader/XPbr.shader | 216 ++++++++++++++++++ .../Game/shader/XShader/XPbr.shader.meta | 12 + .../Assets/Game/shader/XShader/XPbr_Base.hlsl | 141 ++++++++++++ .../Game/shader/XShader/XPbr_Base.hlsl.meta | 10 + 4 files changed, 379 insertions(+) create mode 100644 Client/Assets/Game/shader/XShader/XPbr.shader create mode 100644 Client/Assets/Game/shader/XShader/XPbr.shader.meta create mode 100644 Client/Assets/Game/shader/XShader/XPbr_Base.hlsl create mode 100644 Client/Assets/Game/shader/XShader/XPbr_Base.hlsl.meta diff --git a/Client/Assets/Game/shader/XShader/XPbr.shader b/Client/Assets/Game/shader/XShader/XPbr.shader new file mode 100644 index 00000000..c7dd9651 --- /dev/null +++ b/Client/Assets/Game/shader/XShader/XPbr.shader @@ -0,0 +1,216 @@ +Shader "Test/XPbr" +{ + Properties + { + [MainColor] _BaseColor("Color", Color) = (1,1,1,1) + [MainTexture] _MainTex("Base (RGB:Albedo A:Alpha/AO)", 2D) = "white" {} + _MixTex("Mix (RG:Normal B:Metallic A:Roughness)", 2D) = "white" {} + + // 勾选:A 通道当透明(AlphaTest 裁剪);不勾选:A 通道当 AO + [Toggle(_ALPHATEST_ON)] _AlphaTest("Alpha As Transparency (Clip)", Float) = 0 + _Cutoff("Alpha Cutoff", Range(0.0, 1.0)) = 0.5 + + _Metallic("Metallic", Range(0.0, 2.0)) = 1.0 + _Roughness("Roughness", Range(0.0, 2.0)) = 1.0 + _AO("AO", Range(0.0, 1.0)) = 1.0 + } + + SubShader + { + Tags{ "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" "IgnoreProjector" = "True" } + Pass + { + Name "ForwardLit" + Tags{ "LightMode" = "UniversalForward" } + + ZWrite on + + HLSLPROGRAM + // Required to compile gles 2.0 with standard SRP library + // All shaders must be compiled with HLSLcc and currently only gles is not using HLSLcc by default + #pragma prefer_hlslcc gles + #pragma exclude_renderers d3d11_9x + #pragma target 2.0 + + #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 _ LIGHTMAP_ON + #pragma multi_compile _ DYNAMICLIGHTMAP_ON + + #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS + #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH + + #pragma shader_feature_local_fragment _ALPHATEST_ON + + #pragma vertex vert + #pragma fragment frag + + #include "XPbr_Base.hlsl" + + 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); + + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + + // normalWS and tangentWS already normalize. + // this is required to avoid skewing the direction during interpolation + // also required for per-vertex lighting and SH evaluation + VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS); + + half3 vertexLight = VertexLighting(vertexInput.positionWS, normalInput.normalWS); + + half fogFactor = 0; + #if !defined(_FOG_FRAGMENT) + fogFactor = ComputeFogFactor(vertexInput.positionCS.z); + #endif + + half3 viewDirWS = GetCameraPositionWS() - vertexInput.positionWS; + output.normalWS = half4(normalInput.normalWS, viewDirWS.x); + output.tangentWS = half4(normalInput.tangentWS, viewDirWS.y); + output.bitangentWS = half4(normalInput.bitangentWS, viewDirWS.z); + + OUTPUT_LIGHTMAP_UV(input.staticLightmapUV, unity_LightmapST, output.staticLightmapUV); + #ifdef DYNAMICLIGHTMAP_ON + output.dynamicLightmapUV = input.dynamicLightmapUV.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw; + #endif + OUTPUT_SH(output.normalWS.xyz, output.vertexSH); + #ifdef _ADDITIONAL_LIGHTS_VERTEX + output.fogFactorAndVertexLight = half4(fogFactor, vertexLight); + #else + output.fogFactor = fogFactor; + #endif + + output.positionWS = vertexInput.positionWS; + + #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) + output.shadowCoord = GetShadowCoord(vertexInput); + #endif + + output.positionCS = vertexInput.positionCS; + output.uv = TRANSFORM_TEX(input.texcoord, _MainTex); + + return output; + } + + half4 frag(Varyings input) : SV_Target + { + UNITY_SETUP_INSTANCE_ID(input); + UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input); + + // 基础贴图:rgb 漫反射,a 透明或 AO(由 _ALPHATEST_ON 决定语义) + half4 albedoAlpha = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv); + // 混合贴图:rg 压缩法线,b 金属度,a 粗糙度 + half4 mixTex = SAMPLE_TEXTURE2D(_MixTex, sampler_MixTex, input.uv); + + #if defined(_ALPHATEST_ON) + clip(albedoAlpha.a - _Cutoff); + half occlusion = 1.0; + half alpha = albedoAlpha.a; + #else + half occlusion = lerp(1.0, albedoAlpha.a, _AO); + half alpha = 1.0; + #endif + + half3 normalTS = DecodeSphereMap(mixTex.rg); + half3 normalWS = TransformTangentToWorld(normalTS, + half3x3(input.tangentWS.xyz, input.bitangentWS.xyz, input.normalWS.xyz)); + + SurfaceData surfaceData = (SurfaceData)0; + surfaceData.albedo = albedoAlpha.rgb * _BaseColor.rgb; + surfaceData.metallic = saturate(mixTex.b * _Metallic); + surfaceData.smoothness = 1.0 - saturate(mixTex.a * _Roughness); + surfaceData.alpha = alpha; + surfaceData.normalTS = normalTS; + surfaceData.specular = half3(0.0, 0.0, 0.0); + surfaceData.emission = half3(0.0, 0.0, 0.0); + surfaceData.occlusion = occlusion; + surfaceData.clearCoatMask = half(0.0); + surfaceData.clearCoatSmoothness = half(0.0); + + InputData inputData; + input.normalWS.rgb = normalWS;//法线需要传入 + InitializeInputData(input, surfaceData.normalTS, inputData); + + half4 color = UniversalFragmentPBR(inputData, surfaceData); + + color.rgb = MixFog(color.rgb, inputData.fogCoord); + return color; + } + + ENDHLSL + } + + Pass + { + Name "ShadowCaster" + Tags{ "LightMode" = "ShadowCaster" } + + ZWrite On + ZTest LEqual + ColorMask 0 + + HLSLPROGRAM + // Required to compile gles 2.0 with standard srp library + #pragma prefer_hlslcc gles + #pragma exclude_renderers d3d11_9x + #pragma target 2.0 + + // ------------------------------------- + // Material Keywords + #pragma shader_feature_local_fragment _ALPHATEST_ON + + //-------------------------------------- + // GPU Instancing + #pragma multi_compile_instancing + + #pragma vertex vert + #pragma fragment frag + + #include "XPbr_Base.hlsl" + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl" + + float3 _LightDirection; + + 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 = TransformObjectToWorld(input.positionOS.xyz); + float3 normalWS = TransformObjectToWorldNormal(input.normalOS); + + float4 positionCS = TransformWorldToHClip(ApplyShadowBias(positionWS, normalWS, _LightDirection)); + + #if UNITY_REVERSED_Z + positionCS.z = min(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE); + #else + positionCS.z = max(positionCS.z, positionCS.w * UNITY_NEAR_CLIP_VALUE); + #endif + + output.positionCS = positionCS; + output.uv = TRANSFORM_TEX(input.texcoord, _MainTex); + + return output; + } + + half4 frag(Varyings input) : SV_TARGET + { + #if defined(_ALPHATEST_ON) + half alpha = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv).a; + clip(alpha - _Cutoff); + #endif + return 0; + } + ENDHLSL + } + } + + FallBack Off +} diff --git a/Client/Assets/Game/shader/XShader/XPbr.shader.meta b/Client/Assets/Game/shader/XShader/XPbr.shader.meta new file mode 100644 index 00000000..8e7386e1 --- /dev/null +++ b/Client/Assets/Game/shader/XShader/XPbr.shader.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cab897163f45445fa34263d6670bfc9e +ShaderImporter: + externalObjects: {} + defaultTextures: + - _MainTex: {instanceID: 0} + - _MixTex: {instanceID: 0} + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl b/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl new file mode 100644 index 00000000..6744f6d7 --- /dev/null +++ b/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl @@ -0,0 +1,141 @@ +#ifndef XPBR_LIT_BASE +#define XPBR_LIT_BASE + +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" +#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl" +#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" + +CBUFFER_START(UnityPerMaterial) +float4 _MainTex_ST; +half4 _BaseColor; +half _Cutoff; +half _Metallic; +half _Roughness; +half _AO; +CBUFFER_END +half _XRuntimeAmbientEnabled; +half4 _XRuntimeAmbientSky; +half4 _XRuntimeAmbientEquator; +half4 _XRuntimeAmbientGround; +TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); +TEXTURE2D(_MixTex); SAMPLER(sampler_MixTex); + + +struct Attributes +{ + float4 positionOS : POSITION; + float3 normalOS : NORMAL; + float4 tangentOS : TANGENT; + float2 texcoord : TEXCOORD0; + float2 staticLightmapUV : TEXCOORD1; +#ifdef DYNAMICLIGHTMAP_ON + float2 dynamicLightmapUV : TEXCOORD2; +#endif + UNITY_VERTEX_INPUT_INSTANCE_ID +}; + +struct Varyings +{ + float2 uv : TEXCOORD0; + DECLARE_LIGHTMAP_OR_SH(staticLightmapUV, vertexSH, 1); + + float3 positionWS : TEXCOORD2; + + half4 normalWS : TEXCOORD3; // xyz: normal, w: viewDir.x + half4 tangentWS : TEXCOORD4; // xyz: tangent, w: viewDir.y + half4 bitangentWS : TEXCOORD5; // xyz: bitangent, w: viewDir.z + +#ifdef _ADDITIONAL_LIGHTS_VERTEX + half4 fogFactorAndVertexLight : TEXCOORD6; // x: fogFactor, yzw: vertex light +#else + half fogFactor : TEXCOORD6; +#endif + +#ifdef DYNAMICLIGHTMAP_ON + float2 dynamicLightmapUV : TEXCOORD7; +#endif + + float4 positionCS : SV_POSITION; + #if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) + float4 shadowCoord : TEXCOORD8; + #endif + + UNITY_VERTEX_INPUT_INSTANCE_ID + UNITY_VERTEX_OUTPUT_STEREO +}; + +//解码 RG 两通道球面映射压缩法线(与 XTex 的 MixTex 资源格式一致) +float3 DecodeSphereMap(float2 encoded) +{ + float4 nn = float4(encoded, 1, -1); + float l = dot(nn.xyz, -nn.xyw); + nn.z = l; + nn.xy *= sqrt(l); + return nn.xyz * 2 + float3(0, 0, -1); +} + +half3 SampleXRuntimeAmbient(half3 normalWS) +{ + half y = normalize(normalWS).y; + half useGlobal = step(0.5h, _XRuntimeAmbientEnabled); + half3 sky = lerp(half3(1.353h, 1.452h, 1.568h), _XRuntimeAmbientSky.rgb, useGlobal); + half3 equator = lerp(half3(1.023h, 1.122h, 1.254h), _XRuntimeAmbientEquator.rgb, useGlobal); + half3 ground = lerp(half3(0.561h, 0.528h, 0.462h), _XRuntimeAmbientGround.rgb, useGlobal); + half3 upper = lerp(equator, sky, saturate(y)); + half3 lower = lerp(equator, ground, saturate(-y)); + return lerp(lower, upper, step(0.0h, y)); +} + +void InitializeInputData(Varyings input, half3 normalTS, out InputData inputData) +{ + inputData = (InputData)0; + + inputData.positionWS = input.positionWS; + + half3 viewDirWS = GetWorldSpaceNormalizeViewDir(input.positionWS); + // 法线已在 frag 中经 TBN 转换到世界空间后写回 input.normalWS + inputData.normalWS = input.normalWS.xyz; + + inputData.normalWS = NormalizeNormalPerPixel(inputData.normalWS); + inputData.viewDirectionWS = viewDirWS; + +#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR) + inputData.shadowCoord = input.shadowCoord; +#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS) + inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS); +#else + inputData.shadowCoord = float4(0, 0, 0, 0); +#endif +#ifdef _ADDITIONAL_LIGHTS_VERTEX + inputData.fogCoord = InitializeInputDataFog(float4(input.positionWS, 1.0), input.fogFactorAndVertexLight.x); + inputData.vertexLighting = input.fogFactorAndVertexLight.yzw; +#else + inputData.fogCoord = InitializeInputDataFog(float4(input.positionWS, 1.0), input.fogFactor); +#endif + +#if defined(DYNAMICLIGHTMAP_ON) + inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.dynamicLightmapUV, input.vertexSH, inputData.normalWS); +#elif defined(LIGHTMAP_ON) + inputData.bakedGI = SAMPLE_GI(input.staticLightmapUV, input.vertexSH, inputData.normalWS); +#else + inputData.bakedGI = SampleSH(inputData.normalWS); +#endif + inputData.bakedGI = SampleXRuntimeAmbient(inputData.normalWS); + + inputData.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(input.positionCS); + inputData.shadowMask = SAMPLE_SHADOWMASK(input.staticLightmapUV); + + #if defined(DEBUG_DISPLAY) + #if defined(DYNAMICLIGHTMAP_ON) + inputData.dynamicLightmapUV = input.dynamicLightmapUV; + #endif + #if defined(LIGHTMAP_ON) + inputData.staticLightmapUV = input.staticLightmapUV; + #else + inputData.vertexSH = input.vertexSH; + #endif + #endif +} + +#endif diff --git a/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl.meta b/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl.meta new file mode 100644 index 00000000..5afeb216 --- /dev/null +++ b/Client/Assets/Game/shader/XShader/XPbr_Base.hlsl.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: eca966a3db5d4e8aaa2b324e0dfc9f92 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + preprocessorOverride: 0 + userData: + assetBundleName: + assetBundleVariant: