#include "shared/point.hlsl" #include "shared/quat-functions.hlsl" #include "shared/point-light.hlsl" #include "shared/pbr.hlsl" cbuffer Transforms : register(b0) { float4x4 CameraToClipSpace; float4x4 ClipSpaceToCamera; float4x4 WorldToCamera; float4x4 CameraToWorld; float4x4 WorldToClipSpace; float4x4 ClipSpaceToWorld; float4x4 ObjectToWorld; float4x4 WorldToObject; float4x4 ObjectToCamera; float4x4 ObjectToClipSpace; }; cbuffer Params : register(b1) { float4x4 WorldToLightClipSpace; float4 Color; float Size; float AlphaCutOff; float ShadowOffset; float ShadowBias; float4 ShadowColor; }; cbuffer Params : register(b2) { int SegmentCount; int UseWForSize; int UseStretch; int IsShadowPass; }; cbuffer FogParams : register(b3) { float4 FogColor; float FogDistance; float FogBias; } cbuffer PointLights : register(b4) { PointLight Lights[8]; int ActiveLightCount; } cbuffer PbrParams : register(b5) { float4 BaseColor; float4 EmissiveColor; float Roughness; float Specular; float Metal; } struct psInput { float2 texCoord : TEXCOORD; float4 pixelPosition : SV_POSITION; float4 color : COLOR; float3 worldPosition : POSITION; float3x3 tbnToWorld : TBASIS; float fog : VPOS; float4 positionInLightClipSpace : LIGHTCLIPPOS; }; struct IndicesForDraw { int PointIndex; int3 VertexIndices; }; sampler texSampler : register(s0); sampler clampedSampler : register(s1); StructuredBuffer PbrVertices : register(t0); StructuredBuffer Points : register(t1); StructuredBuffer DrawData : register(t2); Texture2D BaseColorMap : register(t3); Texture2D EmissiveColorMap : register(t4); Texture2D RSMOMap : register(t5); Texture2D NormalMap : register(t6); TextureCube PrefilteredSpecular : register(t7); Texture2D BRDFLookup : register(t8); Texture2D ShadowMap : register(t9); psInput vsMain(uint id : SV_VertexID) { psInput output; uint drawFaceIndex = id / 3; uint faceVertexIndex = id % 3; uint vertexIndex = DrawData[drawFaceIndex].VertexIndices[faceVertexIndex]; uint pointIndex = DrawData[drawFaceIndex].PointIndex; PbrVertex vertex = PbrVertices[vertexIndex]; float4 posInObject = float4(vertex.Position, 1); float resizeFromW = UseWForSize ? Points[pointIndex].FX1 : 1; float3 resizeFromStretch = UseStretch ? Points[pointIndex].Scale : 1; posInObject.xyz *= max(0, resizeFromW) * Size * resizeFromStretch; float4x4 orientationMatrix = transpose(qToMatrix(normalize(Points[pointIndex].Rotation))); posInObject = mul(float4(posInObject.xyz, 1), orientationMatrix); posInObject += float4(Points[pointIndex].Position, 0); output.color = Points[pointIndex].Color; float4 posInClipSpace = mul(posInObject, ObjectToClipSpace); output.pixelPosition = posInClipSpace; float2 uv = vertex.TexCoord; output.texCoord = float2(uv.x, 1 - uv.y); // Offset position along normal for shadow mapping float4 posInWorld = mul(posInObject, ObjectToWorld); output.worldPosition = posInWorld.xyz; float3 worldNormal = normalize(mul(float4(vertex.Normal, 0), (float4x4)ObjectToWorld).xyz); float4 posInWorldOffset = posInWorld; posInWorldOffset.xyz += worldNormal * ShadowOffset; output.positionInLightClipSpace = mul(posInWorldOffset, WorldToLightClipSpace); // Pass tangent space basis vectors (for normal mapping). float3x3 TBN = float3x3(vertex.Tangent, vertex.Bitangent, vertex.Normal); TBN = mul(TBN, (float3x3)orientationMatrix); TBN = mul(TBN, (float3x3)ObjectToWorld); output.tbnToWorld = float3x3( normalize(TBN._m00_m01_m02), normalize(TBN._m10_m11_m12), normalize(TBN._m20_m21_m22)); // Fog if (FogDistance > 0) { float4 posInCamera = mul(posInObject, ObjectToCamera); float fog = pow(saturate(-posInCamera.z / FogDistance), FogBias); output.fog = fog; } return output; } inline float ComputeShadowFactor(float3 shadowCoord, float2 texelSize, float bias) { float shadow = 0.0; int samples = 0; for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) { float2 offset = float2(x, y) * texelSize; float2 sampleUV = shadowCoord.xy + offset; // Sample only if sampleUV is within [0,1] if (sampleUV.x >= 0 && sampleUV.x <= 1 && sampleUV.y >= 0 && sampleUV.y <= 1) { float sampleDepth = ShadowMap.Sample(clampedSampler, sampleUV); // Compare depths with bias if (shadowCoord.z > sampleDepth + bias) { shadow += 1.0; } } samples++; } } return 1.0 - (shadow / samples); } float4 psMain(psInput pin) : SV_TARGET { // Sample input textures to get shading model params. float4 albedo = BaseColorMap.Sample(texSampler, pin.texCoord) * pin.color; if (AlphaCutOff > 0 && albedo.a < AlphaCutOff) discard; float4 roughnessMetallicOcclusion = RSMOMap.Sample(texSampler, pin.texCoord); float roughness = saturate(roughnessMetallicOcclusion.x + Roughness); float metalness = saturate(roughnessMetallicOcclusion.y + Metal); float occlusion = roughnessMetallicOcclusion.z; // Outgoing light direction (vector from world-space fragment position to the "eye"). float3 eyePosition = mul(float4(0, 0, 0, 1), CameraToWorld).xyz; float3 Lo = normalize(eyePosition - pin.worldPosition); // Get current fragment's normal and transform to world space. float3 N = normalize(2.0 * NormalMap.Sample(texSampler, pin.texCoord).rgb - 1.0); // return float4(pin.tbnToWorld[0],1); N = normalize(mul(N, pin.tbnToWorld)); // Angle between surface normal and outgoing light direction. float cosLo = max(0.0, dot(N, Lo)); // Specular reflection vector. float3 Lr = 2.0 * cosLo * N - Lo; // Fresnel reflectance at normal incidence (for metals use albedo color). float3 F0 = lerp(Fdielectric, albedo.rgb, metalness); // Direct lighting calculation for analytical lights. // Direct lighting calculation for analytical lights. float3 directLighting = 0.0; for (int i = 0; i < ActiveLightCount; ++i) { float3 Li = Lights[i].position - pin.worldPosition; //- Lights[i].direction; float distance = length(Li); float intensity = Lights[i].intensity / (pow(distance / Lights[i].range, Lights[i].decay) + 1); float3 Lradiance = Lights[i].color.rgb * intensity; // Lights[i].radiance; // Half-vector between Li and Lo. float3 Lh = normalize(Li + Lo); // Calculate angles between surface normal and various light vectors. float cosLi = max(0.0, dot(N, Li)); float cosLh = max(0.0, dot(N, Lh)); // Calculate Fresnel term for direct lighting. float3 F = fresnelSchlick(F0, max(0.0, dot(Lh, Lo))); // Calculate normal distribution for specular BRDF. float D = ndfGGX(cosLh, roughness); // Calculate geometric attenuation for specular BRDF. float G = gaSchlickGGX(cosLi, cosLo, roughness); // Diffuse scattering happens due to light being refracted multiple times by a dielectric medium. // Metals on the other hand either reflect or absorb energy, so diffuse contribution is always zero. // To be energy conserving we must scale diffuse BRDF contribution based on Fresnel factor & metalness. float3 kd = lerp(float3(1, 1, 1), float3(0, 0, 0), metalness); // return float4(F, 1); // Lambert diffuse BRDF. // We don't scale by 1/PI for lighting & material units to be more convenient. // See: https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/ float3 diffuseBRDF = kd * albedo.rgb; // Cook-Torrance specular microfacet BRDF. float3 specularBRDF = ((F * D * G) / max(Epsilon, 4.0 * cosLi * cosLo)) * Specular; // Total contribution for this light. directLighting += (diffuseBRDF + specularBRDF) * Lradiance * cosLi; } // Ambient lighting (IBL). float3 ambientLighting = 0; { // Sample diffuse irradiance at normal direction. uint width, height, levels; PrefilteredSpecular.GetDimensions(0, width, height, levels); float3 irradiance = PrefilteredSpecular.SampleLevel(texSampler, Lr.xyz, 0.8 * levels).rgb; // Calculate Fresnel term for ambient lighting. // Since we use pre-filtered cubemap(s) and irradiance is coming from many directions // use cosLo instead of angle with light's half-vector (cosLh above). // See: https://seblagarde.wordpress.com/2011/08/17/hello-world/ float3 F = fresnelSchlick(F0, cosLo); // Get diffuse contribution factor (as with direct lighting). float3 kd = lerp(1.0 - F, 0.0, metalness); // Irradiance map contains exitant radiance assuming Lambertian BRDF, no need to scale by 1/PI here either. float3 diffuseIBL = kd * albedo.rgb * irradiance; // Sample pre-filtered specular reflection environment at correct mipmap level. float3 specularIrradiance = PrefilteredSpecular.SampleLevel(texSampler, Lr.xyz, roughness * levels).rgb; // Split-sum approximation factors for Cook-Torrance specular BRDF. float2 specularBRDF = BRDFLookup.SampleLevel(clampedSampler, float2(cosLo, roughness), 0).rg; // Total specular IBL contribution. float3 specularIBL = (F0 * specularBRDF.x + specularBRDF.y) * specularIrradiance; // Total ambient lighting contribution. ambientLighting = diffuseIBL + specularIBL; } float shadowFactor = 1.0; if (!IsShadowPass) { // Compute shadow coordinates float3 shadowCoord = pin.positionInLightClipSpace.xyz / pin.positionInLightClipSpace.w; shadowCoord.xy = shadowCoord.xy * 0.5 + 0.5; shadowCoord.y = 1 - shadowCoord.y; // Compute shadow map texel size uint shadowMapWidth, shadowMapHeight; ShadowMap.GetDimensions(shadowMapWidth, shadowMapHeight); float2 shadowMapTexelSize = float2(1.0 / shadowMapWidth, 1.0 / shadowMapHeight); // float shadowFactor = 1.0; // Check if shadowCoord is within [0,1] if (shadowCoord.x >= 0 && shadowCoord.x <= 1 && shadowCoord.y >= 0 && shadowCoord.y <= 1) { shadowFactor = ComputeShadowFactor(shadowCoord, shadowMapTexelSize, ShadowBias); } } // Final fragment color. float4 litColor = float4(directLighting + ambientLighting, 1.0) * BaseColor * Color; litColor.rgb = lerp(litColor.rgb, ShadowColor.rgb, (1 - shadowFactor) * ShadowColor.a); litColor += float4(EmissiveColorMap.Sample(texSampler, pin.texCoord).rgb * EmissiveColor.rgb, 0); litColor.rgb = lerp(litColor.rgb, FogColor.rgb, pin.fog); litColor.a *= albedo.a; // if (!IsShadowPass) // litColor.r = 0.5; return litColor; }