#ifndef UNITY_LIGHT_LOOP_DEF_INCLUDED #define UNITY_LIGHT_LOOP_DEF_INCLUDED #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/CookieSampling.hlsl" #define DWORD_PER_TILE 16 // See dwordsPerTile in LightLoop.cs, we have roomm for 31 lights and a number of light value all store on 16 bit (ushort) // Some file may not required HD shadow context at all. In this case provide an empty one // Note: if a double defintion error occur it is likely have include HDShadow.hlsl (and so HDShadowContext.hlsl) after lightloopdef.hlsl #ifndef HAVE_HD_SHADOW_CONTEXT struct HDShadowContext { float unused; }; #endif // LightLoopContext is not visible from Material (user should not use these properties in Material file) // It allow the lightloop to have transmit sampling information (do we use atlas, or texture array etc...) struct LightLoopContext { int sampleReflection; HDShadowContext shadowContext; uint contactShadow; // a bit mask of 24 bits that tell if the pixel is in a contact shadow or not real contactShadowFade; // combined fade factor of all contact shadows SHADOW_TYPE shadowValue; // Stores the value of the cascade shadow map }; // LightLoopOutput is the output of the LightLoop fuction call. // It allow to retrieve the data output by the LightLoop struct LightLoopOutput { float3 diffuseLighting; float3 specularLighting; }; //----------------------------------------------------------------------------- // Reflection probe / Sky sampling function // ---------------------------------------------------------------------------- #define SINGLE_PASS_CONTEXT_SAMPLE_REFLECTION_PROBES 0 #define SINGLE_PASS_CONTEXT_SAMPLE_SKY 1 #define PLANAR_ATLAS_SIZE _PlanarAtlasData.x #define PLANAR_ATLAS_RCP_MIP_PADDING _PlanarAtlasData.y // The EnvLightData of the sky light contains a bunch of compile-time constants. // This function sets them directly to allow the compiler to propagate them and optimize the code. EnvLightData InitSkyEnvLightData(int envIndex) { EnvLightData output; ZERO_INITIALIZE(EnvLightData, output); output.lightLayers = 0xFFFFFFFF; // Enable sky for all layers output.influenceShapeType = ENVSHAPETYPE_SKY; // 31 bit index, 1 bit cache type output.envIndex = envIndex; output.influenceForward = float3(0.0, 0.0, 1.0); output.influenceUp = float3(0.0, 1.0, 0.0); output.influenceRight = float3(1.0, 0.0, 0.0); output.influencePositionRWS = float3(0.0, 0.0, 0.0); output.weight = 1.0; output.multiplier = _EnableSkyReflection.x != 0 ? 1.0 : 0.0; output.roughReflections = 1.0; output.distanceBasedRoughness = 0.0; // proxy output.proxyForward = float3(0.0, 0.0, 1.0); output.proxyUp = float3(0.0, 1.0, 0.0); output.proxyRight = float3(1.0, 0.0, 0.0); output.minProjectionDistance = 65504.0f; return output; } bool IsEnvIndexCubemap(int index) { return index >= 0; } bool IsEnvIndexTexture2D(int index) { return index < 0; } // Clamp the UVs to avoid edge beelding caused by bilinear filtering on the edge of the atlas. float2 RemapUVForPlanarAtlas(float2 coord, float2 size, float lod) { float2 scale = rcp(size + PLANAR_ATLAS_RCP_MIP_PADDING) * size; float2 offset = 0.5 * (1.0 - scale); // Avoid edge bleeding for texture when sampling with lod by clamping uvs: float2 mipClamp = pow(2, lod) / (size * PLANAR_ATLAS_SIZE * 2); return clamp(coord * scale + offset, mipClamp, 1 - mipClamp); } // Note: index is whatever the lighting architecture want, it can contain information like in which texture to sample (in case we have a compressed BC6H texture and an uncompressed for real time reflection ?) // EnvIndex can also be use to fetch in another array of struct (to atlas information etc...). // Cubemap : texCoord = direction vector // Texture2D : texCoord = projectedPositionWS - lightData.capturePosition float4 SampleEnv(LightLoopContext lightLoopContext, int index, float3 texCoord, float lod, float rangeCompressionFactorCompensation, float2 positionNDC, int sliceIdx = 0) { // 31 bit index, 1 bit cache type uint cacheType = IsEnvIndexCubemap(index) ? ENVCACHETYPE_CUBEMAP : ENVCACHETYPE_TEXTURE2D; // Index start at 1, because -0 == 0, so we can't known which cache to sample for that index. Thus it is invalid. index = abs(index) - 1; float4 color = float4(0.0, 0.0, 0.0, 1.0); // This code will be inlined as lightLoopContext is hardcoded in the light loop if (lightLoopContext.sampleReflection == SINGLE_PASS_CONTEXT_SAMPLE_REFLECTION_PROBES) { if (cacheType == ENVCACHETYPE_TEXTURE2D) { //_Env2DCaptureVP is in capture space float3 ndc = ComputeNormalizedDeviceCoordinatesWithZ(texCoord, _Env2DCaptureVP[index]); // Apply atlas scale and offset float2 scale = _Env2DAtlasScaleOffset[index].xy; float2 offset = _Env2DAtlasScaleOffset[index].zw; float2 atlasCoords = RemapUVForPlanarAtlas(ndc.xy, scale, lod); atlasCoords = atlasCoords * scale + offset; color.rgb = SAMPLE_TEXTURE2D_LOD(_Env2DTextures, s_trilinear_clamp_sampler, atlasCoords, lod).rgb; #if UNITY_REVERSED_Z // We check that the sample was capture by the probe according to its frustum planes, except the far plane. // When using oblique projection, the far plane is so distorded that it is not reliable for this check. // and most of the time, what we want, is the clipping from the oblique near plane. color.a = any(ndc.xy < 0) || any(ndc.xyz > 1) ? 0.0 : 1.0; #else color.a = any(ndc.xyz < 0) || any(ndc.xy > 1) ? 0.0 : 1.0; #endif float3 capturedForwardWS = _Env2DCaptureForward[index].xyz; if (dot(capturedForwardWS, texCoord) < 0.0) color.a = 0.0; else { // Controls the blending on the edges of the screen const float amplitude = 100.0; float2 rcoords = abs(saturate(ndc.xy) * 2.0 - 1.0); // When the object normal is not aligned with the reflection plane, the reflected ray might deviate too much and go out // of the reflection frustum. So we apply blending when the reflection sample coords are on the edges of the texture // These "edges" depend on the screen space coordinates of the pixel, because it is expected that a pixel on the // edge of the screen will sample on the edge of the texture // Blending factors taking the above into account bool2 blend = (positionNDC < ndc.xy) ^ (ndc.xy < 0.5); float2 alphas = saturate(amplitude * abs(ndc.xy - positionNDC)); alphas = float2(Smoothstep01(alphas.x), Smoothstep01(alphas.y)); float2 weights = lerp(1.0, saturate(2.0 - 2.0 * rcoords), blend * alphas); color.a *= weights.x * weights.y; } } else if (cacheType == ENVCACHETYPE_CUBEMAP) { color.rgb = SAMPLE_TEXTURECUBE_ARRAY_LOD_ABSTRACT(_EnvCubemapTextures, s_trilinear_clamp_sampler, texCoord, _EnvSliceSize * index + sliceIdx, lod).rgb; } color.rgb *= rangeCompressionFactorCompensation; } else // SINGLE_PASS_SAMPLE_SKY { color.rgb = SampleSkyTexture(texCoord, lod, sliceIdx).rgb; } // Planar, Reflection Probes and Sky aren't pre-expose, so best to clamp to max16 here in case of inf color.rgb = ClampToFloat16Max(color.rgb); return color; } //----------------------------------------------------------------------------- // Single Pass and Tile Pass // ---------------------------------------------------------------------------- #ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER // Calculate the offset in global light index light for current light category int GetTileOffset(PositionInputs posInput, uint lightCategory) { uint2 tileIndex = posInput.tileCoord; return (tileIndex.y + lightCategory * _NumTileFtplY) * _NumTileFtplX + tileIndex.x; } void GetCountAndStartTile(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { int tileOffset = GetTileOffset(posInput, lightCategory); #if defined(UNITY_STEREO_INSTANCING_ENABLED) // Eye base offset must match code in lightlistbuild.compute tileOffset += unity_StereoEyeIndex * _NumTileFtplX * _NumTileFtplY * LIGHTCATEGORY_COUNT; #endif // The first entry inside a tile is the number of light for lightCategory (thus the +0) lightCount = g_vLightListGlobal[DWORD_PER_TILE * tileOffset + 0] & 0xffff; start = tileOffset; } #ifdef USE_FPTL_LIGHTLIST uint GetTileSize() { return TILE_SIZE_FPTL; } void GetCountAndStart(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { GetCountAndStartTile(posInput, lightCategory, start, lightCount); } uint FetchIndex(uint tileOffset, uint lightOffset) { const uint lightOffsetPlusOne = lightOffset + 1; // Add +1 as first slot is reserved to store number of light // Light index are store on 16bit return (g_vLightListGlobal[DWORD_PER_TILE * tileOffset + (lightOffsetPlusOne >> 1)] >> ((lightOffsetPlusOne & 1) * DWORD_PER_TILE)) & 0xffff; } #elif defined(USE_CLUSTERED_LIGHTLIST) #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/ClusteredUtils.hlsl" uint GetTileSize() { return TILE_SIZE_CLUSTERED; } uint GetLightClusterIndex(uint2 tileIndex, float linearDepth) { float logBase = g_fClustBase; if (g_isLogBaseBufferEnabled) { const uint logBaseIndex = GenerateLogBaseBufferIndex(tileIndex, _NumTileClusteredX, _NumTileClusteredY, unity_StereoEyeIndex); logBase = g_logBaseBuffer[logBaseIndex]; } return SnapToClusterIdxFlex(linearDepth, logBase, g_isLogBaseBufferEnabled != 0); } void GetCountAndStartCluster(uint2 tileIndex, uint clusterIndex, uint lightCategory, out uint start, out uint lightCount) { int nrClusters = (1 << g_iLog2NumClusters); const int idx = GenerateLayeredOffsetBufferIndex(lightCategory, tileIndex, clusterIndex, _NumTileClusteredX, _NumTileClusteredY, nrClusters, unity_StereoEyeIndex); uint dataPair = g_vLayeredOffsetsBuffer[idx]; start = dataPair & 0x7ffffff; lightCount = (dataPair >> 27) & 31; } void GetCountAndStartCluster(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { // Note: XR depends on unity_StereoEyeIndex already being defined, // which means ShaderVariables.hlsl needs to be defined ahead of this! uint2 tileIndex = posInput.tileCoord; uint clusterIndex = GetLightClusterIndex(tileIndex, posInput.linearDepth); GetCountAndStartCluster(tileIndex, clusterIndex, lightCategory, start, lightCount); } void GetCountAndStart(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { GetCountAndStartCluster(posInput, lightCategory, start, lightCount); } uint FetchIndex(uint lightStart, uint lightOffset) { return g_vLightListGlobal[lightStart + lightOffset]; } #elif defined(USE_BIG_TILE_LIGHTLIST) uint FetchIndex(uint lightStart, uint lightOffset) { return g_vBigTileLightList[lightStart + lightOffset]; } #else // Fallback case (mainly for raytracing right now) uint FetchIndex(uint lightStart, uint lightOffset) { return 0; } #endif // USE_FPTL_LIGHTLIST #else uint GetTileSize() { return 1; } uint FetchIndex(uint lightStart, uint lightOffset) { return lightStart + lightOffset; } #endif // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER uint FetchIndexWithBoundsCheck(uint start, uint count, uint i) { if (i < count) { return FetchIndex(start, i); } else { return UINT_MAX; } } LightData FetchLight(uint start, uint i) { uint j = FetchIndex(start, i); return _LightDatas[j]; } LightData FetchLight(uint index) { return _LightDatas[index]; } EnvLightData FetchEnvLight(uint start, uint i) { int j = FetchIndex(start, i); return _EnvLightDatas[j]; } EnvLightData FetchEnvLight(uint index) { return _EnvLightDatas[index]; } // In the first 8 bits of the target we store the max fade of the contact shadows as a byte void UnpackContactShadowData(uint contactShadowData, out float fade, out uint mask) { fade = float(contactShadowData >> 24) / 255.0; mask = contactShadowData & 0xFFFFFF; // store only the first 24 bits which represent } uint PackContactShadowData(float fade, uint mask) { uint fadeAsByte = (uint(saturate(fade) * 255) << 24); return fadeAsByte | mask; } // We always fetch the screen space shadow texture to reduce the number of shader variant, overhead is negligible, // it is a 1x1 white texture if deferred directional shadow and/or contact shadow are disabled // We perform a single featch a the beginning of the lightloop void InitContactShadow(PositionInputs posInput, inout LightLoopContext context) { // Note: When we ImageLoad outside of texture size, the value returned by Load is 0 (Note: On Metal maybe it clamp to value of texture which is also fine) // We use this property to have a neutral value for contact shadows that doesn't consume a sampler and work also with compute shader (i.e use ImageLoad) // We store inverse contact shadow so neutral is white. So either we sample inside or outside the texture it return 1 in case of neutral uint packedContactShadow = LOAD_TEXTURE2D_X(_ContactShadowTexture, posInput.positionSS).x; UnpackContactShadowData(packedContactShadow, context.contactShadowFade, context.contactShadow); } void InvalidateConctactShadow(PositionInputs posInput, inout LightLoopContext context) { context.contactShadowFade = 0.0; context.contactShadow = 0; } float GetContactShadow(LightLoopContext lightLoopContext, int contactShadowMask, float rayTracedShadow) { bool occluded = (lightLoopContext.contactShadow & contactShadowMask) != 0; return 1.0 - occluded * lerp(lightLoopContext.contactShadowFade, 1.0, rayTracedShadow) * _ContactShadowOpacity; } float GetScreenSpaceShadow(PositionInputs posInput, uint shadowIndex) { uint slot = shadowIndex / 4; uint channel = shadowIndex & 0x3; return LOAD_TEXTURE2D_ARRAY(_ScreenSpaceShadowsTexture, posInput.positionSS, INDEX_TEXTURE2D_ARRAY_X(slot))[channel]; } float2 GetScreenSpaceShadowArea(PositionInputs posInput, uint shadowIndex) { uint slot = shadowIndex / 4; uint channel = shadowIndex & 0x3; return float2(LOAD_TEXTURE2D_ARRAY(_ScreenSpaceShadowsTexture, posInput.positionSS, INDEX_TEXTURE2D_ARRAY_X(slot))[channel], LOAD_TEXTURE2D_ARRAY(_ScreenSpaceShadowsTexture, posInput.positionSS, INDEX_TEXTURE2D_ARRAY_X(slot))[channel + 1]); } float3 GetScreenSpaceColorShadow(PositionInputs posInput, int shadowIndex) { float4 res = LOAD_TEXTURE2D_ARRAY(_ScreenSpaceShadowsTexture, posInput.positionSS, INDEX_TEXTURE2D_ARRAY_X(shadowIndex & SCREEN_SPACE_SHADOW_INDEX_MASK)); return (SCREEN_SPACE_COLOR_SHADOW_FLAG & shadowIndex) ? res.xyz : res.xxx; } #endif // UNITY_LIGHT_LOOP_DEF_INCLUDED