From 61c8475069db1607ea2cb55a5fc32ccc1056995f Mon Sep 17 00:00:00 2001 From: Carter Anderson Date: Mon, 28 Jun 2021 15:36:50 -0700 Subject: [PATCH] Begin WGSL port (sprites work, pbr lights are broken) --- examples/3d/3d_scene_pipelined.rs | 3 - pipelined/bevy_pbr2/src/render/light.rs | 9 +- pipelined/bevy_pbr2/src/render/mod.rs | 51 +- pipelined/bevy_pbr2/src/render/pbr.frag | 423 ---------------- pipelined/bevy_pbr2/src/render/pbr.vert | 28 -- pipelined/bevy_pbr2/src/render/pbr.wgsl | 470 ++++++++++++++++++ pipelined/bevy_render2/Cargo.toml | 10 +- .../src/renderer/render_device.rs | 4 +- pipelined/bevy_render2/src/shader/shader.rs | 294 +++-------- pipelined/bevy_sprite2/src/render/mod.rs | 43 +- pipelined/bevy_sprite2/src/render/sprite.frag | 11 - pipelined/bevy_sprite2/src/render/sprite.vert | 16 - pipelined/bevy_sprite2/src/render/sprite.wgsl | 34 ++ 13 files changed, 620 insertions(+), 776 deletions(-) delete mode 100644 pipelined/bevy_pbr2/src/render/pbr.frag delete mode 100644 pipelined/bevy_pbr2/src/render/pbr.vert create mode 100644 pipelined/bevy_pbr2/src/render/pbr.wgsl delete mode 100644 pipelined/bevy_sprite2/src/render/sprite.frag delete mode 100644 pipelined/bevy_sprite2/src/render/sprite.vert create mode 100644 pipelined/bevy_sprite2/src/render/sprite.wgsl diff --git a/examples/3d/3d_scene_pipelined.rs b/examples/3d/3d_scene_pipelined.rs index adc31e56e7..186c745df5 100644 --- a/examples/3d/3d_scene_pipelined.rs +++ b/examples/3d/3d_scene_pipelined.rs @@ -48,9 +48,6 @@ fn setup( mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), material: materials.add(StandardMaterial { base_color: Color::PINK, - perceptual_roughness: 0.0, - metallic: 1.0, - reflectance: 1.0, ..Default::default() }), transform: Transform::from_xyz(0.0, 1.0, 0.0), diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/pipelined/bevy_pbr2/src/render/light.rs index d9f9326c68..d9d785504b 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/pipelined/bevy_pbr2/src/render/light.rs @@ -78,8 +78,9 @@ impl FromWorld for ShadowShaders { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - // TODO: verify this is correct - min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64), + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(80), }, count: None, }, @@ -120,8 +121,8 @@ impl FromWorld for ShadowShaders { }, ], }], - module: &pbr_shaders.vertex_shader_module, - entry_point: "main", + module: &pbr_shaders.shader_module, + entry_point: "vertex", }, fragment: None, depth_stencil: Some(DepthStencilState { diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/pipelined/bevy_pbr2/src/render/mod.rs index a974e3b157..9b7229981d 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/pipelined/bevy_pbr2/src/render/mod.rs @@ -14,12 +14,11 @@ use bevy_render2::{ renderer::{RenderContext, RenderDevice, RenderQueue}, shader::Shader, texture::{BevyDefault, GpuImage, Image, TextureFormatPixelInfo}, - view::{ExtractedView, ViewMeta, ViewUniform, ViewUniformOffset}, + view::{ExtractedView, ViewMeta, ViewUniformOffset}, }; use bevy_transform::components::GlobalTransform; use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey}; use crevice::std140::AsStd140; -use std::borrow::Cow; use wgpu::{ Extent3d, ImageCopyTexture, ImageDataLayout, Origin3d, TextureDimension, TextureFormat, TextureViewDescriptor, @@ -29,7 +28,7 @@ use crate::{StandardMaterial, StandardMaterialUniformData}; pub struct PbrShaders { pipeline: RenderPipeline, - vertex_shader_module: ShaderModule, + shader_module: ShaderModule, view_layout: BindGroupLayout, material_layout: BindGroupLayout, mesh_layout: BindGroupLayout, @@ -41,26 +40,9 @@ pub struct PbrShaders { impl FromWorld for PbrShaders { fn from_world(world: &mut World) -> Self { let render_device = world.get_resource::().unwrap(); - let vertex_shader = Shader::from_glsl(ShaderStage::VERTEX, include_str!("pbr.vert")) - .get_spirv_shader(None) - .unwrap(); - let fragment_shader = Shader::from_glsl(ShaderStage::FRAGMENT, include_str!("pbr.frag")) - .get_spirv_shader(None) - .unwrap(); - - let vertex_spirv = vertex_shader.get_spirv(None).unwrap(); - let fragment_spirv = fragment_shader.get_spirv(None).unwrap(); - - let vertex_shader_module = render_device.create_shader_module(&ShaderModuleDescriptor { - flags: ShaderFlags::default(), - label: None, - source: ShaderSource::SpirV(Cow::Borrowed(&vertex_spirv)), - }); - let fragment_shader_module = render_device.create_shader_module(&ShaderModuleDescriptor { - flags: ShaderFlags::default(), - label: None, - source: ShaderSource::SpirV(Cow::Borrowed(&fragment_spirv)), - }); + let shader = Shader::from_wgsl(include_str!("pbr.wgsl")); + let shader_module = render_device.create_shader_module(&shader); + println!("{}", GpuLights::std140_size_static()); // TODO: move this into ViewMeta? let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { @@ -72,8 +54,9 @@ impl FromWorld for PbrShaders { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - // TODO: verify this is correct - min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64), + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(80), }, count: None, }, @@ -84,7 +67,9 @@ impl FromWorld for PbrShaders { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: BufferSize::new(GpuLights::std140_size_static() as u64), + // TODO: change this to ViewUniform::std140_size_static once crevice fixes this! + // Context: https://github.com/LPGhatguy/crevice/issues/29 + min_binding_size: BufferSize::new(1264), }, count: None, }, @@ -262,12 +247,12 @@ impl FromWorld for PbrShaders { }, ], }], - module: &&vertex_shader_module, - entry_point: "main", + module: &shader_module, + entry_point: "vertex", }, fragment: Some(FragmentState { - module: &&fragment_shader_module, - entry_point: "main", + module: &shader_module, + entry_point: "fragment", targets: &[ColorTargetState { format: TextureFormat::bevy_default(), blend: Some(BlendState { @@ -359,7 +344,7 @@ impl FromWorld for PbrShaders { view_layout, material_layout, mesh_layout, - vertex_shader_module, + shader_module, dummy_white_gpu_image, } } @@ -473,7 +458,9 @@ fn image_handle_to_view_sampler<'a>( &pbr_shaders.dummy_white_gpu_image.sampler, ), |image_handle| { - let gpu_image = gpu_images.get(image_handle).expect("only materials with valid textures should be drawn"); + let gpu_image = gpu_images + .get(image_handle) + .expect("only materials with valid textures should be drawn"); (&gpu_image.texture_view, &gpu_image.sampler) }, ) diff --git a/pipelined/bevy_pbr2/src/render/pbr.frag b/pipelined/bevy_pbr2/src/render/pbr.frag deleted file mode 100644 index 8a2a8d1938..0000000000 --- a/pipelined/bevy_pbr2/src/render/pbr.frag +++ /dev/null @@ -1,423 +0,0 @@ -#version 450 - -// From the Filament design doc -// https://google.github.io/filament/Filament.html#table_symbols -// Symbol Definition -// v View unit vector -// l Incident light unit vector -// n Surface normal unit vector -// h Half unit vector between l and v -// f BRDF -// f_d Diffuse component of a BRDF -// f_r Specular component of a BRDF -// α Roughness, remapped from using input perceptualRoughness -// σ Diffuse reflectance -// Ω Spherical domain -// f0 Reflectance at normal incidence -// f90 Reflectance at grazing angle -// χ+(a) Heaviside function (1 if a>0 and 0 otherwise) -// nior Index of refraction (IOR) of an interface -// ⟨n⋅l⟩ Dot product clamped to [0..1] -// ⟨a⟩ Saturated value (clamped to [0..1]) - -// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material -// and consists of two components, the diffuse component (f_d) and the specular component (f_r): -// f(v,l) = f_d(v,l) + f_r(v,l) -// -// The form of the microfacet model is the same for diffuse and specular -// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm -// -// In which: -// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets -// G models the visibility (or occlusion or shadow-masking) of the microfacets -// f_m is the microfacet BRDF and differs between specular and diffuse components -// -// The above integration needs to be approximated. - -layout(location = 0) in vec4 v_WorldPosition; -layout(location = 1) in vec3 v_WorldNormal; -layout(location = 2) in vec2 v_Uv; - -layout(location = 0) out vec4 o_Target; - -struct OmniLight { - vec4 color; - float range; - float radius; - vec3 position; - mat4 view_projection; -}; - -// NOTE: this must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs -// TODO: this can be removed if we move to storage buffers for light arrays -const int MAX_OMNI_LIGHTS = 10; - -struct StandardMaterial_t { - vec4 base_color; - vec4 emissive; - float perceptual_roughness; - float metallic; - float reflectance; - // 'flags' is a bit field indicating various option. uint is 32 bits so we have up to 32 options. - uint flags; -}; - -// NOTE: These must match those defined in bevy_pbr2/src/material.rs -const uint FLAGS_BASE_COLOR_TEXTURE_BIT = (1 << 0); -const uint FLAGS_EMISSIVE_TEXTURE_BIT = (1 << 1); -const uint FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT = (1 << 2); -const uint FLAGS_OCCLUSION_TEXTURE_BIT = (1 << 3); -const uint FLAGS_DOUBLE_SIDED_BIT = (1 << 4); -const uint FLAGS_UNLIT_BIT = (1 << 5); - -// View bindings - set 0 -layout(set = 0, binding = 0) uniform View { - mat4 ViewProj; - vec3 ViewWorldPosition; -}; -layout(std140, set = 0, binding = 1) uniform Lights { - vec4 AmbientColor; - uint NumLights; - OmniLight OmniLights[MAX_OMNI_LIGHTS]; -}; -layout(set = 0, binding = 2) uniform texture2DArray t_Shadow; -layout(set = 0, binding = 3) uniform samplerShadow s_Shadow; - -// Material bindings - set 2 -layout(set = 2, binding = 0) uniform StandardMaterial { - StandardMaterial_t Material; -}; - -layout(set = 2, binding = 1) uniform texture2D base_color_texture; -layout(set = 2, binding = 2) uniform sampler base_color_sampler; - -layout(set = 2, binding = 3) uniform texture2D emissive_texture; -layout(set = 2, binding = 4) uniform sampler emissive_sampler; - -layout(set = 2, binding = 5) uniform texture2D metallic_roughness_texture; -layout(set = 2, binding = 6) uniform sampler metallic_roughness_sampler; - -layout(set = 2, binding = 7) uniform texture2D occlusion_texture; -layout(set = 2, binding = 8) uniform sampler occlusion_sampler; - -# define saturate(x) clamp(x, 0.0, 1.0) -const float PI = 3.141592653589793; - -float pow5(float x) { - float x2 = x * x; - return x2 * x2 * x; -} - -// distanceAttenuation is simply the square falloff of light intensity -// combined with a smooth attenuation at the edge of the light radius -// -// light radius is a non-physical construct for efficiency purposes, -// because otherwise every light affects every fragment in the scene -float getDistanceAttenuation(float distanceSquare, float inverseRangeSquared) { - float factor = distanceSquare * inverseRangeSquared; - float smoothFactor = saturate(1.0 - factor * factor); - float attenuation = smoothFactor * smoothFactor; - return attenuation * 1.0 / max(distanceSquare, 1e-4); -} - -// Normal distribution function (specular D) -// Based on https://google.github.io/filament/Filament.html#citation-walter07 - -// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 } - -// Simple implementation, has precision problems when using fp16 instead of fp32 -// see https://google.github.io/filament/Filament.html#listing_speculardfp16 -float D_GGX(float roughness, float NoH, const vec3 h) { - float oneMinusNoHSquared = 1.0 - NoH * NoH; - float a = NoH * roughness; - float k = roughness / (oneMinusNoHSquared + a * a); - float d = k * k * (1.0 / PI); - return d; -} - -// Visibility function (Specular G) -// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) } -// such that f_r becomes -// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0) -// where -// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) } -// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv -float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) { - float a2 = roughness * roughness; - float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); - float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); - float v = 0.5 / (lambdaV + lambdaL); - return v; -} - -// Fresnel function -// see https://google.github.io/filament/Filament.html#citation-schlick94 -// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5 -vec3 F_Schlick(const vec3 f0, float f90, float VoH) { - // not using mix to keep the vec3 and float versions identical - return f0 + (f90 - f0) * pow5(1.0 - VoH); -} - -float F_Schlick(float f0, float f90, float VoH) { - // not using mix to keep the vec3 and float versions identical - return f0 + (f90 - f0) * pow5(1.0 - VoH); -} - -vec3 fresnel(vec3 f0, float LoH) { - // f_90 suitable for ambient occlusion - // see https://google.github.io/filament/Filament.html#lighting/occlusion - float f90 = saturate(dot(f0, vec3(50.0 * 0.33))); - return F_Schlick(f0, f90, LoH); -} - -// Specular BRDF -// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf - -// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m -// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } -vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL, - float NoH, float LoH, float specularIntensity) { - float D = D_GGX(roughness, NoH, h); - float V = V_SmithGGXCorrelated(roughness, NoV, NoL); - vec3 F = fresnel(f0, LoH); - - return (specularIntensity * D * V) * F; -} - -// Diffuse BRDF -// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf -// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm - -// simplest approximation -// float Fd_Lambert() { -// return 1.0 / PI; -// } -// -// vec3 Fd = diffuseColor * Fd_Lambert(); - -// Disney approximation -// See https://google.github.io/filament/Filament.html#citation-burley12 -// minimal quality difference -float Fd_Burley(float roughness, float NoV, float NoL, float LoH) { - float f90 = 0.5 + 2.0 * roughness * LoH * LoH; - float lightScatter = F_Schlick(1.0, f90, NoL); - float viewScatter = F_Schlick(1.0, f90, NoV); - return lightScatter * viewScatter * (1.0 / PI); -} - -// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile -vec3 EnvBRDFApprox(vec3 f0, float perceptual_roughness, float NoV) { - const vec4 c0 = { -1, -0.0275, -0.572, 0.022 }; - const vec4 c1 = { 1, 0.0425, 1.04, -0.04 }; - vec4 r = perceptual_roughness * c0 + c1; - float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; - vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw; - return f0 * AB.x + AB.y; -} - -float perceptualRoughnessToRoughness(float perceptualRoughness) { - // clamp perceptual roughness to prevent precision problems - // According to Filament design 0.089 is recommended for mobile - // Filament uses 0.045 for non-mobile - float clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0); - return clampedPerceptualRoughness * clampedPerceptualRoughness; -} - -// from https://64.github.io/tonemapping/ -// reinhard on RGB oversaturates colors -vec3 reinhard(vec3 color) { - return color / (1.0 + color); -} - -vec3 reinhard_extended(vec3 color, float max_white) { - vec3 numerator = color * (1.0f + (color / vec3(max_white * max_white))); - return numerator / (1.0 + color); -} - -// luminance coefficients from Rec. 709. -// https://en.wikipedia.org/wiki/Rec._709 -float luminance(vec3 v) { - return dot(v, vec3(0.2126, 0.7152, 0.0722)); -} - -vec3 change_luminance(vec3 c_in, float l_out) { - float l_in = luminance(c_in); - return c_in * (l_out / l_in); -} - -vec3 reinhard_luminance(vec3 color) { - float l_old = luminance(color); - float l_new = l_old / (1.0f + l_old); - return change_luminance(color, l_new); -} - -vec3 reinhard_extended_luminance(vec3 color, float max_white_l) { - float l_old = luminance(color); - float numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l))); - float l_new = numerator / (1.0f + l_old); - return change_luminance(color, l_new); -} - -vec3 omni_light(OmniLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { - vec3 light_to_frag = light.position.xyz - v_WorldPosition.xyz; - float distance_square = dot(light_to_frag, light_to_frag); - float rangeAttenuation = - getDistanceAttenuation(distance_square, light.range); - - // Specular. - // Representative Point Area Lights. - // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 - float a = roughness; - vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; - vec3 closestPoint = light_to_frag + centerToRay * saturate(light.radius * inversesqrt(dot(centerToRay, centerToRay))); - float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); - float normalizationFactor = a / saturate(a + (light.radius * 0.5 * LspecLengthInverse)); - float specularIntensity = normalizationFactor * normalizationFactor; - - vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? - vec3 H = normalize(L + V); - float NoL = saturate(dot(N, L)); - float NoH = saturate(dot(N, H)); - float LoH = saturate(dot(L, H)); - - vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); - - // Diffuse. - // Comes after specular since its NoL is used in the lighting equation. - L = normalize(light_to_frag); - H = normalize(L + V); - NoL = saturate(dot(N, L)); - NoH = saturate(dot(N, H)); - LoH = saturate(dot(L, H)); - - vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); - - // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ - // where - // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color - // Φ is light intensity - - // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius - // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out - - // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation - // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance - // light.color.rgb is premultiplied with light.intensity on the CPU - return ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL); -} - -float fetch_shadow(int light_id, vec4 homogeneous_coords) { - if (homogeneous_coords.w <= 0.0) { - return 1.0; - } - // compensate for the Y-flip difference between the NDC and texture coordinates - const vec2 flip_correction = vec2(0.5, -0.5); - // compute texture coordinates for shadow lookup - vec4 light_local = vec4( - homogeneous_coords.xy * flip_correction/homogeneous_coords.w + 0.5, - light_id, - homogeneous_coords.z / homogeneous_coords.w - ); - // do the lookup, using HW PCF and comparison - return texture(sampler2DArrayShadow(t_Shadow, s_Shadow), light_local); -} - -void main() { - vec4 output_color = Material.base_color; - if ((Material.flags & FLAGS_BASE_COLOR_TEXTURE_BIT) != 0) { - output_color *= texture(sampler2D(base_color_texture, base_color_sampler), v_Uv); - } - - // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit - if ((Material.flags & FLAGS_UNLIT_BIT) == 0) { - // TODO use .a for exposure compensation in HDR - vec4 emissive = Material.emissive; - if ((Material.flags & FLAGS_EMISSIVE_TEXTURE_BIT) != 0) { - emissive.rgb *= texture(sampler2D(emissive_texture, emissive_sampler), v_Uv).rgb; - } - - // calculate non-linear roughness from linear perceptualRoughness - float metallic = Material.metallic; - float perceptual_roughness = Material.perceptual_roughness; - if ((Material.flags & FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0) { - vec4 metallic_roughness = texture(sampler2D(metallic_roughness_texture, metallic_roughness_sampler), v_Uv); - // Sampling from GLTF standard channels for now - metallic *= metallic_roughness.b; - perceptual_roughness *= metallic_roughness.g; - } - float roughness = perceptualRoughnessToRoughness(perceptual_roughness); - - float occlusion = 1.0; - if ((Material.flags & FLAGS_OCCLUSION_TEXTURE_BIT) != 0) { - occlusion = texture(sampler2D(occlusion_texture, occlusion_sampler), v_Uv).r; - } - - vec3 N = normalize(v_WorldNormal); - - // FIXME: Normal maps need an additional vertex attribute and vertex stage output/fragment stage input - // Just use a separate shader for lit with normal maps? - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // vec3 T = normalize(v_WorldTangent.xyz); - // vec3 B = cross(N, T) * v_WorldTangent.w; - // # endif - - if ((Material.flags & FLAGS_DOUBLE_SIDED_BIT) != 0) { - N = gl_FrontFacing ? N : -N; - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // T = gl_FrontFacing ? T : -T; - // B = gl_FrontFacing ? B : -B; - // # endif - } - - // # ifdef STANDARDMATERIAL_NORMAL_MAP - // mat3 TBN = mat3(T, B, N); - // N = TBN * normalize(texture(sampler2D(normal_map, normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); - // # endif - - vec3 V; - if (ViewProj[3][3] != 1.0) { // If the projection is not orthographic - // Only valid for a perpective projection - V = normalize(ViewWorldPosition.xyz - v_WorldPosition.xyz); - } else { - // Ortho view vec - V = normalize(vec3(-ViewProj[0][2], -ViewProj[1][2], -ViewProj[2][2])); - } - - // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" - float NdotV = max(dot(N, V), 1e-4); - - // Remapping [0,1] reflectance to F0 - // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping - float reflectance = Material.reflectance; - vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic; - - // Diffuse strength inversely related to metallicity - vec3 diffuse_color = output_color.rgb * (1.0 - metallic); - - vec3 R = reflect(-V, N); - - // accumulate color - vec3 light_accum = vec3(0.0); - for (int i = 0; i < int(NumLights); ++i) { - OmniLight light = OmniLights[i]; - vec3 light_contrib = omni_light(light, roughness, NdotV, N, V, R, F0, diffuse_color); - float shadow = fetch_shadow(i, light.view_projection * v_WorldPosition); - light_accum += light_contrib * shadow; - } - - vec3 diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV); - vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); - - output_color.rgb = light_accum; - output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor.rgb * occlusion; - output_color.rgb += emissive.rgb * output_color.a; - - // tone_mapping - output_color.rgb = reinhard_luminance(output_color.rgb); - // Gamma correction. - // Not needed with sRGB buffer - // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); - } - - o_Target = output_color; -} diff --git a/pipelined/bevy_pbr2/src/render/pbr.vert b/pipelined/bevy_pbr2/src/render/pbr.vert deleted file mode 100644 index acc4a0bd87..0000000000 --- a/pipelined/bevy_pbr2/src/render/pbr.vert +++ /dev/null @@ -1,28 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec3 Vertex_Normal; -layout(location = 2) in vec2 Vertex_Uv; - -layout(location = 0) out vec4 v_WorldPosition; -layout(location = 1) out vec3 v_WorldNormal; -layout(location = 2) out vec2 v_Uv; - -layout(set = 0, binding = 0) uniform View { - mat4 ViewProj; - vec3 ViewWorldPosition; -}; - -layout(set = 1, binding = 0) uniform MeshTransform { - mat4 Model; -}; - -void main() { - v_Uv = Vertex_Uv; - vec4 world_position = Model * vec4(Vertex_Position, 1.0); - v_WorldPosition = world_position; - // FIXME: The inverse transpose of the model matrix should be used to correctly handle scaling - // of normals - v_WorldNormal = mat3(Model) * Vertex_Normal; - gl_Position = ViewProj * world_position; -} diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/pipelined/bevy_pbr2/src/render/pbr.wgsl new file mode 100644 index 0000000000..9eb3e9bf97 --- /dev/null +++ b/pipelined/bevy_pbr2/src/render/pbr.wgsl @@ -0,0 +1,470 @@ +// TODO: try merging this block with the binding? +[[block]] +struct View { + view_proj: mat4x4; + world_position: vec3; +}; +[[group(0), binding(0)]] +var view: View; + + +[[block]] +struct Mesh { + transform: mat4x4; +}; +[[group(1), binding(0)]] +var mesh: Mesh; + +struct Vertex { + [[location(0)]] position: vec3; + [[location(1)]] normal: vec3; + [[location(2)]] uv: vec2; +}; + +struct VertexOutput { + [[builtin(position)]] world_position: vec4; + [[location(0)]] world_normal: vec3; + [[location(1)]] uv: vec2; +}; + +[[stage(vertex)]] +fn vertex(vertex: Vertex) -> VertexOutput { + let world_position = mesh.transform * vec4(vertex.position, 1.0); + + var out: VertexOutput; + out.uv = vertex.uv; + out.world_position = view.view_proj * world_position; + // FIXME: The inverse transpose of the model matrix should be used to correctly handle scaling + // of normals + out.world_normal = mat3x3(mesh.transform.x.xyz, mesh.transform.y.xyz, mesh.transform.z.xyz) * vertex.normal; + return out; +} + +// From the Filament design doc +// https://google.github.io/filament/Filament.html#table_symbols +// Symbol Definition +// v View unit vector +// l Incident light unit vector +// n Surface normal unit vector +// h Half unit vector between l and v +// f BRDF +// f_d Diffuse component of a BRDF +// f_r Specular component of a BRDF +// α Roughness, remapped from using input perceptualRoughness +// σ Diffuse reflectance +// Ω Spherical domain +// f0 Reflectance at normal incidence +// f90 Reflectance at grazing angle +// χ+(a) Heaviside function (1 if a>0 and 0 otherwise) +// nior Index of refraction (IOR) of an interface +// ⟨n⋅l⟩ Dot product clamped to [0..1] +// ⟨a⟩ Saturated value (clamped to [0..1]) + +// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material +// and consists of two components, the diffuse component (f_d) and the specular component (f_r): +// f(v,l) = f_d(v,l) + f_r(v,l) +// +// The form of the microfacet model is the same for diffuse and specular +// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm +// +// In which: +// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets +// G models the visibility (or occlusion or shadow-masking) of the microfacets +// f_m is the microfacet BRDF and differs between specular and diffuse components +// +// The above integration needs to be approximated. + +[[block]] +struct StandardMaterial { + base_color: vec4; + emissive: vec4; + perceptual_roughness: f32; + metallic: f32; + reflectance: f32; + // 'flags' is a bit field indicating various option. uint is 32 bits so we have up to 32 options. + flags: u32; +}; + +struct OmniLight { + color: vec4; + range: f32; + radius: f32; + position: vec3; + view_projection: mat4x4; +}; + +[[block]] +struct Lights { + ambient_color: vec4; + num_lights: u32; + // NOTE: this array size must be kept in sync with the constants defined bevy_pbr2/src/render/light.rs + // TODO: this can be removed if we move to storage buffers for light arrays + omni_lights: array; +}; + +let FLAGS_BASE_COLOR_TEXTURE_BIT: u32 = 1u; +let FLAGS_EMISSIVE_TEXTURE_BIT: u32 = 2u; +let FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT: u32 = 4u; +let FLAGS_OCCLUSION_TEXTURE_BIT: u32 = 8u; +let FLAGS_DOUBLE_SIDED_BIT: u32 = 16u; +let FLAGS_UNLIT_BIT: u32 = 32u; + + +[[group(0), binding(1)]] +var lights: Lights; +[[group(0), binding(2)]] +var shadow_textures: texture_depth_2d_array; +[[group(0), binding(3)]] +var shadow_textures_sampler: sampler_comparison; + +[[group(2), binding(0)]] +var material: StandardMaterial; +[[group(2), binding(1)]] +var base_color_texture: texture_2d; +[[group(2), binding(2)]] +var base_color_sampler: sampler; +[[group(2), binding(3)]] +var emissive_texture: texture_2d; +[[group(2), binding(4)]] +var emissive_sampler: sampler; +[[group(2), binding(5)]] +var metallic_roughness_texture: texture_2d; +[[group(2), binding(6)]] +var metallic_roughness_sampler: sampler; +[[group(2), binding(7)]] +var occlusion_texture: texture_2d; +[[group(2), binding(8)]] +var occlusion_sampler: sampler; + +let PI: f32 = 3.141592653589793; + +fn saturate(value: f32) -> f32 { + return clamp(value, 0.0, 1.0); +} + +// distanceAttenuation is simply the square falloff of light intensity +// combined with a smooth attenuation at the edge of the light radius +// +// light radius is a non-physical construct for efficiency purposes, +// because otherwise every light affects every fragment in the scene +fn getDistanceAttenuation(distanceSquare: f32, inverseRangeSquared: f32) -> f32 { + let factor = distanceSquare * inverseRangeSquared; + let smoothFactor = saturate(1.0 - factor * factor); + let attenuation = smoothFactor * smoothFactor; + return attenuation * 1.0 / max(distanceSquare, 0.0001); +} + +// Normal distribution function (specular D) +// Based on https://google.github.io/filament/Filament.html#citation-walter07 + +// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 } + +// Simple implementation, has precision problems when using fp16 instead of fp32 +// see https://google.github.io/filament/Filament.html#listing_speculardfp16 +fn D_GGX(roughness: f32, NoH: f32, h: vec3) -> f32 { + let oneMinusNoHSquared = 1.0 - NoH * NoH; + let a = NoH * roughness; + let k = roughness / (oneMinusNoHSquared + a * a); + let d = k * k * (1.0 / PI); + return d; +} + +// Visibility function (Specular G) +// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) } +// such that f_r becomes +// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0) +// where +// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) } +// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv +fn V_SmithGGXCorrelated(roughness: f32, NoV: f32, NoL: f32) -> f32 { + let a2 = roughness * roughness; + let lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); + let lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); + let v = 0.5 / (lambdaV + lambdaL); + return v; +} + +// Fresnel function +// see https://google.github.io/filament/Filament.html#citation-schlick94 +// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5 +fn F_Schlick_vec(f0: vec3, f90: f32, VoH: f32) -> vec3 { + // not using mix to keep the vec3 and float versions identical + return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0); +} + +fn F_Schlick(f0: f32, f90: f32, VoH: f32) -> f32 { + // not using mix to keep the vec3 and float versions identical + return f0 + (f90 - f0) * pow(1.0 - VoH, 5.0); +} + +fn fresnel(f0: vec3, LoH: f32) -> vec3 { + // f_90 suitable for ambient occlusion + // see https://google.github.io/filament/Filament.html#lighting/occlusion + let f90 = saturate(dot(f0, vec3(50.0 * 0.33))); + return F_Schlick_vec(f0, f90, LoH); +} + +// Specular BRDF +// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf + +// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m +// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } +fn specular(f0: vec3, roughness: f32, h: vec3, NoV: f32, NoL: f32, + NoH: f32, LoH: f32, specularIntensity: f32) -> vec3 { + let D = D_GGX(roughness, NoH, h); + let V = V_SmithGGXCorrelated(roughness, NoV, NoL); + let F = fresnel(f0, LoH); + + return (specularIntensity * D * V) * F; +} + +// Diffuse BRDF +// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf +// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm +// +// simplest approximation +// float Fd_Lambert() { +// return 1.0 / PI; +// } +// +// vec3 Fd = diffuseColor * Fd_Lambert(); +// +// Disney approximation +// See https://google.github.io/filament/Filament.html#citation-burley12 +// minimal quality difference +fn Fd_Burley(roughness: f32, NoV: f32, NoL: f32, LoH: f32) -> f32 { + let f90 = 0.5 + 2.0 * roughness * LoH * LoH; + let lightScatter = F_Schlick(1.0, f90, NoL); + let viewScatter = F_Schlick(1.0, f90, NoV); + return lightScatter * viewScatter * (1.0 / PI); +} + +// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile +fn EnvBRDFApprox(f0: vec3, perceptual_roughness: f32, NoV: f32) -> vec3 { + let c0 = vec4(-1.0, -0.0275, -0.572, 0.022); + let c1 = vec4(1.0, 0.0425, 1.04, -0.04); + let r = perceptual_roughness * c0 + c1; + let a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; + let AB = vec2(-1.04, 1.04) * a004 + r.zw; + return f0 * AB.x + AB.y; +} + +fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { + // clamp perceptual roughness to prevent precision problems + // According to Filament design 0.089 is recommended for mobile + // Filament uses 0.045 for non-mobile + let clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0); + return clampedPerceptualRoughness * clampedPerceptualRoughness; +} + +// from https://64.github.io/tonemapping/ +// reinhard on RGB oversaturates colors +fn reinhard(color: vec3) -> vec3 { + return color / (1.0 + color); +} + +fn reinhard_extended(color: vec3, max_white: f32) -> vec3 { + let numerator = color * (1.0f + (color / vec3(max_white * max_white))); + return numerator / (1.0 + color); +} + +// luminance coefficients from Rec. 709. +// https://en.wikipedia.org/wiki/Rec._709 +fn luminance(v: vec3) -> f32 { + return dot(v, vec3(0.2126, 0.7152, 0.0722)); +} + +fn change_luminance(c_in: vec3, l_out: f32) -> vec3 { + let l_in = luminance(c_in); + return c_in * (l_out / l_in); +} + +fn reinhard_luminance(color: vec3) -> vec3 { + let l_old = luminance(color); + let l_new = l_old / (1.0f + l_old); + return change_luminance(color, l_new); +} + +fn reinhard_extended_luminance(color: vec3, max_white_l: f32) -> vec3 { + let l_old = luminance(color); + let numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l))); + let l_new = numerator / (1.0f + l_old); + return change_luminance(color, l_new); +} + +fn omni_light( + world_position: vec3, light: OmniLight, roughness: f32, NdotV: f32, N: vec3, V: vec3, + R: vec3, F0: vec3, diffuseColor: vec3 +) -> vec3 { + let light_to_frag = light.position.xyz - world_position.xyz; + let distance_square = dot(light_to_frag, light_to_frag); + let rangeAttenuation = + getDistanceAttenuation(distance_square, light.range); + + // Specular. + // Representative Point Area Lights. + // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 + let a = roughness; + let centerToRay = dot(light_to_frag, R) * R - light_to_frag; + let closestPoint = light_to_frag + centerToRay * saturate(light.radius * inverseSqrt(dot(centerToRay, centerToRay))); + let LspecLengthInverse = inverseSqrt(dot(closestPoint, closestPoint)); + let normalizationFactor = a / saturate(a + (light.radius * 0.5 * LspecLengthInverse)); + let specularIntensity = normalizationFactor * normalizationFactor; + + var L: vec3 = closestPoint * LspecLengthInverse; // normalize() equivalent? + var H: vec3 = normalize(L + V); + var NoL: f32 = saturate(dot(N, L)); + var NoH: f32 = saturate(dot(N, H)); + var LoH: f32 = saturate(dot(L, H)); + + let specular_light = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); + + // Diffuse. + // Comes after specular since its NoL is used in the lighting equation. + L = normalize(light_to_frag); + H = normalize(L + V); + NoL = saturate(dot(N, L)); + NoH = saturate(dot(N, H)); + LoH = saturate(dot(L, H)); + + let diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); + + // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ + // where + // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color + // Φ is light intensity + + // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius + // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out + + // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation + // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance + // light.color.rgb is premultiplied with light.intensity on the CPU + + return ((diffuse + specular_light) * light.color.rgb) * (rangeAttenuation * NoL); +} + +fn fetch_shadow(light_id: i32, homogeneous_coords: vec4) -> f32 { + if (homogeneous_coords.w <= 0.0) { + return 1.0; + } + // compensate for the Y-flip difference between the NDC and texture coordinates + let flip_correction = vec2(0.5, -0.5); + let proj_correction = 1.0 / homogeneous_coords.w; + // compute texture coordinates for shadow lookup + let light_local = homogeneous_coords.xy * flip_correction * proj_correction + vec2(0.5, 0.5); + // do the lookup, using HW PCF and comparison + return textureSampleCompare(shadow_textures, shadow_textures_sampler, light_local, i32(light_id), homogeneous_coords.z * proj_correction); +} + +struct FragmentInput { + [[builtin(front_facing)]] is_front: bool; + [[builtin(position)]] world_position: vec4; + [[location(0)]] world_normal: vec3; + [[location(1)]] uv: vec2; +}; + +[[stage(fragment)]] +fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { + var output_color: vec4 = material.base_color; + if ((material.flags & FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { + output_color = output_color * textureSample(base_color_texture, base_color_sampler, in.uv); + } + + // // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit + if ((material.flags & FLAGS_UNLIT_BIT) == 0u) { + // TODO use .a for exposure compensation in HDR + var emissive: vec4 = material.emissive; + if ((material.flags & FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { + emissive = vec4(emissive.rgb * textureSample(emissive_texture, emissive_sampler, in.uv).rgb, 1.0); + } + + // calculate non-linear roughness from linear perceptualRoughness + var metallic: f32 = material.metallic; + var perceptual_roughness: f32 = material.perceptual_roughness; + if ((material.flags & FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { + let metallic_roughness = textureSample(metallic_roughness_texture, metallic_roughness_sampler, in.uv); + // Sampling from GLTF standard channels for now + metallic = metallic * metallic_roughness.b; + perceptual_roughness = perceptual_roughness * metallic_roughness.g; + } + let roughness = perceptualRoughnessToRoughness(perceptual_roughness); + + var occlusion: f32 = 1.0; + if ((material.flags & FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { + occlusion = textureSample(occlusion_texture, occlusion_sampler, in.uv).r; + } + + var N: vec3 = normalize(in.world_normal); + + // FIXME: Normal maps need an additional vertex attribute and vertex stage output/fragment stage input + // Just use a separate shader for lit with normal maps? + // # ifdef STANDARDMATERIAL_NORMAL_MAP + // vec3 T = normalize(v_WorldTangent.xyz); + // vec3 B = cross(N, T) * v_WorldTangent.w; + // # endif + + if ((material.flags & FLAGS_DOUBLE_SIDED_BIT) != 0u) { + if (!in.is_front) { + N = -N; + } + // # ifdef STANDARDMATERIAL_NORMAL_MAP + // T = gl_FrontFacing ? T : -T; + // B = gl_FrontFacing ? B : -B; + // # endif + } + + // # ifdef STANDARDMATERIAL_NORMAL_MAP + // mat3 TBN = mat3(T, B, N); + // N = TBN * normalize(texture(sampler2D(normal_map, normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); + // # endif + + var V: vec3; + if (view.view_proj.z.z != 1.0) { // If the projection is not orthographic + // Only valid for a perpective projection + V = normalize(view.world_position.xyz - in.world_position.xyz); + } else { + // Ortho view vec + V = normalize(vec3(-view.view_proj.x.z, -view.view_proj.y.z, -view.view_proj.z.z)); + } + + // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" + let NdotV = max(dot(N, V), 0.0001); + + // Remapping [0,1] reflectance to F0 + // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping + let reflectance = material.reflectance; + let F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic; + + // Diffuse strength inversely related to metallicity + let diffuse_color = output_color.rgb * (1.0 - metallic); + + let R = reflect(-V, N); + + // accumulate color + var light_accum: vec3 = vec3(0.0); + for (var i: i32 = 0; i < i32(lights.num_lights); i = i + 1) { + let light = lights.omni_lights[i]; + let light_contrib = omni_light(in.world_position.xyz, light, roughness, NdotV, N, V, R, F0, diffuse_color); + let shadow = fetch_shadow(i, light.view_projection * in.world_position); + light_accum = light_accum + light_contrib * shadow; + } + + let diffuse_ambient = EnvBRDFApprox(diffuse_color, 1.0, NdotV); + let specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); + + output_color = vec4( + light_accum + + (diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion + + emissive.rgb * output_color.a, + output_color.a); + + // tone_mapping + output_color = vec4(reinhard_luminance(output_color.rgb), output_color.a); + // Gamma correction. + // Not needed with sRGB buffer + // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); + } + + return output_color; +} \ No newline at end of file diff --git a/pipelined/bevy_render2/Cargo.toml b/pipelined/bevy_render2/Cargo.toml index 353d0a74c0..b8366c3100 100644 --- a/pipelined/bevy_render2/Cargo.toml +++ b/pipelined/bevy_render2/Cargo.toml @@ -30,6 +30,7 @@ image = { version = "0.23.12", default-features = false } # misc wgpu = "0.8" +naga = { git = "https://github.com/gfx-rs/naga", rev = "0cf5484bba530f1134badbd2a1c1a8e9daf2e9c3", features = ["glsl-in", "spv-out", "spv-in", "wgsl-in"] } serde = { version = "1", features = ["derive"] } bitflags = "1.2.1" smallvec = { version = "1.6", features = ["union", "const_generics"] } @@ -43,15 +44,6 @@ hexasphere = "3.4" parking_lot = "0.11.0" crevice = { path = "../../crates/crevice" } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -spirv-reflect = "0.2.3" - -[target.'cfg(any(all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc")))'.dependencies] -bevy-glsl-to-spirv = "0.2.0" - -[target.'cfg(not(any(target_arch = "wasm32", all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc"))))'.dependencies] -shaderc = "0.7.0" - [features] png = ["image/png"] hdr = ["image/hdr"] diff --git a/pipelined/bevy_render2/src/renderer/render_device.rs b/pipelined/bevy_render2/src/renderer/render_device.rs index 7601ae364e..2063a95ead 100644 --- a/pipelined/bevy_render2/src/renderer/render_device.rs +++ b/pipelined/bevy_render2/src/renderer/render_device.rs @@ -36,8 +36,8 @@ impl RenderDevice { /// Creates a shader module from either SPIR-V or WGSL source code. #[inline] - pub fn create_shader_module(&self, desc: &wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule { - self.device.create_shader_module(desc) + pub fn create_shader_module<'a>(&self, desc: impl Into>) -> wgpu::ShaderModule { + self.device.create_shader_module(&desc.into()) } /// Check for resource cleanups and mapping callbacks. diff --git a/pipelined/bevy_render2/src/shader/shader.rs b/pipelined/bevy_render2/src/shader/shader.rs index ec4249827c..71941d7b8f 100644 --- a/pipelined/bevy_render2/src/shader/shader.rs +++ b/pipelined/bevy_render2/src/shader/shader.rs @@ -1,159 +1,10 @@ use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_reflect::{TypeUuid, Uuid}; use bevy_utils::{tracing::error, BoxedFuture}; -use std::marker::Copy; +use naga::{valid::ModuleInfo, Module}; +use std::{borrow::Cow, marker::Copy}; use thiserror::Error; -use wgpu::ShaderStage; - -/// An error that occurs during shader handling. -#[derive(Error, Debug)] -pub enum ShaderError { - /// Shader compilation error. - #[error("Shader compilation error:\n{0}")] - Compilation(String), - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - /// shaderc error. - #[error("shaderc error: {0}")] - ShaderC(#[from] shaderc::Error), - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - #[error("Error initializing shaderc Compiler")] - ErrorInitializingShadercCompiler, - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - #[error("Error initializing shaderc CompileOptions")] - ErrorInitializingShadercCompileOptions, -} - -#[cfg(any( - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -))] -fn convert_stage(s: ShaderStage) -> bevy_glsl_to_spirv::ShaderType { - match s { - ShaderStage::VERTEX => bevy_glsl_to_spirv::ShaderType::Vertex, - ShaderStage::FRAGMENT => bevy_glsl_to_spirv::ShaderType::Fragment, - ShaderStage::COMPUTE => bevy_glsl_to_spirv::ShaderType::Compute, - _ => panic!("unsupported stage type"), - } -} - -#[cfg(any( - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -))] -pub fn glsl_to_spirv( - glsl_source: &str, - stage: ShaderStage, - shader_defs: Option<&[String]>, -) -> Result, ShaderError> { - bevy_glsl_to_spirv::compile(glsl_source, convert_stage(stage), shader_defs) - .map_err(ShaderError::Compilation) -} - -#[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -)))] -impl Into for ShaderStage { - fn into(self) -> shaderc::ShaderKind { - match self { - ShaderStage::Vertex => shaderc::ShaderKind::Vertex, - ShaderStage::Fragment => shaderc::ShaderKind::Fragment, - ShaderStage::Compute => shaderc::ShaderKind::Compute, - } - } -} - -#[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -)))] -pub fn glsl_to_spirv( - glsl_source: &str, - stage: ShaderStage, - shader_defs: Option<&[String]>, -) -> Result, ShaderError> { - let mut compiler = - shaderc::Compiler::new().ok_or(ShaderError::ErrorInitializingShadercCompiler)?; - let mut options = shaderc::CompileOptions::new() - .ok_or(ShaderError::ErrorInitializingShadercCompileOptions)?; - if let Some(shader_defs) = shader_defs { - for def in shader_defs.iter() { - options.add_macro_definition(def, None); - } - } - - let binary_result = compiler.compile_into_spirv( - glsl_source, - stage.into(), - "shader.glsl", - "main", - Some(&options), - )?; - - Ok(binary_result.as_binary().to_vec()) -} - -fn bytes_to_words(bytes: &[u8]) -> Vec { - let mut words = Vec::new(); - for bytes4 in bytes.chunks(4) { - words.push(u32::from_le_bytes([ - bytes4[0], bytes4[1], bytes4[2], bytes4[3], - ])); - } - - words -} - -/// The full "source" of a shader -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub enum ShaderSource { - Spirv(Vec), - Glsl(String), -} - -impl ShaderSource { - pub fn spirv_from_bytes(bytes: &[u8]) -> ShaderSource { - ShaderSource::Spirv(bytes_to_words(bytes)) - } -} +use wgpu::{ShaderFlags, ShaderModuleDescriptor, ShaderSource}; #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] pub struct ShaderId(Uuid); @@ -165,72 +16,77 @@ impl ShaderId { } } +#[derive(Error, Debug)] +pub enum ShaderReflectError { + #[error(transparent)] + WgslParse(#[from] naga::front::wgsl::ParseError), + #[error(transparent)] + SpirVParse(#[from] naga::front::spv::Error), + #[error(transparent)] + Validation(#[from] naga::valid::ValidationError), +} + /// A shader, as defined by its [ShaderSource] and [ShaderStage] -#[derive(Clone, Debug, TypeUuid)] +#[derive(Debug, TypeUuid)] #[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"] -pub struct Shader { - pub source: ShaderSource, - pub stage: ShaderStage, +pub enum Shader { + Wgsl(Cow<'static, str>), + SpirV(Vec), + // TODO: consider the following + // PrecompiledSpirVMacros(HashMap, Vec>) + // NagaModule(Module) ... Module impls Serialize/Deserialize +} + +pub struct ShaderReflection { + pub module: Module, + pub module_info: ModuleInfo, +} + +impl ShaderReflection { + pub fn get_spirv(&self) -> Result, naga::back::spv::Error> { + naga::back::spv::write_vec( + &self.module, + &self.module_info, + &naga::back::spv::Options { + flags: naga::back::spv::WriterFlags::empty(), + ..naga::back::spv::Options::default() + }, + ) + } } impl Shader { - pub fn new(stage: ShaderStage, source: ShaderSource) -> Shader { - Shader { source, stage } - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn from_spirv(spirv: &[u8]) -> Result { - use spirv_reflect::{types::ReflectShaderStageFlags, ShaderModule}; - - let module = ShaderModule::load_u8_data(spirv) - .map_err(|msg| ShaderError::Compilation(msg.to_string()))?; - let stage = match module.get_shader_stage() { - ReflectShaderStageFlags::VERTEX => ShaderStage::VERTEX, - ReflectShaderStageFlags::FRAGMENT => ShaderStage::FRAGMENT, - other => panic!("cannot load {:?} shader", other), + pub fn reflect(&self) -> Result { + let module = match &self { + // TODO: process macros here + Shader::Wgsl(source) => naga::front::wgsl::parse_str(&source)?, + Shader::SpirV(source) => naga::front::spv::parse_u8_slice( + &source, + &naga::front::spv::Options { + adjust_coordinate_space: false, + ..naga::front::spv::Options::default() + }, + )?, }; + let module_info = naga::valid::Validator::new( + naga::valid::ValidationFlags::default(), + naga::valid::Capabilities::default(), + ) + .validate(&module)?; - Ok(Shader { - source: ShaderSource::spirv_from_bytes(spirv), - stage, + Ok(ShaderReflection { + module, + module_info, }) } - pub fn from_glsl(stage: ShaderStage, glsl: &str) -> Shader { - Shader { - source: ShaderSource::Glsl(glsl.to_string()), - stage, - } + pub fn from_wgsl(source: impl Into>) -> Shader { + Shader::Wgsl(source.into()) } - #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv(&self, macros: Option<&[String]>) -> Result, ShaderError> { - match self.source { - ShaderSource::Spirv(ref bytes) => Ok(bytes.clone()), - ShaderSource::Glsl(ref source) => glsl_to_spirv(&source, self.stage, macros), - } + pub fn from_spirv(source: Vec) -> Shader { + Shader::SpirV(source) } - - #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv_shader(&self, macros: Option<&[String]>) -> Result { - Ok(Shader { - source: ShaderSource::Spirv(self.get_spirv(macros)?), - stage: self.stage, - }) - } -} - -/// All stages in a shader program -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct ShaderStages { - pub vertex: ShaderId, - pub fragment: Option, -} - -/// All stages in a compute shader program -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct ComputeShaderStages { - pub compute: ShaderId, } #[derive(Default)] @@ -246,12 +102,8 @@ impl AssetLoader for ShaderLoader { let ext = load_context.path().extension().unwrap().to_str().unwrap(); let shader = match ext { - "vert" => Shader::from_glsl(ShaderStage::VERTEX, std::str::from_utf8(bytes)?), - "frag" => Shader::from_glsl(ShaderStage::FRAGMENT, std::str::from_utf8(bytes)?), - #[cfg(not(target_arch = "wasm32"))] - "spv" => Shader::from_spirv(bytes)?, - #[cfg(target_arch = "wasm32")] - "spv" => panic!("cannot load .spv file on wasm"), + "spv" => Shader::from_spirv(Vec::from(bytes)), + "wgsl" => Shader::from_wgsl(String::from_utf8(Vec::from(bytes))?), _ => panic!("unhandled extension: {}", ext), }; @@ -261,6 +113,24 @@ impl AssetLoader for ShaderLoader { } fn extensions(&self) -> &[&str] { - &["vert", "frag", "spv"] + &["spv", "wgsl"] + } +} + +impl<'a> From<&'a Shader> for ShaderModuleDescriptor<'a> { + fn from(shader: &'a Shader) -> Self { + ShaderModuleDescriptor { + flags: ShaderFlags::default(), + label: None, + source: match shader { + Shader::Wgsl(source) => ShaderSource::Wgsl(source.clone()), + Shader::SpirV(_) => { + // TODO: we can probably just transmute the u8 array to u32? + let x = shader.reflect().unwrap(); + let spirv = x.get_spirv().unwrap(); + ShaderSource::SpirV(Cow::Owned(spirv)) + } + }, + } } } diff --git a/pipelined/bevy_sprite2/src/render/mod.rs b/pipelined/bevy_sprite2/src/render/mod.rs index 184bbe134e..3e1de274f7 100644 --- a/pipelined/bevy_sprite2/src/render/mod.rs +++ b/pipelined/bevy_sprite2/src/render/mod.rs @@ -2,22 +2,10 @@ use crate::Sprite; use bevy_asset::{Assets, Handle}; use bevy_ecs::{prelude::*, system::SystemState}; use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles}; -use bevy_render2::{ - core_pipeline::Transparent2dPhase, - mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues}, - render_asset::RenderAssets, - render_graph::{Node, NodeRunError, RenderGraphContext}, - render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass}, - render_resource::*, - renderer::{RenderContext, RenderDevice}, - shader::Shader, - texture::{BevyDefault, Image}, - view::{ViewMeta, ViewUniform, ViewUniformOffset}, -}; +use bevy_render2::{core_pipeline::Transparent2dPhase, mesh::{shape::Quad, Indices, Mesh, VertexAttributeValues}, render_asset::RenderAssets, render_graph::{Node, NodeRunError, RenderGraphContext}, render_phase::{Draw, DrawFunctions, Drawable, RenderPhase, TrackedRenderPass}, render_resource::*, renderer::{RenderContext, RenderDevice}, shader::Shader, texture::{BevyDefault, Image}, view::{ViewMeta, ViewUniform, ViewUniformOffset}}; use bevy_transform::components::GlobalTransform; use bevy_utils::slab::{FrameSlabMap, FrameSlabMapKey}; use bytemuck::{Pod, Zeroable}; -use std::borrow::Cow; pub struct SpriteShaders { pipeline: RenderPipeline, @@ -29,25 +17,8 @@ pub struct SpriteShaders { impl FromWorld for SpriteShaders { fn from_world(world: &mut World) -> Self { let render_device = world.get_resource::().unwrap(); - let vertex_shader = Shader::from_glsl(ShaderStage::VERTEX, include_str!("sprite.vert")) - .get_spirv_shader(None) - .unwrap(); - let fragment_shader = Shader::from_glsl(ShaderStage::FRAGMENT, include_str!("sprite.frag")) - .get_spirv_shader(None) - .unwrap(); - let vertex_spirv = vertex_shader.get_spirv(None).unwrap(); - let fragment_spirv = fragment_shader.get_spirv(None).unwrap(); - - let vertex = render_device.create_shader_module(&ShaderModuleDescriptor { - flags: ShaderFlags::default(), - label: None, - source: ShaderSource::SpirV(Cow::Borrowed(&vertex_spirv)), - }); - let fragment = render_device.create_shader_module(&ShaderModuleDescriptor { - flags: ShaderFlags::default(), - label: None, - source: ShaderSource::SpirV(Cow::Borrowed(&fragment_spirv)), - }); + let shader = Shader::from_wgsl(include_str!("sprite.wgsl")); + let shader_module = render_device.create_shader_module(&shader); let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { entries: &[BindGroupLayoutEntry { @@ -114,12 +85,12 @@ impl FromWorld for SpriteShaders { }, ], }], - module: &vertex, - entry_point: "main", + module: &shader_module, + entry_point: "vertex", }, fragment: Some(FragmentState { - module: &fragment, - entry_point: "main", + module: &shader_module, + entry_point: "fragment", targets: &[ColorTargetState { format: TextureFormat::bevy_default(), blend: Some(BlendState { diff --git a/pipelined/bevy_sprite2/src/render/sprite.frag b/pipelined/bevy_sprite2/src/render/sprite.frag deleted file mode 100644 index 1089fa5699..0000000000 --- a/pipelined/bevy_sprite2/src/render/sprite.frag +++ /dev/null @@ -1,11 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Uv; -layout(location = 0) out vec4 o_Target; - -layout(set = 1, binding = 0) uniform texture2D sprite_texture; -layout(set = 1, binding = 1) uniform sampler sprite_sampler; - -void main() { - o_Target = texture(sampler2D(sprite_texture, sprite_sampler), v_Uv); -} diff --git a/pipelined/bevy_sprite2/src/render/sprite.vert b/pipelined/bevy_sprite2/src/render/sprite.vert deleted file mode 100644 index 6d860b2ae8..0000000000 --- a/pipelined/bevy_sprite2/src/render/sprite.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec2 Vertex_Uv; - -layout(location = 0) out vec2 v_Uv; - -layout(set = 0, binding = 0) uniform View { - mat4 ViewProj; - vec3 ViewWorldPosition; -}; - -void main() { - v_Uv = Vertex_Uv; - gl_Position = ViewProj * vec4(Vertex_Position, 1.0); -} diff --git a/pipelined/bevy_sprite2/src/render/sprite.wgsl b/pipelined/bevy_sprite2/src/render/sprite.wgsl new file mode 100644 index 0000000000..0f662d5cc0 --- /dev/null +++ b/pipelined/bevy_sprite2/src/render/sprite.wgsl @@ -0,0 +1,34 @@ +// TODO: try merging this block with the binding? +[[block]] +struct View { + view_proj: mat4x4; + world_position: vec3; +}; +[[group(0), binding(0)]] +var view: View; + +struct VertexOutput { + [[location(0)]] uv: vec2; + [[builtin(position)]] position: vec4; +}; + +[[stage(vertex)]] +fn vertex( + [[location(0)]] vertex_position: vec3, + [[location(1)]] vertex_uv: vec2 +) -> VertexOutput { + var out: VertexOutput; + out.uv = vertex_uv; + out.position = view.view_proj * vec4(vertex_position, 1.0); + return out; +} + +[[group(1), binding(0)]] +var sprite_texture: texture_2d; +[[group(1), binding(1)]] +var sprite_sampler: sampler; + +[[stage(fragment)]] +fn fragment(in: VertexOutput) -> [[location(0)]] vec4 { + return textureSample(sprite_texture, sprite_sampler, in.uv); +} \ No newline at end of file