My goal is to render a volumetric point light as explained in this book's chapter 10: FGED Rendering The book explains the math pretty well, but I'm not sure I understand what is the required setup for a volumetric point light halo to be visible. The book describes the following code snippet to calculate the brightness of the volumetric halo:
float R = 5.f; // Radius of the volumetric point light halo.
float R2 = R * R;
float recipR2 = 1.f / R2;
float recip3R2 = 1.f / (3.f * R2);
float normalizer = 3.f / (4.f * R);
// This shader renders a halo effect of radius R at the object-space position pobject.
// The object-space camera position c and view direction z are given by cameraPosition
// and cameraView. The pixelCoord parameter specifies the viewport coordinates of the
// pixel being processed, and it is used to read from the depth buffer.
float CalculateHaloBrightness(float3 pobject, float2 pixelCoord)
{
float3 vdir = cameraPosition - pobject;
float v2 = dot(vdir, vdir);
float p2 = dot(pobject, pobject);
float pv = -dot(pobject, vdir);
float m = sqrt(max(pv * pv - v2 * (p2 - R2), 0.0));
// Read z0 from the depth buffer.
float2 depth = texture(depthBuffer, pixelCoord);
float t0 = 1.0 + depth / dot(cameraView, vdir);
// Calculate clamped limits of integration.
float t1 = clamp((pv - m) / v2, t0, 1.0);
float t2 = clamp((pv + m) / v2, t0, 1.0);
float u1 = t1 * t1;
float u2 = t2 * t2;
// Evaluate density integral, normalize, and square.
float B = ((1.0 - p2 * recipR2) * (t2 - t1) + pv * recipR2 * (u2 - u1) - v2 * recip3R2 * (t2 * u2 - t1 * u1)) * normalizer;
return (B * B * v2);
}
There's a few things that I don't understand:
What is meant exactly by
pobject
,cameraPosition
andcameraView
being in object-space? Does it just mean that they all have to be in world-space for this function to work correctly?On what surface am I supposed to execute this pixel shader on? From what I read, it sounds like it has to be run on a cube centered at
pobject
but I'm not sure.Is a light scattering medium required, such as fog? It would make sense that it does, but it's not really mentioned in the book's explanation.
I realize this is a difficult question to answer, since I haven't included the entire explanation present in the book here. I assume the author would not like that. So I am hoping that maybe somebody here might roughly understand what's going on and point me in the right direction.
edit: I am almost there! Here's my current HLSL implementation:
ConstantBuffer<pass_data> cb_pass : register(b0);
Texture2D<float> Depth : register(t3);
SamplerState linear_wrap_sampler : register(s0);
float4 ps_main(vertex_out pin) : SV_Target
{
float2 uv = pin.posh.xy / cb_pass.sceen_size;
float depth = Depth.SampleLevel(linear_wrap_sampler, uv, 0.f);
// Volumetric halo variables.
float halo = 0.f;
float halo_radius = cb_pass.radius;
float halo_radius2 = halo_radius * halo_radius;
float rcp_halo_radius2 = 1.f / halo_radius2;
float rcp_3halo_radius2 = 1.f / (3.f * halo_radius2);
float density_integral_normalizer = 3.f / (4.f * halo_radius);
// Render volumetric halo.
float4 world_space_pixel = float4(pin.posw, 1.f);
float3 pobject = float3(mul(world_space_pixel, cb_pass.world_to_object).xyz);
float3 cam_pos = mul(float4(cb_pass.eye_pos, 1.f), cb_pass.world_to_object).xyz;
float3 vdir = cam_pos - pobject;
float v2 = dot(vdir, vdir);
float p2 = dot(pobject, pobject);
float pv = -dot(pobject, vdir);
float m = sqrt(max(pv * pv - v2 * (p2 - halo_radius2), 0.f));
float3 cam_view = mul(float4(cb_pass.eye_forward, 0.f), cb_pass.world_to_object).xyz;
float t0 = 1.f + depth / dot(cam_view, vdir);
float t1 = clamp((pv - m) / v2, t0, 1.f);
float t2 = clamp((pv + m) / v2, t0, 1.f);
float u1 = t1 * t1;
float u2 = t2 * t2;
float B = ((1.f - p2 * rcp_halo_radius2) * (t2 - t1) + pv * rcp_halo_radius2 * (u2 - u1) - v2 * rcp_3halo_radius2 * (t2 * u2 - t1 * u1)) * density_integral_normalizer;
halo = (B * B * v2);
return float4(halo, halo, halo, 1.f);
}
The only problem remaining is that the halo only appears when the camera gets very close to the halo. When the camera is close enough it does show up correctly though. I noticed that scaling cb_pass.eye_forward
will make the halo show up from further away but I'm not sure what is causing this.