forked from bevyengine/bevy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a [parallax mapping] shader to bevy. Please note that this is a 3d technique, NOT a 2d sidescroller feature. - Add related fields to `StandardMaterial` - update the pbr shader - Add an example taking advantage of parallax mapping A pre-existing implementation exists at: https://github.com/nicopap/bevy_mod_paramap/ The implementation is derived from: https://web.archive.org/web/20150419215321/http://sunandblackcat.com/tipFullView.php?l=eng&topicid=28 Further discussion on literature is found in the `bevy_mod_paramap` README. Limitations ----------- - The mesh silhouette isn't affected by the depth map. - The depth of the pixel does not reflect its visual position, resulting in artifacts for depth-dependent features such as fog or SSAO - GLTF does not define a height map texture, so somehow the user will always need to work around this limitation, though [an extension is in the works][gltf] Future work ----------- - It's possible to update the depth in the depth buffer to follow the parallaxed texture. This would enable interop with depth-based visual effects, it also allows `discard`ing pixels of materials when computed depth is higher than the one in depth buffer - Cheap lower quality single-sample method using [offset limiting] - Add distance fading, to disable parallaxing (relatively expensive) on distant objects - GLTF extension to allow defining height maps. Or a workaround implemented through a blender plugin to the GLTF exporter that uses the `extras` field to add height map. - [Quadratic surface vertex attributes][oliveira_3] to enable parallax mapping on bending surfaces and allow clean silhouetting. - noise based sampling, to limit the pancake artifacts. - Cone mapping ([GPU gems], [Simcity (2013)][simcity]). Requires preprocessing, increase depth map size, reduces sample count greatly. - [Quadtree parallax mapping][qpm] (also requires preprocessing) - Self-shadowing of parallax-mapped surfaces by modifying the shadow map https://user-images.githubusercontent.com/26321040/223563792-dffcc6ab-70e8-4ff9-90d1-b36c338695ad.mp4 --- - Add a `depth_map` field to the `StandardMaterial`, it is a greyscale image where white represents bottom and black the top. If `depth_map` is set, bevy's pbr shader will use it to do [parallax mapping] to give an increased feel of depth to the material. This is similar to a displacement map, but with infinite precision at fairly low cost. - The fields `parallax_mapping_method`, `parallax_depth` and `max_parallax_layer_count` allow finer grained control over the behavior of the parallax shader. - Add the `parallax_mapping` example to show off the effect. [parallax mapping]: https://en.wikipedia.org/wiki/Parallax_mapping [oliveira_3]: https://www.inf.ufrgs.br/~oliveira/pubs_files/Oliveira_Policarpo_RP-351_Jan_2005.pdf [GPU gems]: https://developer.nvidia.com/gpugems/gpugems3/part-iii-rendering/chapter-18-relaxed-cone-stepping-relief-mapping [simcity]: https://community.simtropolis.com/omnibus/other-games/building-and-rendering-simcity-2013-r247/ [offset limiting]: https://raw.githubusercontent.com/marcusstenbeck/tncg14-parallax-mapping/master/documents/Parallax%20Mapping%20with%20Offset%20Limiting%20-%20A%20Per-Pixel%20Approximation%20of%20Uneven%20Surfaces.pdf [gltf]: KhronosGroup/glTF#2196 [qpm]: https://www.gamedevs.org/uploads/quadtree-displacement-mapping-with-height-blending.pdf Co-authored-by: Robert Swain <[email protected]>
- Loading branch information
Showing
13 changed files
with
645 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
use bevy_reflect::{FromReflect, Reflect}; | ||
|
||
/// The parallax mapping method to use to compute a displacement based on the | ||
/// material's [`depth_map`]. | ||
/// | ||
/// See the `parallax_mapping.wgsl` shader code for implementation details | ||
/// and explanation of the methods used. | ||
/// | ||
/// [`depth_map`]: crate::StandardMaterial::depth_map | ||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Reflect, FromReflect)] | ||
pub enum ParallaxMappingMethod { | ||
/// A simple linear interpolation, using a single texture sample. | ||
#[default] | ||
ParallaxOcclusionMapping, | ||
/// A discovery of 5 iterations of the best displacement | ||
/// value. Each iteration incurs a texture sample. | ||
/// | ||
/// The result has fewer visual artifacts than `ParallaxOcclusionMapping`. | ||
ReliefMapping { n_steps: u32 }, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
#define_import_path bevy_pbr::parallax_mapping | ||
|
||
fn sample_depth_map(uv: vec2<f32>) -> f32 { | ||
return textureSample(depth_map_texture, depth_map_sampler, uv).r; | ||
} | ||
|
||
// 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, | ||
max_layer_count: f32, | ||
// The original uv | ||
uv: vec2<f32>, | ||
// The vector from camera to the surface of material | ||
V: vec3<f32>, | ||
) -> vec2<f32> { | ||
var uv = uv; | ||
if max_layer_count < 1.0 { | ||
return uv; | ||
} | ||
|
||
// Steep Parallax Mapping | ||
// ====================== | ||
// Split the depth map into `layer_count` layers. | ||
// When V hits the surface of the mesh (excluding depth displacement), | ||
// if the depth is not below or on surface including depth displacement (textureSample), then | ||
// look forward (-= delta_uv) according to V and distance between hit surface and | ||
// depth map surface, repeat until below the surface. | ||
// | ||
// 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)); | ||
// 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; | ||
|
||
var current_layer_height = 0.0; | ||
var current_height = sample_depth_map(uv); | ||
|
||
// This at most runs layer_count times | ||
while true { | ||
if (current_height <= current_layer_height) { | ||
break; | ||
} | ||
current_layer_height += layer_height; | ||
uv -= delta_uv; | ||
current_height = sample_depth_map(uv); | ||
} | ||
|
||
#ifdef RELIEF_MAPPING | ||
// Relief Mapping | ||
// ============== | ||
// "Refine" the rough result from Steep Parallax Mapping | ||
// with a binary search between the layer selected by steep parallax | ||
// and the next one to find a point closer to the depth map surface. | ||
// This reduces the jaggy step artifacts from steep parallax mapping. | ||
let MAX_STEPS: i32 = 5; | ||
|
||
delta_uv *= 0.5; | ||
var delta_height = 0.5 * layer_height; | ||
uv += delta_uv; | ||
current_layer_height -= delta_height; | ||
for (var i: i32 = 0; i < MAX_STEPS; i++) { | ||
// Sample depth at current offset | ||
current_height = sample_depth_map(uv); | ||
|
||
// Halve the deltas for the next step | ||
delta_uv *= 0.5; | ||
delta_height *= 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 { | ||
uv += delta_uv; | ||
current_layer_height -= delta_height; | ||
} | ||
} | ||
#else | ||
// Parallax Occlusion mapping | ||
// ========================== | ||
// "Refine" Steep Parallax Mapping by interpolating between the | ||
// previous layer's height and the computed layer height. | ||
// 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 weight = next_height / (next_height - previous_height); | ||
|
||
uv = mix(uv, previous_uv, weight); | ||
|
||
current_layer_height += mix(next_height, previous_height, weight); | ||
#endif | ||
|
||
// Note: `current_layer_height` is not returned, but may be useful | ||
// for light computation later on in future improvements of the pbr shader. | ||
return uv; | ||
} |
Oops, something went wrong.