@@ -787,54 +787,68 @@ void EvaluateBSDF_Env( float3 V, float3 positionWS, PreLightData prelightData,
787787 // float shrinkedRoughness = AnisotropicStrechAtGrazingAngle(bsdfData.roughness, bsdfData.perceptualRoughness, NdotV);
788788
789789 // Note: As explain in GetPreLightData we use normalWS and not iblNormalWS here (in case of anisotropy)
790- //float3 rayWS = GetSpecularDominantDir(bsdfData.normalWS, prelightData.iblR, bsdfData.roughness);
791-
792- float3 rayWS = prelightData.iblR;
790+ float3 rayWS = GetSpecularDominantDir (bsdfData.normalWS, prelightData.iblR, bsdfData.roughness);
793791
794792 float3 R = rayWS;
795793 float weight = 1.0 ;
796794
797- /*
795+ // In Unity the cubemaps are capture with the localToWorld transform of the component.
796+ // This mean that location and oritention matter. So after intersection of proxy volume we need to convert back to world.
798797 if (lightData.shapeType == ENVSHAPETYPE_BOX)
799798 {
799+ // CAUTION: localToWorld is the transform use to convert the cubemap capture point to world space (mean it include the offset)
800+ // the center of the bounding box is thus in locals space: positionLS - offsetLS
801+ // We use this formulation as it is the one of legacy unity that was using only AABB box.
802+
800803 // worldToLocal assume no scaling
801- float4x4 worldToLocal = transpose(float4x4(float4(lightData.right, 0.0), float4(lightData.up, 0.0), float4(lightData.forward, 0.0), float4(light.positionWS, 1.0)));
802- float3 positionLS = mul(lightData.worldToLocal, float4(positionWS, 1.0)).xyz;
803- float3 rayLS = mul((float3x3)lightData.worldToLocal, rayWS);
804+ float3x3 worldToLocal = transpose (float3x3 (lightData.right, lightData.up, lightData.forward));
805+ float3 positionLS = positionWS - lightData.positionWS;
806+ positionLS = mul (positionLS, worldToLocal).xyz - lightData.offsetLS; // We want to calculate the intersection from the center of the bounding box.
807+
808+ float3 rayLS = mul (rayWS, worldToLocal);
804809 float3 boxOuterDistance = lightData.innerDistance + float3 (lightData.blendDistance, lightData.blendDistance, lightData.blendDistance);
805810 float dist = BoxRayIntersectSimple (positionLS, rayLS, -boxOuterDistance, boxOuterDistance);
806-
807- // No need to normalize for fetching cubemap
808- R = (positionWS + dist * rayWS) - lightData.capturePointWS; // TODO: check that
809811
812+ // No need to normalize for fetching cubemap
813+ // We can reuse dist calculate in LS directly in WS as there is no scaling. Also the offset is already include in lightData.positionWS
814+ R = (positionWS + dist * rayWS) - lightData.positionWS;
815+
810816 // TODO: add distance based roughness
811817
812- // Calculate falloff value, so reflections on the edges of the Volume would gradually blend to previous reflection.
813- // Also this ensures that pixels not located in the reflection Volume AABB won't
814- // accidentally pick up reflections from this Volume.
818+ // Calculate falloff value, so reflections on the edges of the volume would gradually blend to previous reflection.
815819 float distFade = DistancePointBox (positionLS, -lightData.innerDistance, lightData.innerDistance);
816820 weight = saturate (1.0 - distFade / max (lightData.blendDistance, 0.0001 )); // avoid divide by zero
817821
818822 // Smooth weighting
819823 weight = smoothstep01 (weight);
820- }
824+ }
821825 else if (lightData.shapeType == ENVSHAPETYPE_SPHERE)
822826 {
827+ // For now there is no specific interface for sphere proxy and it can have offset and arbitrary orientation. So we need to transform
828+ // to local space position and direction like for OBB.
829+ float3x3 worldToLocal = transpose (float3x3 (lightData.right, lightData.up, lightData.forward));
830+ float3 positionLS = positionWS - lightData.positionWS;
831+ positionLS = mul (positionLS, worldToLocal).xyz - lightData.offsetLS; // We want to calculate the intersection from the center of the bounding box.
832+
833+ float3 rayLS = mul (rayWS, worldToLocal);
823834 float sphereRadius = lightData.innerDistance.x;
824- float2 intersections;
825- SphereRayIntersect(intersections, positionWS - lightData.positionWS, R, sphereRadius);
826- // TODO: check if we can have simplified formula like for box
827- // No need to normalize for fetching cubemap
828- R = (positionWS + intersections.y * rayWS) - lightData.capturePointWS;
835+ float dist = SphereRayIntersectSimple (positionLS, rayLS, sphereRadius + lightData.blendDistance);
836+
837+ R = (positionWS + dist * rayWS) - lightData.positionWS;
829838
830839 float distFade = length (positionWS - lightData.positionWS);
831840 weight = saturate (((sphereRadius + lightData.blendDistance) - distFade) / max (lightData.blendDistance, 0.0001 )); // avoid divide by zero
832841
833842 // Smooth weighting
834843 weight = smoothstep01 (weight);
835844 }
836- */
837845
846+ // TODO: we must always perform a weight calculation as due to tiled rendering we need to smooth out cubemap at boundaries.
847+ // So goal is to split into two category and have an option to say if we parallax correct or not.
848+
849+ // TODO: compare current Morten version: offline cubemap with a particular remap + the bias in perceptualRoughnessToMipmapLevel
850+ // to classic remap like unreal/Frobiste. The function GetSpecularDominantDir can result in a better matching in this case
851+ // We let GetSpecularDominantDir currently as it still an improvement but not as good as it could be
838852 float mip = perceptualRoughnessToMipmapLevel (bsdfData.perceptualRoughness);
839853 float4 preLD = UNITY_SAMPLE_ENV_LOD (_EnvTextures, R, lightData, mip);
840854 specularLighting.rgb = preLD.rgb * prelightData.specularFGD;
0 commit comments