diff --git a/engine/src/Rendering/Internal/ShaderVariantUtils.h b/engine/src/Rendering/Internal/ShaderVariantUtils.h index 207a0f7a..d4defc63 100644 --- a/engine/src/Rendering/Internal/ShaderVariantUtils.h +++ b/engine/src/Rendering/Internal/ShaderVariantUtils.h @@ -276,42 +276,97 @@ inline bool TryBuildRuntimeShaderBindings( return false; } + auto sortBindingIndices = [&outBindings](auto&& predicate) { + std::vector indices; + indices.reserve(outBindings.Size()); + for (size_t index = 0; index < outBindings.Size(); ++index) { + if (predicate(outBindings[index])) { + indices.push_back(index); + } + } + + std::sort( + indices.begin(), + indices.end(), + [&outBindings](size_t leftIndex, size_t rightIndex) { + const Resources::ShaderResourceBindingDesc& left = outBindings[leftIndex]; + const Resources::ShaderResourceBindingDesc& right = outBindings[rightIndex]; + if (left.set != right.set) { + return left.set < right.set; + } + return left.binding < right.binding; + }); + return indices; + }; + Core::uint32 nextConstantBufferRegister = 0; Core::uint32 nextTextureRegister = 0; Core::uint32 nextSamplerRegister = 0; Core::uint32 nextUnorderedAccessRegister = 0; Core::uint32 nextStorageBufferRegister = 0; - for (Resources::ShaderResourceBindingDesc& binding : outBindings) { - binding.set = 0; - switch (binding.type) { - case Resources::ShaderResourceType::ConstantBuffer: - binding.binding = nextConstantBufferRegister++; - break; - case Resources::ShaderResourceType::Texture2D: - case Resources::ShaderResourceType::TextureCube: - binding.binding = nextTextureRegister++; - break; - case Resources::ShaderResourceType::StructuredBuffer: - case Resources::ShaderResourceType::RawBuffer: - binding.binding = - backend == Resources::ShaderBackend::OpenGL - ? nextStorageBufferRegister++ - : nextTextureRegister++; - break; - case Resources::ShaderResourceType::Sampler: - binding.binding = nextSamplerRegister++; - break; - case Resources::ShaderResourceType::RWStructuredBuffer: - case Resources::ShaderResourceType::RWRawBuffer: - binding.binding = - backend == Resources::ShaderBackend::OpenGL - ? nextStorageBufferRegister++ - : nextUnorderedAccessRegister++; - break; - default: - binding.binding = nextUnorderedAccessRegister++; - break; - } + + const auto constantBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::ConstantBuffer; + }); + for (size_t index : constantBufferIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextConstantBufferRegister++; + } + + const auto textureIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::Texture2D || + binding.type == Resources::ShaderResourceType::TextureCube; + }); + for (size_t index : textureIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextTextureRegister++; + } + + const auto srvBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::StructuredBuffer || + binding.type == Resources::ShaderResourceType::RawBuffer; + }); + for (size_t index : srvBufferIndices) { + outBindings[index].set = 0; + outBindings[index].binding = + backend == Resources::ShaderBackend::OpenGL + ? nextStorageBufferRegister++ + : nextTextureRegister++; + } + + const auto samplerIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::Sampler; + }); + for (size_t index : samplerIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextSamplerRegister++; + } + + const auto uavIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type == Resources::ShaderResourceType::RWStructuredBuffer || + binding.type == Resources::ShaderResourceType::RWRawBuffer; + }); + for (size_t index : uavIndices) { + outBindings[index].set = 0; + outBindings[index].binding = + backend == Resources::ShaderBackend::OpenGL + ? nextStorageBufferRegister++ + : nextUnorderedAccessRegister++; + } + + const auto fallbackUavIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) { + return binding.type != Resources::ShaderResourceType::ConstantBuffer && + binding.type != Resources::ShaderResourceType::Texture2D && + binding.type != Resources::ShaderResourceType::TextureCube && + binding.type != Resources::ShaderResourceType::StructuredBuffer && + binding.type != Resources::ShaderResourceType::RawBuffer && + binding.type != Resources::ShaderResourceType::Sampler && + binding.type != Resources::ShaderResourceType::RWStructuredBuffer && + binding.type != Resources::ShaderResourceType::RWRawBuffer; + }); + for (size_t index : fallbackUavIndices) { + outBindings[index].set = 0; + outBindings[index].binding = nextUnorderedAccessRegister++; } return true; diff --git a/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png b/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png new file mode 100644 index 00000000..178e0bb2 Binary files /dev/null and b/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png differ diff --git a/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png.meta b/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png.meta new file mode 100644 index 00000000..32b9ce5d --- /dev/null +++ b/project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 1 +guid: e632e900c73bd6cdc4c51951e1ab02b6 +folderAsset: false +importer: TextureImporter +importerVersion: 9 diff --git a/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png b/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png new file mode 100644 index 00000000..48be26b6 Binary files /dev/null and b/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png differ diff --git a/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png.meta b/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png.meta new file mode 100644 index 00000000..4ab338df --- /dev/null +++ b/project/Assets/Models/nahida/Avatar_Tex_MetalMap.png.meta @@ -0,0 +1,5 @@ +fileFormatVersion: 1 +guid: b1928595729c1c6863c89d17b054288b +folderAsset: false +importer: TextureImporter +importerVersion: 9 diff --git a/project/Assets/Shaders/Toon.shader b/project/Assets/Shaders/Toon.shader index 9e38dbea..e9970c25 100644 --- a/project/Assets/Shaders/Toon.shader +++ b/project/Assets/Shaders/Toon.shader @@ -491,19 +491,25 @@ Shader "Toon" return lerp(faceShadow, 1.0f, faceMask); } - float3 GetShadowColor(float shadow, float materialSelector) + float ResolveShadowRampIndex(float materialSelector) { float rampIndex = 4.0f; rampIndex = lerp(rampIndex, 1.0f, step(0.2f, materialSelector)); rampIndex = lerp(rampIndex, 2.0f, step(0.4f, materialSelector)); rampIndex = lerp(rampIndex, 0.0f, step(0.6f, materialSelector)); rampIndex = lerp(rampIndex, 3.0f, step(0.8f, materialSelector)); + return rampIndex; + } + float3 GetShadowColor(float shadow, float materialSelector) + { + const float rampIndex = ResolveShadowRampIndex(materialSelector); const float rangeMin = 0.5f + _ShadowOffset.x - _ShadowSmoothness.x; const float rangeMax = 0.5f + _ShadowOffset.x; + const float rampV = rampIndex / 10.0f + 0.5f * saturate(_IsDay.x) + 0.05f; const float2 rampUv = float2( smoothstep(rangeMin, rangeMax, shadow), - rampIndex / 10.0f + 0.5f * saturate(_IsDay.x) + 0.05f); + rampV); const float3 shadowRamp = _ShadowRamp.Sample(LinearClampSampler, rampUv).rgb; float3 shadowColor = @@ -559,19 +565,22 @@ Shader "Toon" } const float3 normalWS = ComputeSurfaceNormalWS(input, texcoord); + const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord); float3 color = albedo; if (gMainLightColorAndFlags.a >= 0.5f) { const float3 lightDirectionWS = GetAdjustedMainLightDirection(); - const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord); const float materialSelector = ResolveMaterialSelector(lightMap.a); + const float aoFactor = lightMap.g * input.color.x; float shadow = UseFaceFeature() ? GetFaceShadow(input, texcoord, lightDirectionWS) - : GetGenericShadow(normalWS, lightDirectionWS, lightMap.g * input.color.x); - + : GetGenericShadow(normalWS, lightDirectionWS, aoFactor); const float3 shadowColor = GetShadowColor(shadow, materialSelector); float3 specular = float3(0.0f, 0.0f, 0.0f); + if (UseSpecularFeature()) { + specular = GetSpecular(input, normalWS, lightDirectionWS, albedo, lightMap.rgb); + } float3 emission = float3(0.0f, 0.0f, 0.0f); if (UseEmissionFeature()) { @@ -586,7 +595,7 @@ Shader "Toon" color = albedo * shadowColor + specular + emission + rim; } - return float4(saturate(color), 1.0f); + return float4(color, 1.0f); } ENDHLSL SubShader diff --git a/project/Assets/Shaders/XCCharacterToon.shader b/project/Assets/Shaders/XCCharacterToon.shader deleted file mode 100644 index de0d4d36..00000000 --- a/project/Assets/Shaders/XCCharacterToon.shader +++ /dev/null @@ -1,599 +0,0 @@ -Shader "XC Character Toon" -{ - Properties - { - _BaseMap ("Base Map", 2D) = "white" [Semantic(BaseColorTexture)] - _BaseColor ("Base Color", Color) = (1,1,1,1) [Semantic(BaseColor)] - _Cutoff ("Alpha Cutoff", Range) = 0.5 [Semantic(AlphaCutoff)] - _IsDay ("Is Day", Float) = 1 - _DoubleSided ("Double Sided", Float) = 0 - _LightMap ("Light Map", 2D) = "white" - _LightDirectionMultiplier ("Light Direction Multiplier", Vector) = (1,0.5,1,0) - _ShadowOffset ("Shadow Offset", Float) = 0.1 - _ShadowSmoothness ("Shadow Smoothness", Float) = 0.4 - _ShadowColor ("Shadow Color", Color) = (1.1,1.1,1.1,1) - _ShadowRamp ("Shadow Ramp", 2D) = "white" - _UseCustomMaterialType ("Use Custom Material Type", Float) = 0 - _CustomMaterialType ("Custom Material Type", Float) = 1 - _UseEmission ("Use Emission", Float) = 0 - _EmissionIntensity ("Emission Intensity", Float) = 0.2 - _UseNormalMap ("Use Normal Map", Float) = 0 - _NormalMap ("Normal Map", 2D) = "bump" - _IsFace ("Is Face", Float) = 0 - _FaceDirection ("Face Direction", Vector) = (0,0,1,0) - _FaceShadowOffset ("Face Shadow Offset", Float) = 0 - _FaceBlushColor ("Face Blush Color", Color) = (1,0.72156864,0.69803923,1) - _FaceBlushStrength ("Face Blush Strength", Float) = 0 - _FaceLightMap ("Face Light Map", 2D) = "white" - _FaceShadow ("Face Shadow", 2D) = "white" - _UseSpecular ("Use Specular", Float) = 0 - _SpecularSmoothness ("Specular Smoothness", Float) = 5 - _NonmetallicIntensity ("Nonmetallic Intensity", Float) = 0.3 - _MetallicIntensity ("Metallic Intensity", Float) = 8 - _MetalMap ("Metal Map", 2D) = "white" - _UseRim ("Use Rim", Float) = 0 - _RimOffset ("Rim Offset", Float) = 5 - _RimThreshold ("Rim Threshold", Float) = 0.5 - _RimIntensity ("Rim Intensity", Float) = 0.5 - _UseSmoothNormal ("Use Smooth Normal", Float) = 0 - _OutlineWidth ("Outline Width", Float) = 1.6 - _OutlineWidthParams ("Outline Width Params", Vector) = (0,6,0.1,0.6) - _OutlineZOffset ("Outline Z Offset", Float) = 0.1 - _ScreenOffset ("Screen Offset", Vector) = (0,0,0,0) - _OutlineColor ("Outline Color", Color) = (0.5176471,0.35686275,0.34117648,1) - _OutlineColor2 ("Outline Color 2", Color) = (0.3529412,0.3529412,0.3529412,1) - _OutlineColor3 ("Outline Color 3", Color) = (0.47058824,0.47058824,0.5647059,1) - _OutlineColor4 ("Outline Color 4", Color) = (0.5176471,0.35686275,0.34117648,1) - _OutlineColor5 ("Outline Color 5", Color) = (0.35,0.35,0.35,1) - } - HLSLINCLUDE - cbuffer PerObjectConstants - { - float4x4 gProjectionMatrix; - float4x4 gViewMatrix; - float4x4 gModelMatrix; - float4x4 gNormalMatrix; - }; - - static const int XC_MAX_ADDITIONAL_LIGHTS = 8; - - struct AdditionalLightData - { - float4 colorAndIntensity; - float4 positionAndRange; - float4 directionAndType; - float4 spotAnglesAndFlags; - }; - - struct ShadowMapMetrics - { - float2 inverseMapSize; - float worldTexelSize; - float padding; - }; - - struct ShadowSamplingData - { - float enabled; - float receiverDepthBias; - float normalBiasScale; - float shadowStrength; - }; - - cbuffer LightingConstants - { - float4 gMainLightDirectionAndIntensity; - float4 gMainLightColorAndFlags; - float4 gLightingParams; - AdditionalLightData gAdditionalLights[XC_MAX_ADDITIONAL_LIGHTS]; - }; - - cbuffer MaterialConstants - { - float4 _BaseColor; - float4 _Cutoff; - float4 _IsDay; - float4 _DoubleSided; - float4 _LightDirectionMultiplier; - float4 _ShadowOffset; - float4 _ShadowSmoothness; - float4 _ShadowColor; - float4 _UseCustomMaterialType; - float4 _CustomMaterialType; - float4 _UseEmission; - float4 _EmissionIntensity; - float4 _UseNormalMap; - float4 _IsFace; - float4 _FaceDirection; - float4 _FaceShadowOffset; - float4 _FaceBlushColor; - float4 _FaceBlushStrength; - float4 _UseSpecular; - float4 _SpecularSmoothness; - float4 _NonmetallicIntensity; - float4 _MetallicIntensity; - float4 _UseRim; - float4 _RimOffset; - float4 _RimThreshold; - float4 _RimIntensity; - float4 _UseSmoothNormal; - float4 _OutlineWidth; - float4 _OutlineWidthParams; - float4 _OutlineZOffset; - float4 _ScreenOffset; - float4 _OutlineColor; - float4 _OutlineColor2; - float4 _OutlineColor3; - float4 _OutlineColor4; - float4 _OutlineColor5; - }; - - cbuffer ShadowReceiverConstants - { - float4x4 gWorldToShadowMatrix; - ShadowMapMetrics gShadowMapMetrics; - ShadowSamplingData gShadowSampling; - }; - - Texture2D BaseColorTexture; - Texture2D _LightMap; - Texture2D _ShadowRamp; - Texture2D _NormalMap; - Texture2D _FaceLightMap; - Texture2D _FaceShadow; - Texture2D _MetalMap; - SamplerState LinearClampSampler; - Texture2D ShadowMapTexture; - SamplerState ShadowMapSampler; - - struct VSInput - { - float3 position : POSITION; - float3 normal : NORMAL; - float4 color : COLOR; - float2 texcoord : TEXCOORD0; - float2 backTexcoord : TEXCOORD1; - float3 tangent : TEXCOORD2; - float3 bitangent : TEXCOORD3; - }; - - struct PSInput - { - float4 position : SV_POSITION; - float2 texcoord : TEXCOORD0; - float2 backTexcoord : TEXCOORD1; - float3 positionWS : TEXCOORD2; - float3 positionVS : TEXCOORD3; - float3 normalWS : TEXCOORD4; - float3 tangentWS : TEXCOORD5; - float3 bitangentWS : TEXCOORD6; - float4 color : COLOR; - }; - - float3 NormalizeSafe(float3 value, float3 fallbackValue) - { - const float lengthSq = dot(value, value); - if (lengthSq <= 1e-6f) { - return fallbackValue; - } - - return value * rsqrt(lengthSq); - } - - bool UseEmissionFeature() - { - #ifdef _EMISSION - return true; - #else - return _UseEmission.x > 0.5f; - #endif - } - - bool UseNormalMapFeature() - { - #ifdef _NORMAL_MAP - return true; - #else - return _UseNormalMap.x > 0.5f; - #endif - } - - bool UseFaceFeature() - { - #ifdef _IS_FACE - return true; - #else - return _IsFace.x > 0.5f; - #endif - } - - bool UseSpecularFeature() - { - #ifdef _SPECULAR - return true; - #else - return _UseSpecular.x > 0.5f; - #endif - } - - bool UseRimFeature() - { - #ifdef _RIM - return true; - #else - return _UseRim.x > 0.5f; - #endif - } - - bool UseDoubleSidedFeature() - { - #ifdef _DOUBLE_SIDED - return true; - #else - return _DoubleSided.x > 0.5f; - #endif - } - - float2 ResolveMaterialTexcoord(PSInput input, bool isFrontFace) - { - if (!UseDoubleSidedFeature()) { - return input.texcoord; - } - - return lerp(input.texcoord, input.backTexcoord, isFrontFace ? 0.0f : 1.0f); - } - - PSInput MainVS(VSInput input) - { - PSInput output; - const float4 positionWS = mul(gModelMatrix, float4(input.position, 1.0f)); - const float4 positionVS = mul(gViewMatrix, positionWS); - output.position = mul(gProjectionMatrix, positionVS); - output.texcoord = input.texcoord; - output.backTexcoord = input.backTexcoord; - output.positionWS = positionWS.xyz; - output.positionVS = positionVS.xyz; - output.normalWS = normalize(mul((float3x3)gNormalMatrix, input.normal)); - output.tangentWS = normalize(mul((float3x3)gNormalMatrix, input.tangent)); - output.bitangentWS = normalize(mul((float3x3)gNormalMatrix, input.bitangent)); - output.color = input.color; - return output; - } - - float ComputeShadowAttenuation(float3 positionWS, float3 normalWS, float3 lightDirectionWS) - { - #ifndef XC_MAIN_LIGHT_SHADOWS - return 1.0f; - #else - if (gShadowSampling.enabled < 0.5f) { - return 1.0f; - } - - const float3 resolvedNormalWS = NormalizeSafe(normalWS, float3(0.0f, 1.0f, 0.0f)); - const float3 resolvedLightDirectionWS = - NormalizeSafe(lightDirectionWS, float3(0.0f, -1.0f, 0.0f)); - const float nDotL = saturate(dot(resolvedNormalWS, resolvedLightDirectionWS)); - const float normalBiasWorld = - gShadowMapMetrics.worldTexelSize * gShadowSampling.normalBiasScale * (1.0f - nDotL); - const float3 shadowPositionWS = positionWS + resolvedNormalWS * normalBiasWorld; - const float4 shadowClip = mul(gWorldToShadowMatrix, float4(shadowPositionWS, 1.0f)); - if (shadowClip.w <= 0.0f) { - return 1.0f; - } - - const float3 shadowNdc = shadowClip.xyz / shadowClip.w; - #if UNITY_UV_STARTS_AT_TOP - const float shadowUvY = shadowNdc.y * -0.5f + 0.5f; - #else - const float shadowUvY = shadowNdc.y * 0.5f + 0.5f; - #endif - const float2 shadowUv = float2(shadowNdc.x * 0.5f + 0.5f, shadowUvY); - #if UNITY_NEAR_CLIP_VALUE < 0 - if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || - shadowUv.y < 0.0f || shadowUv.y > 1.0f || - shadowNdc.z < -1.0f || shadowNdc.z > 1.0f) { - return 1.0f; - } - - const float receiverDepth = shadowNdc.z * 0.5f + 0.5f - gShadowSampling.receiverDepthBias; - #else - if (shadowUv.x < 0.0f || shadowUv.x > 1.0f || - shadowUv.y < 0.0f || shadowUv.y > 1.0f || - shadowNdc.z < 0.0f || shadowNdc.z > 1.0f) { - return 1.0f; - } - - const float receiverDepth = shadowNdc.z - gShadowSampling.receiverDepthBias; - #endif - const float2 shadowTexelSize = gShadowMapMetrics.inverseMapSize; - float visibility = 0.0f; - [unroll] - for (int offsetY = -1; offsetY <= 1; ++offsetY) { - [unroll] - for (int offsetX = -1; offsetX <= 1; ++offsetX) { - const float2 sampleUv = - shadowUv + float2((float)offsetX, (float)offsetY) * shadowTexelSize; - if (sampleUv.x < 0.0f || sampleUv.x > 1.0f || - sampleUv.y < 0.0f || sampleUv.y > 1.0f) { - visibility += 1.0f; - continue; - } - - const float shadowDepth = ShadowMapTexture.Sample(ShadowMapSampler, sampleUv).r; - visibility += receiverDepth <= shadowDepth ? 1.0f : 0.0f; - } - } - - visibility *= (1.0f / 9.0f); - const float shadowStrength = saturate(gShadowSampling.shadowStrength); - return lerp(1.0f - shadowStrength, 1.0f, visibility); - #endif - } - - float ComputeRangeAttenuation(float distanceSq, float range) - { - if (range <= 0.0f) { - return 0.0f; - } - - const float clampedRange = max(range, 0.0001f); - const float rangeSq = clampedRange * clampedRange; - if (distanceSq >= rangeSq) { - return 0.0f; - } - - const float distance = sqrt(max(distanceSq, 0.0f)); - const float normalized = saturate(1.0f - distance / clampedRange); - return normalized * normalized; - } - - float ComputeSpotAttenuation(AdditionalLightData light, float3 directionToLightWS) - { - const float cosOuter = light.spotAnglesAndFlags.x; - const float cosInner = light.spotAnglesAndFlags.y; - const float3 spotAxisToLightWS = NormalizeSafe(light.directionAndType.xyz, float3(0.0f, -1.0f, 0.0f)); - const float cosTheta = dot(spotAxisToLightWS, directionToLightWS); - return saturate((cosTheta - cosOuter) / max(cosInner - cosOuter, 1e-4f)); - } - - float3 EvaluateAdditionalLight(AdditionalLightData light, float3 normalWS, float3 positionWS) - { - const float lightType = light.directionAndType.w; - const float3 lightColor = light.colorAndIntensity.rgb; - const float lightIntensity = light.colorAndIntensity.w; - - float3 directionToLightWS = float3(0.0f, 0.0f, 0.0f); - float attenuation = 1.0f; - - if (lightType < 0.5f) { - directionToLightWS = NormalizeSafe(light.directionAndType.xyz, float3(0.0f, -1.0f, 0.0f)); - } else { - const float3 lightVectorWS = light.positionAndRange.xyz - positionWS; - const float distanceSq = dot(lightVectorWS, lightVectorWS); - if (distanceSq <= 1e-6f) { - return float3(0.0f, 0.0f, 0.0f); - } - - directionToLightWS = lightVectorWS * rsqrt(distanceSq); - attenuation = ComputeRangeAttenuation(distanceSq, light.positionAndRange.w); - if (attenuation <= 0.0f) { - return float3(0.0f, 0.0f, 0.0f); - } - - if (lightType > 1.5f) { - attenuation *= ComputeSpotAttenuation(light, directionToLightWS); - if (attenuation <= 0.0f) { - return float3(0.0f, 0.0f, 0.0f); - } - } - } - - const float diffuse = saturate(dot(normalWS, directionToLightWS)); - if (diffuse <= 0.0f) { - return float3(0.0f, 0.0f, 0.0f); - } - - return lightColor * (diffuse * lightIntensity * attenuation); - } - - float3 ComputeSurfaceNormalWS(PSInput input, float2 texcoord) - { - float3 normalWS = NormalizeSafe(input.normalWS, float3(0.0f, 1.0f, 0.0f)); - if (!UseNormalMapFeature()) { - return normalWS; - } - - const float3 tangentWS = NormalizeSafe(input.tangentWS, float3(1.0f, 0.0f, 0.0f)); - const float3 bitangentWS = NormalizeSafe(input.bitangentWS, float3(0.0f, 0.0f, 1.0f)); - const float3 normalTS = _NormalMap.Sample(LinearClampSampler, texcoord).xyz * 2.0f - 1.0f; - const float3 mappedNormalWS = - tangentWS * normalTS.x + - bitangentWS * normalTS.y + - normalWS * normalTS.z; - return NormalizeSafe(mappedNormalWS, normalWS); - } - - float3 GetAdjustedMainLightDirection() - { - const float3 scaledDirection = - gMainLightDirectionAndIntensity.xyz * _LightDirectionMultiplier.xyz; - return NormalizeSafe( - scaledDirection, - NormalizeSafe(gMainLightDirectionAndIntensity.xyz, float3(0.0f, -1.0f, 0.0f))); - } - - float ResolveMaterialSelector(float lightMapAlpha) - { - const float useCustomMaterialType = _UseCustomMaterialType.x > 0.5f ? 1.0f : 0.0f; - return lerp(lightMapAlpha, _CustomMaterialType.x, useCustomMaterialType); - } - - float GetGenericShadow(float3 normalWS, float3 lightDirectionWS, float aoFactor) - { - const float ndotl = dot(normalWS, lightDirectionWS); - const float halfLambert = ndotl * 0.5f + 0.5f; - const float shadow = saturate(2.0f * halfLambert * aoFactor); - return lerp(shadow, 1.0f, step(0.9f, aoFactor)); - } - - float GetFaceShadow(PSInput input, float2 texcoord, float3 lightDirectionWS) - { - const float3 faceDirection = NormalizeSafe( - float3(_FaceDirection.x, 0.0f, _FaceDirection.z), - float3(0.0f, 0.0f, 1.0f)); - const float3 flatLightDirection = NormalizeSafe( - float3(lightDirectionWS.x, 0.0f, lightDirectionWS.z), - float3(0.0f, 0.0f, 1.0f)); - const float faceDotLight = dot(faceDirection, flatLightDirection); - const float faceCrossLight = cross(faceDirection, flatLightDirection).y; - - float2 shadowUv = texcoord; - shadowUv.x = lerp(shadowUv.x, 1.0f - shadowUv.x, step(0.0f, faceCrossLight)); - - const float faceShadowMap = _FaceLightMap.Sample(LinearClampSampler, shadowUv).r; - const float faceShadow = step(-0.5f * faceDotLight + 0.5f + _FaceShadowOffset.x, faceShadowMap); - const float faceMask = _FaceShadow.Sample(LinearClampSampler, texcoord).a; - return lerp(faceShadow, 1.0f, faceMask); - } - - float3 GetShadowColor(float shadow, float materialSelector) - { - float rampIndex = 4.0f; - rampIndex = lerp(rampIndex, 1.0f, step(0.2f, materialSelector)); - rampIndex = lerp(rampIndex, 2.0f, step(0.4f, materialSelector)); - rampIndex = lerp(rampIndex, 0.0f, step(0.6f, materialSelector)); - rampIndex = lerp(rampIndex, 3.0f, step(0.8f, materialSelector)); - - const float rangeMin = 0.5f + _ShadowOffset.x - _ShadowSmoothness.x; - const float rangeMax = 0.5f + _ShadowOffset.x; - const float2 rampUv = float2( - smoothstep(rangeMin, rangeMax, shadow), - rampIndex / 10.0f + 0.5f * saturate(_IsDay.x) + 0.05f); - const float3 shadowRamp = _ShadowRamp.Sample(LinearClampSampler, rampUv).rgb; - - float3 shadowColor = - shadowRamp * lerp(_ShadowColor.rgb, float3(1.0f, 1.0f, 1.0f), smoothstep(0.9f, 1.0f, rampUv.x)); - shadowColor = lerp(shadowColor, float3(1.0f, 1.0f, 1.0f), step(rangeMax, shadow)); - return shadowColor; - } - - float3 GetSpecular( - PSInput input, - float3 normalWS, - float3 lightDirectionWS, - float3 albedo, - float3 lightMap) - { - const float3 viewDirectionVS = NormalizeSafe(-input.positionVS, float3(0.0f, 0.0f, 1.0f)); - const float3 lightDirectionVS = NormalizeSafe( - mul((float3x3)gViewMatrix, lightDirectionWS), - float3(0.0f, 0.0f, 1.0f)); - const float3 normalVS = NormalizeSafe(mul((float3x3)gViewMatrix, normalWS), float3(0.0f, 0.0f, 1.0f)); - const float3 halfDirectionVS = NormalizeSafe(lightDirectionVS + viewDirectionVS, lightDirectionVS); - const float nDotH = dot(normalVS, halfDirectionVS); - const float blinnPhong = pow(saturate(nDotH), max(_SpecularSmoothness.x, 1.0f)); - - const float2 matcapUv = normalVS.xy * 0.5f + 0.5f; - const float3 metalMap = _MetalMap.Sample(LinearClampSampler, matcapUv).rgb; - - const float3 nonMetallic = - step(1.1f, lightMap.b + blinnPhong) * lightMap.r * _NonmetallicIntensity.x; - const float3 metallic = - blinnPhong * lightMap.b * albedo * metalMap * _MetallicIntensity.x; - return lerp(nonMetallic, metallic, step(0.9f, lightMap.r)); - } - - float GetRim(PSInput input, float3 normalWS) - { - const float3 viewDirectionVS = NormalizeSafe(-input.positionVS, float3(0.0f, 0.0f, 1.0f)); - const float3 normalVS = NormalizeSafe(mul((float3x3)gViewMatrix, normalWS), float3(0.0f, 0.0f, 1.0f)); - const float nDotV = dot(normalVS, viewDirectionVS); - const float fresnel = pow(saturate(1.0f - nDotV), max(_RimOffset.x, 0.001f)); - return smoothstep(0.0f, max(_RimThreshold.x, 0.001f), fresnel) * _RimIntensity.x; - } - - float4 MainPS(PSInput input, bool isFrontFace : SV_IsFrontFace) : SV_TARGET - { - const float2 texcoord = ResolveMaterialTexcoord(input, isFrontFace); - float4 baseMap = BaseColorTexture.Sample(LinearClampSampler, texcoord); - float3 albedo = baseMap.rgb * _BaseColor.rgb; - const float alphaMask = baseMap.a; - - if (UseFaceFeature()) { - albedo = lerp(albedo, _FaceBlushColor.rgb, _FaceBlushStrength.x * alphaMask); - } - - const float3 normalWS = ComputeSurfaceNormalWS(input, texcoord); - - float3 color = albedo; - if (gMainLightColorAndFlags.a >= 0.5f) { - const float3 lightDirectionWS = GetAdjustedMainLightDirection(); - const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord); - const float materialSelector = ResolveMaterialSelector(lightMap.a); - float shadow = UseFaceFeature() - ? GetFaceShadow(input, texcoord, lightDirectionWS) - : GetGenericShadow(normalWS, lightDirectionWS, lightMap.g * input.color.x); - shadow *= ComputeShadowAttenuation(input.positionWS, normalWS, lightDirectionWS); - - const float3 shadowColor = GetShadowColor(shadow, materialSelector); - - float3 specular = float3(0.0f, 0.0f, 0.0f); - if (UseSpecularFeature()) { - specular = GetSpecular(input, normalWS, lightDirectionWS, albedo, lightMap.rgb); - } - - float3 emission = float3(0.0f, 0.0f, 0.0f); - if (UseEmissionFeature()) { - emission = albedo * _EmissionIntensity.x * alphaMask; - } - - float3 rim = float3(0.0f, 0.0f, 0.0f); - if (UseRimFeature()) { - rim = albedo * GetRim(input, normalWS); - } - - color = albedo * shadowColor + specular + emission + rim; - } - - const int additionalLightCount = min((int)gLightingParams.x, XC_MAX_ADDITIONAL_LIGHTS); - [unroll] - for (int lightIndex = 0; lightIndex < XC_MAX_ADDITIONAL_LIGHTS; ++lightIndex) { - if (lightIndex >= additionalLightCount) { - break; - } - - color += - albedo * - EvaluateAdditionalLight(gAdditionalLights[lightIndex], normalWS, input.positionWS) * - 0.15f; - } - - return float4(saturate(color), 1.0f); - } - ENDHLSL - SubShader - { - Cull Back - ZWrite On - ZTest LEqual - Pass - { - Name "ForwardLit" - Tags { "LightMode" = "ForwardLit" } - HLSLPROGRAM - #pragma target 4.5 - #pragma vertex MainVS - #pragma fragment MainPS - #pragma multi_compile _ XC_MAIN_LIGHT_SHADOWS - #pragma shader_feature_local _ XC_ALPHA_TEST - #pragma shader_feature_local _ _EMISSION - #pragma shader_feature_local _ _NORMAL_MAP - #pragma shader_feature_local _ _IS_FACE - #pragma shader_feature_local _ _SPECULAR - #pragma shader_feature_local _ _RIM - ENDHLSL - } - UsePass "Builtin Depth Only/DepthOnly" - UsePass "Builtin Shadow Caster/ShadowCaster" - } -} diff --git a/project/Assets/Shaders/XCCharacterToon.shader.meta b/project/Assets/Shaders/XCCharacterToon.shader.meta deleted file mode 100644 index 1ac8095b..00000000 --- a/project/Assets/Shaders/XCCharacterToon.shader.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 1 -guid: 5a51f782e3c149bd8bf65c8b0e25d3a1 -folderAsset: false -importer: ShaderImporter -importerVersion: 8 diff --git a/tests/Rendering/integration/nahida_preview_scene/main.cpp b/tests/Rendering/integration/nahida_preview_scene/main.cpp index 7f1e8881..fb5f57ea 100644 --- a/tests/Rendering/integration/nahida_preview_scene/main.cpp +++ b/tests/Rendering/integration/nahida_preview_scene/main.cpp @@ -50,7 +50,7 @@ constexpr uint32_t kFrameHeight = 720; constexpr int kWarmupFrames = 45; enum class DiagnosticMode { - Original, + Toon, NoShadows, ForwardLit, Unlit @@ -59,10 +59,13 @@ enum class DiagnosticMode { DiagnosticMode GetDiagnosticMode() { const char* value = std::getenv("XC_NAHIDA_DIAG_MODE"); if (value == nullptr) { - return DiagnosticMode::Original; + return DiagnosticMode::Toon; } const std::string mode(value); + if (mode == "toon" || mode == "original") { + return DiagnosticMode::Toon; + } if (mode == "no_shadows") { return DiagnosticMode::NoShadows; } @@ -73,12 +76,12 @@ DiagnosticMode GetDiagnosticMode() { return DiagnosticMode::Unlit; } - return DiagnosticMode::Original; + return DiagnosticMode::Toon; } const char* GetDiagnosticModeName(DiagnosticMode mode) { switch (mode) { - case DiagnosticMode::Original: return "original"; + case DiagnosticMode::Toon: return "toon"; case DiagnosticMode::NoShadows: return "no_shadows"; case DiagnosticMode::ForwardLit: return "forward_lit"; case DiagnosticMode::Unlit: return "unlit"; @@ -88,7 +91,7 @@ const char* GetDiagnosticModeName(DiagnosticMode mode) { const char* GetGoldenFileName(DiagnosticMode mode) { switch (mode) { - case DiagnosticMode::Original: return "GT.ppm"; + case DiagnosticMode::Toon: return "GT.ppm"; case DiagnosticMode::NoShadows: return "GT.no_shadows.ppm"; case DiagnosticMode::ForwardLit: return "GT.forward_lit.ppm"; case DiagnosticMode::Unlit: return "GT.unlit.ppm"; @@ -96,6 +99,278 @@ const char* GetGoldenFileName(DiagnosticMode mode) { } } +constexpr const char* kToonShaderPath = "Assets/Shaders/Toon.shader"; +constexpr const char* kNahidaTextureRoot = "Assets/Models/nahida/"; + +XCEngine::Containers::String RemapMaterialPropertyName( + const Material& targetMaterial, + const XCEngine::Containers::String& sourceName) { + if (targetMaterial.HasProperty(sourceName)) { + return sourceName; + } + if (sourceName == XCEngine::Containers::String("_BaseMap") && targetMaterial.HasProperty("_MainTex")) { + return XCEngine::Containers::String("_MainTex"); + } + if (sourceName == XCEngine::Containers::String("_MainTex") && targetMaterial.HasProperty("_BaseMap")) { + return XCEngine::Containers::String("_BaseMap"); + } + if (sourceName == XCEngine::Containers::String("_Color") && targetMaterial.HasProperty("_BaseColor")) { + return XCEngine::Containers::String("_BaseColor"); + } + if (sourceName == XCEngine::Containers::String("_BaseColor") && targetMaterial.HasProperty("_Color")) { + return XCEngine::Containers::String("_Color"); + } + + return XCEngine::Containers::String(); +} + +bool IsNahidaCharacterRenderable(const GameObject* gameObject) { + if (gameObject == nullptr) { + return false; + } + + const std::string& name = gameObject->GetName(); + return name.rfind("Body_Mesh", 0) == 0 || + name == "Brow" || + name == "EyeStar" || + name == "Face" || + name == "Face_Eye"; +} + +std::string BuildNahidaTextureAssetPath(const char* fileName) { + return std::string(kNahidaTextureRoot) + fileName; +} + +void SetMaterialFloatIfPresent(Material* material, const char* name, float value) { + if (material == nullptr || name == nullptr || !material->HasProperty(name)) { + return; + } + + material->SetFloat(name, value); +} + +void SetMaterialFloat4IfPresent(Material* material, const char* name, const XCEngine::Math::Vector4& value) { + if (material == nullptr || name == nullptr || !material->HasProperty(name)) { + return; + } + + material->SetFloat4(name, value); +} + +void SetMaterialTexturePath(Material* material, const char* name, const char* fileName) { + if (material == nullptr || name == nullptr || fileName == nullptr || !material->HasProperty(name)) { + return; + } + + const std::string assetPath = BuildNahidaTextureAssetPath(fileName); + const ResourceHandle texture = ResourceManager::Get().Load(assetPath.c_str()); + if (texture.Get() != nullptr) { + material->SetTexture(name, texture); + return; + } + + material->SetTextureAssetRef(name, AssetRef(), XCEngine::Containers::String(assetPath.c_str())); +} + +void ApplyNahidaSharedToonDefaults(Material* material) { + if (material == nullptr) { + return; + } + + material->ClearKeywords(); + material->ClearTags(); + material->SetRenderStateOverrideEnabled(false); + + SetMaterialFloat4IfPresent(material, "_BaseColor", XCEngine::Math::Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + SetMaterialFloatIfPresent(material, "_Cutoff", 0.5f); + SetMaterialFloatIfPresent(material, "_IsDay", 1.0f); + SetMaterialFloatIfPresent(material, "_DoubleSided", 0.0f); + SetMaterialFloat4IfPresent(material, "_LightDirectionMultiplier", XCEngine::Math::Vector4(1.0f, 0.5f, 1.0f, 0.0f)); + SetMaterialFloatIfPresent(material, "_ShadowOffset", 0.1f); + SetMaterialFloatIfPresent(material, "_ShadowSmoothness", 0.4f); + SetMaterialFloat4IfPresent(material, "_ShadowColor", XCEngine::Math::Vector4(1.1f, 1.1f, 1.1f, 1.0f)); + SetMaterialFloatIfPresent(material, "_UseCustomMaterialType", 0.0f); + SetMaterialFloatIfPresent(material, "_CustomMaterialType", 1.0f); + SetMaterialFloatIfPresent(material, "_UseEmission", 0.0f); + SetMaterialFloatIfPresent(material, "_EmissionIntensity", 0.2f); + SetMaterialFloatIfPresent(material, "_UseNormalMap", 0.0f); + SetMaterialFloatIfPresent(material, "_IsFace", 0.0f); + SetMaterialFloat4IfPresent(material, "_FaceDirection", XCEngine::Math::Vector4(0.0f, 0.0f, 1.0f, 0.0f)); + SetMaterialFloatIfPresent(material, "_FaceShadowOffset", 0.0f); + SetMaterialFloat4IfPresent(material, "_FaceBlushColor", XCEngine::Math::Vector4(1.0f, 0.72156864f, 0.69803923f, 1.0f)); + SetMaterialFloatIfPresent(material, "_FaceBlushStrength", 0.0f); + SetMaterialFloatIfPresent(material, "_UseSpecular", 0.0f); + SetMaterialFloatIfPresent(material, "_SpecularSmoothness", 5.0f); + SetMaterialFloatIfPresent(material, "_NonmetallicIntensity", 0.3f); + SetMaterialFloatIfPresent(material, "_MetallicIntensity", 8.0f); + SetMaterialFloatIfPresent(material, "_UseRim", 0.0f); + SetMaterialFloatIfPresent(material, "_RimOffset", 5.0f); + SetMaterialFloatIfPresent(material, "_RimThreshold", 0.5f); + SetMaterialFloatIfPresent(material, "_RimIntensity", 0.5f); + SetMaterialFloatIfPresent(material, "_UseSmoothNormal", 0.0f); + SetMaterialFloatIfPresent(material, "_OutlineWidth", 1.6f); + SetMaterialFloat4IfPresent(material, "_OutlineWidthParams", XCEngine::Math::Vector4(0.0f, 6.0f, 0.1f, 0.6f)); + SetMaterialFloatIfPresent(material, "_OutlineZOffset", 0.1f); + SetMaterialFloat4IfPresent(material, "_ScreenOffset", XCEngine::Math::Vector4(0.0f, 0.0f, 0.0f, 0.0f)); + SetMaterialFloat4IfPresent(material, "_OutlineColor", XCEngine::Math::Vector4(0.5176471f, 0.35686275f, 0.34117648f, 1.0f)); + SetMaterialFloat4IfPresent(material, "_OutlineColor2", XCEngine::Math::Vector4(0.3529412f, 0.3529412f, 0.3529412f, 1.0f)); + SetMaterialFloat4IfPresent(material, "_OutlineColor3", XCEngine::Math::Vector4(0.47058824f, 0.47058824f, 0.5647059f, 1.0f)); + SetMaterialFloat4IfPresent(material, "_OutlineColor4", XCEngine::Math::Vector4(0.5176471f, 0.35686275f, 0.34117648f, 1.0f)); + SetMaterialFloat4IfPresent(material, "_OutlineColor5", XCEngine::Math::Vector4(0.35f, 0.35f, 0.35f, 1.0f)); +} + +void ApplyNahidaSurfaceRecipe( + Material* material, + const char* baseMapFile, + const char* lightMapFile, + const char* normalMapFile, + const char* shadowRampFile, + bool doubleSided, + bool useSmoothNormal, + float outlineZOffset, + const XCEngine::Math::Vector4* outlineColor = nullptr) { + if (material == nullptr) { + return; + } + + ApplyNahidaSharedToonDefaults(material); + + SetMaterialTexturePath(material, "_BaseMap", baseMapFile); + SetMaterialTexturePath(material, "_LightMap", lightMapFile); + SetMaterialTexturePath(material, "_NormalMap", normalMapFile); + SetMaterialTexturePath(material, "_ShadowRamp", shadowRampFile); + SetMaterialTexturePath(material, "_MetalMap", "Avatar_Tex_MetalMap.png"); + + SetMaterialFloatIfPresent(material, "_UseEmission", 1.0f); + SetMaterialFloatIfPresent(material, "_UseNormalMap", 1.0f); + SetMaterialFloatIfPresent(material, "_UseRim", 1.0f); + SetMaterialFloatIfPresent(material, "_UseSpecular", 1.0f); + SetMaterialFloatIfPresent(material, "_UseSmoothNormal", useSmoothNormal ? 1.0f : 0.0f); + SetMaterialFloatIfPresent(material, "_DoubleSided", doubleSided ? 1.0f : 0.0f); + if (outlineZOffset != 0.0f) { + SetMaterialFloatIfPresent(material, "_OutlineZOffset", outlineZOffset); + } + if (outlineColor != nullptr) { + SetMaterialFloat4IfPresent(material, "_OutlineColor", *outlineColor); + } + + material->EnableKeyword("_EMISSION"); + material->EnableKeyword("_NORMAL_MAP"); + material->EnableKeyword("_SPECULAR"); + material->EnableKeyword("_RIM"); + + if (doubleSided) { + MaterialRenderState renderState = material->GetRenderState(); + renderState.cullMode = MaterialCullMode::None; + material->SetRenderState(renderState); + } +} + +void ApplyNahidaFaceRecipe(Material* material, bool eyebrowVariant) { + if (material == nullptr) { + return; + } + + ApplyNahidaSharedToonDefaults(material); + + SetMaterialTexturePath(material, "_BaseMap", "Avatar_Loli_Catalyst_Nahida_Tex_Face_Diffuse.png"); + SetMaterialTexturePath(material, "_FaceLightMap", "Avatar_Loli_Tex_FaceLightmap.png"); + SetMaterialTexturePath(material, "_FaceShadow", "Avatar_Tex_Face_Shadow.png"); + SetMaterialTexturePath(material, "_ShadowRamp", "Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png"); + + SetMaterialFloatIfPresent(material, "_IsFace", 1.0f); + SetMaterialFloatIfPresent(material, "_UseCustomMaterialType", 1.0f); + SetMaterialFloatIfPresent(material, "_UseRim", 1.0f); + SetMaterialFloat4IfPresent( + material, + "_FaceDirection", + XCEngine::Math::Vector4(-0.0051237254f, -0.11509954f, -0.99334073f, 0.0f)); + + if (eyebrowVariant) { + SetMaterialFloatIfPresent(material, "_OutlineWidth", 0.0f); + SetMaterialFloat4IfPresent( + material, + "_BaseColor", + XCEngine::Math::Vector4(0.9764706f, 0.80103135f, 0.76164705f, 1.0f)); + } else { + SetMaterialFloatIfPresent(material, "_FaceBlushStrength", 0.3f); + SetMaterialFloatIfPresent(material, "_OutlineZOffset", 0.5f); + } + + material->EnableKeyword("_IS_FACE"); + material->EnableKeyword("_RIM"); +} + +void ApplyNahidaToonRecipe(Material* material, const GameObject* owner) { + if (material == nullptr || owner == nullptr) { + return; + } + + const std::string& name = owner->GetName(); + if (name == "Body_Mesh0") { + const XCEngine::Math::Vector4 outlineColor(0.2784314f, 0.18039216f, 0.14901961f, 1.0f); + ApplyNahidaSurfaceRecipe( + material, + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Diffuse.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Lightmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Normalmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Shadow_Ramp.png", + false, + true, + 0.0f, + &outlineColor); + return; + } + + if (name == "Body_Mesh1") { + ApplyNahidaSurfaceRecipe( + material, + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Diffuse.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Lightmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Normalmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png", + false, + true, + 0.0f); + return; + } + + if (name == "Body_Mesh2") { + ApplyNahidaSurfaceRecipe( + material, + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Diffuse.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Lightmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Normalmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Body_Shadow_Ramp.png", + true, + true, + 0.5f); + return; + } + + if (name == "Body_Mesh3") { + ApplyNahidaSurfaceRecipe( + material, + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Diffuse.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Lightmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Normalmap.png", + "Avatar_Loli_Catalyst_Nahida_Tex_Hair_Shadow_Ramp.png", + true, + false, + 0.5f); + return; + } + + if (name == "Brow") { + ApplyNahidaFaceRecipe(material, true); + return; + } + + if (name == "EyeStar" || name == "Face" || name == "Face_Eye") { + ApplyNahidaFaceRecipe(material, false); + } +} + std::unordered_set GetIsolationObjectNames() { std::unordered_set result; const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY"); @@ -338,7 +613,7 @@ private: void ApplyDiagnosticOverrides(); void DumpTargetDiagnostics(); RHIResourceView* GetCurrentBackBufferView(); - Material* CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath); + Material* CreateOverrideMaterial(const Material* sourceMaterial, const char* shaderPath, const GameObject* owner); HMODULE m_assimpModule = nullptr; std::unique_ptr m_scene; @@ -489,7 +764,20 @@ void NahidaPreviewSceneTest::TouchSceneMaterialsAndTextures() { continue; } - meshFilter->GetMesh(); + Mesh* mesh = meshFilter->GetMesh(); + if (mesh == nullptr) { + continue; + } + + for (Material* material : mesh->GetMaterials()) { + if (material == nullptr) { + continue; + } + + for (uint32_t bindingIndex = 0; bindingIndex < material->GetTextureBindingCount(); ++bindingIndex) { + material->GetTextureBindingTexture(bindingIndex); + } + } } const std::vector meshRenderers = m_scene->FindObjectsOfType(); @@ -539,31 +827,111 @@ void NahidaPreviewSceneTest::ApplyIsolationFilter() { } } -Material* NahidaPreviewSceneTest::CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath) { +Material* NahidaPreviewSceneTest::CreateOverrideMaterial( + const Material* sourceMaterial, + const char* shaderPath, + const GameObject* owner) { if (sourceMaterial == nullptr || shaderPath == nullptr) { return nullptr; } + const ResourceHandle shader = ResourceManager::Get().Load(shaderPath); + if (shader.Get() == nullptr) { + return nullptr; + } + auto material = std::make_unique(); IResource::ConstructParams params = {}; params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial"); params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str()); params.guid = ResourceGUID::Generate(params.path); material->Initialize(params); - material->SetShader(ResourceManager::Get().Load(shaderPath)); + material->SetShader(shader); + material->SetRenderQueue(sourceMaterial->GetRenderQueue()); + if (std::string(shaderPath) == kToonShaderPath) { + ApplyNahidaToonRecipe(material.get(), owner); + } else { + if (sourceMaterial->HasRenderStateOverride()) { + material->SetRenderState(sourceMaterial->GetRenderState()); + } - const ResourceHandle baseMap = sourceMaterial->GetTexture("_BaseMap"); - if (baseMap.Get() != nullptr) { - material->SetTexture("_MainTex", baseMap); + for (uint32_t tagIndex = 0; tagIndex < sourceMaterial->GetTagCount(); ++tagIndex) { + material->SetTag(sourceMaterial->GetTagName(tagIndex), sourceMaterial->GetTagValue(tagIndex)); + } + + for (uint32_t keywordIndex = 0; keywordIndex < sourceMaterial->GetKeywordCount(); ++keywordIndex) { + material->EnableKeyword(sourceMaterial->GetKeyword(keywordIndex)); + } + + for (const MaterialProperty& property : sourceMaterial->GetProperties()) { + const XCEngine::Containers::String targetName = RemapMaterialPropertyName(*material, property.name); + if (targetName.Empty()) { + continue; + } + + switch (property.type) { + case MaterialPropertyType::Float: + material->SetFloat(targetName, property.value.floatValue[0]); + break; + case MaterialPropertyType::Float2: + material->SetFloat2( + targetName, + XCEngine::Math::Vector2(property.value.floatValue[0], property.value.floatValue[1])); + break; + case MaterialPropertyType::Float3: + material->SetFloat3( + targetName, + XCEngine::Math::Vector3( + property.value.floatValue[0], + property.value.floatValue[1], + property.value.floatValue[2])); + break; + case MaterialPropertyType::Float4: + material->SetFloat4( + targetName, + XCEngine::Math::Vector4( + property.value.floatValue[0], + property.value.floatValue[1], + property.value.floatValue[2], + property.value.floatValue[3])); + break; + case MaterialPropertyType::Int: + material->SetInt(targetName, property.value.intValue[0]); + break; + case MaterialPropertyType::Bool: + material->SetBool(targetName, property.value.boolValue); + break; + case MaterialPropertyType::Texture: + case MaterialPropertyType::Cubemap: + case MaterialPropertyType::Int2: + case MaterialPropertyType::Int3: + case MaterialPropertyType::Int4: + default: + break; + } + } + + for (uint32_t bindingIndex = 0; bindingIndex < sourceMaterial->GetTextureBindingCount(); ++bindingIndex) { + const XCEngine::Containers::String sourceName = sourceMaterial->GetTextureBindingName(bindingIndex); + const XCEngine::Containers::String targetName = RemapMaterialPropertyName(*material, sourceName); + if (targetName.Empty()) { + continue; + } + + const ResourceHandle texture = sourceMaterial->GetTextureBindingTexture(bindingIndex); + if (texture.Get() != nullptr) { + material->SetTexture(targetName, texture); + continue; + } + + const AssetRef textureRef = sourceMaterial->GetTextureBindingAssetRef(bindingIndex); + const XCEngine::Containers::String texturePath = sourceMaterial->GetTextureBindingPath(bindingIndex); + if (textureRef.IsValid() || !texturePath.Empty()) { + material->SetTextureAssetRef(targetName, textureRef, texturePath); + } + } } - if (std::string(shaderPath) == "builtin://shaders/forward-lit") { - material->EnableKeyword("XC_ALPHA_TEST"); - material->SetFloat("_Cutoff", sourceMaterial->GetFloat("_Cutoff")); - } - - material->SetFloat4("_BaseColor", sourceMaterial->GetFloat4("_BaseColor")); - m_overrideMaterials.push_back(std::move(material)); return m_overrideMaterials.back().get(); } @@ -574,10 +942,6 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() { const DiagnosticMode mode = GetDiagnosticMode(); std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode)); - if (mode == DiagnosticMode::Original) { - return; - } - if (mode == DiagnosticMode::NoShadows) { const std::vector lights = m_scene->FindObjectsOfType(); for (LightComponent* light : lights) { @@ -588,36 +952,46 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() { return; } - const char* shaderPath = mode == DiagnosticMode::ForwardLit - ? "builtin://shaders/forward-lit" - : "builtin://shaders/unlit"; + const char* shaderPath = nullptr; + switch (mode) { + case DiagnosticMode::Toon: + shaderPath = "Assets/Shaders/Toon.shader"; + break; + case DiagnosticMode::ForwardLit: + shaderPath = "builtin://shaders/forward-lit"; + break; + case DiagnosticMode::Unlit: + shaderPath = "builtin://shaders/unlit"; + break; + case DiagnosticMode::NoShadows: + default: + return; + } - std::unordered_map overrideBySource; const std::vector meshRenderers = m_scene->FindObjectsOfType(); for (MeshRendererComponent* meshRenderer : meshRenderers) { - if (meshRenderer == nullptr) { + if (meshRenderer == nullptr || !IsNahidaCharacterRenderable(meshRenderer->GetGameObject())) { continue; } - for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) { - Material* sourceMaterial = meshRenderer->GetMaterial(materialIndex); + auto* meshFilter = meshRenderer->GetGameObject()->GetComponent(); + Mesh* mesh = meshFilter != nullptr ? meshFilter->GetMesh() : nullptr; + const size_t materialCount = std::max( + meshRenderer->GetMaterialCount(), + mesh != nullptr ? static_cast(mesh->GetMaterials().Size()) : 0u); + for (size_t materialIndex = 0; materialIndex < materialCount; ++materialIndex) { + const Material* sourceMaterial = nullptr; + if (materialIndex < meshRenderer->GetMaterialCount()) { + sourceMaterial = meshRenderer->GetMaterial(materialIndex); + } + if (sourceMaterial == nullptr && mesh != nullptr && materialIndex < mesh->GetMaterials().Size()) { + sourceMaterial = mesh->GetMaterial(static_cast(materialIndex)); + } if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) { continue; } - if (std::string(sourceMaterial->GetShader()->GetPath().CStr()) != "Assets/Shaders/XCCharacterToon.shader") { - continue; - } - - Material* overrideMaterial = nullptr; - const auto found = overrideBySource.find(sourceMaterial); - if (found != overrideBySource.end()) { - overrideMaterial = found->second; - } else { - overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath); - overrideBySource.emplace(sourceMaterial, overrideMaterial); - } - + Material* overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath, meshRenderer->GetGameObject()); if (overrideMaterial != nullptr) { meshRenderer->SetMaterial(materialIndex, overrideMaterial); } @@ -632,10 +1006,11 @@ void NahidaPreviewSceneTest::DumpTargetDiagnostics() { const char* const targetObjects[] = { "Body_Mesh0", + "Body_Mesh1", + "Body_Mesh2", + "Body_Mesh3", "Brow", "EyeStar", - "Dress_Mesh0", - "Hair_Mesh0", "Face", "Face_Eye" };