Conversation
…mek's remove_core_matrix branch, added create from mat3x3
…c cast to/from truncated quat
| vector2_type binarySearch(const uint32_t2 coord) | ||
| { | ||
| // We use _lastWarpPixel here for corner sampling | ||
| float32_t2 xi = float32_t2(coord)/ _lastWarpPixel; | ||
| uint32_t2 p = uint32_t2(0, 0); | ||
| const uint32_t2 mip2x1 = findMSB(_mapSize.y); |
There was a problem hiding this comment.
to be able to use this as a sampler, I also need a codomain_type generate(const domaint_type xi) so best you construct this binarySearch in terms of that
There was a problem hiding this comment.
you also wouldn't store _lastWarpPixel here then, because it would just be a warpmap agnostic sampler
| // declare concept | ||
| #define NBL_CONCEPT_NAME LuminanceReadAccessor | ||
| #define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) | ||
| #define NBL_CONCEPT_TPLT_PRM_NAMES (U)(ScalarT) | ||
| // not the greatest syntax but works | ||
| #define NBL_CONCEPT_PARAM_0 (a,U) | ||
| #define NBL_CONCEPT_PARAM_1 (coord,uint32_t2) | ||
| #define NBL_CONCEPT_PARAM_2 (level,uint32_t) | ||
| // start concept | ||
| NBL_CONCEPT_BEGIN(3) | ||
| // need to be defined AFTER the concept begins | ||
| #define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 | ||
| #define coord NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 | ||
| #define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 | ||
| NBL_CONCEPT_END( | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template texelFetch(coord,level)) , ::nbl::hlsl::is_same_v, ScalarT)) | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template texelGather(coord,level)) , ::nbl::hlsl::is_same_v, vector<ScalarT, 4>)) | ||
| ); | ||
| #undef level | ||
| #undef coord | ||
| #undef a | ||
| #include <nbl/builtin/hlsl/concepts/__end.hlsl> |
There was a problem hiding this comment.
if you merge latest path tracer branch, you can forego this concept in lieu of a LoadableImage with Components=1 and add a GatherableImage with all the other accessors , see #969 (comment)
| template <typename ScalarT, typename LuminanceAccessorT, typename HierarchicalSamplerT, typename PostWarpT | ||
| NBL_PRIMARY_REQUIRES(is_scalar_v<ScalarT> && | ||
| concepts::accessors::GenericReadAccessor<LuminanceAccessorT, ScalarT, float32_t2> && | ||
| hierarchical_image::HierarchicalSampler<HierarchicalSamplerT, ScalarT> && | ||
| concepts::Warp<PostWarpT>) | ||
| struct HierarchicalImage | ||
| { | ||
| using scalar_type = ScalarT; | ||
| using vector2_type = vector<ScalarT, 2>; | ||
| using vector3_type = vector<ScalarT, 3>; | ||
| using vector4_type = vector<ScalarT, 4>; | ||
| LuminanceAccessorT _lumaMap; | ||
| HierarchicalSamplerT _warpMap; | ||
| uint32_t2 _warpSize; | ||
| uint32_t2 _lastWarpPixel; | ||
| scalar_type _rcpAvgLuma; | ||
|
|
||
| static HierarchicalImage create(NBL_CONST_REF_ARG(LuminanceAccessorT) lumaMap, NBL_CONST_REF_ARG(HierarchicalSamplerT) warpMap, uint32_t2 warpSize, scalar_type avgLuma) | ||
| { | ||
| HierarchicalImage<ScalarT, LuminanceAccessorT, HierarchicalSamplerT, PostWarpT> result; | ||
| result._lumaMap = lumaMap; | ||
| result._warpMap = warpMap; | ||
| result._warpSize = warpSize; | ||
| result._lastWarpPixel = warpSize - uint32_t2(1, 1); | ||
| result._rcpAvgLuma = ScalarT(1.0) / avgLuma; | ||
| return result; | ||
| } | ||
|
|
||
| vector2_type inverseWarp_and_deferredPdf(NBL_REF_ARG(scalar_type) pdf, vector3_type direction) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| vector2_type envmapUv = PostWarpT::inverseWarp(direction); | ||
| scalar_type luma; | ||
| _lumaMap.get(envmapUv, luma); | ||
| pdf = (luma * _rcpAvgLuma) * PostWarpT::backwardDensity(direction); | ||
| return envmapUv; | ||
| } | ||
|
|
||
| scalar_type deferredPdf(vector3_type direction) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| vector2_type envmapUv = PostWarpT::inverseWarp(direction); | ||
| scalar_type luma; | ||
| _lumaMap.get(envmapUv, luma); | ||
| return luma * _rcpAvgLuma * PostWarpT::backwardDensity(direction); | ||
| } | ||
|
|
||
| vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(vector2_type) uv, vector2_type xi) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| const vector2_type texelCoord = xi * float32_t2(_lastWarpPixel); | ||
|
|
||
| matrix<scalar_type, 4, 2> uvs = _warpMap.sampleUvs(uint32_t2(texelCoord)); | ||
|
|
||
| const vector2_type interpolant = frac(texelCoord); | ||
|
|
||
| const vector2_type xDiffs[] = { | ||
| uvs[2] - uvs[3], | ||
| uvs[1] - uvs[0] | ||
| }; | ||
| const vector2_type yVals[] = { | ||
| xDiffs[0] * interpolant.x + uvs[3], | ||
| xDiffs[1] * interpolant.x + uvs[0] | ||
| }; | ||
| const vector2_type yDiff = yVals[1] - yVals[0]; | ||
| uv = yDiff * interpolant.y + yVals[0]; | ||
|
|
||
| const WarpResult<vector3_type> warpResult = PostWarpT::warp(uv); | ||
|
|
||
| const scalar_type detInterpolJacobian = determinant(matrix<scalar_type, 2, 2>( | ||
| lerp(xDiffs[0], xDiffs[1], interpolant.y), // first column dFdx | ||
| yDiff // second column dFdy | ||
| )) * _lastWarpPixel.x * _lastWarpPixel.y; | ||
|
|
||
| pdf = abs(warpResult.density / detInterpolJacobian); | ||
|
|
||
| return warpResult.dst; | ||
| } | ||
| }; |
There was a problem hiding this comment.
separate file, also struct needs better name like WarpMap sampler
| #undef a | ||
| #include <nbl/builtin/hlsl/concepts/__end.hlsl> | ||
|
|
||
| // sampleUvs return 4 UVs in a square to calculate the jacobian matrix |
There was a problem hiding this comment.
replace "to calculate the jacobian matrix" with "for manual bilinear interpolation with differentiability"
| struct SLumaGenPushConstants | ||
| { | ||
| float32_t3 lumaRGBCoefficients; | ||
| uint32_t2 lumaMapResolution; |
There was a problem hiding this comment.
you can use uint32_t bitfield of 16 bit each per axis (don't use uint16_t2 though because AMD doesn't support 16 bit push constants)
| [[vk::binding(0, 0)]] Texture2D<float32_t4> envMap; | ||
| [[vk::binding(1, 0)]] RWTexture2D<float32_t> outImage; | ||
|
|
||
| [numthreads(WORKGROUP_DIM, WORKGROUP_DIM, 1)] |
There was a problem hiding this comment.
imho don't pass a define, and hardcode to 16x16 (but obviously leave a cosntexpr in a shared header so the c++ can be in-sync)
|
|
||
| const float uv_y = (float(threadID.y) + float(0.5f)) / pc.lumaMapResolution.y; | ||
| const float32_t3 envMapSample = envMap.Load(float32_t3(threadID.xy, 0)); | ||
| const float32_t luma = hlsl::dot(envMapSample, pc.lumaRGBCoefficients) * sin(numbers::pi<float32_t> * uv_y); |
There was a problem hiding this comment.
this assumes a spherical warp
| [[vk::binding(0, 0)]] Texture2D<float32_t4> envMap; | ||
| [[vk::binding(1, 0)]] RWTexture2D<float32_t> outImage; |
There was a problem hiding this comment.
this assumes a 2D map and not a cubemap/layered thing, use layered images instead
| return float32_t4( | ||
| lumaMap.Load(uint32_t3(coord, level), uint32_t2(0, 1)), | ||
| lumaMap.Load(uint32_t3(coord, level), uint32_t2(1, 1)), | ||
| lumaMap.Load(uint32_t3(coord, level), uint32_t2(1, 0)), | ||
| lumaMap.Load(uint32_t3(coord, level), uint32_t2(0, 0)) | ||
| ); |
There was a problem hiding this comment.
btw OOB reads using texelFetch are undefined, asser that coord is between 0 and lastPixel-1 for both axes
| #define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename) | ||
| #define NBL_CONCEPT_TPLT_PRM_NAMES (HierarchicalSamplerT)(ScalarT) | ||
| // not the greatest syntax but works | ||
| #define NBL_CONCEPT_PARAM_0 (sampler,HierarchicalSamplerT) |
There was a problem hiding this comment.
sampler is a reserved keyword in HLSL
| #define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 | ||
| #define coord NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 | ||
| NBL_CONCEPT_END( | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.template sampleUvs(coord)) , ::nbl::hlsl::is_same_v, matrix<ScalarT, 4, 2>)) |
There was a problem hiding this comment.
signature should be template sampleUvs<matrix<>,vector<>>(outMatrix,coord)
| LuminanceSampler luminanceSampler = | ||
| LuminanceSampler::create(luminanceAccessor, uint32_t2(lumaMapWidth, lumaMapHeight), lumaMapWidth != lumaMapHeight, uint32_t2(lumaMapWidth, lumaMapHeight)); |
There was a problem hiding this comment.
why are we passing the uint32_t2(lumaMapWidth, lumaMapHeight) resolution twice ?
| uint32_t2 pixelCoord = threadID.xy; | ||
|
|
||
| outImage[pixelCoord] = luminanceSampler.binarySearch(pixelCoord); | ||
|
|
There was a problem hiding this comment.
you need to handle OOB, I can have a 4x4 sphere map, but my workgroup is 16x16
| template <typename T = float32_t> | ||
| struct Spherical | ||
| { | ||
| using density_type = T; | ||
| using domain_type = vector<density_type, 2>; | ||
| using codomain_type = vector<density_type, 3>; | ||
|
|
||
| template <typename DomainT NBL_FUNC_REQUIRES(is_same_v<DomainT, domain_type>) | ||
| static WarpResult<codomain_type> warp(const DomainT uv) | ||
| { | ||
| codomain_type dir; | ||
| dir.x = cos(uv.x * density_type(2) * numbers::pi<density_type>); | ||
| dir.z = sqrt(density_type(1) - (dir.x * dir.x)); | ||
| if (uv.x > density_type(0.5)) | ||
| dir.z = -dir.z; | ||
| const density_type theta = uv.y * numbers::pi<density_type>; | ||
| const density_type cosTheta = cos(theta); | ||
| const density_type sinTheta = sqrt(density_type(1) - (cosTheta * cosTheta)); | ||
| dir.xz *= sinTheta; | ||
| dir.y = cosTheta; | ||
|
|
||
| WarpResult<codomain_type> warpResult; | ||
| warpResult.dst = dir; | ||
| warpResult.density = density_type(1) / (density_type(2) * sinTheta * numbers::pi<density_type> * numbers::pi<density_type>); | ||
|
|
||
| return warpResult; | ||
| } | ||
|
|
||
| template <typename CodomainT NBL_FUNC_REQUIRES(is_same_v<CodomainT, codomain_type>) | ||
| static domain_type inverseWarp(const CodomainT v) | ||
| { | ||
| const density_type phi = atan2(v.z, v.x); | ||
| const density_type theta = acos(v.y); | ||
| density_type uv_x = phi * density_type(0.5) * numbers::inv_pi<density_type>; | ||
| if (uv_x < density_type(0)) | ||
| uv_x += density_type(1); | ||
| density_type uv_y = theta * numbers::inv_pi<density_type>; | ||
| return domain_type(uv_x, uv_y); | ||
| } | ||
|
|
||
|
|
||
| template <typename DomainT NBL_FUNC_REQUIRES(is_same_v<DomainT, domain_type>) | ||
| static density_type forwardDensity(const DomainT uv) | ||
| { | ||
| const density_type theta = uv.y * numbers::pi<density_type>; | ||
| return density_type(1) / (sin(theta) * density_type(2) * numbers::pi<density_type> * numbers::pi<density_type>); | ||
|
|
||
| } | ||
|
|
||
| template <typename CodomainT NBL_FUNC_REQUIRES(is_same_v<CodomainT, codomain_type>) | ||
| static density_type backwardDensity(const CodomainT dst) | ||
| { | ||
| const density_type cosTheta = dst.y; | ||
| const density_type sinTheta = sqrt(density_type(1) - (cosTheta * cosTheta)); | ||
| return density_type(1) / (sinTheta * density_type(2) * numbers::pi<density_type> * numbers::pi<density_type>); | ||
| } | ||
| }; |
There was a problem hiding this comment.
under the framework of #1001 we can make this into a Bijective Sampler
| matrix<scalar_type, 4, 2> sampleUvs(uint32_t2 sampleCoord) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| const vector2_type dir0 = binarySearch(sampleCoord + vector2_type(0, 1)); | ||
| const vector2_type dir1 = binarySearch(sampleCoord + vector2_type(1, 1)); | ||
| const vector2_type dir2 = binarySearch(sampleCoord + vector2_type(1, 0)); | ||
| const vector2_type dir3 = binarySearch(sampleCoord); | ||
| return matrix<scalar_type, 4, 2>( | ||
| dir0, | ||
| dir1, | ||
| dir2, | ||
| dir3 | ||
| ); | ||
| } |
There was a problem hiding this comment.
not sure that sampleUvs should be exposed like that (if something needs it -like a test- let them call binarySearch 4 times), why?
- if not using a warpmap you wouldn't actually importance sample 4 times to perform finite difference and get your jacobian, you'd use the Luma of the image (with NEAREST not linear sampler) as the PDF because thats 100% accurate
- if using a warmap you'd textureGather it and work out the Jacobian from differntiating the bilinear interpolation equation
| is_scalar_v<ScalarT> && | ||
| hierarchical_image::LuminanceReadAccessor<LuminanceAccessorT, ScalarT> | ||
| ) | ||
| struct LuminanceMapSampler |
There was a problem hiding this comment.
this one is actually the HierarchicalLuminanceSampler
| // We use _lastWarpPixel here for corner sampling | ||
| float32_t2 xi = float32_t2(coord)/ _lastWarpPixel; | ||
| uint32_t2 p = uint32_t2(0, 0); | ||
| const uint32_t2 mip2x1 = findMSB(_mapSize.y); |
There was a problem hiding this comment.
why are you using a uint32_t2 to store this, its a uint16_t at most, its a scalar quantity
Also should be precomputed
| LuminanceAccessorT _map; | ||
| uint32_t2 _mapSize; | ||
| uint32_t2 _lastWarpPixel; | ||
| bool _aspect2x1; |
There was a problem hiding this comment.
store these instead
float32_t2 rcpMapSize;
uint16_t mip2x1 : 15;
uint16_t aspect2x1 : 1;| template <typename ScalarT, typename LuminanceAccessorT | ||
| NBL_PRIMARY_REQUIRES( | ||
| is_scalar_v<ScalarT> && | ||
| hierarchical_image::LuminanceReadAccessor<LuminanceAccessorT, ScalarT> | ||
| ) |
There was a problem hiding this comment.
add a bool for whether the accessor is corner sampled or not
|
|
||
|
|
||
| // If we don`t add xi, the sample will clump to the lowest corner of environment map texel. We add xi to simulate uniform distribution within a pixel and make the sample continuous. This is why we compute the pdf not from the normalized luminance of the texel, instead from the reciprocal of the Jacobian. | ||
| const vector2_type directionUV = (vector2_type(p.x, p.y) + xi) / vector2_type(_mapSize); |
There was a problem hiding this comment.
if your spheremap or octahedral maps are corner sampled, the edge pixels need special treatment
Essentially whenever your X or Y coordinate is an edge coordinate, you'd need to weight their luma contribution down by 50% and also change how the remaining xi gets added:
- coord==0 then xi gets rescaled to [0.5,1.0]
- coord==Last then xi gets rescaled to [0,0.5]
There was a problem hiding this comment.
then you'd actually rescale that final UV from [0.5/size,1-0.5/size] to [0,1] before outputting it in the warpmap
| #ifndef _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_WARP_INCLUDED_ | ||
| #define _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_WARP_INCLUDED_ | ||
|
|
||
| #include <nbl/builtin/hlsl/concepts.hlsl> | ||
|
|
||
| namespace nbl | ||
| { | ||
| namespace hlsl | ||
| { | ||
| namespace sampling | ||
| { | ||
|
|
||
| template <typename CodomainT, typename DensityT = float32_t> | ||
| struct WarpResult | ||
| { | ||
| CodomainT dst; | ||
| DensityT density; | ||
| }; | ||
| } | ||
|
|
||
| namespace concepts | ||
| { | ||
|
|
||
| // declare concept | ||
| #define NBL_CONCEPT_NAME Warp | ||
| #define NBL_CONCEPT_TPLT_PRM_KINDS (typename) | ||
| #define NBL_CONCEPT_TPLT_PRM_NAMES (U) | ||
| // not the greatest syntax but works | ||
| #define NBL_CONCEPT_PARAM_0 (warper,U) | ||
| #define NBL_CONCEPT_PARAM_1 (xi,typename U::domain_type) | ||
| #define NBL_CONCEPT_PARAM_2 (dst,typename U::codomain_type) | ||
| // start concept | ||
| NBL_CONCEPT_BEGIN(3) | ||
| #define warper NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 | ||
| #define xi NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 | ||
| #define dst NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 | ||
| NBL_CONCEPT_END( | ||
| ((NBL_CONCEPT_REQ_TYPE)(U::domain_type)) | ||
| ((NBL_CONCEPT_REQ_TYPE)(U::codomain_type)) | ||
| ((NBL_CONCEPT_REQ_TYPE)(U::density_type)) | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((warper.template warp<typename U::domain_type>(xi)) , ::nbl::hlsl::is_same_v, sampling::WarpResult<typename U::codomain_type, typename U::density_type>)) | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((warper.template forwardDensity<typename U::domain_type>(xi)) , ::nbl::hlsl::is_same_v, typename U::density_type)) | ||
| ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((warper.template backwardDensity<typename U::codomain_type>(dst)) , ::nbl::hlsl::is_same_v, typename U::density_type)) | ||
| ); | ||
| #undef dst | ||
| #undef xi | ||
| #undef warper | ||
| #include <nbl/builtin/hlsl/concepts/__end.hlsl> | ||
|
|
||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| #endif No newline at end of file |
There was a problem hiding this comment.
I think this file will be made obsolete by #1001 s Bijective Sampler concept
| #include "nbl/video/declarations.h" | ||
|
|
||
| namespace nbl::core | ||
| { |
There was a problem hiding this comment.
completely wrong namespace, you're using video, this file needs to live in nbl/video/sampling
| set(NBL_CORE_SOURCES | ||
| core/alloc/refctd_memory_resource.cpp | ||
| core/hash/blake.cpp | ||
| core/sampling/EnvmapSampler.cpp |
There was a problem hiding this comment.
again, this should be video
| option(NBL_BUILD_MITSUBA_LOADER "Enable nbl::ext::MitsubaLoader?" ON) | ||
| option(NBL_BUILD_IMGUI "Enable nbl::ext::ImGui?" ON) | ||
| option(NBL_BUILD_DEBUG_DRAW "Enable Nabla Debug Draw extension?" ON) | ||
| option(NBL_BUILD_ENVMAP_IMPORTANCE_SAMPLING "Enable Nabla Envmap Importance Sampling extension?" ON) |
There was a problem hiding this comment.
no longer an extension, dont need the option
| vector2_type inverseWarp_and_deferredPdf(NBL_REF_ARG(scalar_type) pdf, vector3_type direction) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| vector2_type envmapUv = PostWarpT::inverseWarp(direction); | ||
| scalar_type luma; | ||
| _lumaMap.get(envmapUv, luma); | ||
| pdf = (luma * _rcpAvgLuma) * PostWarpT::backwardDensity(direction); | ||
| return envmapUv; | ||
| } | ||
|
|
||
| scalar_type deferredPdf(vector3_type direction) NBL_CONST_MEMBER_FUNC | ||
| { | ||
| vector2_type envmapUv = PostWarpT::inverseWarp(direction); | ||
| scalar_type luma; | ||
| _lumaMap.get(envmapUv, luma); | ||
| return luma * _rcpAvgLuma * PostWarpT::backwardDensity(direction); | ||
| } | ||
|
|
||
| vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(vector2_type) uv, vector2_type xi) NBL_CONST_MEMBER_FUNC |
There was a problem hiding this comment.
make it conform to #1001 's ResamplableSampler
| template <typename ScalarT, typename LuminanceAccessorT, typename HierarchicalSamplerT, typename PostWarpT | ||
| NBL_PRIMARY_REQUIRES(is_scalar_v<ScalarT> && | ||
| concepts::accessors::GenericReadAccessor<LuminanceAccessorT, ScalarT, float32_t2> && | ||
| hierarchical_image::HierarchicalSampler<HierarchicalSamplerT, ScalarT> && | ||
| concepts::Warp<PostWarpT>) | ||
| struct HierarchicalImage |
There was a problem hiding this comment.
this should be called WarpmapSampler or something like that
| const vector2_type texelCoord = xi * float32_t2(_lastWarpPixel); | ||
|
|
||
| matrix<scalar_type, 4, 2> uvs = _warpMap.sampleUvs(uint32_t2(texelCoord)); | ||
|
|
There was a problem hiding this comment.
warpmap should convert you from [0,1] normalized xi to its own texels,the sampleUvs should really work same as textureGather, it should take normalized uvs as input
|
|
||
| matrix<scalar_type, 4, 2> uvs = _warpMap.sampleUvs(uint32_t2(texelCoord)); | ||
|
|
||
| const vector2_type interpolant = frac(texelCoord); |
There was a problem hiding this comment.
make the _warpMap.gather spit out the interpolant so its cleaner (requires a specialized gather overload, adding on top of Gatherable concept)
| LuminanceAccessorT _lumaMap; | ||
| HierarchicalSamplerT _warpMap; | ||
| uint32_t2 _warpSize; | ||
| uint32_t2 _lastWarpPixel; |
There was a problem hiding this comment.
you actually only need to store the _lastWarpPixel.x * _lastWarpPixel.y prodct, I expect the warpmap to handle gathering
| if (choseSecond(wx_0, wx_1, xi.x)) | ||
| p.x |= 1; |
There was a problem hiding this comment.
btw you can get the PDF of the finally chosen texel as metadata if on the final call to choseSecond you save wx_0 or wx_1 and thats your chosen pixel luminance, you just need to know the average to and total number of texels (corner sampled edges count as half pixels) get the PDF
(obvioulsy if mip2x1 is 0, the PDF is either 1.0 or the dummy from the choice on line 64)
Description
Rework environment map importance sampling to vulkan and hlsl
Testing
Rework example 0 to use vulkan and hlsl
Unit Test Pull Request
TODO list: