Fix Nahida toon binding and test assets

This commit is contained in:
2026-04-13 21:09:40 +08:00
parent e462f7d6f7
commit 48daaa1bd0
9 changed files with 531 additions and 686 deletions

View File

@@ -276,42 +276,97 @@ inline bool TryBuildRuntimeShaderBindings(
return false; return false;
} }
auto sortBindingIndices = [&outBindings](auto&& predicate) {
std::vector<size_t> 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 nextConstantBufferRegister = 0;
Core::uint32 nextTextureRegister = 0; Core::uint32 nextTextureRegister = 0;
Core::uint32 nextSamplerRegister = 0; Core::uint32 nextSamplerRegister = 0;
Core::uint32 nextUnorderedAccessRegister = 0; Core::uint32 nextUnorderedAccessRegister = 0;
Core::uint32 nextStorageBufferRegister = 0; Core::uint32 nextStorageBufferRegister = 0;
for (Resources::ShaderResourceBindingDesc& binding : outBindings) {
binding.set = 0; const auto constantBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
switch (binding.type) { return binding.type == Resources::ShaderResourceType::ConstantBuffer;
case Resources::ShaderResourceType::ConstantBuffer: });
binding.binding = nextConstantBufferRegister++; for (size_t index : constantBufferIndices) {
break; outBindings[index].set = 0;
case Resources::ShaderResourceType::Texture2D: outBindings[index].binding = nextConstantBufferRegister++;
case Resources::ShaderResourceType::TextureCube: }
binding.binding = nextTextureRegister++;
break; const auto textureIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
case Resources::ShaderResourceType::StructuredBuffer: return binding.type == Resources::ShaderResourceType::Texture2D ||
case Resources::ShaderResourceType::RawBuffer: binding.type == Resources::ShaderResourceType::TextureCube;
binding.binding = });
backend == Resources::ShaderBackend::OpenGL for (size_t index : textureIndices) {
? nextStorageBufferRegister++ outBindings[index].set = 0;
: nextTextureRegister++; outBindings[index].binding = nextTextureRegister++;
break; }
case Resources::ShaderResourceType::Sampler:
binding.binding = nextSamplerRegister++; const auto srvBufferIndices = sortBindingIndices([](const Resources::ShaderResourceBindingDesc& binding) {
break; return binding.type == Resources::ShaderResourceType::StructuredBuffer ||
case Resources::ShaderResourceType::RWStructuredBuffer: binding.type == Resources::ShaderResourceType::RawBuffer;
case Resources::ShaderResourceType::RWRawBuffer: });
binding.binding = for (size_t index : srvBufferIndices) {
backend == Resources::ShaderBackend::OpenGL outBindings[index].set = 0;
? nextStorageBufferRegister++ outBindings[index].binding =
: nextUnorderedAccessRegister++; backend == Resources::ShaderBackend::OpenGL
break; ? nextStorageBufferRegister++
default: : nextTextureRegister++;
binding.binding = nextUnorderedAccessRegister++; }
break;
} 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; return true;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,5 @@
fileFormatVersion: 1
guid: e632e900c73bd6cdc4c51951e1ab02b6
folderAsset: false
importer: TextureImporter
importerVersion: 9

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,5 @@
fileFormatVersion: 1
guid: b1928595729c1c6863c89d17b054288b
folderAsset: false
importer: TextureImporter
importerVersion: 9

View File

