mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
Begin WGSL port (sprites work, pbr lights are broken)
This commit is contained in:
parent
829f723bf3
commit
61c8475069
13 changed files with 620 additions and 776 deletions
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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::<RenderDevice>().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)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
470
pipelined/bevy_pbr2/src/render/pbr.wgsl
Normal file
470
pipelined/bevy_pbr2/src/render/pbr.wgsl
Normal file
|
@ -0,0 +1,470 @@
|
|||
// TODO: try merging this block with the binding?
|
||||
[[block]]
|
||||
struct View {
|
||||
view_proj: mat4x4<f32>;
|
||||
world_position: vec3<f32>;
|
||||
};
|
||||
[[group(0), binding(0)]]
|
||||
var view: View;
|
||||
|
||||
|
||||
[[block]]
|
||||
struct Mesh {
|
||||
transform: mat4x4<f32>;
|
||||
};
|
||||
[[group(1), binding(0)]]
|
||||
var mesh: Mesh;
|
||||
|
||||
struct Vertex {
|
||||
[[location(0)]] position: vec3<f32>;
|
||||
[[location(1)]] normal: vec3<f32>;
|
||||
[[location(2)]] uv: vec2<f32>;
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
[[builtin(position)]] world_position: vec4<f32>;
|
||||
[[location(0)]] world_normal: vec3<f32>;
|
||||
[[location(1)]] uv: vec2<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
let world_position = mesh.transform * vec4<f32>(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<f32>(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<f32>;
|
||||
emissive: vec4<f32>;
|
||||
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<f32>;
|
||||
range: f32;
|
||||
radius: f32;
|
||||
position: vec3<f32>;
|
||||
view_projection: mat4x4<f32>;
|
||||
};
|
||||
|
||||
[[block]]
|
||||
struct Lights {
|
||||
ambient_color: vec4<f32>;
|
||||
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<OmniLight, 10>;
|
||||
};
|
||||
|
||||
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<f32>;
|
||||
[[group(2), binding(2)]]
|
||||
var base_color_sampler: sampler;
|
||||
[[group(2), binding(3)]]
|
||||
var emissive_texture: texture_2d<f32>;
|
||||
[[group(2), binding(4)]]
|
||||
var emissive_sampler: sampler;
|
||||
[[group(2), binding(5)]]
|
||||
var metallic_roughness_texture: texture_2d<f32>;
|
||||
[[group(2), binding(6)]]
|
||||
var metallic_roughness_sampler: sampler;
|
||||
[[group(2), binding(7)]]
|
||||
var occlusion_texture: texture_2d<f32>;
|
||||
[[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>) -> 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<f32>, f90: f32, VoH: f32) -> vec3<f32> {
|
||||
// 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<f32>, LoH: f32) -> vec3<f32> {
|
||||
// f_90 suitable for ambient occlusion
|
||||
// see https://google.github.io/filament/Filament.html#lighting/occlusion
|
||||
let f90 = saturate(dot(f0, vec3<f32>(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<f32>, roughness: f32, h: vec3<f32>, NoV: f32, NoL: f32,
|
||||
NoH: f32, LoH: f32, specularIntensity: f32) -> vec3<f32> {
|
||||
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<f32>, perceptual_roughness: f32, NoV: f32) -> vec3<f32> {
|
||||
let c0 = vec4<f32>(-1.0, -0.0275, -0.572, 0.022);
|
||||
let c1 = vec4<f32>(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<f32>(-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<f32>) -> vec3<f32> {
|
||||
return color / (1.0 + color);
|
||||
}
|
||||
|
||||
fn reinhard_extended(color: vec3<f32>, max_white: f32) -> vec3<f32> {
|
||||
let numerator = color * (1.0f + (color / vec3<f32>(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>) -> f32 {
|
||||
return dot(v, vec3<f32>(0.2126, 0.7152, 0.0722));
|
||||
}
|
||||
|
||||
fn change_luminance(c_in: vec3<f32>, l_out: f32) -> vec3<f32> {
|
||||
let l_in = luminance(c_in);
|
||||
return c_in * (l_out / l_in);
|
||||
}
|
||||
|
||||
fn reinhard_luminance(color: vec3<f32>) -> vec3<f32> {
|
||||
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<f32>, max_white_l: f32) -> vec3<f32> {
|
||||
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<f32>, light: OmniLight, roughness: f32, NdotV: f32, N: vec3<f32>, V: vec3<f32>,
|
||||
R: vec3<f32>, F0: vec3<f32>, diffuseColor: vec3<f32>
|
||||
) -> vec3<f32> {
|
||||
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<f32> = closestPoint * LspecLengthInverse; // normalize() equivalent?
|
||||
var H: vec3<f32> = 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>) -> 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<f32>(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<f32>(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<f32>;
|
||||
[[location(0)]] world_normal: vec3<f32>;
|
||||
[[location(1)]] uv: vec2<f32>;
|
||||
};
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment(in: FragmentInput) -> [[location(0)]] vec4<f32> {
|
||||
var output_color: vec4<f32> = 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<f32> = material.emissive;
|
||||
if ((material.flags & FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) {
|
||||
emissive = vec4<f32>(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<f32> = 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<f32>;
|
||||
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<f32>(-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<f32> = vec3<f32>(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<f32>(
|
||||
light_accum +
|
||||
(diffuse_ambient + specular_ambient) * lights.ambient_color.rgb * occlusion +
|
||||
emissive.rgb * output_color.a,
|
||||
output_color.a);
|
||||
|
||||
// tone_mapping
|
||||
output_color = vec4<f32>(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;
|
||||
}
|
|
@ -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"]
|
||||
|
|
|
@ -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::ShaderModuleDescriptor<'a>>) -> wgpu::ShaderModule {
|
||||
self.device.create_shader_module(&desc.into())
|
||||
}
|
||||
|
||||
/// Check for resource cleanups and mapping callbacks.
|
||||
|
|
|
@ -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<Vec<u32>, 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<shaderc::ShaderKind> 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<Vec<u32>, 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<u32> {
|
||||
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<u32>),
|
||||
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<u8>),
|
||||
// TODO: consider the following
|
||||
// PrecompiledSpirVMacros(HashMap<HashSet<String>, Vec<u32>>)
|
||||
// NagaModule(Module) ... Module impls Serialize/Deserialize
|
||||
}
|
||||
|
||||
pub struct ShaderReflection {
|
||||
pub module: Module,
|
||||
pub module_info: ModuleInfo,
|
||||
}
|
||||
|
||||
impl ShaderReflection {
|
||||
pub fn get_spirv(&self) -> Result<Vec<u32>, 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<Shader, ShaderError> {
|
||||
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<ShaderReflection, ShaderReflectError> {
|
||||
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<Cow<'static, str>>) -> Shader {
|
||||
Shader::Wgsl(source.into())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn get_spirv(&self, macros: Option<&[String]>) -> Result<Vec<u32>, 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<u8>) -> Shader {
|
||||
Shader::SpirV(source)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn get_spirv_shader(&self, macros: Option<&[String]>) -> Result<Shader, ShaderError> {
|
||||
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<ShaderId>,
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::<RenderDevice>().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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
34
pipelined/bevy_sprite2/src/render/sprite.wgsl
Normal file
34
pipelined/bevy_sprite2/src/render/sprite.wgsl
Normal file
|
@ -0,0 +1,34 @@
|
|||
// TODO: try merging this block with the binding?
|
||||
[[block]]
|
||||
struct View {
|
||||
view_proj: mat4x4<f32>;
|
||||
world_position: vec3<f32>;
|
||||
};
|
||||
[[group(0), binding(0)]]
|
||||
var view: View;
|
||||
|
||||
struct VertexOutput {
|
||||
[[location(0)]] uv: vec2<f32>;
|
||||
[[builtin(position)]] position: vec4<f32>;
|
||||
};
|
||||
|
||||
[[stage(vertex)]]
|
||||
fn vertex(
|
||||
[[location(0)]] vertex_position: vec3<f32>,
|
||||
[[location(1)]] vertex_uv: vec2<f32>
|
||||
) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
out.uv = vertex_uv;
|
||||
out.position = view.view_proj * vec4<f32>(vertex_position, 1.0);
|
||||
return out;
|
||||
}
|
||||
|
||||
[[group(1), binding(0)]]
|
||||
var sprite_texture: texture_2d<f32>;
|
||||
[[group(1), binding(1)]]
|
||||
var sprite_sampler: sampler;
|
||||
|
||||
[[stage(fragment)]]
|
||||
fn fragment(in: VertexOutput) -> [[location(0)]] vec4<f32> {
|
||||
return textureSample(sprite_texture, sprite_sampler, in.uv);
|
||||
}
|
Loading…
Reference in a new issue