From 48daaa1bd020ca4a43cf6d09daa6271334272bc6 Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Mon, 13 Apr 2026 21:09:40 +0800 Subject: [PATCH] Fix Nahida toon binding and test assets --- .../Rendering/Internal/ShaderVariantUtils.h | 117 +++- .../Models/nahida/Avatar_Tex_Face_Shadow.png | Bin 0 -> 14136 bytes .../nahida/Avatar_Tex_Face_Shadow.png.meta | 5 + .../Models/nahida/Avatar_Tex_MetalMap.png | Bin 0 -> 14599 bytes .../nahida/Avatar_Tex_MetalMap.png.meta | 5 + project/Assets/Shaders/Toon.shader | 21 +- project/Assets/Shaders/XCCharacterToon.shader | 599 ------------------ .../Shaders/XCCharacterToon.shader.meta | 5 - .../integration/nahida_preview_scene/main.cpp | 465 ++++++++++++-- 9 files changed, 531 insertions(+), 686 deletions(-) create mode 100644 project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png create mode 100644 project/Assets/Models/nahida/Avatar_Tex_Face_Shadow.png.meta create mode 100644 project/Assets/Models/nahida/Avatar_Tex_MetalMap.png create mode 100644 project/Assets/Models/nahida/Avatar_Tex_MetalMap.png.meta delete mode 100644 project/Assets/Shaders/XCCharacterToon.shader delete mode 100644 project/Assets/Shaders/XCCharacterToon.shader.meta 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 0000000000000000000000000000000000000000..178e0bb2261e189cbe911e511ddd6b3c670fe478 GIT binary patch literal 14136 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..48be26b6250dd9c0d40c39a6168ffef8e19d7fc8 GIT binary patch literal 14599 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 qhMoSN!yk!5(9u;y6m_ohb)lrjm)Tf_q= 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" };