@@ -491,19 +491,25 @@ Shader "Toon"
return lerp(faceShadow, 1.0f, faceMask); return lerp(faceShadow, 1.0f, faceMask);
} }
float3 GetShadowColor(float shadow, float materialSelector) float ResolveShadowRampIndex(float materialSelector)
{ {
float rampIndex = 4.0f; float rampIndex = 4.0f;
rampIndex = lerp(rampIndex, 1.0f, step(0.2f, materialSelector)); rampIndex = lerp(rampIndex, 1.0f, step(0.2f, materialSelector));
rampIndex = lerp(rampIndex, 2.0f, step(0.4f, materialSelector)); rampIndex = lerp(rampIndex, 2.0f, step(0.4f, materialSelector));
rampIndex = lerp(rampIndex, 0.0f, step(0.6f, materialSelector)); rampIndex = lerp(rampIndex, 0.0f, step(0.6f, materialSelector));
rampIndex = lerp(rampIndex, 3.0f, step(0.8f, 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 rangeMin = 0.5f + _ShadowOffset.x - _ShadowSmoothness.x;
const float rangeMax = 0.5f + _ShadowOffset.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( const float2 rampUv = float2(
smoothstep(rangeMin, rangeMax, shadow), smoothstep(rangeMin, rangeMax, shadow),
rampIndex / 10.0f + 0.5f * saturate(_IsDay.x) + 0.05f); rampV);
const float3 shadowRamp = _ShadowRamp.Sample(LinearClampSampler, rampUv).rgb; const float3 shadowRamp = _ShadowRamp.Sample(LinearClampSampler, rampUv).rgb;
float3 shadowColor = float3 shadowColor =
@@ -559,19 +565,22 @@ Shader "Toon"
} }
const float3 normalWS = ComputeSurfaceNormalWS(input, texcoord); const float3 normalWS = ComputeSurfaceNormalWS(input, texcoord);
const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord);
float3 color = albedo; float3 color = albedo;
if (gMainLightColorAndFlags.a >= 0.5f) { if (gMainLightColorAndFlags.a >= 0.5f) {
const float3 lightDirectionWS = GetAdjustedMainLightDirection(); const float3 lightDirectionWS = GetAdjustedMainLightDirection();
const float4 lightMap = _LightMap.Sample(LinearClampSampler, texcoord);
const float materialSelector = ResolveMaterialSelector(lightMap.a); const float materialSelector = ResolveMaterialSelector(lightMap.a);
const float aoFactor = lightMap.g * input.color.x;
float shadow = UseFaceFeature() float shadow = UseFaceFeature()
? GetFaceShadow(input, texcoord, lightDirectionWS) ? GetFaceShadow(input, texcoord, lightDirectionWS)
: GetGenericShadow(normalWS, lightDirectionWS, lightMap.g * input.color.x); : GetGenericShadow(normalWS, lightDirectionWS, aoFactor);
const float3 shadowColor = GetShadowColor(shadow, materialSelector); const float3 shadowColor = GetShadowColor(shadow, materialSelector);
float3 specular = float3(0.0f, 0.0f, 0.0f); 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); float3 emission = float3(0.0f, 0.0f, 0.0f);
if (UseEmissionFeature()) { if (UseEmissionFeature()) {
@@ -586,7 +595,7 @@ Shader "Toon"
color = albedo * shadowColor + specular + emission + rim; color = albedo * shadowColor + specular + emission + rim;
} }
return float4(saturate(color), 1.0f); return float4(color, 1.0f);
} }
ENDHLSL ENDHLSL
SubShader SubShader

View File

@@ -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"
}
}

View File

@@ -1,5 +0,0 @@
fileFormatVersion: 1
guid: 5a51f782e3c149bd8bf65c8b0e25d3a1
folderAsset: false
importer: ShaderImporter
importerVersion: 8

View File

