Add specular highlights to sunlight

This commit is contained in:
Magnus Norddahl 2025-07-17 02:52:28 +02:00
commit a9c1fdf23a
15 changed files with 193 additions and 30 deletions

View file

@ -40,6 +40,11 @@ struct HWViewpointUniforms
int mLightTilesWidth = 0;
FVector3 SunDir;
float Padding = 0.0f;
FVector3 SunColor;
float SunIntensity = 0.0f;
FVector3 mCameraNormal;
void CalcDependencies()

View file

@ -185,7 +185,7 @@ void VkLightmapper::Render()
.RenderPass(raytrace.renderPass.get())
.RenderArea(0, 0, bakeImage.maxX, bakeImage.maxY)
.Framebuffer(bakeImage.raytrace.Framebuffer.get())
.AddClearColor(0.0f, 0.0f, 0.0f, 0.0f)
.AddClearColor(0.0f, 0.0f, 0.0f, -1.0f)
.Execute(cmdbuffer);
VkDeviceSize offset = 0;

View file

@ -138,6 +138,9 @@ void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uni
VPUniforms.mPalLightLevels = static_cast<int>(gl_bandedswlight) | (static_cast<int>(gl_fogmode) << 8) | ((int)lightmode << 16);
}
VPUniforms.mClipLine.X = -10000000.0f;
VPUniforms.SunDir = FVector3(level.SunDirection.X, level.SunDirection.Z, level.SunDirection.Y);
VPUniforms.SunColor = level.SunColor;
VPUniforms.SunIntensity = level.SunIntensity;
}
mClipper->SetViewpoint(Viewpoint);
vClipper->SetViewpoint(Viewpoint);

View file

@ -8,7 +8,7 @@ vec4 centerFragColor;
vec4 clampedSample(vec4 f)
{
return f.a != 0.0 ? f : centerFragColor;
return f.a != -1.0 ? f : centerFragColor;
}
void main()
@ -18,7 +18,7 @@ void main()
centerFragColor = textureOffset(tex, texCoord, ivec2(0, 0));
if (centerFragColor.a != 0.0)
if (centerFragColor.a != -1.0)
{
#if defined(BLUR_HORIZONTAL)
fragcolor =

View file

@ -24,11 +24,12 @@ void main()
vec3 origin = worldpos;
#if defined(USE_SUNLIGHT)
vec3 incoming = TraceSunLight(origin, normal);
float sunAttenuation = TraceSunAttenuation(origin, normal);
#else
vec3 incoming = vec3(0.0);
float sunAttenuation = 0.0;
#endif
vec3 incoming = vec3(0.0);
for (uint j = LightStart; j < LightEnd; j++)
{
incoming += TraceLight(origin, normal, lights[lightIndexes[j]], 0.0, false);
@ -42,5 +43,5 @@ void main()
incoming.rgb *= TraceAmbientOcclusion(origin, normal);
#endif
fragcolor = vec4(incoming, 1.0);
fragcolor = vec4(incoming, sunAttenuation);
}

View file

@ -4,27 +4,36 @@ layout(set = 0, binding = 0) uniform sampler2DMS tex;
layout(location = 0) in vec2 TexCoord;
layout(location = 0) out vec4 fragcolor;
vec4 samplePixel(ivec2 pos, int count)
vec4 samplePixel(ivec2 pos, int samplecount)
{
vec4 c = vec4(0.0);
for (int i = 0; i < count; i++)
float count = 0.0;
for (int i = 0; i < samplecount; i++)
{
c += texelFetch(tex, pos, i);
vec4 p = texelFetch(tex, pos, i);
if (p.a != -1.0)
{
c += p;
count++;
}
}
if (c.a > 0.0)
c /= c.a;
return c;
if (count == 0.0)
return vec4(0.0, 0.0, 0.0, -1.0);
else
return c / count;
}
void main()
{
int count = textureSamples(tex);
int samplecount = textureSamples(tex);
ivec2 size = textureSize(tex);
ivec2 pos = ivec2(gl_FragCoord.xy);
vec4 c = samplePixel(pos, count);
if (c.a == 0.0)
vec4 c = samplePixel(pos, samplecount);
if (c.a == -1.0)
{
float count = 0.0;
c = vec4(0.0, 0.0, 0.0, -1.0);
for (int y = -1; y <= 1; y++)
{
for (int x = -1; x <= 1; x++)
@ -34,12 +43,17 @@ void main()
ivec2 pos2;
pos2.x = clamp(pos.x + x, 0, size.x - 1);
pos2.y = clamp(pos.y + y, 0, size.y - 1);
c += samplePixel(pos2, count);
vec4 p = samplePixel(pos2, samplecount);
if (p.a != -1.0)
{
c += p;
count++;
}
}
}
}
if (c.a > 0.0)
c /= c.a;
if (count != 0.0)
c /= count;
}
fragcolor = c;

View file

