prefilter environment map to be physically correct

This commit is contained in:
Dmitry Stepanov
2025-06-14 21:32:26 +03:00
parent 4dda250466
commit dbf8b90eee
8 changed files with 182 additions and 19 deletions
+2 -2
View File
@@ -192,7 +192,7 @@ impl GpuFrameBufferTrait for GlFrameBuffer {
self.depth_attachment.as_ref()
}
fn set_cubemap_face(&self, attachment_index: usize, face: CubeMapFace) {
fn set_cubemap_face(&self, attachment_index: usize, face: CubeMapFace, level: usize) {
let server = self.state.upgrade().unwrap();
unsafe {
@@ -209,7 +209,7 @@ impl GpuFrameBufferTrait for GlFrameBuffer {
glow::COLOR_ATTACHMENT0 + attachment_index as u32,
face.into_gl(),
Some(texture.id()),
0,
level as i32,
);
}
}
+3 -2
View File
@@ -213,8 +213,9 @@ pub trait GpuFrameBufferTrait: GpuFrameBufferAsAny {
/// Returns an optional depth/stencil attachment.
fn depth_attachment(&self) -> Option<&Attachment>;
/// Sets an active face of a cube map (only for frame buffers that using cube maps for rendering).
fn set_cubemap_face(&self, attachment_index: usize, face: CubeMapFace);
/// Sets an active face of a cube map of the given mip level. Only for frame buffers that using
/// cube maps for rendering.
fn set_cubemap_face(&self, attachment_index: usize, face: CubeMapFace, level: usize);
/// Performs data transfer from one frame buffer to another with scaling. It copies a region
/// defined by `src_x0`, `src_y0`, `src_x1`, `src_y1` coordinates from the frame buffer and
+7 -8
View File
@@ -26,6 +26,7 @@ use crate::{
},
resource::texture::{Texture, TextureResource},
};
use fyrox_core::err_once;
use fyrox_graphics::gpu_texture::{GpuTexture, GpuTextureDescriptor, GpuTextureKind};
use fyrox_graphics::sampler::{
GpuSampler, GpuSamplerDescriptor, MagnificationFilter, MinificationFilter, WrapMode,
@@ -247,13 +248,11 @@ impl TextureCache {
}
Err(e) => {
drop(texture_data_guard);
Log::writeln(
MessageKind::Error,
format!(
"Failed to create GPU texture from {} texture. Reason: {:?}",
texture_resource.kind(),
e
),
err_once!(
texture_resource.key() as usize,
"Failed to create GPU texture from {} texture. Reason: {:?}",
texture_resource.kind(),
e,
);
}
}
@@ -269,7 +268,7 @@ impl TextureCache {
self.cache.clear();
}
pub fn unload(&mut self, texture: TextureResource) {
pub fn unload(&mut self, texture: &TextureResource) {
if let Some(texture) = texture.state().data() {
self.cache.remove(&texture.cache_index);
}
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! Contains various algorithms for image convolution, primarily for lighting.
use crate::{
core::{
algebra::{Matrix4, Point3},
color::Color,
math::Rect,
ImmutableString,
},
renderer::{
cache::{
shader::{binding, property, PropertyGroup, RenderMaterial},
uniform::UniformBufferCache,
},
resources::RendererResources,
utils::CubeMapFaceDescriptor,
},
};
use fyrox_core::some_or_break;
use fyrox_graphics::{
error::FrameworkError,
framebuffer::{Attachment, GpuFrameBuffer},
gpu_texture::{GpuTexture, GpuTextureDescriptor, GpuTextureKind, PixelKind},
server::GraphicsServer,
stats::RenderPassStatistics,
ElementRange,
};
pub struct EnvironmentMapConvolution {
framebuffer: GpuFrameBuffer,
mip_count: usize,
pub(crate) size: usize,
}
impl EnvironmentMapConvolution {
pub fn new(server: &dyn GraphicsServer, size: usize) -> Result<Self, FrameworkError> {
let mip_count = ((size as f32).log2().floor() + 1.0) as usize;
let cube_map = server.create_texture(GpuTextureDescriptor {
name: "EnvironmentMapConvolution",
kind: GpuTextureKind::Cube { size },
pixel_kind: PixelKind::RGB8,
mip_count,
data: None,
base_level: 0,
max_level: mip_count,
})?;
Ok(Self {
framebuffer: server.create_frame_buffer(None, vec![Attachment::color(cube_map)])?,
mip_count,
size,
})
}
pub fn cube_map(&self) -> &GpuTexture {
&self.framebuffer.color_attachments()[0].texture
}
pub fn render(
&self,
environment_map: &GpuTexture,
uniform_buffer_cache: &mut UniformBufferCache,
renderer_resources: &RendererResources,
) -> Result<RenderPassStatistics, FrameworkError> {
let mut stats = RenderPassStatistics::default();
let projection_matrix =
Matrix4::new_perspective(1.0, std::f32::consts::FRAC_PI_2, 0.0125, 32.0);
for mip in 0..self.mip_count {
let roughness = (mip as f32) / (self.mip_count - 1) as f32;
let size = some_or_break!(self.size.checked_shr(mip as u32));
let viewport = Rect::new(0, 0, size as i32, size as i32);
for face in CubeMapFaceDescriptor::cube_faces() {
self.framebuffer.set_cubemap_face(0, face.face, mip);
self.framebuffer
.clear(viewport, Some(Color::WHITE), None, None);
let view_matrix =
Matrix4::look_at_rh(&Default::default(), &Point3::from(face.look), &face.up);
let wvp = projection_matrix * view_matrix;
let properties = PropertyGroup::from([
property("worldViewProjection", &wvp),
property("roughness", &roughness),
]);
let material = RenderMaterial::from([
binding(
"environmentMap",
(environment_map, &renderer_resources.linear_clamp_sampler),
),
binding("properties", &properties),
]);
stats += renderer_resources
.shaders
.environment_map_convolution
.run_pass(
1,
&ImmutableString::new("Primary"),
&self.framebuffer,
&renderer_resources.cube,
viewport,
&material,
uniform_buffer_cache,
ElementRange::Full,
None,
)?;
}
}
Ok(stats)
}
}
+29 -3
View File
@@ -38,6 +38,7 @@ pub mod utils;
pub mod visibility;
mod bloom;
mod convolution;
mod forward_renderer;
mod fxaa;
mod gbuffer;
@@ -49,6 +50,7 @@ mod settings;
mod shadow;
mod ssao;
use crate::renderer::convolution::EnvironmentMapConvolution;
use crate::renderer::ssao::ScreenSpaceAmbientOcclusionRenderer;
use crate::{
asset::{event::ResourceEvent, manager::ResourceManager},
@@ -183,6 +185,9 @@ fn recreate_render_data_if_needed<T: Any>(
/// A set of frame buffers and renderers that can be used to render to.
pub struct RenderDataContainer {
/// Prefiltered environment map.
pub environment_map_convolution: Rc<RefCell<Option<EnvironmentMapConvolution>>>,
/// Screen space ambient occlusion renderer.
pub ssao_renderer: ScreenSpaceAmbientOcclusionRenderer,
@@ -290,6 +295,7 @@ impl RenderDataContainer {
}
Ok(Self {
environment_map_convolution: Default::default(),
ssao_renderer: ScreenSpaceAmbientOcclusionRenderer::new(server, width, height)?,
gbuffer: GBuffer::new(server, width, height)?,
hdr_renderer: HighDynamicRangeRenderer::new(server)?,
@@ -694,7 +700,7 @@ impl Renderer {
}
/// Unloads texture from GPU memory.
pub fn unload_texture(&mut self, texture: TextureResource) {
pub fn unload_texture(&mut self, texture: &TextureResource) {
self.texture_cache.unload(texture)
}
@@ -941,7 +947,7 @@ impl Renderer {
if let Some(face) = observer.cube_map_face {
observer_render_data
.ldr_scene_framebuffer
.set_cubemap_face(0, face);
.set_cubemap_face(0, face, 0);
}
self.texture_cache.try_register(
@@ -1257,6 +1263,7 @@ impl Renderer {
// At first, render the reflection probes to off-screen render target and generate mipmaps
// for the cube maps.
for observer in observers.reflection_probes.iter() {
let server = self.server.clone();
let render_data = self.render_scene_observer(
observer,
scene_handle,
@@ -1266,7 +1273,26 @@ impl Renderer {
resource_manager,
)?;
let probe_cube_map = render_data.ldr_scene_frame_texture().clone();
self.server.generate_mipmap(&probe_cube_map);
let size = if let GpuTextureKind::Cube { size } = probe_cube_map.kind() {
size
} else {
unreachable!()
};
let convolution = render_data.environment_map_convolution.clone();
let mut convolution = convolution.borrow_mut();
if convolution.is_none() || convolution.as_ref().is_some_and(|conv| conv.size != size) {
*convolution = Some(EnvironmentMapConvolution::new(&*server, size)?);
}
let convolution = convolution.as_ref().unwrap();
self.statistics += convolution.render(
&probe_cube_map,
&mut self.uniform_buffer_cache,
&self.renderer_resources,
)?;
let rt = observer.render_target.as_ref().unwrap();
self.texture_cache.unload(rt);
self.texture_cache
.try_register(&*self.server, rt, convolution.cube_map().clone())?;
}
// Then render everything else.
+6
View File
@@ -92,6 +92,8 @@ pub struct ShadersContainer {
pub box_blur: RenderPassContainer,
/// User interface shader.
pub ui: RenderPassContainer,
/// Environment map convolution shader.
pub environment_map_convolution: RenderPassContainer,
}
impl ShadersContainer {
@@ -171,6 +173,10 @@ impl ShadersContainer {
server,
include_str!("../../../fyrox-material/src/shader/standard/widget.shader"),
)?,
environment_map_convolution: RenderPassContainer::from_str(
server,
include_str!("shaders/prefilter.shader"),
)?,
})
}
}
@@ -44,7 +44,6 @@
r#"
layout (location = 0) in vec3 vertexPosition;
out vec4 color;
out vec3 localPos;
void main()
@@ -96,7 +95,6 @@
return normalize(sampleVec);
}
const float PI = 3.14159265359;
void main()
{
@@ -104,7 +102,7 @@
vec3 R = N;
vec3 V = R;
const uint SAMPLE_COUNT = 1024u;
const uint SAMPLE_COUNT = 256u;
float totalWeight = 0.0;
vec3 prefilteredColor = vec3(0.0);
for(uint i = 0u; i < SAMPLE_COUNT; ++i)
+1 -1
View File
@@ -163,7 +163,7 @@ impl PointShadowMapRenderer {
Matrix4::new_perspective(1.0, std::f32::consts::FRAC_PI_2, z_near, z_far);
for face in self.faces.iter() {
framebuffer.set_cubemap_face(0, face.face);
framebuffer.set_cubemap_face(0, face.face, 0);
framebuffer.clear(viewport, Some(Color::WHITE), Some(1.0), None);
let light_look_at = light_pos + face.look;