From 6346ba2809eda85f5cff0ff94b32b55765f08754 Mon Sep 17 00:00:00 2001 From: Adrien Bennadji Date: Thu, 9 May 2024 10:43:44 +0200 Subject: [PATCH] refactor --- crates/libs/model/src/lib.rs | 8 + crates/libs/model/src/material.rs | 29 ++ crates/libs/model/src/mesh.rs | 8 + crates/viewer/shaders/gbuffer.vert | 18 +- crates/viewer/shaders/gbuffer.vert.spv | Bin 4820 -> 4960 bytes crates/viewer/shaders/libs/camera.glsl | 8 + crates/viewer/shaders/libs/material.glsl | 61 ++++ crates/viewer/shaders/model.frag | 177 ++++------- crates/viewer/shaders/model.frag.spv | Bin 44684 -> 45960 bytes crates/viewer/shaders/model.vert | 20 +- crates/viewer/shaders/model.vert.spv | Bin 6052 -> 6188 bytes crates/viewer/shaders/skybox.frag | 9 - crates/viewer/shaders/skybox.frag.spv | Bin 1296 -> 780 bytes crates/viewer/shaders/skybox.vert | 18 +- crates/viewer/shaders/skybox.vert.spv | Bin 2116 -> 2252 bytes crates/viewer/shaders/ssao.frag | 20 +- crates/viewer/shaders/ssao.frag.spv | Bin 6400 -> 6540 bytes crates/viewer/shaders/ssao.vert | 16 +- crates/viewer/shaders/ssao.vert.spv | Bin 1852 -> 1984 bytes crates/viewer/src/renderer/mod.rs | 68 ++++- crates/viewer/src/renderer/model/lightpass.rs | 283 +++++++++++------- crates/viewer/src/renderer/model/mod.rs | 48 ++- crates/viewer/src/renderer/model/uniform.rs | 41 ++- 23 files changed, 514 insertions(+), 318 deletions(-) create mode 100644 crates/viewer/shaders/libs/camera.glsl create mode 100644 crates/viewer/shaders/libs/material.glsl diff --git a/crates/libs/model/src/lib.rs b/crates/libs/model/src/lib.rs index 10dfc30..5f94494 100644 --- a/crates/libs/model/src/lib.rs +++ b/crates/libs/model/src/lib.rs @@ -35,6 +35,7 @@ pub struct Model { animations: Option, skins: Vec, textures: Textures, + materials: Vec, lights: Vec, } @@ -99,6 +100,8 @@ impl Model { &images, ); + let materials = create_materials_from_gltf(&document); + let lights = create_lights_from_gltf(&document); let model = Model { @@ -109,6 +112,7 @@ impl Model { animations, skins, textures, + materials, lights, }; @@ -220,6 +224,10 @@ impl Model { &self.textures.textures } + pub fn materials(&self) -> &[Material] { + &self.materials + } + pub fn lights(&self) -> &[Light] { &self.lights } diff --git a/crates/libs/model/src/material.rs b/crates/libs/model/src/material.rs index 360baf5..97f35fe 100644 --- a/crates/libs/model/src/material.rs +++ b/crates/libs/model/src/material.rs @@ -1,6 +1,7 @@ use gltf::{ material::{AlphaMode, Material as GltfMaterial, NormalTexture, OcclusionTexture}, texture::Info, + Document, }; const ALPHA_MODE_OPAQUE: u32 = 0; @@ -24,6 +25,30 @@ pub struct Material { clearcoat: Option, } +impl Default for Material { + fn default() -> Self { + Self { + color: [1.0, 1.0, 1.0, 1.0], + emissive: [0.0, 0.0, 0.0], + occlusion: 0.0, + color_texture: None, + emissive_texture: None, + normals_texture: None, + occlusion_texture: None, + workflow: Workflow::MetallicRoughness(MetallicRoughnessWorkflow { + metallic: 1.0, + roughness: 1.0, + metallic_roughness_texture: None, + }), + alpha_mode: ALPHA_MODE_OPAQUE, + alpha_cutoff: 0.5, + double_sided: false, + is_unlit: false, + clearcoat: None, + } + } +} + #[derive(Clone, Copy, Debug)] pub struct TextureInfo { index: usize, @@ -213,6 +238,10 @@ impl TextureInfo { } } +pub(crate) fn create_materials_from_gltf(document: &Document) -> Vec { + document.materials().map(Material::from).collect() +} + impl<'a> From> for Material { fn from(material: GltfMaterial) -> Material { let color = match material.pbr_specular_glossiness() { diff --git a/crates/libs/model/src/mesh.rs b/crates/libs/model/src/mesh.rs index 9fbd6b9..8f57274 100644 --- a/crates/libs/model/src/mesh.rs +++ b/crates/libs/model/src/mesh.rs @@ -42,6 +42,7 @@ pub struct Primitive { vertices: VertexBuffer, indices: Option, material: Material, + material_index: Option, aabb: Aabb, } @@ -62,6 +63,10 @@ impl Primitive { self.material } + pub fn material_index(&self) -> Option { + self.material_index + } + pub fn aabb(&self) -> Aabb { self.aabb } @@ -78,6 +83,7 @@ struct PrimitiveData { indices: Option, vertices: VertexBufferPart, material: Material, + material_index: Option, aabb: Aabb, } @@ -172,6 +178,7 @@ pub fn create_meshes_from_gltf( indices, vertices: (offset, accessor.count()), material, + material_index: primitive.material().index(), aabb, }); } @@ -227,6 +234,7 @@ pub fn create_meshes_from_gltf( vertices: vertex_buffer, indices: index_buffer, material: buffers.material, + material_index: buffers.material_index, aabb: buffers.aabb, } }) diff --git a/crates/viewer/shaders/gbuffer.vert b/crates/viewer/shaders/gbuffer.vert index 1142fd6..6885396 100644 --- a/crates/viewer/shaders/gbuffer.vert +++ b/crates/viewer/shaders/gbuffer.vert @@ -1,4 +1,7 @@ #version 450 +#extension GL_GOOGLE_include_directive : require + +#include "libs/camera.glsl" layout(location = 0) in vec3 vPositions; layout(location = 1) in vec3 vNormals; @@ -9,14 +12,9 @@ layout(location = 5) in vec4 vWeights; layout(location = 6) in uvec4 vJoints; layout(location = 7) in vec4 vColors; -layout(binding = 0, set = 0) uniform CameraUBO { - mat4 view; - mat4 proj; - mat4 invertedProj; - vec4 eye; - float zNear; - float zFar; -} cameraUBO; +layout(binding = 0, set = 0) uniform Frame { + Camera camera; +}; layout(binding = 1, set = 0) uniform TransformUBO { mat4 matrix; @@ -40,10 +38,10 @@ void main() { + vWeights.w * skin.jointMatrices[vJoints.w]; } - oViewSpaceNormal = normalize((cameraUBO.view * world * vec4(vNormals, 0.0)).xyz); + oViewSpaceNormal = normalize((camera.view * world * vec4(vNormals, 0.0)).xyz); oTexcoords0 = vTexcoords0; oTexcoords1 = vTexcoords1; oAlpha = vColors.a; - gl_Position = cameraUBO.proj * cameraUBO.view * world * vec4(vPositions, 1.0); + gl_Position = camera.proj * camera.view * world * vec4(vPositions, 1.0); } diff --git a/crates/viewer/shaders/gbuffer.vert.spv b/crates/viewer/shaders/gbuffer.vert.spv index 2ed462efc8ca82860f72ee721fd65edcc4a1ffcb..378f685cccfb7878708a4213989d4729193cff36 100644 GIT binary patch delta 1756 zcmYk5OH5Ny5Qa~mAP*lQkr)g?iEfOE>`V-r2C*q=y^k0FaI#VQ&s@%o)Fs2j$SmOnx=2UagJy5g=99BR+f-AWzN$F(=OU9>IAO35exkZq)*CZEUfO@X5u;y z$Hl}|AS2nk8LRnw>U_r3Dd-M8JtM03%axT#`xcvRzVao~evq=s&4WOz=VPeFw%shB z<#CnYk+yy!43si1(WT$bmv+%1M1JQHAQm5Y_52Q=yBtbypW$h#(PGWq1uW$$CZ`J-(ATec)gKuLGWb+1y@iy>dDyU0) zMc}?i1oU+ONPtJr+rW=0!qlHJ1Uq)_lS7R4c`djpd?DZJxFV}tkxk5A$4l9qg}0hl zvg@yGRX#fSUQ?>w*=sp#g1w5jvf)^kdnX%?ky`G(&oN?qWgld79zAg$qP?<@vR4H( XOl`ee#wUT-O=pY=AK_np>ag$+E>zhP delta 1624 zcmY+D+e=hY6vp?N;~Y&h1)(7f(ZXILNP-B$#HKJ`QmKgOAqJBYMo-M>WY?K)R%YdR zn(o$UH?zli?xlzRg5G+l|09IH-|_6pj(o1YzV+?3*IsMS?;Srn8{OFUT4Nm3VCu}5 zV&kzr*Nidugc)I0cqq&XMd6uH7FGmTe=*r_jw$~9)f_jIa+0aBkxc5u!2uI|cM3gm zb5Z1Ql{6-UO2KL@v8AM)eCN_<2L0^l#Hc^6SgoSF+*YR)^t-Ly zXxueTax$GhG@6}AjSr6~iyL-;{oJE0o3mMT6S$SiqWi~5^+`%UXi%Sup$*#vKI?a^ zxuH)>+5{(x(~k8%!@#%sW&_7dY@kT8%>^CuG)8koC6t08ujLe%AX`IZ!-&>ofMc)a zSi{u_RL)*|Qdw(G=HslD%2)4XEZ?qWP82dOx-&tvXqb!h~slnxP<&^Sx>O~ zyINBUJbHW@0Y|?3vg}nn9||Do45Zz_b72EMah?is5RrqBYMP{Z_@F z$fE5M+O(oCblKDSVGsd{pURR10g3VXt-$S+ze)DFKw{!_q8$0eIb=yNrqDyaWv$rD zVdyE0-yHk*YQ(Y1LVc%~kDF!@Qvz*o61pX>lf5LsTmQ1w^}@V16Tc#$fk(rLt_zH3 zNE!;#9Yap0$`vAxLWc5@@J2*3$3fLh)i(ta6a?a%g`)JtZV42CWB9|*h$f;=328+)WRZ-E$Z1ix1Wwad;6%>A^0p4$fk zJbK;;K6*O-LQu!^;Y`^;OJ7C-D|)pi`fn_+#lhP#ENIO>c$?>q*6T<0Rz5m-G<0K8 zDAQj%t9KD8Q{~=A9Idpv4-vP1R3F8$A3gCU!H(*a))at-i!IB37I@rE3H_rz5C~T_ HwSB^0u2H=} diff --git a/crates/viewer/shaders/libs/camera.glsl b/crates/viewer/shaders/libs/camera.glsl new file mode 100644 index 0000000..c90774b --- /dev/null +++ b/crates/viewer/shaders/libs/camera.glsl @@ -0,0 +1,8 @@ +struct Camera { + mat4 view; + mat4 proj; + mat4 invertedProj; + vec4 eye; + float zNear; + float zFar; +}; diff --git a/crates/viewer/shaders/libs/material.glsl b/crates/viewer/shaders/libs/material.glsl new file mode 100644 index 0000000..241bfe4 --- /dev/null +++ b/crates/viewer/shaders/libs/material.glsl @@ -0,0 +1,61 @@ +const uint NO_TEXTURE_ID = 3; + +const uint ALPHA_MODE_MASK = 1; +const uint ALPHA_MODE_BLEND = 2; +const float ALPHA_CUTOFF_BIAS = 0.0000001; + +const uint METALLIC_ROUGHNESS_WORKFLOW = 0; + +struct Material { + vec4 color; + vec3 emissiveFactor; + // - roughness for metallic/roughness workflows + // - glossiness for specular/glossiness workflows + float roughnessGlossiness; + // Contains the metallic (or specular) factor. + // - metallic: r (for metallic/roughness workflows) + // - specular: rgb (for specular/glossiness workflows) + vec3 metallicSpecular; + float occlusion; + float alphaCutoff; + float clearcoatFactor; + float clearcoatRoughness; + // Contains the texture channels. Each channel taking 2 bits + // [0-1] Color texture channel + // [2-3] metallic/roughness or specular/glossiness texture channel + // [4-5] emissive texture channel + // [6-7] normals texture channel + // [8-9] Occlusion texture channel + // [10-11] Clearcoat factor texture channel + // [12-13] Clearcoat roughness texture channel + // [14-15] Clearcoat normal texture channel + // [16-31] Reserved + uint texturesChannels; + uint alphaMode; + bool isUnlit; + uint workflow; +}; + +struct TextureChannels { + uint color; + uint material; + uint emissive; + uint normal; + uint occlusion; + uint clearcoatFactor; + uint clearcoatRoughness; + uint clearcoatNormal; +}; + +TextureChannels getTextureChannels(Material m) { + return TextureChannels( + (m.texturesChannels >> 30) & 3, + (m.texturesChannels >> 28) & 3, + (m.texturesChannels >> 26) & 3, + (m.texturesChannels >> 24) & 3, + (m.texturesChannels >> 22) & 3, + (m.texturesChannels >> 20) & 3, + (m.texturesChannels >> 18) & 3, + (m.texturesChannels >> 16) & 3 + ); +} diff --git a/crates/viewer/shaders/model.frag b/crates/viewer/shaders/model.frag index ffbb99e..2bf331e 100644 --- a/crates/viewer/shaders/model.frag +++ b/crates/viewer/shaders/model.frag @@ -1,4 +1,8 @@ #version 450 +#extension GL_GOOGLE_include_directive : require + +#include "libs/camera.glsl" +#include "libs/material.glsl" // -- Constants -- layout(constant_id = 0) const uint MAX_LIGHT_COUNT = 1; @@ -29,32 +33,11 @@ const vec3 DIELECTRIC_SPECULAR = vec3(0.04); const vec3 BLACK = vec3(0.0); const float PI = 3.14159; -const uint NO_TEXTURE_ID = 3; - -const uint ALPHA_MODE_MASK = 1; -const uint ALPHA_MODE_BLEND = 2; -const float ALPHA_CUTOFF_BIAS = 0.0000001; - const uint DIRECTIONAL_LIGHT_TYPE = 0; const uint POINT_LIGHT_TYPE = 1; const uint SPOT_LIGHT_TYPE = 2; -const uint UNLIT_FLAG_UNLIT = 1; - -const uint METALLIC_ROUGHNESS_WORKFLOW = 0; - // -- Structures -- -struct TextureChannels { - uint color; - uint material; - uint emissive; - uint normal; - uint occlusion; - uint clearcoatFactor; - uint clearcoatRoughness; - uint clearcoatNormal; -}; - struct Light { vec4 position; vec4 direction; @@ -78,6 +61,11 @@ struct PbrInfo { vec3 clearcoatNormal; }; +struct Config { + uint outputMode; + float emissiveIntensity; +}; + // -- Inputs -- layout(location = 0) in vec3 oNormals; layout(location = 1) in vec2 oTexcoords0; @@ -86,87 +74,43 @@ layout(location = 3) in vec3 oPositions; layout(location = 4) in vec4 oColors; layout(location = 5) in mat3 oTBN; -// -- Push constants -layout(push_constant) uniform MaterialUniform { - vec4 color; - vec3 emissiveFactor; - // - roughness for metallic/roughness workflows - // - glossiness for specular/glossiness workflows - float roughnessGlossiness; - // Contains the metallic (or specular) factor. - // - metallic: r (for metallic/roughness workflows) - // - specular: rgb (for specular/glossiness workflows) - vec3 metallicSpecular; - float occlusion; - float alphaCutoff; - float clearcoatFactor; - float clearcoatRoughness; - // Contains the texture channels. Each channel taking 2 bits - // [0-1] Color texture channel - // [2-3] metallic/roughness or specular/glossiness texture channel - // [4-5] emissive texture channel - // [6-7] normals texture channel - // [8-9] Occlusion texture channel - // [10-11] Clearcoat factor texture channel - // [12-13] Clearcoat roughness texture channel - // [14-15] Clearcoat normal texture channel - // [16-31] Reserved - uint texturesChannels; - // Contains alpha mode, unlit flag and workflow flag - // [0-7] Alpha mode - // [8-15] Unlit flag - // [16-23] Workflow (metallic/roughness or specular/glossiness) - // [24-31] Reserved - uint alphaModeUnlitFlagAndWorkflow; - uint lightCount; - uint outputMode; - float emissiveIntensity; -} material; - // -- Descriptors -- -layout(binding = 0, set = 0) uniform Camera { - mat4 view; - mat4 proj; - mat4 invertedProj; - vec4 eye; - float zNear; - float zFar; -} cameraUBO; -layout(binding = 1, set = 0) uniform Lights { +layout(binding = 0, set = 0) uniform CameraUBO { + Camera camera; +}; + +layout(binding = 1, set = 0) uniform ConfigUBO { + Config config; +}; + +layout(binding = 2, set = 0) uniform LightsUBO { + uint count; Light lights[MAX_LIGHT_COUNT]; } lights; -layout(binding = 4, set = 1) uniform samplerCube irradianceMapSampler; -layout(binding = 5, set = 1) uniform samplerCube preFilteredSampler; -layout(binding = 6, set = 1) uniform sampler2D brdfLookupSampler; -layout(binding = 7, set = 2) uniform sampler2D colorSampler; -layout(binding = 8, set = 2) uniform sampler2D normalsSampler; + +layout(binding = 5, set = 1) uniform samplerCube irradianceMapSampler; +layout(binding = 6, set = 1) uniform samplerCube preFilteredSampler; +layout(binding = 7, set = 1) uniform sampler2D brdfLookupSampler; +layout(binding = 8, set = 2) uniform sampler2D colorSampler; +layout(binding = 9, set = 2) uniform sampler2D normalsSampler; // This sampler contains either: // - metallic (b) + glossiness (g) for metallic/roughness workflow // - specular (rgb) + glossiness (a) for specular/glossiness workflow -layout(binding = 9, set = 2) uniform sampler2D materialSampler; -layout(binding = 10, set = 2) uniform sampler2D occlusionSampler; -layout(binding = 11, set = 2) uniform sampler2D emissiveSampler; -layout(binding = 12, set = 2) uniform sampler2D clearcoatFactorSampler; -layout(binding = 13, set = 2) uniform sampler2D clearcoatRoughnessSampler; -layout(binding = 14, set = 2) uniform sampler2D clearcoatNormalSampler; -layout(binding = 15, set = 3) uniform sampler2D aoMapSampler; +layout(binding = 10, set = 2) uniform sampler2D materialSampler; +layout(binding = 11, set = 2) uniform sampler2D occlusionSampler; +layout(binding = 12, set = 2) uniform sampler2D emissiveSampler; +layout(binding = 13, set = 2) uniform sampler2D clearcoatFactorSampler; +layout(binding = 14, set = 2) uniform sampler2D clearcoatRoughnessSampler; +layout(binding = 15, set = 2) uniform sampler2D clearcoatNormalSampler; +layout(binding = 16, set = 3) uniform sampler2D aoMapSampler; + +layout(binding = 17, set = 4) uniform MaterialUBO { + Material material; +}; // Output layout(location = 0) out vec4 outColor; -TextureChannels getTextureChannels() { - return TextureChannels( - (material.texturesChannels >> 30) & 3, - (material.texturesChannels >> 28) & 3, - (material.texturesChannels >> 26) & 3, - (material.texturesChannels >> 24) & 3, - (material.texturesChannels >> 22) & 3, - (material.texturesChannels >> 20) & 3, - (material.texturesChannels >> 18) & 3, - (material.texturesChannels >> 16) & 3 - ); -} - vec2 getUV(uint texChannel) { if (texChannel == 0) { return oTexcoords0; @@ -238,7 +182,7 @@ vec3 getEmissiveColor(TextureChannels textureChannels) { vec2 uv = getUV(textureChannels.emissive); emissive *= texture(emissiveSampler, uv).rgb; } - return emissive * material.emissiveIntensity; + return emissive * config.emissiveIntensity; } vec3 getNormal(TextureChannels textureChannels) { @@ -273,7 +217,7 @@ vec3 occludeAmbientColor(vec3 ambientColor, TextureChannels textureChannels) { } uint getAlphaMode() { - return (material.alphaModeUnlitFlagAndWorkflow >> 24) & 255; + return material.alphaMode; } bool isMasked(vec4 baseColor) { @@ -305,15 +249,11 @@ float getAlpha(vec4 baseColor) { } bool isUnlit() { - uint unlitFlag = (material.alphaModeUnlitFlagAndWorkflow >> 16) & 255; - if (unlitFlag == UNLIT_FLAG_UNLIT) { - return true; - } - return false; + return material.isUnlit; } bool isMetallicRoughnessWorkflow() { - uint workflow = (material.alphaModeUnlitFlagAndWorkflow >> 8) & 255; + uint workflow = material.workflow; if (workflow == METALLIC_ROUGHNESS_WORKFLOW) { return true; } @@ -523,7 +463,7 @@ vec3 computeIBL(PbrInfo pbrInfo, vec3 v) { } void main() { - TextureChannels textureChannels = getTextureChannels(); + TextureChannels textureChannels = getTextureChannels(material); vec4 baseColor = getBaseColor(textureChannels); if (isMasked(baseColor)) { @@ -555,7 +495,7 @@ void main() { vec3 clearcoatNormal = getClearcoatNormal(textureChannels); vec3 n = getNormal(textureChannels); - vec3 v = normalize(cameraUBO.eye.xyz - oPositions); + vec3 v = normalize(camera.eye.xyz - oPositions); PbrInfo pbrInfo = PbrInfo( baseColor.rgb, @@ -571,7 +511,7 @@ void main() { vec3 color = vec3(0.0); - for (int i = 0; i < material.lightCount; i++) { + for (int i = 0; i < lights.count; i++) { Light light = lights.lights[i]; uint lightType = light.type; @@ -589,36 +529,37 @@ void main() { color += emissive + occludeAmbientColor(ambient, textureChannels); - if (material.outputMode == OUTPUT_MODE_FINAL) { + uint outputMode = config.outputMode; + if (outputMode == OUTPUT_MODE_FINAL) { outColor = vec4(color, alpha); - } else if (material.outputMode == OUTPUT_MODE_COLOR) { + } else if (outputMode == OUTPUT_MODE_COLOR) { outColor = vec4(baseColor.rgb, 1.0); - } else if (material.outputMode == OUTPUT_MODE_EMISSIVE) { + } else if (outputMode == OUTPUT_MODE_EMISSIVE) { outColor = vec4(emissive, 1.0); - } else if (material.outputMode == OUTPUT_MODE_METALLIC) { + } else if (outputMode == OUTPUT_MODE_METALLIC) { outColor = vec4(vec3(metallic), 1.0); - } else if (material.outputMode == OUTPUT_MODE_SPECULAR) { + } else if (outputMode == OUTPUT_MODE_SPECULAR) { outColor = vec4(specular, 1.0); - } else if (material.outputMode == OUTPUT_MODE_ROUGHNESS) { + } else if (outputMode == OUTPUT_MODE_ROUGHNESS) { outColor = vec4(vec3(roughness), 1.0); - } else if (material.outputMode == OUTPUT_MODE_OCCLUSION) { + } else if (outputMode == OUTPUT_MODE_OCCLUSION) { outColor = vec4(occludeAmbientColor(vec3(1.0), textureChannels), 1.0); - } else if (material.outputMode == OUTPUT_MODE_NORMAL) { + } else if (outputMode == OUTPUT_MODE_NORMAL) { outColor = vec4(n*0.5 + 0.5, 1.0); - } else if (material.outputMode == OUTPUT_MODE_ALPHA) { + } else if (outputMode == OUTPUT_MODE_ALPHA) { outColor = vec4(vec3(baseColor.a), 1.0); - } else if (material.outputMode == OUTPUT_MODE_UVS0) { + } else if (outputMode == OUTPUT_MODE_UVS0) { outColor = vec4(vec2(oTexcoords0), 0.0, 1.0); - } else if (material.outputMode == OUTPUT_MODE_UVS1) { + } else if (outputMode == OUTPUT_MODE_UVS1) { outColor = vec4(vec2(oTexcoords1), 0.0, 1.0); - } else if (material.outputMode == OUTPUT_MODE_SSAO) { + } else if (outputMode == OUTPUT_MODE_SSAO) { float ao = sampleAOMap(); outColor = vec4(vec3(ao), 1.0); - } else if (material.outputMode == OUTPUT_MODE_CLEARCOAT_FACTOR) { + } else if (outputMode == OUTPUT_MODE_CLEARCOAT_FACTOR) { outColor = vec4(vec3(clearcoatFactor), 1.0); - } else if (material.outputMode == OUTPUT_MODE_CLEARCOAT_ROUGHNESS) { + } else if (outputMode == OUTPUT_MODE_CLEARCOAT_ROUGHNESS) { outColor = vec4(vec3(clearcoatRoughness), 1.0); - } else if (material.outputMode == OUTPUT_MODE_CLEARCOAT_NORMAL) { + } else if (outputMode == OUTPUT_MODE_CLEARCOAT_NORMAL) { outColor = outColor = vec4(clearcoatNormal*0.5 + 0.5, 1.0); } } diff --git a/crates/viewer/shaders/model.frag.spv b/crates/viewer/shaders/model.frag.spv index cf50fd8de6d98956cb75046dc21566da0d7bb9c8..5f20b7b382fc7439a31c2fd61c75736bf5132c7e 100644 GIT binary patch literal 45960 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
b@Ar=BV`%ER z#+?ARoqF1x2)14MY`*|a-8nVRNnqQm=exwoVCQr_itiEnn5XBMw&eM7uv+*jHBU~b z!qxOkd$r_u8rb~GYx(JD>WO&<*!fM&Gr{_($LB1tdBo=vVExqf@p^v_*lU`3cx{kt zb8nspc73#Yosh?N0oXmL&1;4{wu`{-HEmu$77yp# z=V{}4{n0MIjM_2fdVe|C>%HSJU-Pnm+vw}{UZ3)Me>wghmr%UMdriEw#-0b4QM|@~ zirQ%qD_0qxdQQzh4ZoU_dA|m%kGgr=Ud{H`QQt_pq2|u}O<;3K{LjE` zb0EFi@6BM_=;yp>&)nSt)^730+&Ok_<=C%oyR`oQ*15l3iI#b|3hdZFO`ZGOZNw<= zZ+AENJq`Yi2EVVt?=QG>`#`~6i|;k~&l>#U27k1{pKS2o72N)wF8K5C=L&BBuQoW_ zc*yuW8oaZ?yBd60!R>$Pf{%i~rNLKj@Kp+K`_&3={%aI`6ns>JZ_?mf*4*<`|IaaA z_dw21_v7c$)ZM$Tzg+)~u-y*UKlh1cz+XU9e+~aTs6G7tsQyKYn)^zee!m1Z<># zBVR^S*T=EmS^MPKq8sflH1&K_`3hJqB|lU9Dw^lQ#*_iRRTx{}j7huSH^y06|I1_hCfM^zo9BW&wr_!rrOop~p0R%$Y!2l;;X7#R z`g`8U{pM$W-vv*nwtxFEo<90|j%rW8-v_H@4u1fCv+q(rMAxRjaXdHGlH-rS=2))J zkI~fgv%H^xjjf*ke+o8MzNL?YVKHn0e0T!Y6Q;;+E! z7LQyPjqf!@d-8b%td{lvHMqGqe}ks(HQpS43$~rQF}*ISCFWyb*FDd&kHghse*)|n z@@;Y<*c>NN^wpkcyG6BaJU0F8&+DFg;yndc)8;i#o^^T#+}umgqN(feHBp{_Z~pgS z?~}&SrjOTEb^G)0;Jg5SzUI!yAHa?$`TP;=`17pyB3vK!JYT&8cFgNhwEK*eoc{!G z=KM06dj7uhpTWjZ&t7;1Y;M{naphi_*na`5TRiO3F?sFRmYn|zcAVj_fjv)>)8D|h zDSw9kcer}?@;|_~Q%}3s!L}=Zrv6X3dd|Urfz?tL)cfq;XvWl@z4srmxoUG9uTrbo zpV#yyxW+qo*HSW;CDGLN$E}i!y?=Rqj9)iA*DLR3^4OLF+tzm8*W?-FGGO~Ge{QoZ zn!5hp_vE==EeG~`W&f>vCbd5LdXLqfe%}f%&!y$zYO${XHm{sZD}r+_>8m~G(n_^$ zJWceoKkvcniMJ|PO`G>$dB*uRaC4liqp9of{aK!K>Fr?8CF5w*$9ufG{dq2}2`v0G1PM|tqV3cZ8?|T30Ajw zDxG&$;v-u(@h;ZG3)GOWvD;y;p^A2KQQ@_M5}?QIF3SV9&|)`(C(y>h|F? znOgj}0^4uKxHZ`ID&JSPK~vA%YzwxXx-osOQ%lV4!1fdVK5#kz+ryiE?4Um-ee4Lf qoqFbWC-9p+D~v(cW}D2-&S3MQL?<{iPS^ghdA)Mv_ literal 44684 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 diff --git a/crates/viewer/shaders/model.vert b/crates/viewer/shaders/model.vert index 582cb2d..a77cf5b 100644 --- a/crates/viewer/shaders/model.vert +++ b/crates/viewer/shaders/model.vert @@ -1,4 +1,7 @@ #version 450 +#extension GL_GOOGLE_include_directive : require + +#include "libs/camera.glsl" layout(location = 0) in vec3 vPositions; layout(location = 1) in vec3 vNormals; @@ -9,20 +12,15 @@ layout(location = 5) in vec4 vWeights; layout(location = 6) in uvec4 vJoints; layout(location = 7) in vec4 vColors; -layout(binding = 0, set = 0) uniform CameraUBO { - mat4 view; - mat4 proj; - mat4 invertedProj; - vec4 eye; - float zNear; - float zFar; -} cameraUBO; +layout(binding = 0, set = 0) uniform Frame { + Camera camera; +}; -layout(binding = 2, set = 0) uniform TransformUBO { +layout(binding = 3, set = 0) uniform TransformUBO { mat4 matrix; } transform; -layout(binding = 3, set = 0) uniform SkinUBO { +layout(binding = 4, set = 0) uniform SkinUBO { mat4 jointMatrices[512]; } skin; @@ -53,5 +51,5 @@ void main() { oPositions = (world * vec4(vPositions, 1.0)).xyz; oTBN = mat3(tangent, bitangent, normal); oColors = vColors; - gl_Position = cameraUBO.proj * cameraUBO.view * world * vec4(vPositions, 1.0); + gl_Position = camera.proj * camera.view * world * vec4(vPositions, 1.0); } diff --git a/crates/viewer/shaders/model.vert.spv b/crates/viewer/shaders/model.vert.spv index 2dffcb6327850544afc438d718a32f05a0bd24f1..3099cb12d1f180ca706ab0a89d0b8f9dd7f4c2bc 100644 GIT binary patch delta 594 zcmYk4y-UMT6vc0nXIopVg5W4(#|pX>1W_oWaVS*(f>6>7!CFeJf>Q-&k=oH!2hl}3 zIXXB9e$=n}ZT&|C&k2b(;pEH+#Kp{6WUf35u(6d^ zasy+*gM%269=}E^whsv$Ls~Q%hlZ^gjb62_spgIv(ld))c!~%v6F8UD{apE@V{G6u zvBy^PJkl{TBLT+kjKaP}c-Op)pCxJ$8ANdp5g1?tngJ9;e1O=? z;xObMp^zgRa!)~T4`<+98{#bekaSLg^@#zGOqx4=XA^WFnb z>+(JVk0)G`C~lEYi1XyxKZfd9m%T5D8yF{)g?WM4fvzB)Y!;uCHNhYJKvU2!zs_V3 delta 458 zcmYk3yGq1R6h%*R6Ehgb8Sn>uHDaNSAjk-6F<7aEjomO#ojNk{zm|w6FZ6uW+ zVCQ3ezn#X`PY_%?CStLo8hq z3DaX{^Ip3vo1h<{cGIQ!G;W7hrMX4|x~K+FqTV3o^Y+W!;>A=@!QA1M@55;39%)W_ z!93vmQNcXo#VN0~c~}ecgbvXv8}(72^NjMBz2uqqFMG{1oovM-ZnHO}dGT65LLK(i d+B?zSi3Z(%asC9v=Lb#qGyU--*wEo#3&*)j{2#P&%+cUpM-;p z(@P{gEDeXu>kAx}HoLC&h#ixT4T$Ic$9$$qiDE5i@TUu39BPXGV_ diff --git a/crates/viewer/shaders/skybox.vert b/crates/viewer/shaders/skybox.vert index ee992cf..538afae 100644 --- a/crates/viewer/shaders/skybox.vert +++ b/crates/viewer/shaders/skybox.vert @@ -1,20 +1,18 @@ #version 450 +#extension GL_GOOGLE_include_directive : require + +#include "libs/camera.glsl" layout(location = 0) in vec3 vPositions; -layout(binding = 0) uniform CameraUBO { - mat4 view; - mat4 proj; - mat4 invertedProj; - vec4 eye; - float zNear; - float zFar; -} cameraUBO; +layout(binding = 0) uniform Frame { + Camera camera; +}; layout(location = 0) out vec3 oPositions; mat4 getViewAtOrigin() { - mat4 view = mat4(cameraUBO.view); + mat4 view = mat4(camera.view); view[3][0] = 0; view[3][1] = 0; view[3][2] = 0; @@ -26,5 +24,5 @@ void main() { mat4 view = getViewAtOrigin(); - gl_Position = cameraUBO.proj * view * vec4(vPositions, 1.0); + gl_Position = camera.proj * view * vec4(vPositions, 1.0); } diff --git a/crates/viewer/shaders/skybox.vert.spv b/crates/viewer/shaders/skybox.vert.spv index 12632857b330137e1d76ed3519d32c979e8889ef..f494608ef97fb68aada45e5daaabae225a307b79 100644 GIT binary patch literal 2252 zcmZ9N+fEcg5QZCemxBn1$U#M1K#hum2S5-xiLzV}!H{sf*(?(>W|<|k0}5BXf#ypn>X{|iI??W2i|cY7ZrvrT&$SA1!rJ1!w6(i6)rj_| zW@ewk=yAE22&Y$Zti0r~;s@xHlBbdxNlt&Q+JA_gcUN3#t6bXNE^WOpSC5X$jp(%I zm1}<<7;M$ehxdSiS^S#e&(RSz`_`yR#4sN&h z6FJQ8k+NPn^umf$hXwVMEaMnPe@9{c6W%tR0gqn%;KU0fZ-1xh%f~*Z;+=ZUoYZ*s z!K;MYEoT92BZlJIdS!2fDkkM%v#M!*11g9iN-;#r?u!t!of zG1gB(r@xSWP%H0v;U2YrA!S>NqvK1n3T==1^dIAX5bgSBl-Tq!2l>`({?R+X5mkaJ zeJ63u0pD@0*7VT_?*nU7cVaVV9-HdH?1`9h8Pf~uu4Uq=D;tMR+^WQKf&Cvx&Dl6? z;?5-&2X+yc)I~q=zDtb9-GcqhVB`k-m11_6V62j60LB?5&47Cym$1wpOU&5jI~vW{ zd{bsq2Y1TqO18K*FnR}LHy&q#0r#k zu;mn^=N)xLUn>&g;p1mkUzHc%s)Y05tO;XwZzar|KIjvBRq{tC%}bambu$N6r-YgE zJ=#pWrIX)gGa%h&LtdN3pmcoH4}VAk#xiVJI+*nd?`Dd%YB%1k6yuKRof>aT$Vm=j z;LsEG;iJBy_Av<<-xuEu95B8w<4j05&UnfJ%X02#IDAXw0fS>bKS()$H8XT(`VX1h BoEiWC literal 2116 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 literal 6400 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 bb@au3_T6+ZeQ~E2fbB`&e|cWoKHL8R98On( diff --git a/crates/viewer/shaders/ssao.vert b/crates/viewer/shaders/ssao.vert index 44de874..8022c53 100644 --- a/crates/viewer/shaders/ssao.vert +++ b/crates/viewer/shaders/ssao.vert @@ -1,22 +1,20 @@ #version 450 +#extension GL_GOOGLE_include_directive : require + +#include "libs/camera.glsl" layout(location = 0) in vec2 vPos; layout(location = 1) in vec2 vCoords; -layout(binding = 4, set = 2) uniform CameraUBO { - mat4 view; - mat4 proj; - mat4 invertedProj; - vec4 eye; - float zNear; - float zFar; -} cameraUBO; +layout(binding = 4, set = 2) uniform Frame { + Camera camera; +}; layout(location = 0) out vec2 oCoords; layout(location = 1) out vec3 oViewRay; void main() { oCoords = vCoords; - oViewRay = (cameraUBO.invertedProj * vec4(vPos.x, vPos.y, 0.0, 1.0)).xyz; + oViewRay = (camera.invertedProj * vec4(vPos.x, vPos.y, 0.0, 1.0)).xyz; gl_Position = vec4(vPos.x, vPos.y, 0.0, 1.0); } diff --git a/crates/viewer/shaders/ssao.vert.spv b/crates/viewer/shaders/ssao.vert.spv index 39bd9caa52802780cf7f7e9479e5cc1d861d2730..5575db1c05cac33c4e59dca7bc5bc730cb4ca0f1 100644 GIT binary patch literal 1984 zcmZ9M-A)rx6om&$0YyPXepIXl{6`TLjEV7wfRu|y8cDcqns#Ew3^UDisKOPm;2RmA zzzZM78xz-eW_F!$n%#5O+H0S)_dcgB4X=!O-jFxpjeDD3Hm1E{F`l?lH`nS1^|fBw zS=-oLM=|M@oDj{FchRj!^nKI{BESXNgltN-D0?C+=`*bPr-DWlQ>!;?`}?)}hi1Fm zZT8ZW&~Ju8d0_JB4{+J#6dwlu z*H5hzxt!*z=Iz#zpR_zxT=u|>zGTeCQExYizhvB)(@|3p_5CFEJB@*F#QErwf8r0~ z$@TQ3-%2#wo@KaQN4Z>Pgm)7qlWV|?cAj5ZeVolGr>DRDM%;rcYu8oHpTlOuPY%iH z8~kX_v&I+qf;5ODv13k8U5*c;^dLCp=z)*=OvN-C)cN~TTANg_L zj9Pvll?*z}eSkR=KX1hFp0M?vzU-+Y!z;p!BBzJzU2$9%$DYMFFmdl?76*4ejv9+` zVB*eX76KPno&=c-=`5aynW^er5x%HHD zxpM}+Wf(`TaLmSW23)DYv4+d`FS-5D%_t-0C8^P{p3`bnb5%C42zmm>R}~)Dj28ZN zj`>Z(;pg9ie9RBF9NF)(F#DN)GGD{9ADB0TA3gVvp4=DoJ*^oITv0Z>A2>796I0PH z^c@YkUDKS^ot*{dF5qV6pVM5Gk^gFr=e1@SUeMZlaXnviHn~$PcSk(?-O^l@F^C7- zy^_yuVYK9G{^gtl!{3!L4BvC;?A@aLkF-WZOhv0z%~n4?jB_4qrl0u96P*2LXZ{zt zpV*grZ`4PB8N~e&!yA7imV3P<<4)ho%iV*wbUyvKA)}}Cjb0H$e{Ra~ZD}X!uFCA4 zqoIzaoHxx%uAx5D+|e4op-jm4zKl3{dPRS}>kNDkWSom;UvGss_D}|oF@|K+OD()R x{C57D*5`A4Dh}Q>>$#?w<1=yamg94+iG#NsiKp)yGVX$Ykpme0UuE`6_7503hS>lB literal 1852 zcmZ9L+inv<42GvU778r|dH_n@MI$PDi=t+Q7Nyy069Vm3Ux7D5 zJOLLx3^z#p-|RXKoyhU{_ivBwna$^_W#=Z`l$&&GE?u)OC&URWdEMOD+Nk#jo%-6N zRT$GQ?}>0`+)2ML=zq5z^)OCIrX(|xi;|iouU}63r-F*Epx+T6o0zx}$4RH}{lpJP zcG2gGx1;du`}V$E;uPIk=|+1uOxhn_Z)#u4F#JPN(S}j$UJ`#!v9gb)o~SnrlR?;N zjeG^~qr-4N9K}=b!Mm`XNL$V#*jo?zdd|z<8LNlB=zrLX`%>bR+%4%(-H)v>+2-7D z;H3;_k1y^=gDCC^Eqgw-dpzn5wxWX&p84oOzD76NdlU5s?cNSGr*ZUv?@PBk@)dOt z+y|?f&3!pFz0KUQITJs3X!g2nX3nDbb^V)N_2~bY9+o@fd1)Lu$K$YxdnvJ8V8`R= zaXb#2xI>A>fgQ!AbwF!};R8^#%6)M*%durz<(=Yw}n6`?C)!(l(KT}e8SEGUBhvGE16C#B(nKh12u zGcf#oxzuBRY^#xcr)87R_|w@QoP5~45B%`CYxvZ@sQ+1MFzjVj!(72kjGy*UP>*Tt z@W_wN{OM;-_MCJeq2^j<&uee}E@XT16Hh{ntipG65XWp419)ddMS9A?0KW^T*|4n19wfE%Zx zJ)A&doE7cCTdIOuuStjlXKu{uyUxIOUBbC=HuW}mQ#T~w2x~$@uk^t?!*A!`)&6*m rbz$JfxtDPQX{&KndvL4qzV^g{TaCms^M?}dg}a~zZ1{gw*%Qe>7-DVj diff --git a/crates/viewer/src/renderer/mod.rs b/crates/viewer/src/renderer/mod.rs index 63089ad..9c35fe0 100644 --- a/crates/viewer/src/renderer/mod.rs +++ b/crates/viewer/src/renderer/mod.rs @@ -81,7 +81,8 @@ pub struct Renderer { command_buffers: Vec, in_flight_frames: InFlightFrames, environment: Environment, - camera_uniform_buffers: Vec, + camera_ubos: Vec, + config_ubos: Vec, attachments: Attachments, skybox_renderer: SkyboxRenderer, model_renderer: Option, @@ -131,8 +132,8 @@ impl Renderer { let in_flight_frames = create_sync_objects(&context); - let camera_uniform_buffers = - create_camera_uniform_buffers(&context, swapchain.image_count() as u32); + let camera_ubos = create_camera_ubos(&context, swapchain.image_count() as u32); + let config_ubos = create_config_ubos(&context, swapchain.image_count() as u32); let attachments = Attachments::new( &context, @@ -143,7 +144,7 @@ impl Renderer { let skybox_renderer = SkyboxRenderer::create( Arc::clone(&context), - &camera_uniform_buffers, + &camera_ubos, &environment, msaa_samples, depth_format, @@ -153,7 +154,7 @@ impl Renderer { Arc::clone(&context), &attachments.gbuffer_normals, &attachments.gbuffer_depth, - &camera_uniform_buffers, + &camera_ubos, settings, ); @@ -195,7 +196,8 @@ impl Renderer { command_buffers, in_flight_frames, environment, - camera_uniform_buffers, + camera_ubos, + config_ubos, attachments, skybox_renderer, model_renderer: None, @@ -268,17 +270,28 @@ fn create_sync_objects(context: &Arc) -> InFlightFrames { InFlightFrames::new(Arc::clone(context), sync_objects_vec) } -fn create_camera_uniform_buffers(context: &Arc, count: u32) -> Vec { +fn create_camera_ubos(context: &Arc, count: u32) -> Vec { (0..count) .map(|_| { - let mut buffer = Buffer::create( + Buffer::create( Arc::clone(context), size_of::() as _, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, - ); - buffer.map_memory(); - buffer + ) + }) + .collect::>() +} + +fn create_config_ubos(context: &Arc, count: u32) -> Vec { + (0..count) + .map(|_| { + Buffer::create( + Arc::clone(context), + size_of::() as _, + vk::BufferUsageFlags::UNIFORM_BUFFER, + vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, + ) }) .collect::>() } @@ -815,11 +828,12 @@ impl Renderer { if let Some(model_renderer) = self.model_renderer.as_mut() { model_renderer .gbuffer_pass - .set_model(&model_data, &self.camera_uniform_buffers); + .set_model(&model_data, &self.camera_ubos); model_renderer.light_pass.set_model( &model_data, - &self.camera_uniform_buffers, + &self.camera_ubos, + &self.config_ubos, &self.environment, ao_map, ); @@ -829,14 +843,15 @@ impl Renderer { let gbuffer_pass = GBufferPass::create( Arc::clone(&self.context), &model_data, - &self.camera_uniform_buffers, + &self.camera_ubos, self.depth_format, ); let light_pass = LightPass::create( Arc::clone(&self.context), &model_data, - &self.camera_uniform_buffers, + &self.camera_ubos, + &self.config_ubos, &self.environment, ao_map, self.msaa_samples, @@ -1037,7 +1052,20 @@ impl Renderer { let inverted_proj = proj.invert().unwrap(); let ubo = CameraUBO::new(view, proj, inverted_proj, camera.position(), z_near, z_far); - let buffer = &mut self.camera_uniform_buffers[frame_index]; + let buffer = &mut self.camera_ubos[frame_index]; + unsafe { + let data_ptr = buffer.map_memory(); + mem_copy(data_ptr, &[ubo]); + } + } + + // Config + { + let ubo = ConfigUBO { + emissive_intensity: self.settings.emissive_intensity, + output_mode: self.settings.output_mode as u32, + }; + let buffer = &mut self.config_ubos[frame_index]; unsafe { let data_ptr = buffer.map_memory(); mem_copy(data_ptr, &[ubo]); @@ -1195,3 +1223,11 @@ impl SyncObjects { } } } + +#[repr(C, align(4))] +#[allow(dead_code)] +#[derive(Debug, Clone, Copy)] +struct ConfigUBO { + output_mode: u32, + emissive_intensity: f32, +} diff --git a/crates/viewer/src/renderer/model/lightpass.rs b/crates/viewer/src/renderer/model/lightpass.rs index a160d4f..a694925 100644 --- a/crates/viewer/src/renderer/model/lightpass.rs +++ b/crates/viewer/src/renderer/model/lightpass.rs @@ -12,29 +12,30 @@ use vulkan::{Buffer, Context, Texture as VulkanTexture}; const SAMPLERS_PER_PRIMITIVE: u32 = 8; -const DYNAMIC_DATA_SET_INDEX: u32 = 0; +const PER_NODE_DYNAMIC_DATA_SET_INDEX: u32 = 0; const STATIC_DATA_SET_INDEX: u32 = 1; const PER_PRIMITIVE_DATA_SET_INDEX: u32 = 2; const INPUT_SET_INDEX: u32 = 3; +const PER_PRIMITIVE_DYNAMIC_DATA_SET_INDEX: u32 = 4; const CAMERA_UBO_BINDING: u32 = 0; -const LIGHT_UBO_BINDING: u32 = 1; -const TRANSFORMS_UBO_BINDING: u32 = 2; -const SKINS_UBO_BINDING: u32 = 3; -const IRRADIANCE_SAMPLER_BINDING: u32 = 4; -const PRE_FILTERED_SAMPLER_BINDING: u32 = 5; -const BRDF_SAMPLER_BINDING: u32 = 6; -const COLOR_SAMPLER_BINDING: u32 = 7; -const NORMALS_SAMPLER_BINDING: u32 = 8; -const MATERIAL_SAMPLER_BINDING: u32 = 9; -const OCCLUSION_SAMPLER_BINDING: u32 = 10; -const EMISSIVE_SAMPLER_BINDING: u32 = 11; -const CLEARCOAT_FACTOR_SAMPLER_BINDING: u32 = 12; -const CLEARCOAT_ROUGHNESS_SAMPLER_BINDING: u32 = 13; -const CLEARCOAT_NORMAL_SAMPLER_BINDING: u32 = 14; -const AO_MAP_SAMPLER_BINDING: u32 = 15; - -const MAX_LIGHT_COUNT: u32 = 8; +const CONFIG_UBO_BINDING: u32 = 1; +const LIGHT_UBO_BINDING: u32 = 2; +const TRANSFORMS_UBO_BINDING: u32 = 3; +const SKINS_UBO_BINDING: u32 = 4; +const IRRADIANCE_SAMPLER_BINDING: u32 = 5; +const PRE_FILTERED_SAMPLER_BINDING: u32 = 6; +const BRDF_SAMPLER_BINDING: u32 = 7; +const COLOR_SAMPLER_BINDING: u32 = 8; +const NORMALS_SAMPLER_BINDING: u32 = 9; +const MATERIAL_SAMPLER_BINDING: u32 = 10; +const OCCLUSION_SAMPLER_BINDING: u32 = 11; +const EMISSIVE_SAMPLER_BINDING: u32 = 12; +const CLEARCOAT_FACTOR_SAMPLER_BINDING: u32 = 13; +const CLEARCOAT_ROUGHNESS_SAMPLER_BINDING: u32 = 14; +const CLEARCOAT_NORMAL_SAMPLER_BINDING: u32 = 15; +const AO_MAP_SAMPLER_BINDING: u32 = 16; +const MATERIAL_UBO_BINDING: u32 = 17; pub struct LightPass { context: Arc, @@ -113,13 +114,6 @@ impl OutputMode { } } -#[allow(dead_code)] -struct ConfigUniform { - light_count: u32, - output_mode: u32, - emissive_intensity: f32, -} - #[derive(Debug, Clone, Copy)] enum Pass { /// Opaque geometry only, will discard masked fragments. @@ -136,7 +130,8 @@ impl LightPass { pub fn create( context: Arc, model_data: &ModelData, - camera_buffers: &[Buffer], + camera_ubos: &[Buffer], + config_ubos: &[Buffer], environment: &Environment, ao_map: Option<&VulkanTexture>, msaa_samples: vk::SampleCountFlags, @@ -153,16 +148,17 @@ impl LightPass { let descriptors = create_descriptors( &context, DescriptorsResources { - camera_buffers, + camera_ubos, + config_ubos, model_transform_buffers: &model_data.transform_ubos, model_skin_buffers: &model_data.skin_ubos, - light_buffers: &model_data.light_buffers, + model_materials_buffer: &model_data.materials_ubo, + light_buffers: &model_data.light_ubos, dummy_texture: &dummy_texture, environment, - model: &model_rc.borrow(), + ao_map: ao_map.unwrap_or(&dummy_texture), }, - ao_map.unwrap_or(&dummy_texture), ); let pipeline_layout = create_pipeline_layout(context.device(), &descriptors); @@ -236,7 +232,8 @@ impl LightPass { pub fn set_model( &mut self, model_data: &ModelData, - camera_buffers: &[Buffer], + camera_ubos: &[Buffer], + config_ubos: &[Buffer], environment: &Environment, ao_map: Option<&VulkanTexture>, ) { @@ -248,16 +245,18 @@ impl LightPass { self.descriptors = create_descriptors( &self.context, DescriptorsResources { - camera_buffers, + camera_ubos, + config_ubos, model_transform_buffers: &model_data.transform_ubos, model_skin_buffers: &model_data.skin_ubos, - light_buffers: &model_data.light_buffers, + model_materials_buffer: &model_data.materials_ubo, + light_buffers: &model_data.light_ubos, dummy_texture: &self.dummy_texture, environment, model: &model_rc.borrow(), + ao_map: ao_map.unwrap_or(&self.dummy_texture), }, - ao_map.unwrap_or(&self.dummy_texture), ); } @@ -380,8 +379,8 @@ impl LightPass { command_buffer, vk::PipelineBindPoint::GRAPHICS, self.pipeline_layout, - DYNAMIC_DATA_SET_INDEX, - &self.descriptors.dynamic_data_sets[frame_index..=frame_index], + PER_NODE_DYNAMIC_DATA_SET_INDEX, + &self.descriptors.per_node_dynamic_data_sets[frame_index..=frame_index], &[ model_transform_ubo_offset * index as u32, model_skin_ubo_offset * skin_index as u32, @@ -391,6 +390,11 @@ impl LightPass { for primitive in mesh.primitives().iter().filter(primitive_filter) { let primitive_index = primitive.index(); + let material_index = primitive + .material_index() + .map(|i| i + 1) + .unwrap_or_default(); + let material_ubo_offset = self.context.get_ubo_alignment::(); // Bind descriptor sets unsafe { @@ -401,8 +405,17 @@ impl LightPass { PER_PRIMITIVE_DATA_SET_INDEX, &self.descriptors.per_primitive_sets[primitive_index..=primitive_index], &[], - ) - }; + ); + + device.cmd_bind_descriptor_sets( + command_buffer, + vk::PipelineBindPoint::GRAPHICS, + self.pipeline_layout, + PER_PRIMITIVE_DYNAMIC_DATA_SET_INDEX, + &[self.descriptors.per_primitive_dynamic_data_set], + &[material_index as u32 * material_ubo_offset], + ); + } unsafe { device.cmd_bind_vertex_buffers( @@ -424,34 +437,6 @@ impl LightPass { } } - // Push material constants - unsafe { - let material: MaterialUniform = primitive.material().into(); - let mut data = any_as_u8_slice(&material).to_vec(); - - let light_count = model - .nodes() - .nodes() - .iter() - .filter(|n| n.light_index().is_some()) - .count() as u32; - - let config = ConfigUniform { - light_count, - output_mode: self.output_mode as _, - emissive_intensity: self.emissive_intensity, - }; - data.extend_from_slice(any_as_u8_slice(&config)); - - device.cmd_push_constants( - command_buffer, - self.pipeline_layout, - vk::ShaderStageFlags::FRAGMENT, - 0, - data.as_slice(), - ); - }; - // Draw geometry match primitive.indices() { Some(index_buffer) => { @@ -499,26 +484,31 @@ impl Drop for LightPass { #[derive(Copy, Clone)] struct DescriptorsResources<'a> { - camera_buffers: &'a [Buffer], + camera_ubos: &'a [Buffer], + config_ubos: &'a [Buffer], model_transform_buffers: &'a [Buffer], model_skin_buffers: &'a [Buffer], + model_materials_buffer: &'a Buffer, light_buffers: &'a [Buffer], dummy_texture: &'a VulkanTexture, environment: &'a Environment, model: &'a Model, + ao_map: &'a VulkanTexture, } pub struct Descriptors { context: Arc, pool: vk::DescriptorPool, - dynamic_data_layout: vk::DescriptorSetLayout, - dynamic_data_sets: Vec, + per_node_dynamic_data_layout: vk::DescriptorSetLayout, + per_node_dynamic_data_sets: Vec, static_data_layout: vk::DescriptorSetLayout, static_data_set: vk::DescriptorSet, per_primitive_layout: vk::DescriptorSetLayout, per_primitive_sets: Vec, input_layout: vk::DescriptorSetLayout, input_set: vk::DescriptorSet, + per_primitive_dynamic_data_layout: vk::DescriptorSetLayout, + per_primitive_dynamic_data_set: vk::DescriptorSet, } impl Drop for Descriptors { @@ -526,24 +516,26 @@ impl Drop for Descriptors { let device = self.context.device(); unsafe { device.destroy_descriptor_pool(self.pool, None); + device.destroy_descriptor_set_layout(self.per_primitive_dynamic_data_layout, None); device.destroy_descriptor_set_layout(self.input_layout, None); - device.destroy_descriptor_set_layout(self.dynamic_data_layout, None); + device.destroy_descriptor_set_layout(self.per_node_dynamic_data_layout, None); device.destroy_descriptor_set_layout(self.static_data_layout, None); device.destroy_descriptor_set_layout(self.per_primitive_layout, None); } } } -fn create_descriptors( - context: &Arc, - resources: DescriptorsResources, - ao_map: &VulkanTexture, -) -> Descriptors { +fn create_descriptors(context: &Arc, resources: DescriptorsResources) -> Descriptors { let pool = create_descriptor_pool(context.device(), resources); - let dynamic_data_layout = create_dynamic_data_descriptor_set_layout(context.device()); - let dynamic_data_sets = - create_dynamic_data_descriptor_sets(context, pool, dynamic_data_layout, resources); + let per_node_dynamic_data_layout = + create_per_node_dynamic_data_descriptor_set_layout(context.device()); + let per_node_dynamic_data_sets = create_per_node_dynamic_data_descriptor_sets( + context, + pool, + per_node_dynamic_data_layout, + resources, + ); let static_data_layout = create_static_data_descriptor_set_layout(context.device()); let static_data_set = @@ -554,19 +546,30 @@ fn create_descriptors( create_per_primitive_descriptor_sets(context, pool, per_primitive_layout, resources); let input_layout = create_input_descriptor_set_layout(context.device()); - let input_set = create_input_descriptor_set(context, pool, input_layout, ao_map); + let input_set = create_input_descriptor_set(context, pool, input_layout, resources.ao_map); + + let per_primitive_dynamic_data_layout = + create_per_primitive_dynamic_data_descriptor_set_layout(context.device()); + let per_primitive_dynamic_data_set = create_per_primitive_dynamic_data_descriptor_set( + context, + pool, + per_primitive_dynamic_data_layout, + resources, + ); Descriptors { context: Arc::clone(context), pool, - dynamic_data_layout, - dynamic_data_sets, + per_node_dynamic_data_layout, + per_node_dynamic_data_sets, static_data_layout, static_data_set, per_primitive_layout, per_primitive_sets, input_layout, input_set, + per_primitive_dynamic_data_layout, + per_primitive_dynamic_data_set, } } @@ -575,21 +578,24 @@ fn create_descriptor_pool( descriptors_resources: DescriptorsResources, ) -> vk::DescriptorPool { const GLOBAL_TEXTURES_COUNT: u32 = 4; // irradiance, prefiltered, brdf lut, ao + const PER_FRAME_SETS_COUNT: u32 = 3; // camera, config, lights const STATIC_SETS_COUNT: u32 = 1; const INPUT_SETS_COUNT: u32 = 1; + const PER_NODE_SETS_COUNT: u32 = 2; + const MATERIAL_SETS_COUNT: u32 = 1; - let descriptor_count = descriptors_resources.camera_buffers.len() as u32; + let descriptor_count = descriptors_resources.camera_ubos.len() as u32; let primitive_count = descriptors_resources.model.primitive_count() as u32; let textures_desc_count = primitive_count * SAMPLERS_PER_PRIMITIVE; let pool_sizes = [ vk::DescriptorPoolSize { ty: vk::DescriptorType::UNIFORM_BUFFER, - descriptor_count: descriptor_count * 2, + descriptor_count: descriptor_count * PER_FRAME_SETS_COUNT, }, vk::DescriptorPoolSize { ty: vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC, - descriptor_count: descriptor_count * 2, + descriptor_count: descriptor_count * PER_NODE_SETS_COUNT + MATERIAL_SETS_COUNT, }, vk::DescriptorPoolSize { ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, @@ -599,13 +605,19 @@ fn create_descriptor_pool( let create_info = vk::DescriptorPoolCreateInfo::builder() .pool_sizes(&pool_sizes) - .max_sets(descriptor_count + STATIC_SETS_COUNT + INPUT_SETS_COUNT + primitive_count) + .max_sets( + descriptor_count + + STATIC_SETS_COUNT + + INPUT_SETS_COUNT + + primitive_count + + MATERIAL_SETS_COUNT, + ) .flags(vk::DescriptorPoolCreateFlags::FREE_DESCRIPTOR_SET); unsafe { device.create_descriptor_pool(&create_info, None).unwrap() } } -fn create_dynamic_data_descriptor_set_layout(device: &Device) -> vk::DescriptorSetLayout { +fn create_per_node_dynamic_data_descriptor_set_layout(device: &Device) -> vk::DescriptorSetLayout { let bindings = [ vk::DescriptorSetLayoutBinding::builder() .binding(CAMERA_UBO_BINDING) @@ -613,6 +625,12 @@ fn create_dynamic_data_descriptor_set_layout(device: &Device) -> vk::DescriptorS .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT) .build(), + vk::DescriptorSetLayoutBinding::builder() + .binding(CONFIG_UBO_BINDING) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT) + .build(), vk::DescriptorSetLayoutBinding::builder() .binding(LIGHT_UBO_BINDING) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) @@ -642,13 +660,13 @@ fn create_dynamic_data_descriptor_set_layout(device: &Device) -> vk::DescriptorS } } -fn create_dynamic_data_descriptor_sets( +fn create_per_node_dynamic_data_descriptor_sets( context: &Arc, pool: vk::DescriptorPool, layout: vk::DescriptorSetLayout, resources: DescriptorsResources, ) -> Vec { - let layouts = (0..resources.camera_buffers.len()) + let layouts = (0..resources.camera_ubos.len()) .map(|_| layout) .collect::>(); @@ -663,7 +681,8 @@ fn create_dynamic_data_descriptor_sets( }; sets.iter().enumerate().for_each(|(i, set)| { - let camera_ubo = &resources.camera_buffers[i]; + let camera_ubo = &resources.camera_ubos[i]; + let config_ubo = &resources.config_ubos[i]; let light_buffer = &resources.light_buffers[i]; let model_transform_ubo = &resources.model_transform_buffers[i]; let model_skin_ubo = &resources.model_skin_buffers[i]; @@ -674,6 +693,12 @@ fn create_dynamic_data_descriptor_sets( .range(vk::WHOLE_SIZE) .build()]; + let config_buffer_info = [vk::DescriptorBufferInfo::builder() + .buffer(config_ubo.buffer) + .offset(0) + .range(vk::WHOLE_SIZE) + .build()]; + let light_buffer_info = [vk::DescriptorBufferInfo::builder() .buffer(light_buffer.buffer) .offset(0) @@ -699,6 +724,12 @@ fn create_dynamic_data_descriptor_sets( .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) .buffer_info(&camera_buffer_info) .build(), + vk::WriteDescriptorSet::builder() + .dst_set(*set) + .dst_binding(CONFIG_UBO_BINDING) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) + .buffer_info(&config_buffer_info) + .build(), vk::WriteDescriptorSet::builder() .dst_set(*set) .dst_binding(LIGHT_UBO_BINDING) @@ -912,6 +943,7 @@ fn create_per_primitive_descriptor_sets( for mesh in model.meshes() { for primitive in mesh.primitives() { let material = primitive.material(); + let albedo_info = create_descriptor_image_info( material.get_color_texture_index(), textures, @@ -1066,6 +1098,64 @@ fn create_input_descriptor_set( set } +fn create_per_primitive_dynamic_data_descriptor_set_layout( + device: &Device, +) -> vk::DescriptorSetLayout { + let bindings = [vk::DescriptorSetLayoutBinding::builder() + .binding(MATERIAL_UBO_BINDING) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT) + .build()]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::builder().bindings(&bindings); + + unsafe { + device + .create_descriptor_set_layout(&layout_info, None) + .unwrap() + } +} + +fn create_per_primitive_dynamic_data_descriptor_set( + context: &Arc, + pool: vk::DescriptorPool, + layout: vk::DescriptorSetLayout, + resources: DescriptorsResources, +) -> vk::DescriptorSet { + let layouts = [layout]; + let allocate_info = vk::DescriptorSetAllocateInfo::builder() + .descriptor_pool(pool) + .set_layouts(&layouts); + let set = unsafe { + context + .device() + .allocate_descriptor_sets(&allocate_info) + .unwrap()[0] + }; + + let material_buffer_info = [vk::DescriptorBufferInfo::builder() + .buffer(resources.model_materials_buffer.buffer) + .offset(0) + .range(size_of::() as _) + .build()]; + + let descriptor_writes = [vk::WriteDescriptorSet::builder() + .dst_set(set) + .dst_binding(MATERIAL_UBO_BINDING) + .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER_DYNAMIC) + .buffer_info(&material_buffer_info) + .build()]; + + unsafe { + context + .device() + .update_descriptor_sets(&descriptor_writes, &[]) + } + + set +} + fn update_input_descriptor_set( context: &Arc, set: vk::DescriptorSet, @@ -1111,21 +1201,14 @@ fn create_descriptor_image_info( fn create_pipeline_layout(device: &Device, descriptors: &Descriptors) -> vk::PipelineLayout { let layouts = [ - descriptors.dynamic_data_layout, + descriptors.per_node_dynamic_data_layout, descriptors.static_data_layout, descriptors.per_primitive_layout, descriptors.input_layout, + descriptors.per_primitive_dynamic_data_layout, ]; - let size = size_of::() + size_of::(); - let push_constant_range = [vk::PushConstantRange { - stage_flags: vk::ShaderStageFlags::FRAGMENT, - offset: 0, - size: size as _, - }]; - let layout_info = vk::PipelineLayoutCreateInfo::builder() - .set_layouts(&layouts) - .push_constant_ranges(&push_constant_range); + let layout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(&layouts); unsafe { device.create_pipeline_layout(&layout_info, None).unwrap() } } @@ -1269,7 +1352,7 @@ fn create_model_frag_shader_specialization( let max_reflection_lod = (PRE_FILTERED_MAP_SIZE as f32).log2().floor() as u32; let constants = ModelShaderConstants { - max_light_count: MAX_LIGHT_COUNT, + max_light_count: MAX_LIGHT_COUNT as _, max_reflection_lod, pass: pass as _, }; diff --git a/crates/viewer/src/renderer/model/mod.rs b/crates/viewer/src/renderer/model/mod.rs index 66cc470..4ae579b 100644 --- a/crates/viewer/src/renderer/model/mod.rs +++ b/crates/viewer/src/renderer/model/mod.rs @@ -6,12 +6,14 @@ mod uniform; use gbufferpass::GBufferPass; use lightpass::LightPass; use math::cgmath::Matrix4; +use model::Material; use model::Model; use model::MAX_JOINTS_PER_MESH; use std::cell::RefCell; use std::rc::Weak; use std::sync::Arc; use uniform::*; +use vulkan::ash::vk; use vulkan::{mem_copy, mem_copy_aligned, Buffer, Context}; type JointsBuffer = [Matrix4; MAX_JOINTS_PER_MESH]; @@ -22,7 +24,8 @@ pub struct ModelData { transform_ubos: Vec, skin_ubos: Vec, skin_matrices: Vec>, - light_buffers: Vec, + materials_ubo: Buffer, + light_ubos: Vec, } pub struct ModelRenderer { @@ -40,7 +43,8 @@ impl ModelData { let transform_ubos = create_transform_ubos(&context, &model_rc.borrow(), image_count); let (skin_ubos, skin_matrices) = create_skin_ubos(&context, &model_rc.borrow(), image_count); - let light_buffers = create_lights_ubos(&context, &model_rc.borrow(), image_count); + let materials_ubo = create_materials_ubo(&context, &model_rc.borrow()); + let light_ubos = create_lights_ubos(&context, image_count); Self { context, @@ -48,7 +52,8 @@ impl ModelData { transform_ubos, skin_ubos, skin_matrices, - light_buffers, + materials_ubo, + light_ubos, } } @@ -90,29 +95,52 @@ impl ModelData { } } - let elem_size = &self.context.get_ubo_alignment::(); + let elem_size = self.context.get_ubo_alignment::(); let buffer = &mut self.skin_ubos[frame_index]; unsafe { let data_ptr = buffer.map_memory(); - mem_copy_aligned(data_ptr, u64::from(*elem_size), skin_matrices); + mem_copy_aligned(data_ptr, u64::from(elem_size), skin_matrices); } } // Update light buffers { - let uniforms = model + let mut lights_ubo = LightsUBO::default(); + + for (i, ln) in model .nodes() .nodes() .iter() .filter(|n| n.light_index().is_some()) .map(|n| (n.transform(), n.light_index().unwrap())) .map(|(t, i)| (t, model.lights()[i]).into()) - .collect::>(); + .enumerate() + .take(MAX_LIGHT_COUNT) + { + lights_ubo.count += 1; + lights_ubo.lights[i] = ln; + } + + let buffer = &mut self.light_ubos[frame_index]; + let data_ptr = buffer.map_memory(); + unsafe { mem_copy(data_ptr, &[lights_ubo]) }; + } - if !uniforms.is_empty() { - let buffer = &mut self.light_buffers[frame_index]; + // Update materials buffer + { + let mut ubos: Vec = vec![Material::default().into()]; + model + .materials() + .iter() + .copied() + .map(|m| m.into()) + .for_each(|m| ubos.push(m)); + + let elem_size = self.context.get_ubo_alignment::() as vk::DeviceSize; + let buffer = &mut self.materials_ubo; + unsafe { let data_ptr = buffer.map_memory(); - unsafe { mem_copy(data_ptr, &uniforms) }; + mem_copy_aligned(data_ptr, elem_size, &ubos); } } } diff --git a/crates/viewer/src/renderer/model/uniform.rs b/crates/viewer/src/renderer/model/uniform.rs index be36368..c7b372b 100644 --- a/crates/viewer/src/renderer/model/uniform.rs +++ b/crates/viewer/src/renderer/model/uniform.rs @@ -4,6 +4,7 @@ use model::{Light, Material, Model, Type, Workflow, MAX_JOINTS_PER_MESH}; use std::{mem::size_of, sync::Arc}; use vulkan::{ash::vk, Buffer, Context}; +pub const MAX_LIGHT_COUNT: usize = 8; const DEFAULT_LIGHT_DIRECTION: [f32; 4] = [0.0, 0.0, -1.0, 0.0]; const DIRECTIONAL_LIGHT_TYPE: u32 = 0; const POINT_LIGHT_TYPE: u32 = 1; @@ -14,8 +15,15 @@ const UNLIT_FLAG_UNLIT: u32 = 1; const METALLIC_ROUGHNESS_WORKFLOW: u32 = 0; const SPECULAR_GLOSSINESS_WORKFLOW: u32 = 1; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] #[repr(C)] +pub struct LightsUBO { + pub count: u32, + pub lights: [LightUniform; MAX_LIGHT_COUNT], +} + +#[derive(Copy, Clone, Debug, Default)] +#[repr(C, align(16))] pub struct LightUniform { position: [f32; 4], direction: [f32; 4], @@ -77,7 +85,7 @@ impl From<(Matrix4, Light)> for LightUniform { } #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[allow(dead_code)] pub struct MaterialUniform { color: [f32; 4], @@ -266,23 +274,28 @@ pub fn create_skin_ubos( (buffers, matrices) } -pub fn create_lights_ubos(context: &Arc, model: &Model, count: u32) -> Vec { - let light_count = model - .nodes() - .nodes() - .iter() - .filter(|n| n.light_index().is_some()) - .count(); - - // Buffer size cannot be 0 so we allocate at least anough space for one light - // Probably a bad idea but I'd rather avoid creating a specific shader - let buffer_size = std::cmp::max(1, light_count) * size_of::(); +/// create a ubo containing model's materials +/// first is a default material (used for primitives that do no reference a material) +/// then the materials actually defined by the model +pub fn create_materials_ubo(context: &Arc, model: &Model) -> Buffer { + let material_count = 1 + model.materials().len() as vk::DeviceSize; + let elem_size = context.get_ubo_alignment::() as vk::DeviceSize; + let size = elem_size * material_count; + Buffer::create( + Arc::clone(context), + size, + vk::BufferUsageFlags::UNIFORM_BUFFER, + vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, + ) +} +pub fn create_lights_ubos(context: &Arc, count: u32) -> Vec { + let size = size_of::(); (0..count) .map(|_| { Buffer::create( Arc::clone(context), - buffer_size as vk::DeviceSize, + size as _, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, )