@ -24,6 +24,19 @@ vec2 GetSurfaceUV(int primitiveIndex, vec3 primitiveWeights)
vertices[elements[index + 0]].uv * primitiveWeights.z;
}
float PassAttenuationThroughSurface(SurfaceInfo surface, vec2 uv, float attentuation)
{
if (surface.TextureIndex == 0)
{
return attentuation;
}
else
{
vec4 color = texture(textures[surface.TextureIndex], uv);
return attentuation * (1.0 - color.a * surface.Alpha);
}
}
vec3 PassRayThroughSurface(SurfaceInfo surface, vec2 uv, vec3 rayColor)
{
if (surface.TextureIndex == 0)

View file

@ -2,6 +2,7 @@
#include <shaders/lightmap/montecarlo.glsl>
vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor);
float TraceSunRayAttenuation(vec3 origin, float tmin, vec3 dir, float tmax);
vec3 TraceSunLight(vec3 origin, vec3 normal)
{
@ -41,6 +42,44 @@ vec3 TraceSunLight(vec3 origin, vec3 normal)
return incoming * angleAttenuation;
}
float TraceSunAttenuation(vec3 origin, vec3 normal)
{
float angleAttenuation = max(dot(normal, SunDir), 0.0);
if (angleAttenuation == 0.0)
return 0.0;
const float minDistance = 0.01;
vec3 incoming = vec3(0.0);
const float dist = 65536.0;
float attenuation = 0.0;
#if defined(USE_SOFTSHADOWS)
vec3 target = origin + SunDir * dist;
vec3 dir = SunDir;
vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
vec3 xdir = normalize(cross(dir, v));
vec3 ydir = cross(dir, xdir);
float lightsize = 100;
int step_count = 10;
for (int i = 0; i < step_count; i++)
{
vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize;
vec3 pos = target + xdir * gridoffset.x + ydir * gridoffset.y;
attenuation += TraceSunRayAttenuation(origin, minDistance, normalize(pos - origin), dist) / float(step_count);
}
#else
attenuation = TraceSunRayAttenuation(origin, minDistance, SunDir, dist);
#endif
return attenuation;
}
vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor)
{
for (int i = 0; i < 3; i++)
@ -75,3 +114,39 @@ vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor)
}
return vec3(0.0);
}
float TraceSunRayAttenuation(vec3 origin, float tmin, vec3 dir, float tmax)
{
float attenuation = 1.0;
for (int i = 0; i < 3; i++)
{
TraceResult result = TraceFirstHit(origin, tmin, dir, tmax);
// Stop if we hit nothing. We have to hit a sky surface to hit the sky.
if (result.primitiveIndex == -1)
return 0.0;
SurfaceInfo surface = GetSurface(result.primitiveIndex);
// Stop if we hit the sky.
if (surface.Sky > 0.0)
return attenuation;
// Pass through surface texture
attenuation = PassAttenuationThroughSurface(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), attenuation);
// Stop if there is no light left
if (attenuation <= 0.0)
return 0.0;
// Move to surface hit point
origin += dir * result.t;
tmax -= result.t;
if (tmax <= tmin)
return 0.0;
// Move through the portal, if any
TransformRay(surface.PortalIndex, origin, dir);
}
return 0.0;
}

View file

@ -19,6 +19,11 @@ layout(set = 1, binding = 0, std140) uniform readonly ViewpointUBO
int uLightTilesWidth; // Levelmesh light tiles
vec3 SunDir;
float Padding;
vec3 SunColor;
float SunIntensity;
vec3 uCameraNormal;
};

View file

@ -103,15 +103,18 @@ vec4 getLightColor(Material material)
//
// apply lightmaps
//
float sunlightAttenuation = 0.0;
if (vLightmapIndex != -1)
{
color.rgb += texture(textures[nonuniformEXT(vLightmapIndex)], vLightmap.xy).rgb;
vec4 lightmap = texture(textures[nonuniformEXT(vLightmapIndex)], vLightmap.xy);
color.rgb += lightmap.rgb;
sunlightAttenuation = lightmap.a;
}
//
// apply dynamic lights
//
vec4 frag = vec4(ProcessMaterialLight(material, color.rgb), material.Base.a * vColor.a);
vec4 frag = vec4(ProcessMaterialLight(material, color.rgb, sunlightAttenuation), material.Base.a * vColor.a);
//
// colored fog

View file

@ -1,7 +1,14 @@
vec3 ProcessMaterialLight(Material material, vec3 color)
vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation)
{
return material.Base.rgb * clamp(color + desaturate(uDynLightColor).rgb, 0.0, 1.4);
vec4 dynlight = uDynLightColor;
if (sunlightAttenuation > 0.0)
{
sunlightAttenuation *= clamp(dot(material.Normal, SunDir), 0.0, 1.0);
dynlight.rgb += SunColor.rgb * SunIntensity * sunlightAttenuation;
}
return material.Base.rgb * clamp(color + desaturate(dynlight).rgb, 0.0, 1.4);
}
vec3 ProcessSWLight(Material material)

