Skip to content

Commit 1125ce9

Browse files
committed
Client: Fix alpha compositing for translucent water surfaces
After some investigation, it's become clear that the reference client seems to incorrectly mix sRGB and linear-space colors. While this is a bit of a guess, it would explain why the contrast correction was needed in the first place; it makes up for the underexposure introduced by the lack of gamma-decompression steps after loading sRGB-encoded textures. Indeed, screen blending is used for this exact purpose in digital photo editing (apparently). At least that's the best explanation I could come up with so far. In order to reproduce the resulting outputs, neither sRGB nor linearized colors can be used. The "in-between" method used so far, however, trips up the alpha blend stage while the underlying texture view is in sRGB format: WebGPU automatically converts the fragment colors - first to sRGB and then back to linear as part of the blend stage. Unfortunately, the color space used is actually neither of the two by this point, and so the results can't possibly match expectations unless a non-sRGB output format is used. Originally, sRGB was selected because it's the preferred surface format. Using a different one grants full control of the color space used and eliminates the undesirable conversion, but may incur a performance hit. However, conversions between RGBA and sRGB should be hardware-accelerated on any reasonably modern device. Switching to RGBA also simplifies the shader logic as the added linearization step is no longer needed. So overall it's probably a net gain? Either way, for now that's a secondary concern.
1 parent 3ffe75f commit 1125ce9

8 files changed

+22
-26
lines changed

Core/NativeClient/Renderer.lua

+3-4
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ local Renderer = {
9999

100100
function Renderer:InitializeWithGLFW(nativeWindowHandle)
101101
Renderer:CreateGraphicsContext(nativeWindowHandle)
102-
103-
-- Need to compute the preferred texture format first
104-
self.backingSurface:UpdateConfiguration()
105-
Renderer:CompileMaterials(self.backingSurface.preferredTextureFormat)
102+
Renderer:CompileMaterials(self.backingSurface.textureFormat)
106103

107104
Renderer:CreateUniformBuffers()
108105

@@ -134,6 +131,8 @@ function Renderer:CreateGraphicsContext(nativeWindowHandle)
134131

135132
printf("Creating depth buffer with texture dimensions %d x %d", viewportWidth, viewportHeight)
136133
self.depthStencilTexture = DepthStencilTexture(device, viewportWidth, viewportHeight)
134+
135+
self.backingSurface:UpdateConfiguration()
137136
end
138137

139138
function Renderer:CompileMaterials(outputTextureFormat)

Core/NativeClient/WebGPU/Pipelines/GroundMeshDrawingPipeline.lua

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ function GroundMeshDrawingPipeline:Construct(wgpuDeviceHandle, textureFormatID)
4646
dstFactor = ffi.C.WGPUBlendFactor_OneMinusSrcAlpha,
4747
operation = ffi.C.WGPUBlendOperation_Add,
4848
},
49+
alpha = {
50+
srcFactor = ffi.C.WGPUBlendFactor_One,
51+
dstFactor = ffi.C.WGPUBlendFactor_OneMinusSrcAlpha,
52+
operation = ffi.C.WGPUBlendOperation_Add,
53+
},
4954
}),
5055
writeMask = ffi.C.WGPUColorWriteMask_All,
5156
}),

Core/NativeClient/WebGPU/Pipelines/WaterPlaneDrawingPipeline.lua

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ function WaterPlaneDrawingPipeline:Construct(wgpuDeviceHandle, textureFormatID)
4646
dstFactor = ffi.C.WGPUBlendFactor_OneMinusSrcAlpha,
4747
operation = ffi.C.WGPUBlendOperation_Add,
4848
},
49+
alpha = {
50+
srcFactor = ffi.C.WGPUBlendFactor_One,
51+
dstFactor = ffi.C.WGPUBlendFactor_OneMinusSrcAlpha,
52+
operation = ffi.C.WGPUBlendOperation_Add,
53+
},
4954
}),
5055
writeMask = ffi.C.WGPUColorWriteMask_All,
5156
}),

Core/NativeClient/WebGPU/RenderTargets/ScreenshotCaptureTexture.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ local Device = require("Core.NativeClient.WebGPU.Device")
1010
local format = string.format
1111

1212
local ScreenshotCaptureTexture = {
13-
OUTPUT_TEXTURE_FORMAT = ffi.C.WGPUTextureFormat_RGBA8UnormSrgb,
13+
OUTPUT_TEXTURE_FORMAT = ffi.C.WGPUTextureFormat_RGBA8Unorm,
1414
}
1515

1616
function ScreenshotCaptureTexture:Construct(wgpuDevice, width, height)

Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl

+1-6
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,5 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
167167

168168
// Should be a no-op if fog is disabled, since the fogFactor would be zero
169169
let foggedColor = mix(fragmentColor.rgb, uPerSceneData.fogColor.rgb, in.fogFactor);
170-
171-
// Gamma-correction:
172-
// WebGPU assumes that the colors output by the fragment shader are given in linear space
173-
// When setting the surface format to BGRA8UnormSrgb it performs a linear to sRGB conversion
174-
let gammaCorrectedColor = pow(foggedColor.rgb, vec3f(2.2));
175-
return vec4f(gammaCorrectedColor, diffuseTextureColor.a + DEBUG_ALPHA_OFFSET);
170+
return vec4f(foggedColor.rgb, diffuseTextureColor.a + DEBUG_ALPHA_OFFSET);
176171
}