@@ -50,7 +50,7 @@ constexpr uint32_t kFrameHeight = 720;
constexpr int kWarmupFrames = 45; constexpr int kWarmupFrames = 45;
enum class DiagnosticMode { enum class DiagnosticMode {
Original, Toon,
NoShadows, NoShadows,
ForwardLit, ForwardLit,
Unlit Unlit
@@ -59,10 +59,13 @@ enum class DiagnosticMode {
DiagnosticMode GetDiagnosticMode() { DiagnosticMode GetDiagnosticMode() {
const char* value = std::getenv("XC_NAHIDA_DIAG_MODE"); const char* value = std::getenv("XC_NAHIDA_DIAG_MODE");
if (value == nullptr) { if (value == nullptr) {
return DiagnosticMode::Original; return DiagnosticMode::Toon;
} }
const std::string mode(value); const std::string mode(value);
if (mode == "toon" || mode == "original") {
return DiagnosticMode::Toon;
}
if (mode == "no_shadows") { if (mode == "no_shadows") {
return DiagnosticMode::NoShadows; return DiagnosticMode::NoShadows;
} }
@@ -73,12 +76,12 @@ DiagnosticMode GetDiagnosticMode() {
return DiagnosticMode::Unlit; return DiagnosticMode::Unlit;
} }
return DiagnosticMode::Original; return DiagnosticMode::Toon;
} }
const char* GetDiagnosticModeName(DiagnosticMode mode) { const char* GetDiagnosticModeName(DiagnosticMode mode) {
switch (mode) { switch (mode) {
case DiagnosticMode::Original: return "original"; case DiagnosticMode::Toon: return "toon";
case DiagnosticMode::NoShadows: return "no_shadows"; case DiagnosticMode::NoShadows: return "no_shadows";
case DiagnosticMode::ForwardLit: return "forward_lit"; case DiagnosticMode::ForwardLit: return "forward_lit";
case DiagnosticMode::Unlit: return "unlit"; case DiagnosticMode::Unlit: return "unlit";
@@ -88,7 +91,7 @@ const char* GetDiagnosticModeName(DiagnosticMode mode) {
const char* GetGoldenFileName(DiagnosticMode mode) { const char* GetGoldenFileName(DiagnosticMode mode) {
switch (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::NoShadows: return "GT.no_shadows.ppm";
case DiagnosticMode::ForwardLit: return "GT.forward_lit.ppm"; case DiagnosticMode::ForwardLit: return "GT.forward_lit.ppm";
case DiagnosticMode::Unlit: return "GT.unlit.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> texture = ResourceManager::Get().Load<Texture>(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<std::string> GetIsolationObjectNames() { std::unordered_set<std::string> GetIsolationObjectNames() {
std::unordered_set<std::string> result; std::unordered_set<std::string> result;
const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY"); const char* value = std::getenv("XC_NAHIDA_DIAG_ONLY");
@@ -338,7 +613,7 @@ private:
void ApplyDiagnosticOverrides(); void ApplyDiagnosticOverrides();
void DumpTargetDiagnostics(); void DumpTargetDiagnostics();
RHIResourceView* GetCurrentBackBufferView(); RHIResourceView* GetCurrentBackBufferView();
Material* CreateOverrideMaterial(Material* sourceMaterial, const char* shaderPath); Material* CreateOverrideMaterial(const Material* sourceMaterial, const char* shaderPath, const GameObject* owner);
HMODULE m_assimpModule = nullptr; HMODULE m_assimpModule = nullptr;
std::unique_ptr<Scene> m_scene; std::unique_ptr<Scene> m_scene;
@@ -489,7 +764,20 @@ void NahidaPreviewSceneTest::TouchSceneMaterialsAndTextures() {
continue; 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<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>(); const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
@@ -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) { if (sourceMaterial == nullptr || shaderPath == nullptr) {
return nullptr; return nullptr;
} }
const ResourceHandle<Shader> shader = ResourceManager::Get().Load<Shader>(shaderPath);
if (shader.Get() == nullptr) {
return nullptr;
}
auto material = std::make_unique<Material>(); auto material = std::make_unique<Material>();
IResource::ConstructParams params = {}; IResource::ConstructParams params = {};
params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial"); params.name = XCEngine::Containers::String("NahidaDiagnosticMaterial");
params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str()); params.path = XCEngine::Containers::String(("memory://nahida/" + std::string(shaderPath)).c_str());
params.guid = ResourceGUID::Generate(params.path); params.guid = ResourceGUID::Generate(params.path);
material->Initialize(params); material->Initialize(params);
material->SetShader(ResourceManager::Get().Load<Shader>(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<Texture> baseMap = sourceMaterial->GetTexture("_BaseMap"); for (uint32_t tagIndex = 0; tagIndex < sourceMaterial->GetTagCount(); ++tagIndex) {
if (baseMap.Get() != nullptr) { material->SetTag(sourceMaterial->GetTagName(tagIndex), sourceMaterial->GetTagValue(tagIndex));
material->SetTexture("_MainTex", baseMap); }
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> 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)); m_overrideMaterials.push_back(std::move(material));
return m_overrideMaterials.back().get(); return m_overrideMaterials.back().get();
} }
@@ -574,10 +942,6 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
const DiagnosticMode mode = GetDiagnosticMode(); const DiagnosticMode mode = GetDiagnosticMode();
std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode)); std::printf("[NahidaDiag] diagnosticMode=%s\n", GetDiagnosticModeName(mode));
if (mode == DiagnosticMode::Original) {
return;
}
if (mode == DiagnosticMode::NoShadows) { if (mode == DiagnosticMode::NoShadows) {
const std::vector<LightComponent*> lights = m_scene->FindObjectsOfType<LightComponent>(); const std::vector<LightComponent*> lights = m_scene->FindObjectsOfType<LightComponent>();
for (LightComponent* light : lights) { for (LightComponent* light : lights) {
@@ -588,36 +952,46 @@ void NahidaPreviewSceneTest::ApplyDiagnosticOverrides() {
return; return;
} }
const char* shaderPath = mode == DiagnosticMode::ForwardLit const char* shaderPath = nullptr;
? "builtin://shaders/forward-lit" switch (mode) {
: "builtin://shaders/unlit"; 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<Material*, Material*> overrideBySource;
const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>(); const std::vector<MeshRendererComponent*> meshRenderers = m_scene->FindObjectsOfType<MeshRendererComponent>();
for (MeshRendererComponent* meshRenderer : meshRenderers) { for (MeshRendererComponent* meshRenderer : meshRenderers) {
if (meshRenderer == nullptr) { if (meshRenderer == nullptr || !IsNahidaCharacterRenderable(meshRenderer->GetGameObject())) {
continue; continue;
} }
for (size_t materialIndex = 0; materialIndex < meshRenderer->GetMaterialCount(); ++materialIndex) { auto* meshFilter = meshRenderer->GetGameObject()->GetComponent<MeshFilterComponent>();
Material* sourceMaterial = meshRenderer->GetMaterial(materialIndex); Mesh* mesh = meshFilter != nullptr ? meshFilter->GetMesh() : nullptr;
const size_t materialCount = std::max(
meshRenderer->GetMaterialCount(),
mesh != nullptr ? static_cast<size_t>(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<uint32_t>(materialIndex));
}
if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) { if (sourceMaterial == nullptr || sourceMaterial->GetShader() == nullptr) {
continue; continue;
} }
if (std::string(sourceMaterial->GetShader()->GetPath().CStr()) != "Assets/Shaders/XCCharacterToon.shader") { Material* overrideMaterial = CreateOverrideMaterial(sourceMaterial, shaderPath, meshRenderer->GetGameObject());
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);
}
if (overrideMaterial != nullptr) { if (overrideMaterial != nullptr) {
meshRenderer->SetMaterial(materialIndex, overrideMaterial); meshRenderer->SetMaterial(materialIndex, overrideMaterial);
} }
@@ -632,10 +1006,11 @@ void NahidaPreviewSceneTest::DumpTargetDiagnostics() {
const char* const targetObjects[] = { const char* const targetObjects[] = {
"Body_Mesh0", "Body_Mesh0",
"Body_Mesh1",
"Body_Mesh2",
"Body_Mesh3",
"Brow", "Brow",
"EyeStar", "EyeStar",
"Dress_Mesh0",
"Hair_Mesh0",
"Face", "Face",
"Face_Eye" "Face_Eye"
}; };