View file

@ -51,11 +51,17 @@
}
}
vec3 ProcessMaterialLight(Material material, vec3 color)
vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation)
{
vec4 dynlight = uDynLightColor;
vec3 normal = material.Normal;
if (sunlightAttenuation > 0.0)
{
sunlightAttenuation *= clamp(dot(normal, SunDir), 0.0, 1.0);
dynlight.rgb += SunColor.rgb * SunIntensity * sunlightAttenuation;
}
#ifndef UBERSHADER
#ifdef SHADE_VERTEX
@ -160,7 +166,7 @@
}
#else
vec3 ProcessMaterialLight(Material material, vec3 color)
vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation)
{
return material.Base.rgb;
}

View file

@ -1,4 +1,5 @@
const float PI = 3.14159265359;
const float PBRBrightnessScale = 2.5; // For making non-PBR and PBR lights roughly the same intensity
float DistributionGGX(vec3 N, vec3 H, float roughness)
{
@ -49,8 +50,6 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r
vec3 L = normalize(light.pos.xyz - pixelpos.xyz);
vec3 H = normalize(V + L);
const float brightnessScale = 2.5; // For making non-PBR and PBR lights roughly the same intensity
float attenuation = distanceAttenuation(distance(light.pos.xyz, pixelpos.xyz), light.radius, light.strength, light.linearity);
if ((light.flags & LIGHTINFO_SPOT) != 0)
{
@ -71,7 +70,7 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r
attenuation *= shadowAttenuation(light.pos.xyz, light.shadowIndex, light.softShadowRadius, light.flags);
}
vec3 radiance = light.color.rgb * attenuation * brightnessScale;
vec3 radiance = light.color.rgb * attenuation * PBRBrightnessScale;
// cook-torrance brdf
float NDF = DistributionGGX(N, H, roughness);
@ -91,7 +90,7 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r
return vec3(0.0);
}
vec3 ProcessMaterialLight(Material material, vec3 ambientLight)
vec3 ProcessMaterialLight(Material material, vec3 ambientLight, float sunlightAttenuation)
{
vec3 albedo = material.Base.rgb;
@ -106,6 +105,27 @@ vec3 ProcessMaterialLight(Material material, vec3 ambientLight)
vec3 Lo = uDynLightColor.rgb;
if (sunlightAttenuation > 0.0)
{
vec3 L = SunDir;
vec3 H = normalize(V + L);
sunlightAttenuation *= clamp(dot(N, L), 0.0, 1.0);
vec3 radiance = SunColor * SunIntensity * PBRBrightnessScale * sunlightAttenuation;
// cook-torrance brdf
float NDF = DistributionGGX(N, H, roughness);
float G = GeometrySmith(N, V, L, roughness);
vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
vec3 kS = F;
vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic);
vec3 nominator = NDF * G * F;
float denominator = 4.0 * clamp(dot(N, V), 0.0, 1.0) * clamp(dot(N, L), 0.0, 1.0);
vec3 specular = nominator / max(denominator, 0.001);
Lo += (kD * albedo / PI + specular) * radiance;
}
#ifndef UBERSHADER
if (uLightIndex >= 0)
{

View file

@ -36,7 +36,7 @@ vec2 lightAttenuation(DynLightInfo light, vec3 normal, vec3 viewdir, float gloss
return vec2(attenuation, attenuation * specularLevel * pow(specAngle, phExp));
}
vec3 ProcessMaterialLight(Material material, vec3 color)
vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation)
{
vec4 dynlight = uDynLightColor;
vec4 specular = vec4(0.0, 0.0, 0.0, 1.0);
@ -44,6 +44,17 @@ vec3 ProcessMaterialLight(Material material, vec3 color)
vec3 normal = material.Normal;
vec3 viewdir = normalize(uCameraPos.xyz - pixelpos.xyz);
if (sunlightAttenuation > 0.0)
{
sunlightAttenuation *= clamp(dot(normal, SunDir), 0.0, 1.0);
vec3 color = SunColor.rgb * SunIntensity * sunlightAttenuation;
vec3 halfdir = normalize(viewdir + SunDir);
float specAngle = clamp(dot(halfdir, normal), 0.0f, 1.0f);
float phExp = material.Glossiness * 4.0f;
dynlight.rgb += color;
specular.rgb += color * material.SpecularLevel * pow(specAngle, phExp);
}
#ifndef UBERSHADER
if (uLightIndex >= 0)
{

View file

@ -20,7 +20,7 @@ struct Material
vec4 Process(vec4 color);
void SetupMaterial(inout Material mat);
vec3 ProcessMaterialLight(Material material, vec3 color);
vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation);
vec3 ProcessSWLight(Material material);
vec2 GetTexCoord();