UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

794 lines (786 loc) 30.8 kB
import { F as F32, B as BU, S as SS, k as getOrCreateSampler, b1 as createRenderTarget, i as TU, b2 as drawList, as as getBilinearSampler, b3 as _vis, b4 as biasedMipLevelCount, aa as PBR2_HAS_REFRACTION, T as ThrowLiteError, aD as PBR_HAS_THICKNESS_MAP } from './index-By0tcgYN.esm.js'; import { recordMipmaps } from './generate-mipmaps-BL7R_dXN.esm.js'; function createImageProcessingTask(config, engine, scene) { let state = null; const task = { name: config.name ?? "image-processing", engine, scene, _passes: [], record() { disposeImageProcessingState(state); state = createImageProcessingState(engine, config.source); }, execute() { if (!state) { return 0; } const img = scene.imageProcessing; const data = new F32([img.exposure, img.contrast, img.toneMappingEnabled === true ? 1 : 0, 0]); engine._device.queue.writeBuffer(state.params, 0, data); const pass = engine._currentEncoder.beginRenderPass({ colorAttachments: [ { view: engine.scRT._colorView, loadOp: "clear", storeOp: "store", clearValue: scene.clearColor } ] }); pass.setPipeline(state.pipeline); pass.setBindGroup(0, state.bindGroup); pass.draw(3); pass.end(); return 1; }, dispose() { disposeImageProcessingState(state); state = null; this._passes.length = 0; } }; return task; } function createImageProcessingState(engine, source) { const texture = resolveImageProcessingTexture(source); if (!texture) { throw new Error("Image processing source has no color texture"); } const device = engine._device; const sampleCount = texture.sampleCount ?? 1; const multisampled = sampleCount > 1; const params = device.createBuffer({ size: 16, usage: BU.UNIFORM | BU.COPY_DST }); const bgl = device.createBindGroupLayout({ entries: [ { binding: 0, visibility: SS.FRAGMENT, buffer: { type: "uniform" } }, { binding: 1, visibility: SS.FRAGMENT, texture: { sampleType: multisampled ? "unfilterable-float" : "float", multisampled } } ] }); const common = `struct P{e:f32,c:f32,t:f32,p:f32} @group(0)@binding(0)var<uniform> p:P; @vertex fn vs(@builtin(vertex_index)i:u32)->@builtin(position) vec4f{var a=array<vec2f,3>(vec2f(-1,-3),vec2f(3,1),vec2f(-1,1));return vec4f(a[i],0,1);} fn ip(r:vec4f)->vec4f{var c=r.rgb*p.e; if(p.t>0.5){c=1.0-exp2(-1.590579*c);} c=clamp(pow(max(c,vec3f(0)),vec3f(1/2.2)),vec3f(0),vec3f(1)); let h=c*c*(3.0-2.0*c); if(p.c<1.0){c=mix(vec3f(0.5),c,p.c);}else{c=mix(c,h,p.c-1.0);} return vec4f(max(c,vec3f(0)),r.a);}`; const textureDecl = multisampled ? `@group(0)@binding(1)var s:texture_multisampled_2d<f32>;` : `@group(0)@binding(1)var s:texture_2d<f32>;`; const fragment = multisampled ? `@fragment fn fs(@builtin(position) q:vec4f)->@location(0) vec4f{let d=textureDimensions(s);let px=clamp(vec2i(q.xy),vec2i(0),vec2i(d)-1);let n=textureNumSamples(s);var c=vec4f(0);for(var i=0u;i<n;i++){c+=ip(textureLoad(s,px,i));}return c/f32(n);}` : `@fragment fn fs(@builtin(position) q:vec4f)->@location(0) vec4f{let d=textureDimensions(s);return ip(textureLoad(s,clamp(vec2i(q.xy),vec2i(0),vec2i(d)-1),0));}`; const shader = device.createShaderModule({ code: `${common}${textureDecl}${fragment}` }); const pipeline = device.createRenderPipeline({ layout: device.createPipelineLayout({ bindGroupLayouts: [bgl] }), vertex: { module: shader, entryPoint: "vs" }, fragment: { module: shader, entryPoint: "fs", targets: [{ format: engine.format }] }, primitive: { topology: "triangle-list" } }); const bindGroup = device.createBindGroup({ layout: bgl, entries: [ { binding: 0, resource: { buffer: params } }, { binding: 1, resource: texture.createView() } ] }); return { pipeline, bindGroup, params }; } function resolveImageProcessingTexture(source) { const resolved = typeof source === "function" ? source() : source; if (!resolved) { return null; } if ("_colorTexture" in resolved) { return resolved._colorTexture; } return resolved.texture; } function disposeImageProcessingState(state) { state?.params.destroy(); } const _trilinearAnisotropicDesc = { magFilter: "linear", minFilter: "linear", mipmapFilter: "linear", addressModeU: "repeat", addressModeV: "repeat", addressModeW: "repeat", maxAnisotropy: 4 }; function getTrilinearAnisotropicSampler(engine) { return getOrCreateSampler(engine, _trilinearAnisotropicDesc); } let _depthGrab = null; function _installDepthGrab(impl) { _depthGrab = impl; } const BLIT_SHADER = `@group(0)@binding(0)var t:texture_2d<f32>;@group(0)@binding(1)var s:sampler;struct V{@builtin(position)p:vec4f,@location(0)u:vec2f};@vertex fn vs(@builtin(vertex_index)i:u32)->V{var p=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));var u=array<vec2f,3>(vec2f(0,1),vec2f(2,1),vec2f(0,-1));return V(vec4f(p[i],0,1),u[i]);}@fragment fn fs(v:V)->@location(0)vec4f{return textureSample(t,s,v.u);}`; const BLIT_MSAA_SHADER = `@group(0)@binding(0)var t:texture_multisampled_2d<f32>;struct V{@builtin(position)p:vec4f,@location(0)u:vec2f};@vertex fn vs(@builtin(vertex_index)i:u32)->V{var p=array<vec2f,3>(vec2f(-1,-1),vec2f(3,-1),vec2f(-1,3));var u=array<vec2f,3>(vec2f(0,1),vec2f(2,1),vec2f(0,-1));return V(vec4f(p[i],0,1),u[i]);}fn l(p:vec2i)->vec4f{let n=textureNumSamples(t);var c=vec4f(0);for(var i=0u;i<n;i++){c+=textureLoad(t,p,i);}return c/f32(n);}@fragment fn fs(v:V)->@location(0)vec4f{let d=vec2i(textureDimensions(t));let q=clamp(v.u*vec2f(d)-.5,vec2f(0),vec2f(d-vec2i(1)));let p=vec2i(floor(q));let f=fract(q);let p1=min(p+vec2i(1),d-vec2i(1));return mix(mix(l(p),l(vec2i(p1.x,p.y)),f.x),mix(l(vec2i(p.x,p1.y)),l(p1),f.x),f.y);}`; const REFRACTION_LOD_BIAS = 4; let blitPipelines = null; let blitShader = null; let blitMsaaShader = null; let blitBgl = null; let blitMsaaBgl = null; let blitDevice = null; function enableSceneTransmission(scene, engine) { markPbrMaterialsLinear(scene); enableSceneTransmissionTasks(scene, engine); } function _t(scene, engine) { const states = scene.meshes.flatMap((mesh) => { const mat = mesh.material; return mat ? [[mat, mat._linearImageProcessing, mat._renderFeatures]] : []; }); markPbrMaterialsLinear(scene); return [ () => enableSceneTransmissionTasks(scene, engine), () => { for (const [mat, linear, features] of states) { mat._linearImageProcessing = linear; mat._renderFeatures = features; } } ]; } function enableSceneTransmissionTasks(scene, engine) { let lastRenderTask = null; for (const task of scene._frameGraph._tasks) { if ("_renderables" in task) { const renderTask = task; enableRenderTaskTransmission(renderTask, engine); lastRenderTask = renderTask; } } if (lastRenderTask && !scene._frameGraph._tasks.some((task) => task.name === "transmission-image-processing")) { scene._frameGraph._tasks.push(createImageProcessingTask({ name: "transmission-image-processing", source: lastRenderTask._config.rt }, engine, scene)); } } function enableRenderTaskTransmission(task, engine, options) { if (task._config.transmission?.grabDepth) { const priorPreload = task._preload?.bind(task); task._preload = async () => { if (priorPreload) { await priorPreload(); } await import('./transmission-depth-grab-D7CMQ5g6.esm.js'); }; } const grab = { get texture() { return task._targetSignature._transmissionTexture ?? null; }, get depthTexture() { return task._targetSignature._transmissionDepthTexture ?? null; } }; if (task._executeWithTransmission) { return grab; } { retargetRenderTaskToLinearOffscreen(task); } let state = null; const record = task.record.bind(task); const execute = task.execute?.bind(task); const dispose = task.dispose?.bind(task); task.record = () => { disposeRenderTaskTransmission(state); state = createRenderTaskTransmission(task, engine); task._targetSignature._transmissionTexture = state.texture; record(); configureTransmissionSource(state, task, engine); }; if (execute) { task.execute = () => executeRenderTaskLinear(task.scene, execute); } task.dispose = () => { disposeRenderTaskTransmission(state); state = null; dispose?.(); }; task._executeWithTransmission = (sampleCount) => executePassWithTransmission(task, engine, state, sampleCount); return grab; } function retargetRenderTaskToLinearOffscreen(task) { const cfg = task._config; const oldDesc = cfg.rt._descriptor; const surface = task.scene.surface; const sampleCount = surface.msaaSamples; const ownsDepth = !cfg.depth; const newRt = createRenderTarget({ lbl: "transmission-linear", format: "rgba16float", dFormat: ownsDepth ? oldDesc.dFormat ?? "depth24plus-stencil8" : void 0, _depthClearValue: oldDesc._depthClearValue, _depthCompare: oldDesc._depthCompare, samples: sampleCount, size: surface }); cfg.rt = newRt; cfg.rst = void 0; const sig = task._targetSignature; sig._colorFormat = "rgba16float"; sig._depthStencilFormat = cfg.depth?._descriptor.dFormat ?? newRt._descriptor.dFormat; sig._depthCompare = newRt._descriptor._depthCompare; sig._sampleCount = sampleCount; task._opaqueBundles.length = 0; task._lastVersion = -1; } function executeRenderTaskLinear(scene, execute) { const imageProcessing = scene.imageProcessing; const toneMappingEnabled = imageProcessing.toneMappingEnabled; const clearColor = scene.clearColor; const linearClearColor = inverseImageProcessedColor(clearColor, imageProcessing.exposure, imageProcessing.contrast, toneMappingEnabled === true); imageProcessing.toneMappingEnabled = -1; scene.clearColor = linearClearColor; try { return execute(); } finally { scene.clearColor = clearColor; imageProcessing.toneMappingEnabled = toneMappingEnabled; } } function inverseImageProcessedColor(color, exposure, contrast, toneMapping) { return { r: inverseImageProcessedChannel(color.r, exposure, contrast, toneMapping), g: inverseImageProcessedChannel(color.g, exposure, contrast, toneMapping), b: inverseImageProcessedChannel(color.b, exposure, contrast, toneMapping), a: color.a }; } function inverseImageProcessedChannel(value, exposure, contrast, toneMapping) { let c = clamp01(value); if (contrast < 1) { c = contrast > 0 ? clamp01((c - 0.5 * (1 - contrast)) / contrast) : 0.5; } else if (contrast > 1) { const mixAmount = contrast - 1; let lo = 0; let hi = 1; for (let i = 0; i < 16; i++) { const mid = (lo + hi) * 0.5; const high = mid * mid * (3 - 2 * mid); const out = mid + (high - mid) * mixAmount; if (out < c) { lo = mid; } else { hi = mid; } } c = (lo + hi) * 0.5; } c = c ** 2.2; if (toneMapping) { c = -Math.log2(Math.max(1 - c, 1e-6)) / 1.5905790328979492; } return exposure > 0 ? c / exposure : c; } function clamp01(v) { return Math.min(Math.max(v, 0), 1); } function markPbrMaterialsLinear(scene) { for (const mesh of scene.meshes) { const mat = mesh.material; if (mat) { mat._linearImageProcessing = true; mat._renderFeatures = void 0; } } } function createRenderTaskTransmission(task, engine) { const rt = task._config.rt; const width = 1024; const height = 1024; const format = "rgba16float"; const mipLevelCount = transmissionMipLevelCount(task._config.transmission, width, height); const generateMipmaps = mipLevelCount > 1; const texture = engine._device.createTexture({ label: task.name, size: { width, height }, format, mipLevelCount, usage: TU.RENDER_ATTACHMENT | TU.TEXTURE_BINDING | TU.COPY_DST }); const tex = { texture, view: texture.createView(), sampler: getTrilinearAnisotropicSampler(engine), width, height, invertY: false }; return { texture: tex, _baseView: texture.createView({ baseMipLevel: 0, mipLevelCount: 1 }), _sourceWidth: rt._width, _sourceHeight: rt._height, _sourceTexture: null, _blit: null, _depth: null, _copyCount: normalizeCopyCount(task._config.transmission), _generateMipmaps: generateMipmaps, _copies: 0 }; } function configureTransmissionSource(state, task, engine) { const rt = task._config.rt; state._sourceWidth = rt._width; state._sourceHeight = rt._height; state._sourceTexture = rt._colorTexture; const sampleCount = task._targetSignature._sampleCount; const sig = task._targetSignature; sig._transmissionDepthTexture = null; const depthSource = rt._depthTexture; if (task._config.transmission?.grabDepth && _depthGrab && depthSource && rt._width > 0 && rt._height > 0) { state._depth = _depthGrab.create(engine, depthSource, rt._width, rt._height, sampleCount > 1); sig._transmissionDepthTexture = state._depth.texture; } if (!state._sourceTexture) { return; } state._blit = shouldBlitTransmission(state, sampleCount) ? createTransmissionBlit(state, engine, state._sourceTexture, sampleCount > 1) : null; } function disposeRenderTaskTransmission(state) { state?.texture.texture.destroy(); state?._depth?.texture.texture.destroy(); } function executePassWithTransmission(task, engine, state, sampleCount) { state._copies = 0; const transparent = task._transparentBindings; const resolveView = sampleCount > 1 ? task._config.rst?._colorView ?? null : null; let pass = beginTaskPass(task, resolveView, sampleCount, false); let draws = drawBaseTask(task, pass); let lastPipeline = null; let overlay = null; for (let i = 0; i < transparent.length; i++) { const binding = transparent[i]; if (binding.renderable.mesh?.renderOnTop === true) { (overlay ??= []).push(binding); continue; } const transmissive = binding.renderable._transmissive === true; if (transmissive && canUpdateTransmission(state)) { pass.end(); updateTransmissionTexture(state, engine); pass = beginTaskPass(task, resolveView, sampleCount, true); setPassState(task, pass); lastPipeline = null; } const mesh = binding.renderable.mesh; if (mesh && mesh.visible === false) { continue; } if (binding.pipeline !== lastPipeline) { pass.setPipeline(binding.pipeline); lastPipeline = binding.pipeline; } draws += binding.draw(pass, engine); } if (overlay) { draws += drawList(pass, overlay, engine); } pass.end(); return draws; } function updateTransmissionTexture(state, engine) { if (!state._sourceTexture) { throw new Error("No transmission source"); } if (state._blit) { blitToTransmission(state, engine); } else { engine._currentEncoder.copyTextureToTexture( { texture: state._sourceTexture }, { texture: state.texture.texture }, { width: state.texture.width, height: state.texture.height } ); } if (state._generateMipmaps) { recordMipmaps(engine, state.texture.texture, engine._currentEncoder); } if (state._depth) { _depthGrab?.record(engine, state._depth); } state._copies++; } function getBlitPipeline(engine, format, multisampled) { const device = engine._device; if (device !== blitDevice) { blitPipelines?.clear(); blitPipelines = null; blitShader = null; blitMsaaShader = null; blitBgl = null; blitMsaaBgl = null; blitDevice = device; } if (multisampled) { blitMsaaShader ??= device.createShaderModule({ code: BLIT_MSAA_SHADER }); blitMsaaBgl ??= device.createBindGroupLayout({ entries: [{ binding: 0, visibility: SS.FRAGMENT, texture: { sampleType: "unfilterable-float", multisampled: true } }] }); } else { blitShader ??= device.createShaderModule({ code: BLIT_SHADER }); blitBgl ??= device.createBindGroupLayout({ entries: [ { binding: 0, visibility: SS.FRAGMENT, texture: { sampleType: "float" } }, { binding: 1, visibility: SS.FRAGMENT, sampler: {} } ] }); } blitPipelines ??= /* @__PURE__ */ new Map(); const key = `${format}:${multisampled ? "msaa" : ""}`; let pipeline = blitPipelines.get(key); if (!pipeline) { const bgl = multisampled ? blitMsaaBgl : blitBgl; pipeline = device.createRenderPipeline({ label: "transmission-copy", layout: device.createPipelineLayout({ bindGroupLayouts: [bgl] }), vertex: { module: multisampled ? blitMsaaShader : blitShader, entryPoint: "vs" }, fragment: { module: multisampled ? blitMsaaShader : blitShader, entryPoint: "fs", targets: [{ format }] }, primitive: { topology: "triangle-list" } }); blitPipelines.set(key, pipeline); } return pipeline; } function shouldBlitTransmission(state, sampleCount) { return sampleCount > 1 || state._sourceWidth !== state.texture.width || state._sourceHeight !== state.texture.height; } function createTransmissionBlit(state, engine, source, multisampled) { const device = engine._device; const pipeline = getBlitPipeline(engine, state.texture.texture.format, multisampled); const bindGroup = device.createBindGroup({ layout: multisampled ? blitMsaaBgl : blitBgl, entries: multisampled ? [{ binding: 0, resource: source.createView() }] : [ { binding: 0, resource: source.createView() }, { binding: 1, resource: getBilinearSampler(engine) } ] }); return { _pipeline: pipeline, _bindGroup: bindGroup }; } function blitToTransmission(state, engine) { const blit = state._blit; const pass = engine._currentEncoder.beginRenderPass({ colorAttachments: [{ view: state._baseView, loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 0 } }] }); pass.setPipeline(blit._pipeline); pass.setBindGroup(0, blit._bindGroup); pass.draw(3); pass.end(); } function canUpdateTransmission(state) { return state._copyCount === 0 || state._copies < state._copyCount; } function beginTaskPass(task, resolveTarget, sampleCount, load) { const att = task._colorAttachment; const depthLoadOp = load || !task._config.clr ? "load" : "clear"; if (load) { att.loadOp = "load"; } const depthAttachment = task._renderPassDescriptor.depthStencilAttachment; if (depthAttachment) { depthAttachment.depthLoadOp = depthLoadOp; if (depthAttachment.stencilLoadOp) { depthAttachment.stencilLoadOp = depthLoadOp; } } if (sampleCount > 1) { att.resolveTarget = resolveTarget ?? void 0; } else { att.resolveTarget = void 0; } return task.engine._currentEncoder.beginRenderPass(task._renderPassDescriptor); } function setPassState(task, pass) { const cfg = task._config; const rt = cfg.rt; const scene = task.scene; const camera = cfg.cam ?? scene.camera; const v = camera?.viewport; if (v) { const rw = rt._width; const rh = rt._height; const x = Math.floor(v.x * rw); const y = Math.floor((1 - v.y - v.height) * rh); const w = Math.ceil((v.x + v.width) * rw) - x; const h = Math.ceil((1 - v.y) * rh) - y; pass.setViewport(x, y, w, h, 0, 1); pass.setScissorRect(x, y, w, h); } pass.setBindGroup(0, task._sceneBG); } function drawBaseTask(task, pass) { const eng = task.engine; const rt = task._config.rt; const scene = task.scene; const opaqueBindings = task._opaqueBindings; const opaqueBundles = task._opaqueBundles; setPassState(task, pass); if (task._lastVersion !== scene._renderableVersion || task._lastVis !== _vis || opaqueBundles.length === 0) { const desc = rt._descriptor; const be = eng._device.createRenderBundleEncoder({ colorFormats: desc.format ? [desc.format] : [], depthStencilFormat: desc.dFormat, sampleCount: desc.samples ?? 1 }); be.setBindGroup(0, task._sceneBG); drawList(be, opaqueBindings, eng); opaqueBundles[0] = be.finish(); task._lastVersion = scene._renderableVersion; task._lastVis = _vis; } let draws = opaqueBindings.length; pass.executeBundles(opaqueBundles); pass.setBindGroup(0, task._sceneBG); draws += drawList(pass, task._directBindings, eng); return draws; } function normalizeCopyCount(cfg) { const count = cfg?.copyCount ?? 1; return count === Infinity ? 0 : Math.max(0, count | 0); } function transmissionMipLevelCount(cfg, width, height) { if (cfg?.generateMipmaps === false) { return 1; } const full = Math.floor(Math.log2(Math.max(width, height))) + 1; const defaultCount = biasedMipLevelCount(width, height, REFRACTION_LOD_BIAS); const requested = cfg?.mipLevelCount; if (requested === void 0) { return Math.min(full, defaultCount); } return Math.min(full, Math.max(1, requested | 0)); } const PBR2_HAS_VOLUME = 1 << 5; const PBR2_HAS_REFRACTION_MAP = 1 << 6; const PBR2_HAS_THICKNESS_GLTF_CHANNEL = 1 << 7; const PBR2_LINEAR_IMAGE_PROCESSING = 1 << 14; const PBR2_HAS_DISPERSION = 1 << 20; const LINEAR_IMAGE_PROCESSING_SLOTS = { NI: `if(scene.vImageInfos.w>=0.0){`, BC: `}` }; function makeRefractionMod(hasVolume, hasMap, hasThicknessMap, useGltfThicknessChannel, hasDispersion, dispersionSampleWgsl) { const thicknessScaleLine = hasVolume || hasThicknessMap ? `let ts=max(length(mesh.world[0].xyz),max(length(mesh.world[1].xyz),length(mesh.world[2].xyz)));` : ``; const mapUvDecl = hasMap ? `let refractionMapUV=vec2<f32>(dot(material.refractionMapUVm.xy,input.uv),dot(material.refractionMapUVm.zw,input.uv))+material.refractionMapUVt.xy; ` : ``; const thickUvDecl = hasThicknessMap ? `let thicknessUV=vec2<f32>(dot(material.thicknessUVm.xy,input.uv),dot(material.thicknessUVm.zw,input.uv))+material.thicknessUVt.xy; ` : ``; const thicknessLine = hasThicknessMap ? `let ths=textureSample(thicknessTexture_,thicknessSampler_,thicknessUV).${useGltfThicknessChannel ? "g" : "r"}; let th=(material.thicknessParams.x+ths*material.thicknessParams.y)*ts;` : hasVolume ? `let th=material.refractionParams.z*ts;` : `let th=material.refractionParams.z;`; const textureLine = hasMap ? `let ri=material.refractionParams.x*textureSample(refractionMapTexture,refractionMapSampler,refractionMapUV).r;` : `let ri=material.refractionParams.x;`; const absorptionLine = hasVolume ? `let ab=exp(material.volumeParams.rgb*th);` : ``; const refractionLine = hasVolume ? `let fr=er*surfaceAlbedo*(ri*ab)*(vec3<f32>(1.0)-colorSpecularEnvReflectance.rgb);` : `let fr=er*surfaceAlbedo*ri*(vec3<f32>(1.0)-colorSpecularEnvReflectance.rgb);`; const sampleLines = hasDispersion && dispersionSampleWgsl ? dispersionSampleWgsl : `let rd=refract(-V,N,material.refractionParams.y); let cp=scene.viewProjection*vec4<f32>(input.worldPos+rd*th,1.0); let ruv=(cp.xy/cp.w)*vec2<f32>(0.5,-0.5)+vec2<f32>(0.5,0.5); let er=textureSampleLevel(refractionTexture,refractionSampler_,ruv,lv).rgb*material.environmentIntensity;`; return `{ ${thicknessScaleLine} ${mapUvDecl}${thickUvDecl}${textureLine} ${thicknessLine} let ro=1.0-ri; let ra=mix(alphaG,0.0,clamp(material.refractionParams.w*3.0-2.0,0.0,1.0)); let lv=clamp(log2(f32(textureDimensions(refractionTexture).x)*ra)-4.0,0.0,f32(textureNumLevels(refractionTexture)-1)); ${sampleLines} ${absorptionLine} ${refractionLine} color=finalIrradiance*ro*ro+finalRadianceScaled+finalSpecularScaled+directDiffuse*ro*ro+fr+emissive; }`; } function createRefractionRttFragment(hasVolume, hasMap, hasThicknessMap, useGltfThicknessChannel, linearImageProcessing, hasDispersion, dispersionSampleWgsl) { const uboFields = [{ _name: "refractionParams", _type: "vec4<f32>" }]; if (hasVolume) { uboFields.push({ _name: "volumeParams", _type: "vec4<f32>" }); } if (hasThicknessMap) { uboFields.push({ _name: "thicknessParams", _type: "vec4<f32>" }); } if (hasMap) { uboFields.push({ _name: "refractionMapUVm", _type: "vec4<f32>" }, { _name: "refractionMapUVt", _type: "vec4<f32>" }); } if (hasThicknessMap) { uboFields.push({ _name: "thicknessUVm", _type: "vec4<f32>" }, { _name: "thicknessUVt", _type: "vec4<f32>" }); } const bindings = [ { _name: "refractionTexture", _type: { _kind: "texture", _textureType: "texture_2d<f32>" }, _visibility: 2 }, { _name: "refractionSampler_", _type: { _kind: "sampler", _samplerType: "sampler" }, _visibility: 2 } ]; if (hasMap) { bindings.push( { _name: "refractionMapTexture", _type: { _kind: "texture", _textureType: "texture_2d<f32>" }, _visibility: 2 }, { _name: "refractionMapSampler", _type: { _kind: "sampler", _samplerType: "sampler" }, _visibility: 2 } ); } if (hasThicknessMap) { bindings.push( { _name: "thicknessTexture_", _type: { _kind: "texture", _textureType: "texture_2d<f32>" }, _visibility: 2 }, { _name: "thicknessSampler_", _type: { _kind: "sampler", _samplerType: "sampler" }, _visibility: 2 } ); } return { _id: "refraction", _dependencies: ["ibl"], _uboFields: uboFields, _bindings: bindings, _fragmentSlots: linearImageProcessing ? { AI: makeRefractionMod(hasVolume, hasMap, hasThicknessMap, useGltfThicknessChannel, hasDispersion, dispersionSampleWgsl), ...LINEAR_IMAGE_PROCESSING_SLOTS } : { AI: makeRefractionMod(hasVolume, hasMap, hasThicknessMap, useGltfThicknessChannel, hasDispersion, dispersionSampleWgsl) } }; } function writeRefractionUvTransform(data, offsets, name, tex) { const mOff = offsets.get(`${name}m`); const tOff = offsets.get(`${name}t`); if (mOff === void 0 || tOff === void 0) { return; } const mi = mOff / 4; const ti = tOff / 4; const sx = tex?.uScale ?? 1; const sy = tex?.vScale ?? 1; const ang = tex?.uAng ?? 0; if (ang === 0) { data[mi] = sx; data[mi + 1] = 0; data[mi + 2] = 0; data[mi + 3] = sy; } else { const c = Math.cos(ang); const s = Math.sin(ang); data[mi] = c * sx; data[mi + 1] = s * sy; data[mi + 2] = -s * sx; data[mi + 3] = c * sy; } data[ti] = tex?.uOffset ?? 0; data[ti + 1] = tex?.vOffset ?? 0; data[ti + 2] = 0; data[ti + 3] = 0; } function writeRefractionUBO(data, mat, offsets) { const ss = mat.subsurface; const refr = ss?.refraction; if (!refr) { return; } const off = offsets.get("refractionParams"); if (off === void 0) { return; } const o = off / 4; data[o] = refr.intensity ?? 0; const ior = refr.indexOfRefraction ?? 1.5; const thick = ss.thickness; data[o + 1] = 1 / (refr.useThicknessAsDepth && thick?.max ? ior : 1); data[o + 2] = refr.useThicknessAsDepth ? thick?.max ?? 0 : 0; data[o + 3] = 1 / ior; const vOff = offsets.get("volumeParams"); if (vOff !== void 0) { const vo = vOff / 4; const tint = ss.tint?.color ?? [1, 1, 1]; const dist = Math.max(ss.tint?.atDistance ?? 1, 1e-4); data[vo] = Math.log(Math.max(tint[0], 1e-6)) / dist; data[vo + 1] = Math.log(Math.max(tint[1], 1e-6)) / dist; data[vo + 2] = Math.log(Math.max(tint[2], 1e-6)) / dist; data[vo + 3] = refr.dispersion ?? 0; } const tOff = offsets.get("thicknessParams"); if (tOff !== void 0) { const to = tOff / 4; const min = thick?.min ?? 0; const max = thick?.max ?? 1; data[to] = min; data[to + 1] = max - min; } writeRefractionUvTransform(data, offsets, "refractionMapUV", refr.texture); writeRefractionUvTransform(data, offsets, "thicknessUV", thick?.texture); } function makeRefractionRttExt(dispersionSampleWgsl) { return { id: "refraction", phase: "fragment", detect(mat) { const m = mat; const ss = m.subsurface; const refr = ss?.refraction; const linearImageProcessing = m._linearImageProcessing ? PBR2_LINEAR_IMAGE_PROCESSING : 0; const intensity = m.transmissive ? refr?.intensity ?? 0 : 0; if (intensity <= 0) { return { f: 0, f2: linearImageProcessing }; } let f = 0; let f2 = linearImageProcessing | PBR2_HAS_REFRACTION; if (refr?.texture) { f2 |= PBR2_HAS_REFRACTION_MAP; } if (ss?.thickness?.texture) { f |= PBR_HAS_THICKNESS_MAP; } if (ss?.thickness?.useGlTFChannel) { f2 |= PBR2_HAS_THICKNESS_GLTF_CHANNEL; } if (ss?.tint?.atDistance !== void 0) { f2 |= PBR2_HAS_VOLUME; if (refr?.dispersion) { f2 |= PBR2_HAS_DISPERSION; } } return { f, f2 }; }, frag(ctx) { const linearImageProcessing = (ctx._features2 & PBR2_LINEAR_IMAGE_PROCESSING) !== 0; if (!(ctx._features2 & PBR2_HAS_REFRACTION)) { return linearImageProcessing ? { _id: "linear", _fragmentSlots: LINEAR_IMAGE_PROCESSING_SLOTS } : null; } return createRefractionRttFragment( (ctx._features2 & PBR2_HAS_VOLUME) !== 0, (ctx._features2 & PBR2_HAS_REFRACTION_MAP) !== 0, (ctx._features & PBR_HAS_THICKNESS_MAP) !== 0, (ctx._features2 & PBR2_HAS_THICKNESS_GLTF_CHANNEL) !== 0, linearImageProcessing, (ctx._features2 & PBR2_HAS_DISPERSION) !== 0, dispersionSampleWgsl ); }, writeUbo(data, mat, offsets) { writeRefractionUBO(data, mat, offsets); }, bind(ctx, entries, b) { if (!(ctx._features2 & PBR2_HAS_REFRACTION)) { return b; } const texture = ctx._refractionTexture; if (!texture) { ThrowLiteError(201); } entries.push({ binding: b++, resource: texture.view }); entries.push({ binding: b++, resource: texture.sampler }); if ((ctx._features2 & PBR2_HAS_REFRACTION_MAP) !== 0) { const map = ctx._material.subsurface?.refraction?.texture; entries.push({ binding: b++, resource: map.view }); entries.push({ binding: b++, resource: getTrilinearAnisotropicSampler(ctx._engine) }); } if ((ctx._features & PBR_HAS_THICKNESS_MAP) !== 0) { const thickness = ctx._material.subsurface?.thickness?.texture; entries.push({ binding: b++, resource: thickness.view }); entries.push({ binding: b++, resource: thickness.sampler }); } return b; }, textures(mat, out) { const tex = mat.subsurface?.refraction?.texture; if (tex) { out.push(tex); } const thickness = mat.subsurface?.thickness?.texture; if (thickness) { out.push(thickness); } } }; } function registerPbrTransmission(scene, engine, register, dispersionSampleWgsl) { scene._p?.(_t(scene, engine)) || enableSceneTransmission(scene, engine); register(makeRefractionRttExt(dispersionSampleWgsl)); } var pbrTransmissionExt = /*#__PURE__*/Object.freeze({ __proto__: null, registerPbrTransmission: registerPbrTransmission }); export { _installDepthGrab as _, pbrTransmissionExt as p }; //# sourceMappingURL=pbr-transmission-ext-BTqVnS2g.esm.js.map