#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/Decal.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalPrepassBuffer.hlsl" DECLARE_DBUFFER_TEXTURE(_DBufferTexture); // In order that the lod for with transpartent decal better match the lod for opaque decal // We use ComputeTextureLOD with bias == 0.5 void EvalDecalMask( PositionInputs posInput, float3 vtxNormal, float3 positionRWSDdx, float3 positionRWSDdy, DecalData decalData, inout float4 DBuffer0, inout float4 DBuffer1, inout float4 DBuffer2, inout float2 DBuffer3, inout float alpha) { // Get the relative world camera to decal matrix float4x4 worldToDecal = ApplyCameraTranslationToInverseMatrix(decalData.worldToDecal); float3 positionDS = mul(worldToDecal, float4(posInput.positionWS, 1.0)).xyz; positionDS = positionDS * float3(1.0, -1.0, 1.0) + float3(0.5, 0.5, 0.5); // decal clip space if ((all(positionDS.xyz > 0.0) && all(1.0 - positionDS.xyz > 0.0))) { float2 uvScale = float2(decalData.normalToWorld[3][0], decalData.normalToWorld[3][1]); float2 uvBias = float2(decalData.normalToWorld[3][2], decalData.normalToWorld[3][3]); positionDS.xz = positionDS.xz * uvScale + uvBias; positionDS.xz = frac(positionDS.xz); // clamp by half a texel to avoid sampling neighboring textures in the atlas float2 clampAmount = float2(0.5 / _DecalAtlasResolution.x, 0.5 / _DecalAtlasResolution.y); // need to compute the mipmap LOD manually because we are sampling inside a loop float3 positionDSDdx = mul(worldToDecal, float4(positionRWSDdx, 0.0)).xyz; // transform the derivatives to decal space, any translation is irrelevant float3 positionDSDdy = mul(worldToDecal, float4(positionRWSDdy, 0.0)).xyz; // Following code match the code in DecalData.hlsl used for DBuffer. It have the same kind of condition and similar code structure uint affectFlags = (int)(decalData.blendParams.z + 0.5f); // 1 albedo, 2 normal, 4 metal, 8 AO, 16 smoothness float fadeFactor = decalData.normalToWorld[0][3]; // Check if this decal projector require angle fading float2 angleFade = float2(decalData.normalToWorld[1][3], decalData.normalToWorld[2][3]); // Angle fade is disabled if decal layers isn't enabled for consistency with DBuffer Decal // The test against _EnableDecalLayers is done here to refresh realtime as AngleFade is cached data and need a decal refresh to be updated. if (angleFade.y < 0.0f && _EnableDecalLayers) // if angle fade is enabled { float3 decalNormal = float3(decalData.normalToWorld[0].z, decalData.normalToWorld[1].z, decalData.normalToWorld[2].z); float dotAngle = dot(vtxNormal, decalNormal); // See equation in DecalSystem.cs - simplified to a madd mul add here float angleFadeFactor = saturate(angleFade.x + angleFade.y * (dotAngle * (dotAngle - 2.0))); fadeFactor *= angleFadeFactor; } float albedoMapBlend = fadeFactor; float maskMapBlend = fadeFactor; // Albedo // We must always sample diffuse texture due to opacity that can affect everything) { float4 src = decalData.baseColor; // We use scaleBias value to now if we have init a texture. 0 mean a texture is bound bool diffuseTextureBound = (decalData.diffuseScaleBias.x > 0) && (decalData.diffuseScaleBias.y > 0); if (diffuseTextureBound) { // Caution: We can't compute LOD inside a dynamic loop. The gradient are not accessible. float2 diffuseMin = decalData.diffuseScaleBias.zw + clampAmount; // offset into atlas is in .zw float2 diffuseMax = decalData.diffuseScaleBias.zw + decalData.diffuseScaleBias.xy - clampAmount; // scale relative to full atlas size is in .xy so total texture extent in atlas is (1,1) * scale float2 sampleDiffuse = clamp(positionDS.xz * decalData.diffuseScaleBias.xy + decalData.diffuseScaleBias.zw, diffuseMin, diffuseMax); float2 sampleDiffuseDdx = positionDSDdx.xz * decalData.diffuseScaleBias.xy; // factor in the atlas scale float2 sampleDiffuseDdy = positionDSDdy.xz * decalData.diffuseScaleBias.xy; float lodDiffuse = ComputeTextureLOD(sampleDiffuseDdx, sampleDiffuseDdy, _DecalAtlasResolution, 0.5); src *= SAMPLE_TEXTURE2D_LOD(_DecalAtlas2D, _trilinear_clamp_sampler_DecalAtlas2D, sampleDiffuse, lodDiffuse); } src.w *= fadeFactor; albedoMapBlend = src.w; // diffuse texture alpha affects all other channels // Accumulate in dbuffer (mimic what ROP are doing) DBuffer0.xyz = (affectFlags & 1) ? src.xyz * src.w + DBuffer0.xyz * (1.0 - src.w) : DBuffer0.xyz; // Albedo DBuffer0.w = (affectFlags & 1) ? DBuffer0.w * (1.0 - src.w) : DBuffer0.w; // Albedo alpha // Specific to transparent and requested by the artist: use decal alpha if it is higher than transparent alpha alpha = max(alpha, albedoMapBlend); } // Metal/ao/smoothness - 28 -> 1C #ifdef DECALS_4RT if (affectFlags & 0x1C) #else // only smoothness in 3RT mode if (affectFlags & 0x10) #endif { float4 src; // We use scaleBias value to now if we have init a texture. 0 mean a texture is bound bool maskTextureBound = (decalData.maskScaleBias.x > 0) && (decalData.maskScaleBias.y > 0); if (maskTextureBound) { // Caution: We can't compute LOD inside a dynamic loop. The gradient are not accessible. float2 maskMin = decalData.maskScaleBias.zw + clampAmount; float2 maskMax = decalData.maskScaleBias.zw + decalData.maskScaleBias.xy - clampAmount; float2 sampleMask = clamp(positionDS.xz * decalData.maskScaleBias.xy + decalData.maskScaleBias.zw, maskMin, maskMax); float2 sampleMaskDdx = positionDSDdx.xz * decalData.maskScaleBias.xy; float2 sampleMaskDdy = positionDSDdy.xz * decalData.maskScaleBias.xy; float lodMask = ComputeTextureLOD(sampleMaskDdx, sampleMaskDdy, _DecalAtlasResolution, 0.5); src = SAMPLE_TEXTURE2D_LOD(_DecalAtlas2D, _trilinear_clamp_sampler_DecalAtlas2D, sampleMask, lodMask); src.z *= decalData.scalingBAndRemappingM.y; // Blue channel (opacity) maskMapBlend *= src.z; // store before overwriting with smoothness #ifdef DECALS_4RT src.x = lerp(decalData.scalingBAndRemappingM.z, decalData.scalingBAndRemappingM.w, src.x); // Remap Metal src.y = lerp(decalData.remappingAOS.x, decalData.remappingAOS.y, src.y); // Remap AO #endif src.z = lerp(decalData.remappingAOS.z, decalData.remappingAOS.w, src.w); // Remap Smoothness } else { src.z = decalData.scalingBAndRemappingM.y; // Blue channel (opacity) maskMapBlend *= src.z; // store before overwriting with smoothness #ifdef DECALS_4RT src.x = decalData.scalingBAndRemappingM.z; // Metal src.y = decalData.remappingAOS.x; // AO #endif src.z = decalData.remappingAOS.z; // Smoothness } src.w = (decalData.blendParams.y == 1.0) ? maskMapBlend : albedoMapBlend; // Accumulate in dbuffer (mimic what ROP are doing) #ifdef DECALS_4RT DBuffer2.x = (affectFlags & 4) ? src.x * src.w + DBuffer2.x * (1.0 - src.w) : DBuffer2.x; // Metal DBuffer3.x = (affectFlags & 4) ? DBuffer3.x * (1.0 - src.w) : DBuffer3.x; // Metal alpha DBuffer2.y = (affectFlags & 8) ? src.y * src.w + DBuffer2.y * (1.0 - src.w) : DBuffer2.y; // AO DBuffer3.y = (affectFlags & 8) ? DBuffer3.y * (1.0 - src.w) : DBuffer3.y; // AO alpha #endif DBuffer2.z = (affectFlags & 16) ? src.z * src.w + DBuffer2.z * (1.0 - src.w) : DBuffer2.z; // Smoothness DBuffer2.w = (affectFlags & 16) ? DBuffer2.w * (1.0 - src.w) : DBuffer2.w; // Smoothness alpha } // Normal if (affectFlags & 2) { float3 normalTS; // We use scaleBias value to now if we have init a texture. 0 mean a texture is bound bool normalTextureBound = (decalData.normalScaleBias.x > 0) && (decalData.normalScaleBias.y > 0); if (normalTextureBound) { // Caution: We can't compute LOD inside a dynamic loop. The gradient are not accessible. float2 normalMin = decalData.normalScaleBias.zw + clampAmount; float2 normalMax = decalData.normalScaleBias.zw + decalData.normalScaleBias.xy - clampAmount; float2 sampleNormal = clamp(positionDS.xz * decalData.normalScaleBias.xy + decalData.normalScaleBias.zw, normalMin, normalMax); float2 sampleNormalDdx = positionDSDdx.xz * decalData.normalScaleBias.xy; float2 sampleNormalDdy = positionDSDdy.xz * decalData.normalScaleBias.xy; float lodNormal = ComputeTextureLOD(sampleNormalDdx, sampleNormalDdy, _DecalAtlasResolution, 0.5); normalTS = UnpackNormalmapRGorAG(SAMPLE_TEXTURE2D_LOD(_DecalAtlas2D, _trilinear_clamp_sampler_DecalAtlas2D, sampleNormal, lodNormal)); } else { normalTS = float3(0.0, 0.0, 1.0); } float4 src; src.xyz = mul((float3x3)decalData.normalToWorld, normalTS) * 0.5 + 0.5; // The " * 0.5 + 0.5" mimic what is happening when calling EncodeIntoDBuffer() src.w = (decalData.blendParams.x == 1.0) ? maskMapBlend : albedoMapBlend; // Accumulate in dbuffer (mimic what ROP are doing) DBuffer1.xyz = src.xyz * src.w + DBuffer1.xyz * (1.0 - src.w); DBuffer1.w = DBuffer1.w * (1.0 - src.w); } } } #if defined(_SURFACE_TYPE_TRANSPARENT) && defined(HAS_LIGHTLOOP) // forward transparent using clustered decals DecalData FetchDecal(uint start, uint i) { #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER int j = FetchIndex(start, i); #else int j = start + i; #endif return _DecalDatas[j]; } DecalData FetchDecal(uint index) { return _DecalDatas[index]; } #endif DecalSurfaceData GetDecalSurfaceData(PositionInputs posInput, float3 vtxNormal, inout float alpha) { #if defined(_SURFACE_TYPE_TRANSPARENT) && defined(HAS_LIGHTLOOP) // forward transparent using clustered decals uint decalCount, decalStart; DBufferType0 DBuffer0 = float4(0.0, 0.0, 0.0, 1.0); DBufferType1 DBuffer1 = float4(0.5, 0.5, 0.5, 1.0); DBufferType2 DBuffer2 = float4(0.0, 0.0, 0.0, 1.0); #ifdef DECALS_4RT DBufferType3 DBuffer3 = float2(1.0, 1.0); #else float2 DBuffer3 = float2(1.0, 1.0); #endif #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER GetCountAndStart(posInput, LIGHTCATEGORY_DECAL, decalStart, decalCount); // Fast path is when we all pixels in a wave are accessing same tile or cluster. uint decalStartLane0; bool fastPath = IsFastPath(decalStart, decalStartLane0); #else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER decalCount = _DecalCount; decalStart = 0; #endif float3 positionRWS = posInput.positionWS; // get world space ddx/ddy for adjacent pixels to be used later in mipmap lod calculation float3 positionRWSDdx = ddx(positionRWS); float3 positionRWSDdy = ddy(positionRWS); uint decalLayerMask = GetMeshRenderingDecalLayer(); // Scalarized loop. All decals that are in a tile/cluster touched by any pixel in the wave are loaded (scalar load), only the ones relevant to current thread/pixel are processed. // For clarity, the following code will follow the convention: variables starting with s_ are wave uniform (meant for scalar register), // v_ are variables that might have different value for each thread in the wave (meant for vector registers). // This will perform more loads than it is supposed to, however, the benefits should offset the downside, especially given that decal data accessed should be largely coherent // Note that the above is valid only if wave intriniscs are supported. uint v_decalListOffset = 0; uint v_decalIdx = decalStart; while (v_decalListOffset < decalCount) { #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER v_decalIdx = FetchIndex(decalStart, v_decalListOffset); #else v_decalIdx = decalStart + v_decalListOffset; #endif // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER uint s_decalIdx = ScalarizeElementIndex(v_decalIdx, fastPath); if (s_decalIdx == -1) break; DecalData s_decalData = FetchDecal(s_decalIdx); bool isRejected = (s_decalData.decalLayerMask & decalLayerMask) == 0; // If current scalar and vector decal index match, we process the decal. The v_decalListOffset for current thread is increased. // Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could // end up with a unique v_decalIdx value that is smaller than s_decalIdx hence being stuck in a loop. All the active lanes will not have this problem. if (s_decalIdx >= v_decalIdx) { v_decalListOffset++; if (!isRejected) EvalDecalMask(posInput, vtxNormal, positionRWSDdx, positionRWSDdy, s_decalData, DBuffer0, DBuffer1, DBuffer2, DBuffer3, alpha); } } #else // Opaque - used DBuffer FETCH_DBUFFER(DBuffer, _DBufferTexture, int2(posInput.positionSS.xy)); #endif DecalSurfaceData decalSurfaceData; DECODE_FROM_DBUFFER(DBuffer, decalSurfaceData); return decalSurfaceData; } DecalSurfaceData GetDecalSurfaceData(PositionInputs posInput, FragInputs input, inout float alpha) { float3 vtxNormal = input.tangentToWorld[2]; DecalSurfaceData decalSurfaceData = GetDecalSurfaceData(posInput, vtxNormal, alpha); #ifdef _DOUBLESIDED_ON // 'doubleSidedConstants' is float3(-1, -1, -1) in flip mode and float3(1, 1, -1) in mirror mode. // It's float3(1, 1, 1) in the none mode. float3 flipSign = input.isFrontFace ? float3(1.0, 1.0, 1.0) : _DoubleSidedConstants.xyz; decalSurfaceData.normalWS.xyz *= flipSign; #endif // _DOUBLESIDED_ON return decalSurfaceData; }