# CubeMap Sampling

Unity provides the Unity_GlossyEnvironment function to sample cubemap s. This function is implemented as follows:

half3 Unity_GlossyEnvironment (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn) { half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ; // TODO: CAUTION: remap from Morten may work only with offline convolution, see impact with runtime convolution! // For now disabled #if 0 float m = PerceptualRoughnessToRoughness(perceptualRoughness); // m is the real roughness parameter const float fEps = 1.192092896e-07F; // smallest such that 1.0+FLT_EPSILON != 1.0 (+1e-4h is NOT good here. is visibly very wrong) float n = (2.0/max(fEps, m*m))-2.0; // remap to spec power. See eq. 21 in --> https://dl.dropboxusercontent.com/u/55891920/papers/mm_brdf.pdf n /= 4; // remap from n_dot_h formulatino to n_dot_r. See section "Pre-convolved Cube Maps vs Path Tracers" --> https://s3.amazonaws.com/docs.knaldtech.com/knald/1.0.0/lys_power_drops.html perceptualRoughness = pow( 2/(n+2), 0.25); // remap back to square root of real roughness (0.25 include both the sqrt root of the conversion and sqrt for going from roughness to perceptualRoughness) #else // MM: came up with a surprisingly close approximation to what the #if 0'ed out code above does. perceptualRoughness = perceptualRoughness*(1.7 - 0.7*perceptualRoughness); #endif half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness); half3 R = glossIn.reflUVW; half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip); return DecodeHDR(rgbm, hdr); }

The UNITY_ARGS_TEXCUBE macro is a macro that defines cubemap as a function parameter and is used to declare functions. When calling a function, we need to use the UNITY_PASS_TEXCUBE macro to pass the cubmap parameter accordingly. The hdr parameter is used to convert hdr to rgb color when the cubemap contains the hdr color. Usually, the unit_Cube0_hdr is passed directly. unity_GlossyEnvironData is the U color.Nity defines a data structure, we need to set its roughness and reflUVW attributes, roughness is the coarseness of the material, the more obscure the reflection of the coarser object; reflUVW is the reflection vector used to sample cubemap. We can call this function as follows:

float3 reflectionDir = reflect(-viewDir, i.normal); Unity_GlossyEnvironmentData envData; envData.roughness = 1 - _Smoothness; envData.reflUVW = reflectionDir; float3 specular = Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, envData);

This function first transforms roughness, because the mipmap level of roughness and cubemap is not linear, Unity uses an approximate formula to simulate:

r
=
1.7
r
âˆ’
0.7
r
2
r = 1.7r - 0.7r^2
r=1.7râˆ’0.7r2

Because the larger the roughness, the more obscure the reflection of the object, which is similar to the higher the mipmap level of the sampled cubemap. Once the corresponding mipmap level is obtained, we can use UNITY_SAMPLE_TEXCUBE_LOD to sample the cubemap and get the result of the sample. If it is in hdr format, it is further converted to rgb format.

# box projection

Using the reflection probe provided by Unity, we can easily achieve the reflection effect. If an object that shows the reflection effect will move, we need to check Box Projection in the reflection probe.Similarly, Unity provides the BoxProjectedCubemapDirection function to calculate the reflection vectors under the box projection:

inline float3 BoxProjectedCubemapDirection (float3 worldRefl, float3 worldPos, float4 cubemapCenter, float4 boxMin, float4 boxMax) { // Do we have a valid reflection probe? UNITY_BRANCH if (cubemapCenter.w > 0.0) { float3 nrdir = normalize(worldRefl); #if 1 float3 rbmax = (boxMax.xyz - worldPos) / nrdir; float3 rbmin = (boxMin.xyz - worldPos) / nrdir; float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin; #else // Optimized version float3 rbmax = (boxMax.xyz - worldPos); float3 rbmin = (boxMin.xyz - worldPos); float3 select = step (float3(0,0,0), nrdir); float3 rbminmax = lerp (rbmax, rbmin, select); rbminmax /= nrdir; #endif float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z); worldPos -= cubemapCenter.xyz; worldRefl = worldPos + nrdir * fa; } return worldRefl; }

The worldRefl is the reflection vector of the world space, the worldPos is the world coordinate of the point to be calculated, the cubemapCenter is the coordinate of the reflection probe, and the boxMin and boxMax are the minimum and maximum coordinates of the reflection probe bounding box. We can call the BoxProjectedCubemapDirection as follows:

float3 reflectionDir = reflect(-viewDir, i.normal); float3 reflUVW = BoxProjectedCubemapDirection(reflectionDir, i.worldPos, unity_SpecCube0_ProbePosition, unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);

Continuing to look at the internal implementation of the function, here is a conditional judgment that when cubemapCenter.w > 0, the reflection probe has box projection enabled, and we only need to calculate with it enabled.

When box projection is enabled, the incoming worldRefl vector is not exactly the vector that we sampled for cubemap. As shown in the figure, I is the current point, C is the center point of the reflection probe, P is the sample point of cubemap, and we require the vector W.

First, we need to calculate the vector U based on the principle of cubemap sampling. The direction of vector U is the same as the worldRefl vector and the length is the distance of the point intersecting the nearest bounding box's polygon. It is easy to know that the distance from six polygons to point I can be expressed as two sets of three-dimensional vectors:

float3 rbmax = (boxMax.xyz - worldPos); float3 rbmin = (boxMin.xyz - worldPos);

Then, based on the direction of the normalized worldRefl vector, that is, the worldRefl vector, you can get the time it takes to go along that direction to reach the six faces:

float3 nrdir = normalize(worldRefl); float3 rbmax = (boxMax.xyz - worldPos) / nrdir; float3 rbmin = (boxMin.xyz - worldPos) / nrdir;

The minimum time we need must be the minimum of time greater than 0:

float3 rbminmax = (nrdir > 0.0f) ? rbmax : rbmin; float fa = min(min(rbminmax.x, rbminmax.y), rbminmax.z);

Furthermore, the vector U can be found:

float3 u = nrdir * fa;

The value of vector V is obviously the difference between the world coordinates of two points:

float3 v = cubemapCenter.xyz - worldPos;

Then, the vector W = U - V is:

float3 w = nrdir * fa + worldPos - cubemapCenter.xyz;

# Reflection Probe Interpolation

Unity allows us to interpolate and fuse the values sampled by two reflection probes to achieve a transitional effect. Unity provides a UNITY_SPECCUBE_BLENDING macro to determine if the current platform supports reflection probe fusion. In addition, the w component of unity_SpecCube0_BoxMin stores the weight of the first reflection probe, and if the weight is large, our implementation code can ignore it.A second reflection probe exists to avoid unnecessary performance overhead:

#if UNITY_SPECCUBE_BLENDING float interpolator = unity_SpecCube0_BoxMin.w; UNITY_BRANCH if (interpolator < 0.99999) { float3 probe1 = Unity_GlossyEnvironment( UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1, unity_SpecCube0), unity_SpecCube0_HDR, envData ); float3 specular = lerp(probe1, probe0, interpolator); } else { float3 specular = probe0; } #else float3 specular = probe0; #endif

# Multiple reflection

To achieve effects such as mirroring, Unity supports rendering the cubemap of the reflection probe multiple times so that the reflected information can also be rendered into the cubemap. Related settings are in Window/Rendering/Lighting Settings:

# Reference

[1] Reflections

If you find my article helpful, please follow my WeChat Public Number (the road to game development for older social animals)-