Core/NativeClient/WebGPU/Shaders/WaterSurfaceShader.wgsl

+1-5
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,5 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
170170
let materialColor = vec4f(uMaterialInstanceData.diffuseRed, uMaterialInstanceData.diffuseGreen, uMaterialInstanceData.diffuseBlue, uMaterialInstanceData.materialOpacity);
171171
let finalColor = in.color * diffuseTextureColor.rgb * materialColor.rgb;
172172

173-
// Gamma-correction:
174-
// WebGPU assumes that the colors output by the fragment shader are given in linear space
175-
// When setting the surface format to BGRA8UnormSrgb it performs a linear to sRGB conversion
176-
let gammaCorrectedColor = pow(finalColor.rgb, vec3f(2.2));
177-
return vec4f(gammaCorrectedColor, diffuseTextureColor.a * materialColor.a + DEBUG_ALPHA_OFFSET);
173+
return vec4f(finalColor.rgb, diffuseTextureColor.a * materialColor.a + DEBUG_ALPHA_OFFSET );
178174
}

Core/NativeClient/WebGPU/Surface.lua

+5-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ local Surface = {
1616
BACKING_SURFACE_OUTDATED = "The backing surface is outdated and can't be used (window resized or moved?)",
1717
BACKING_SURFACE_TIMEOUT = "The backing surface couldn't be accessed in time (CPU or GPU too busy?)",
1818
},
19+
textureFormat = ffi.C.WGPUTextureFormat_BGRA8Unorm,
1920
}
2021

2122
function Surface:Construct(wgpuInstance, wgpuAdapter, wgpuDevice, glfwWindow)
@@ -33,23 +34,16 @@ function Surface:Construct(wgpuInstance, wgpuAdapter, wgpuDevice, glfwWindow)
3334
end
3435

3536
function Surface:UpdateConfiguration()
36-
local preferredTextureFormat = webgpu.bindings.wgpu_surface_get_preferred_format(self.wgpuSurface, self.wgpuAdapter)
37-
self.preferredTextureFormat = preferredTextureFormat -- Required to create the render pipeline
38-
assert(
39-
preferredTextureFormat == ffi.C.WGPUTextureFormat_BGRA8UnormSrgb,
40-
"Only sRGB texture formats are currently supported"
41-
)
42-
4337
local textureViewDescriptor = self.wgpuTextureViewDescriptor
4438
textureViewDescriptor.dimension = ffi.C.WGPUTextureViewDimension_2D
45-
textureViewDescriptor.format = preferredTextureFormat
39+
textureViewDescriptor.format = self.textureFormat
4640
textureViewDescriptor.mipLevelCount = 1
4741
textureViewDescriptor.arrayLayerCount = 1
4842
textureViewDescriptor.aspect = ffi.C.WGPUTextureAspect_All
4943

5044
local surfaceConfiguration = self.wgpuSurfaceConfiguration
5145
surfaceConfiguration.device = self.wgpuDevice
52-
surfaceConfiguration.format = preferredTextureFormat
46+
surfaceConfiguration.format = self.textureFormat
5347
surfaceConfiguration.usage = ffi.C.WGPUTextureUsage_RenderAttachment
5448

5549
-- The underlying framebuffer may be different if DPI scaling is applied, but let's ignore that for now
@@ -62,12 +56,14 @@ function Surface:UpdateConfiguration()
6256

6357
webgpu.bindings.wgpu_surface_configure(self.wgpuSurface, surfaceConfiguration)
6458

59+
local preferredTextureFormat = webgpu.bindings.wgpu_surface_get_preferred_format(self.wgpuSurface, self.wgpuAdapter)
6560
printf(
6661
"Surface configuration changed: Frame buffer size is now %dx%d (preferred texture format: %d)",
6762
viewportWidth,
6863
viewportHeight,
6964
tonumber(preferredTextureFormat)
7065
)
66+
self.preferredTextureFormat = preferredTextureFormat
7167
end
7268

7369
function Surface:AcquireTextureView()

Core/NativeClient/WebGPU/Texture.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ local new = ffi.new
1010
local math_floor = math.floor
1111

1212
local Texture = {
13-
DEFAULT_TEXTURE_FORMAT = ffi.C.WGPUTextureFormat_RGBA8Unorm, -- TBD: WGPUTextureFormat_BGRA8UnormSrgb ?
13+
DEFAULT_TEXTURE_FORMAT = ffi.C.WGPUTextureFormat_RGBA8Unorm,
1414
MAX_TEXTURE_DIMENSION = 4096,
1515
ERROR_DIMENSIONS_NOT_POWER_OF_TWO = "Texture dimensions should always be a power of two",
1616
ERROR_DIMENSIONS_EXCEEDING_LIMIT = "Texture dimensions must not exceed the configured GPU limit",

0 commit comments

Comments
 (0)