prefilterEquirectRadiance function

Texture prefilterEquirectRadiance(
  1. Texture sourceEquirect, {
  2. bool sourceIsLinear = false,
})

Prefilters an equirectangular radiance texture into a vertical roughness-band atlas for image-based specular lighting.

Renders kPrefilterBandCount GGX-prefiltered equirectangular bands stacked vertically into a single r16g16b16a16Float texture in one full-screen GPU pass (see flutter_scene_prefilter_env.frag). Intended to run once when an EnvironmentMap is constructed; the result is cached on the environment and sampled at draw time by the standard shader's SamplePrefilteredRadiance.

sourceEquirect is an equirectangular radiance map. By default it is treated as sRGB-encoded; pass sourceIsLinear when it already holds linear radiance (an HDR environment), so it is not linearized twice. The atlas always stores linear radiance.

Implementation

gpu.Texture prefilterEquirectRadiance(
  gpu.Texture sourceEquirect, {
  bool sourceIsLinear = false,
}) {
  final atlas = gpu.gpuContext.createTexture(
    gpu.StorageMode.devicePrivate,
    kPrefilterBandWidth,
    kPrefilterBandHeight * kPrefilterBandCount,
    format: gpu.PixelFormat.r16g16b16a16Float,
    enableRenderTargetUsage: true,
    enableShaderReadUsage: true,
    coordinateSystem: gpu.TextureCoordinateSystem.renderToTexture,
  );

  final fragmentShader = baseShaderLibrary['PrefilterEnvFragment']!;
  final commandBuffer = gpu.gpuContext.createCommandBuffer();
  final renderPass = commandBuffer.createRenderPass(
    gpu.RenderTarget.singleColor(
      gpu.ColorAttachment(texture: atlas, clearValue: Vector4.zero()),
    ),
  );
  renderPass.bindPipeline(
    gpu.gpuContext.createRenderPipeline(
      baseShaderLibrary['FullscreenVertex']!,
      fragmentShader,
    ),
  );
  renderPass.bindVertexBuffer(_fullscreenQuadView, 6);
  renderPass.bindTexture(
    fragmentShader.getUniformSlot('source_equirect'),
    sourceEquirect,
    sampler: gpu.SamplerOptions(
      minFilter: gpu.MinMagFilter.linear,
      magFilter: gpu.MinMagFilter.linear,
      widthAddressMode: gpu.SamplerAddressMode.repeat,
      heightAddressMode: gpu.SamplerAddressMode.clampToEdge,
    ),
  );
  // A single float (std140-padded to 16 bytes): the sRGB-vs-linear flag.
  final info = Float32List(4)..[0] = sourceIsLinear ? 1.0 : 0.0;
  renderPass.bindUniform(
    fragmentShader.getUniformSlot('PrefilterInfo'),
    gpu.gpuContext.createHostBuffer().emplace(ByteData.sublistView(info)),
  );
  renderPass.draw();
  commandBuffer.submit();
  return atlas;
}