Skip to content

Commit

Permalink
Fix Transparency
Browse files Browse the repository at this point in the history
  • Loading branch information
cwfitzgerald committed May 3, 2024
1 parent 5aa9f4a commit 430445a
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 3 deletions.
Binary file modified examples/src/scene_viewer/bistro.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 104 additions & 2 deletions rend3-routine/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
//!
//! Will default to the PBR shader code if custom code is not specified.
use std::{marker::PhantomData, sync::Arc};
use std::{cmp::Ordering, marker::PhantomData, sync::Arc};

use arrayvec::ArrayVec;
use encase::{ShaderSize, StorageBuffer};
use ordered_float::OrderedFloat;
use rend3::{
graph::{DataHandle, NodeResourceUsage, RenderGraph, RenderPassTargets},
types::{Material, SampleCount},
managers::{CameraState, InternalObject, MaterialArchetypeView, TextureBindGroupIndex},
types::{Material, RawObjectHandle, SampleCount, SortingOrder, SortingReason},
util::bind_merge::BindGroupBuilder,
ProfileData, Renderer, RendererDataCore, RendererProfile, ShaderPreProcessor,
};
Expand Down Expand Up @@ -165,6 +167,8 @@ impl<M: Material> ForwardRoutine<M> {
CameraSpecifier::Shadow(idx) => &ctx.eval_output.shadows[idx as usize].camera,
};

let objects = sort(objects, archetype_view, self.material_key, camera);

let per_camera_uniform_values = PerCameraUniform {
view: camera.view(),
view_proj: camera.view_proj(),
Expand Down Expand Up @@ -230,6 +234,104 @@ impl<M: Material> ForwardRoutine<M> {
}
}

