diff --git a/samples/03_model_render/model_loader.h b/samples/03_model_render/model_loader.h index 2c8d31b..dfa6521 100644 --- a/samples/03_model_render/model_loader.h +++ b/samples/03_model_render/model_loader.h @@ -46,7 +46,7 @@ struct VertexData vec4 m_Normal; vec2 m_TexCoord0 = vec2{0.0f, 0.0f}; vec2 m_TexCoord1 = vec2{0.0f, 0.0f}; - vec4 m_Color0 = vec4{1.0f, 0.0f, 1.0f, 1.0f}; + vec4 m_Color0 = vec4{1.0f, 1.0f, 1.0f, 1.0f}; }; struct Model diff --git a/samples/03_model_render/model_render.cpp b/samples/03_model_render/model_render.cpp index 9e4af6c..83a94e8 100644 --- a/samples/03_model_render/model_render.cpp +++ b/samples/03_model_render/model_render.cpp @@ -90,7 +90,7 @@ main(int, char **) .m_Position = vec4{0.0f, 2.0f, 2.0f, 1.0f}, }; - lightManager.AddDirectional(vec3(0.0f, -1.0f, 0.0f), {0.0f, 1.0f, 0.0f}); + //lightManager.AddDirectional(vec3(0.0f, -1.0f, 0.0f), {0.0f, 1.0f, 0.0f}); lightManager.AddPoint(vec3{2.0f, 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, 15.0f); lightManager.AddPoint(vec3{-2.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}, 15.0f); diff --git a/samples/03_model_render/pipeline_utils.cpp b/samples/03_model_render/pipeline_utils.cpp index 70b71b7..642c51c 100644 --- a/samples/03_model_render/pipeline_utils.cpp +++ b/samples/03_model_render/pipeline_utils.cpp @@ -42,7 +42,7 @@ CreatePipeline(const Device *device, vk::Format attachmentFormat, const GpuResou .binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, - .stageFlags = vk::ShaderStageFlagBits::eVertex, + .stageFlags = vk::ShaderStageFlagBits::eAll, }, }; vk::DescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { diff --git a/samples/03_model_render/shader/bindless_structs.hlsli b/samples/03_model_render/shader/bindless_structs.hlsli index 5d53d40..5124762 100644 --- a/samples/03_model_render/shader/bindless_structs.hlsli +++ b/samples/03_model_render/shader/bindless_structs.hlsli @@ -39,7 +39,7 @@ struct Block uint NodeIdx; }; -struct Light +struct PointLight { float Position[3]; float Range; @@ -47,6 +47,14 @@ struct Light float Intensity; }; +struct DirectionalLight +{ + float Direction[3]; + float Validity_; + uint Color; + float Intensity; +}; + struct CameraData { float4x4 View; @@ -60,11 +68,14 @@ struct CameraData #define INVALID_HANDLE 0xFFFFFFFF +static const float PI = 3.14159265f; + [[vk::binding(0, 0)]] StructuredBuffer VertexBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer VertexDataBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer MaterialsBuffer[]; [[vk::binding(0, 0)]] StructuredBuffer NodeBuffer[]; -[[vk::binding(0, 0)]] StructuredBuffer LightBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer PointLightBuffer[]; +[[vk::binding(0, 0)]] StructuredBuffer DirectionalLightBuffer[]; [[vk::binding(1, 0)]] Texture2D Textures[]; [[vk::binding(1, 0)]] SamplerState ImmutableSamplers[]; diff --git a/samples/03_model_render/shader/model.ps.hlsl b/samples/03_model_render/shader/model.ps.hlsl index 9e1560b..9da9149 100644 --- a/samples/03_model_render/shader/model.ps.hlsl +++ b/samples/03_model_render/shader/model.ps.hlsl @@ -13,102 +13,198 @@ struct FS_Output float4 ColorTarget : SV_Target0; }; -float4 GetAlbedo(int MaterialIdx, float2 UV) +float4 GetAlbedo(float2 UV, float4 InColor) { - uint AlbedoTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].AlbedoTex; - if (AlbedoTexId == INVALID_HANDLE) - { - return (float4) MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].AlbedoFactor; - } - else - { - return Textures[AlbedoTexId].Sample(ImmutableSamplers[AlbedoTexId], UV); - } + float4 AlbedoFactor = (float4) MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].AlbedoFactor; + uint AlbedoTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].AlbedoTex; + + return AlbedoFactor * InColor * (AlbedoTexId != INVALID_HANDLE ? Textures[AlbedoTexId].Sample(ImmutableSamplers[AlbedoTexId], UV) : 1.0f.xxxx); } -float2 GetMetalRough(int MaterialIdx, float2 UV) +float3 GetEmissive(float2 UV) { - uint MetalRoughTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].MetalRoughTex; - if (MetalRoughTexId == INVALID_HANDLE) - { - return float2(MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].MetalFactor, MaterialsBuffer[PushConstant.MaterialBufferHandle][MaterialIdx].RoughFactor); - } - else - { - return Textures[MetalRoughTexId].Sample(ImmutableSamplers[MetalRoughTexId], UV).bg; // Metal in Blue, Roughness in Green - } + float3 EmissionFactor = (float3) MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].EmissionFactor; + uint EmissionTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].EmissionTex; + + return EmissionFactor * (EmissionTexId != INVALID_HANDLE ? Textures[EmissionTexId].Sample(ImmutableSamplers[EmissionTexId], UV).rgb : 1.0f.xxx); } -float3 GetDirectionalLightInfluence(float3 Normal) +float3 GetNormal(float3 Position, float3 Normal, float2 UV) +{ + float3 N = normalize(Normal); + + uint NormalTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].NormalTex; + if (NormalTexId == INVALID_HANDLE) + { + return N; + } + + float3 TangentSpaceNormal = Textures[NormalTexId].Sample(ImmutableSamplers[NormalTexId], UV).xyz * 2.0f - 1.0f; + + float3 q1 = ddx(Position); + float3 q2 = ddy(Position); + float2 st1 = ddx(UV); + float2 st2 = ddy(UV); + + float3 T = normalize(q1 * st2.y - q2 * st1.y).xyz; + float3 B = -normalize(cross(N, T)); + float3x3 TBN = float3x3(T, B, N); // Construction is Row by Row. + + return normalize(mul(TangentSpaceNormal, TBN)); // Post multiple to avoid transpose. +} + +float2 GetMetalRough(float2 UV) +{ + float2 MetalRoughFactors = float2(MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].MetalFactor, MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].RoughFactor); + uint MetalRoughTexId = MaterialsBuffer[PushConstant.MaterialBufferHandle][PushConstant.MaterialIdx].MetalRoughTex; + + return MetalRoughFactors * (MetalRoughTexId != INVALID_HANDLE ? Textures[MetalRoughTexId].Sample(ImmutableSamplers[MetalRoughTexId], UV).bg : 1.0f.xx); // Metal is B, Rough is G. +} + +float TrowbridgeReitzGGX(float3 Normal, float3 Halfway, float Roughness) +{ + float Coeff = Roughness * Roughness; + float Alpha2 = Coeff * Coeff; + float NdotH = max(dot(Normal, Halfway), 0.0f); + float NdotH2 = NdotH * NdotH; + + float Numerator = Alpha2; + float Denominator = NdotH2 * (Alpha2 - 1.0f) + 1.0f; + Denominator = PI * Denominator * Denominator; + + return Numerator / Denominator; +} + +float GeometrySchlickGGX(float NdotV, float Roughness) +{ + float R = Roughness + 1.0f; + float K = (R * R) / 8.0f; + + float Numerator = NdotV; + float Denominator = NdotV * (1.0f - K) + K; + + return Numerator / Denominator; +} + +float GeometrySmith(float NdotV, float NdotL, float Roughness) +{ + float GGX1 = GeometrySchlickGGX(NdotV, Roughness); + float GGX2 = GeometrySchlickGGX(NdotL, Roughness); + + return GGX1 * GGX2; +} + +// https://en.wikipedia.org/wiki/Schlick%27s_approximation +float3 FresnelSchlick(float cosine, float3 F_0) +{ + return F_0 + (1.0f - F_0) * pow(clamp(1.0f - cosine, 0.0f, 1.0f), 5.0f); // Clamp to avoid artifacts. +} + +float3 GetPointLightInfluence(float3 Albedo, float2 MetalRough, float3 Position, float3 Normal) { if (PushConstant.LightHandle == INVALID_HANDLE) - return float3(0.0f, 0.0f, 0.0f); - - uint Count = IndexerCount(PushConstant.DirectionLightIndexer); - - float3 Contrib = 0.0f; - for (uint i = 0; i < Count; ++i) - { - Light Light = LightBuffer[PushConstant.LightHandle][i]; - float3 LightDir = - (float3) Light.Position; // Position is actually direction for directionalLight; LightDir is Direction towards the light (-direction) - float DiffuseFactor = max(dot(Normal, LightDir), 0.0f); - - float R = (Light.Color & 0xFF000000) >> 24; - float G = (Light.Color & 0x00FF0000) >> 16; - float B = (Light.Color & 0x0000FF00) >> 8; - - float3 Color = float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 - - float3 Diffuse = DiffuseFactor * Color; - - Contrib += (Light.Range < 0 ? float3(0.0f, 0.0f, 0.0f) : Diffuse); - } - - return Contrib; -} - -float3 GetPointLightInfluence(float3 Position, float3 Normal) -{ - if (PushConstant.LightHandle == INVALID_HANDLE) - return float3(0.0f, 0.0f, 0.0f); + return 0.0f.xxx; uint Offset = IndexerOffset(PushConstant.PointLightIndexer); uint Count = IndexerCount(PushConstant.PointLightIndexer); + + float3 ViewDir = normalize(Camera.Position.xyz - Position); + + float Metallic = MetalRough.r; + float Roughness = MetalRough.g; + + // Dielectric F_0 based on LearnOpenGL. + // TODO: Cite + float3 F_0 = 0.04f.xxx; + F_0 = lerp(F_0, Albedo, Metallic); + float3 LightAmp = float3(23.47f, 21.31f, 20.79f); float3 Contrib = 0.0f; for (uint i = 0; i < Count; ++i) { - Light Light = LightBuffer[PushConstant.LightHandle][i + Offset]; - float3 LightDir = normalize(((float3)Light.Position) - Position); - float DiffuseFactor = max(dot(Normal, LightDir), 0.0f); - + PointLight Light = PointLightBuffer[PushConstant.LightHandle][i + Offset]; + + if (Light.Range < 0.0f) + continue; + + float3 LightDir = float3(Light.Position) - Position; + float LightDistance = length(LightDir); + + if (LightDistance > Light.Range) + continue; + + LightDir /= LightDistance; // Normalization + + // Color Unpack float R = (Light.Color & 0xFF000000) >> 24; float G = (Light.Color & 0x00FF0000) >> 16; float B = (Light.Color & 0x0000FF00) >> 8; - float3 Color = float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 + float3 LightColor = LightAmp * float3(R, G, B) * 0.00392156862f; // 0.00392156862 = 1/255 - float3 Diffuse = DiffuseFactor * Color; + float Attenuation = 1.0f / (LightDistance * LightDistance); // TODO: Controlled Attenuation + float3 Halfway = normalize(ViewDir + LightDir); - Contrib += (Light.Range < 0 ? float3(0.0f, 0.0f, 0.0f) : Diffuse); + float CosineFactor = max(dot(Halfway, ViewDir), 0.0f); + float NdotV = max(dot(Normal, ViewDir), 0.0f); + float NdotL = max(dot(Normal, LightDir), 0.0f); + + float3 Radiance = LightColor * Attenuation; + + float NormalDistribution = TrowbridgeReitzGGX(Normal, Halfway, Roughness); + float Geometry = GeometrySmith(NdotV, NdotL, Roughness); + float3 Fresnel = FresnelSchlick(CosineFactor, F_0); + + float3 Numerator = (NormalDistribution * Geometry) * Fresnel; + float Denominator = 4.0f * NdotV * NdotL; + float3 Specular = Numerator / max(Denominator, 0.00001); + + float3 K_Specular = Fresnel; + float3 K_Diffuse = 1.0f.xxx - K_Specular; + + K_Diffuse *= 1.0f - Metallic; + + Contrib += NdotL * Radiance * ((K_Diffuse * Albedo / PI) + Specular); } return Contrib; } +float3 Uncharted2Tonemap(float3 color) +{ + float A = 0.15f; + float B = 0.50f; + float C = 0.10f; + float D = 0.20f; + float E = 0.02f; + float F = 0.30f; + float W = 11.2f; + return ((color * (A * color + C * B) + D * E) / (color * (A * color + B) + D * F)) - E / F; +} + FS_Output main(FS_Input StageInput) { + FS_Output Output; + float3 Ambient = float3(0.02f, 0.02f, 0.02f); - float3 Normal = normalize(StageInput.InNormal.xyz); + // TODO: This should be invalid on the CPU side. + if (PushConstant.MaterialIdx < 0) + { + Output.ColorTarget = float4(1.0f, 0.0f, 1.0f, 1.0f); + return Output; + } + float3 Position = StageInput.InPosition.xyz; + float3 Normal = GetNormal(Position, StageInput.InNormal.xyz, StageInput.InUV0); - float4 ObjColor = PushConstant.MaterialIdx < 0 ? StageInput.InColor : GetAlbedo(PushConstant.MaterialIdx, StageInput.InUV0); - float2 MetalRough = PushConstant.MaterialIdx < 0 ? float2(0.0f, 0.0f) : GetMetalRough(PushConstant.MaterialIdx, StageInput.InUV0); + float4 AlbedoAlpha = GetAlbedo(StageInput.InUV0, StageInput.InColor); + float3 Albedo = AlbedoAlpha.rgb; + float Alpha = AlbedoAlpha.a; + float2 MetalRough = GetMetalRough(StageInput.InUV0); + float3 Emission = GetEmissive(StageInput.InUV0); + float3 Color = /*GetDirectionalLightInfluence(Albedo, MetalRough, Position, Normal) +*/ GetPointLightInfluence(Albedo, MetalRough, Position, Normal); - float3 Diffuse = GetDirectionalLightInfluence(Normal) + GetPointLightInfluence(Position, Normal); - - FS_Output Output; - Output.ColorTarget = float4(ObjColor.rgb * (Diffuse + Ambient), ObjColor.a); + Output.ColorTarget = float4(Uncharted2Tonemap(Color + Emission), Alpha); return Output; }