Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to parallax mapping code for comprehensibility and correctness #1

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ required-features = [ "jpeg" ]

[package.metadata.example.parallax_mapping]
name = "Parallax Mapping"
description = "Demonstrates use of a normal map and height map for parallax mapping"
description = "Demonstrates use of a normal map and depth map for parallax mapping"
category = "3D Rendering"
wasm = true

Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ pub struct StandardMaterial {
/// The visual result is similar to a displacement map,
/// but does not require additional geometry.
///
/// Use the [`parallax_depth`] field to control the depth of the parallax.
/// Use the [`parallax_depth_scale`] field to control the depth of the parallax.
///
/// ## Limitations
///
Expand Down Expand Up @@ -273,7 +273,7 @@ pub struct StandardMaterial {
///
/// [this paper]: https://www.diva-portal.org/smash/get/diva2:831762/FULLTEXT01.pdf
/// [parallax mapping]: https://en.wikipedia.org/wiki/Parallax_mapping
/// [`parallax_depth`]: StandardMaterial::parallax_depth
/// [`parallax_depth_scale`]: StandardMaterial::parallax_depth_scale
/// [`parallax_mapping_method`]: StandardMaterial::parallax_mapping_method
/// [`max_parallax_layer_count`]: StandardMaterial::max_parallax_layer_count
#[texture(11)]
Expand All @@ -286,11 +286,11 @@ pub struct StandardMaterial {
/// Lower values lessen the effect.
///
/// The depth is relative to texture size. This means that if your texture
/// occupies a surface of `1` world unit, and `parallax_depth` is `0.1`, then
/// occupies a surface of `1` world unit, and `parallax_depth_scale` is `0.1`, then
/// the in-world depth will be of `0.1` world units.
/// If the texture stretches for `10` world units, then the final depth
/// will be of `1` world unit.
pub parallax_depth: f32,
pub parallax_depth_scale: f32,

/// Which parallax mapping method to use.
///
Expand Down Expand Up @@ -339,7 +339,7 @@ impl Default for StandardMaterial {
alpha_mode: AlphaMode::Opaque,
depth_bias: 0.0,
depth_map: None,
parallax_depth: 0.1,
parallax_depth_scale: 0.1,
max_parallax_layer_count: 16.0,
parallax_mapping_method: ParallaxMappingMethod::Occlusion,
}
Expand Down Expand Up @@ -425,10 +425,10 @@ pub struct StandardMaterialUniform {
/// and any below means fully transparent.
pub alpha_cutoff: f32,
/// The depth of the [`StandardMaterial::depth_map`] to apply.
pub parallax_depth: f32,
pub parallax_depth_scale: f32,
/// In how many layers to split the depth maps for Steep parallax mapping.
///
/// If your `parallax_depth` is >0.1 and you are seeing jaggy edges,
/// If your `parallax_depth_scale` is >0.1 and you are seeing jaggy edges,
/// increase this value. However, this incurs a performance cost.
pub max_parallax_layer_count: f32,
/// Using [`ParallaxMappingMethod::ReliefMapping`], how many additional
Expand Down Expand Up @@ -503,7 +503,7 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
reflectance: self.reflectance,
flags: flags.bits(),
alpha_cutoff,
parallax_depth: self.parallax_depth,
parallax_depth_scale: self.parallax_depth_scale,
max_parallax_layer_count: self.max_parallax_layer_count,
max_relief_mapping_search_steps: self.parallax_mapping_method.max_steps(),
}
Expand Down
69 changes: 36 additions & 33 deletions crates/bevy_pbr/src/render/parallax_mapping.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ fn sample_depth_map(uv: vec2<f32>) -> f32 {
// An implementation of parallax mapping, see https://en.wikipedia.org/wiki/Parallax_mapping
// Code derived from: https://web.archive.org/web/20150419215321/http://sunandblackcat.com/tipFullView.php?l=eng&topicid=28
fn parallaxed_uv(
depth: f32,
depth_scale: f32,
max_layer_count: f32,
max_steps: u32,
// The original uv
// The original interpolated uv
uv: vec2<f32>,
// The vector from camera to the surface of material
V: vec3<f32>,
// The vector from the camera to the fragment at the surface in tangent
// space
Vt: vec3<f32>,
) -> vec2<f32> {
var uv = uv;
if max_layer_count < 1.0 {
return uv;
}
var uv = uv;

// Steep Parallax Mapping
// ======================
Expand All @@ -31,22 +32,24 @@ fn parallaxed_uv(
// Where `layer_count` is interpolated between `min_layer_count` and
// `max_layer_count` according to the steepness of V.

let view_steepness = abs(dot(vec3<f32>(0.0, 0.0, 1.0), V));
let view_steepness = abs(Vt.z);
// We mix with minimum value 1.0 because otherwise, with 0.0, we get
// a nice division by zero in surfaces parallel to viewport, resulting
// in a singularity.
let layer_count = mix(max_layer_count, 1.0, view_steepness);
let layer_height = 1.0 / layer_count;
var delta_uv = depth * V.xy / V.z / layer_count;
let layer_depth = 1.0 / layer_count;
var delta_uv = depth_scale * layer_depth * Vt.xy * vec2(1.0, -1.0) / view_steepness;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Multiplying this by vec2(1.0, -1.0) is very surprising. I understand it's to avoid the -B, but in this case I think it should be explained. I know it's necessary, but I don't understand why.


var current_layer_height = 0.0;
var current_height = sample_depth_map(uv);
var current_layer_depth = 0.0;
var current_depth = sample_depth_map(uv);

// This at most runs layer_count times
for (var i: i32 = 0; current_height > current_layer_height && i <= i32(layer_count); i++) {
current_layer_height += layer_height;
uv -= delta_uv;
current_height = sample_depth_map(uv);
// current_depth > current_layer_depth means the depth map depth is deeper
// than the depth the ray would be at at this UV offset so the ray has not
// intersected the surface
for (var i: i32 = 0; current_depth > current_layer_depth && i <= i32(layer_count); i++) {
current_layer_depth += layer_depth;
uv += delta_uv;
current_depth = sample_depth_map(uv);
}

#ifdef RELIEF_MAPPING
Expand All @@ -58,45 +61,45 @@ fn parallaxed_uv(
// This reduces the jaggy step artifacts from steep parallax mapping.

delta_uv *= 0.5;
var delta_height = 0.5 * layer_height;
uv += delta_uv;
current_layer_height -= delta_height;
var delta_depth = 0.5 * layer_depth;
uv -= delta_uv;
current_layer_depth -= delta_depth;
for (var i: u32 = 0u; i < max_steps; i++) {
// Sample depth at current offset
current_height = sample_depth_map(uv);
current_depth = sample_depth_map(uv);

// Halve the deltas for the next step
delta_uv *= 0.5;
delta_height *= 0.5;
delta_depth *= 0.5;

// Step based on whether the current depth is above or below the depth map
if (current_height > current_layer_height) {
uv -= delta_uv;
current_layer_height += delta_height;
} else {
if (current_depth > current_layer_depth) {
uv += delta_uv;
current_layer_height -= delta_height;
current_layer_depth += delta_depth;
} else {
uv -= delta_uv;
current_layer_depth -= delta_depth;
}
}
#else
#else
// Parallax Occlusion mapping
// ==========================
// "Refine" Steep Parallax Mapping by interpolating between the
// previous layer's height and the computed layer height.
// previous layer's depth and the computed layer depth.
// Only requires a single lookup, unlike Relief Mapping, but
// may incur artifacts on very steep relief.
let previous_uv = uv + delta_uv;
let next_height = current_height - current_layer_height;
let previous_height = sample_depth_map(previous_uv) - current_layer_height + layer_height;
let previous_uv = uv - delta_uv;
let next_depth = current_depth - current_layer_depth;
let previous_depth = sample_depth_map(previous_uv) - current_layer_depth + layer_depth;

let weight = next_height / (next_height - previous_height);
let weight = next_depth / (next_depth - previous_depth);

uv = mix(uv, previous_uv, weight);

current_layer_height += mix(next_height, previous_height, weight);
current_layer_depth += mix(next_depth, previous_depth, weight);
#endif

// Note: `current_layer_height` is not returned, but may be useful
// Note: `current_layer_depth` is not returned, but may be useful
// for light computation later on in future improvements of the pbr shader.
return uv;
}
10 changes: 7 additions & 3 deletions crates/bevy_pbr/src/render/pbr.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
let N = in.world_normal;
let T = in.world_tangent.xyz;
let B = in.world_tangent.w * cross(N, T);
let Vt = vec3<f32>(dot(V, T), dot(V, -B), dot(V, N));
// Transform V from fragment to camera in world space to tangent space.
let Vt = vec3(dot(V, T), dot(V, B), dot(V, N));
uv = parallaxed_uv(
material.parallax_depth,
material.parallax_depth_scale,
material.max_parallax_layer_count,
material.max_relief_mapping_search_steps,
uv,
Vt,
// Flip the direction of Vt to go toward the surface to make the
// parallax mapping algorithm easier to understand and reason
// about.
-Vt,
);
}
#endif
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_pbr/src/render/pbr_types.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct StandardMaterial {
// 'flags' is a bit field indicating various options. u32 is 32 bits so we have up to 32 options.
flags: u32,
alpha_cutoff: f32,
parallax_depth: f32,
parallax_depth_scale: f32,
max_parallax_layer_count: f32,
max_relief_mapping_search_steps: u32,
};
Expand Down Expand Up @@ -46,7 +46,7 @@ fn standard_material_new() -> StandardMaterial {
material.reflectance = 0.5;
material.flags = STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE;
material.alpha_cutoff = 0.5;
material.parallax_depth = 0.1;
material.parallax_depth_scale = 0.1;
material.max_parallax_layer_count = 16.0;
material.max_relief_mapping_search_steps = 5u;

Expand Down
18 changes: 9 additions & 9 deletions examples/3d/parallax_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn main() {
spin,
update_normal,
move_camera,
update_parallax_depth,
update_parallax_depth_scale,
update_parallax_layers,
switch_method,
close_on_esc,
Expand Down Expand Up @@ -48,7 +48,7 @@ impl Default for TargetLayers {
TargetLayers(5.0)
}
}
fn update_parallax_depth(
fn update_parallax_depth_scale(
input: Res<Input<KeyCode>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut target_depth: Local<TargetDepth>,
Expand All @@ -68,11 +68,11 @@ fn update_parallax_depth(
if *depth_update {
let mut text = text.single_mut();
for (_, mat) in materials.iter_mut() {
let current_depth = mat.parallax_depth;
let current_depth = mat.parallax_depth_scale;
let new_depth =
current_depth * (1.0 - DEPTH_CHANGE_RATE) + (target_depth.0 * DEPTH_CHANGE_RATE);
mat.parallax_depth = new_depth;
text.sections[0].value = format!("Parallax depth: {new_depth:.5}\n");
mat.parallax_depth_scale = new_depth;
text.sections[0].value = format!("Parallax depth scale: {new_depth:.5}\n");
if (new_depth - current_depth).abs() <= 0.000000001 {
*depth_update = false;
}
Expand Down Expand Up @@ -247,7 +247,7 @@ fn setup(
// needs tangents generated.
cube.generate_tangents().unwrap();

let parallax_depth = TargetDepth::default().0;
let parallax_depth_scale = TargetDepth::default().0;
let max_parallax_layer_count = TargetLayers::default().0.exp2();
let parallax_material = materials.add(StandardMaterial {
perceptual_roughness: 0.4,
Expand All @@ -256,7 +256,7 @@ fn setup(
// The depth map is a greyscale texture where black is the highest level and
// white the lowest.
depth_map: Some(asset_server.load("textures/parallax_example/cube_depth.jpg")),
parallax_depth,
parallax_depth_scale,
parallax_mapping_method: ParallaxMappingMethod::DEFAULT_RELIEF_MAPPING,
max_parallax_layer_count,
..default()
Expand Down Expand Up @@ -299,7 +299,7 @@ fn setup(
commands.spawn(
TextBundle::from_sections(vec![
TextSection::new(
format!("Parallax depth: {parallax_depth:.5}\n"),
format!("Parallax depth scale: {parallax_depth_scale:.5}\n"),
style.clone(),
),
TextSection::new(
Expand All @@ -311,7 +311,7 @@ fn setup(
TextSection::new("Controls\n", style.clone()),
TextSection::new("---------------\n", style.clone()),
TextSection::new("Left click - Change view angle\n", style.clone()),
TextSection::new("1/2 - Decrease/Increase parallax depth\n", style.clone()),
TextSection::new("1/2 - Decrease/Increase parallax depth scale\n", style.clone()),
TextSection::new("3/4 - Decrease/Increase layer count\n", style.clone()),
TextSection::new("Space - Switch parallaxing algorithm\n", style),
])
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ Example | Description
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
[Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications)
[Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and height map for parallax mapping
[Parallax Mapping](../examples/3d/parallax_mapping.rs) | Demonstrates use of a normal map and depth map for parallax mapping
[Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
Expand Down