fn sort<'a, M, I>(
objects: I,
material_archetype: MaterialArchetypeView<'_, M>,
requested_material_key: u64,
camera: &CameraState,
) -> Vec<(RawObjectHandle, &'a InternalObject<M>)>
where
M: Material,
I: IntoIterator<Item = (RawObjectHandle, &'a InternalObject<M>)>,
I::IntoIter: ExactSizeIterator,
{
let objects = objects.into_iter();

let mut sorted_objects = Vec::with_capacity(objects.len());
{
profiling::scope!("Sort Key Creation");
for (raw_handle, object) in objects {
let material = material_archetype.material(*object.material_handle);
let object_material_key = material.inner.key();
let sorting = material.inner.sorting();

if object_material_key != requested_material_key {
continue;
}

// Frustum culling
if !camera.world_frustum().contains_sphere(object.inner.bounding_sphere) {
continue;
}

let bind_group_index = material.bind_group_index.map_gpu(|_| TextureBindGroupIndex::DUMMY).into_common();

let mut distance_sq = camera.location().distance_squared(object.location.into());

if sorting.order == SortingOrder::BackToFront {
distance_sq = -distance_sq;
}
sorted_objects.push((
ObjectSortingKey {
bind_group_index,
distance: OrderedFloat(distance_sq),
sorting_reason: sorting.reason,
},
(raw_handle, object),
))
}
}

{
profiling::scope!("Sorting");
sorted_objects.sort_unstable_by_key(|(k, _)| *k);
}

sorted_objects.into_iter().map(|(_, o)| o).collect()
}

#[derive(Debug, Clone, Copy, Eq)]
pub(super) struct ObjectSortingKey {
pub bind_group_index: TextureBindGroupIndex,
pub distance: OrderedFloat<f32>,
pub sorting_reason: SortingReason,
}

impl PartialEq for ObjectSortingKey {
fn eq(&self, other: &Self) -> bool {
self.cmp(other).is_eq()
}
}

impl PartialOrd for ObjectSortingKey {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}

impl Ord for ObjectSortingKey {
fn cmp(&self, other: &Self) -> Ordering {
match self.sorting_reason.cmp(&other.sorting_reason) {
Ordering::Equal => {}
ord => return ord,
}
// The above comparison means that both sides are equal
if self.sorting_reason == SortingReason::Requirement {
match self.distance.cmp(&other.distance) {
Ordering::Equal => {}
ord => return ord,
}
self.bind_group_index.cmp(&other.bind_group_index)
} else {
match self.bind_group_index.cmp(&other.bind_group_index) {
Ordering::Equal => {}
ord => return ord,
}
self.distance.cmp(&other.distance)
}
}
}

fn build_forward_pipeline_inner<M: Material>(
pll: &wgpu::PipelineLayout,
args: &ForwardRoutineCreateArgs<'_, M>,
Expand Down
11 changes: 10 additions & 1 deletion rend3-test/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::sync::Arc;

use glam::{Mat4, Vec3, Vec4};
use rend3::types::{DirectionalLightHandle, MaterialHandle, MeshBuilder, ObjectHandle};
use rend3_routine::pbr::PbrMaterial;
use rend3_routine::pbr::{PbrMaterial, Transparency};
use wgpu::Device;

use crate::TestRunner;
Expand Down Expand Up @@ -43,6 +43,15 @@ impl TestRunner {
})
}

pub fn add_transparent_material(&self, color: Vec4) -> MaterialHandle {
self.renderer.add_material(PbrMaterial {
albedo: rend3_routine::pbr::AlbedoComponent::Value(color),
unlit: true,
transparency: Transparency::Blend,
..Default::default()
})
}

pub fn add_lit_material(&self, color: Vec4) -> MaterialHandle {
self.renderer.add_material(PbrMaterial {
albedo: rend3_routine::pbr::AlbedoComponent::Value(color),
Expand Down
Binary file added rend3-test/tests/results/transparency/blending.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions rend3-test/tests/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ mod msaa;
mod object;
mod shadow;
mod simple;
mod transparency;
50 changes: 50 additions & 0 deletions rend3-test/tests/transparency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use anyhow::Context;
use glam::{Mat4, Quat, Vec3, Vec4};
use rend3::types::{Camera, Handedness};
use rend3_test::{no_gpu_return, test_attr, FrameRenderSettings, TestRunner, Threshold};

/// Ensure that transparency is ordered correctly
//
// Todo: This test never fails, even if I remove the sort.
#[test_attr]
pub async fn transparency() -> anyhow::Result<()> {
let iad = no_gpu_return!(rend3::create_iad(None, None, None, None).await)
.context("InstanceAdapterDevice creation failed")?;

let Ok(runner) = TestRunner::builder().iad(iad.clone()).handedness(Handedness::Left).build().await else {
return Ok(());
};

runner.set_camera_data(Camera {
projection: rend3::types::CameraProjection::Raw(Mat4::IDENTITY),
view: Mat4::IDENTITY,
});

let material1 = runner.add_transparent_material(Vec4::new(1.0, 0.0, 0.0, 0.5));
let material2 = runner.add_transparent_material(Vec4::new(0.0, 1.0, 0.0, 0.5));
let _object_left_1 = runner.plane(
material1.clone(),
Mat4::from_scale_rotation_translation(Vec3::new(-0.25, 0.25, 0.25), Quat::IDENTITY, Vec3::new(-0.5, 0.0, -0.5)),
);

let _object_left_2 = runner.plane(
material2.clone(),
Mat4::from_scale_rotation_translation(Vec3::new(-0.25, 0.25, 0.25), Quat::IDENTITY, Vec3::new(-0.5, 0.0, 0.5)),
);

let _object_right_2 = runner.plane(
material2,
Mat4::from_scale_rotation_translation(Vec3::new(-0.25, 0.25, 0.25), Quat::IDENTITY, Vec3::new(0.5, 0.0, 0.5)),
);

let _object_right_1 = runner.plane(
material1,
Mat4::from_scale_rotation_translation(Vec3::new(-0.25, 0.25, 0.25), Quat::IDENTITY, Vec3::new(0.5, 0.0, -0.5)),
);

runner
.render_and_compare(FrameRenderSettings::new(), "tests/results/transparency/blending.png", Threshold::Mean(0.0))
.await?;

Ok(())
}
8 changes: 8 additions & 0 deletions rend3/src/managers/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ pub struct MaterialArchetypeView<'a, M: Material> {
data_vec: &'a [Option<InternalMaterial<M>>],
}

impl<'a, M: Material> Copy for MaterialArchetypeView<'a, M> {}

impl<'a, M: Material> Clone for MaterialArchetypeView<'a, M> {
fn clone(&self) -> Self {
*self
}
}

impl<'a, M: Material> MaterialArchetypeView<'a, M> {
pub fn buffer(&self) -> &'a Buffer {
self.buffer
Expand Down

0 comments on commit 430445a

Please sign in to comment.