Files
rfcp/docs/devlog/gpu_supp/RFCP-WebGL-Smooth-Coverage-Task.md
2026-02-07 12:56:25 +02:00

282 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RFCP v3.10.5: WebGL Smooth Coverage Implementation
## Контекст проблеми
**Поточний стан:**
- Backend повертає grid точок з lat/lon/RSRP (50m = 6,675 pts, 200m = 1,975 pts)
- WebGL texture-based rendering: points → texture → GL_LINEAR → colormap
- **Проблема:** Видимі grid squares/pixelation, особливо при zoom in або sparse grids (200m)
**Причина:**
- `GL_LINEAR` дає тільки C0 continuity (значення співпадають на краях, але похідні — ні)
- Це створює видимі "шви" між клітинками
## Рішення з ресерчу
### Ключовий інсайт
**Catmull-Rom spline interpolation** дає C1 continuity (smooth derivatives) І проходить через exact data values (на відміну від B-spline який blurs peaks).
**9-tap Catmull-Rom** замість `texture2D()`:
- 9 texture fetches замість 1
- ~0.32ms vs ~0.30ms на GTX 980 при 1920×1080
- Для нашої ~80×85 текстури — практично безкоштовно
### Критичне правило
**Інтерполювати RAW RSRP values ПЕРЕД colormap!**
- ❌ Неправильно: texture → colormap → interpolate (muddy colors)
- ✅ Правильно: texture → interpolate → colormap (clean gradients)
---
## Етап 1: Quick Fix (30 хвилин)
### Smoothstep coordinate remapping
Найшвидший спосіб прибрати grid edges — одна зміна в shader:
```glsl
// ЗАМІСТЬ:
vec4 texColor = texture2D(u_texture, v_uv);
// ВИКОРИСТАТИ:
vec4 textureSmooth(sampler2D tex, vec2 uv, vec2 texSize) {
vec2 p = uv * texSize + 0.5;
vec2 i = floor(p);
vec2 f = p - i;
f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); // quintic hermite
return texture2D(tex, (i + f - 0.5) / texSize);
}
// В main():
vec4 texColor = textureSmooth(u_texture, v_uv, u_textureSize);
```
**Що це дає:**
- C2 continuity з одним texture read
- Прибирає видимі grid edges
- Мінімальний positional bias
**Потрібно додати uniform:**
```javascript
const textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
gl.uniform2f(textureSizeLocation, textureWidth, textureHeight);
```
---
## Етап 2: Production Implementation (1-2 години)
### 9-tap Catmull-Rom Shader
```glsl
precision highp float;
uniform sampler2D u_texture;
uniform vec2 u_textureSize;
uniform float u_opacity;
varying vec2 v_uv;
// Catmull-Rom 9-tap interpolation
// Source: TheRealMJP's gist (108 GitHub stars)
vec4 SampleTextureCatmullRom(sampler2D tex, vec2 uv, vec2 texSize) {
vec2 samplePos = uv * texSize;
vec2 texPos1 = floor(samplePos - 0.5) + 0.5;
vec2 f = samplePos - texPos1;
// Catmull-Rom weights
vec2 w0 = f * (-0.5 + f * (1.0 - 0.5 * f));
vec2 w1 = 1.0 + f * f * (-2.5 + 1.5 * f);
vec2 w2 = f * (0.5 + f * (2.0 - 1.5 * f));
vec2 w3 = f * f * (-0.5 + 0.5 * f);
// Combine weights for optimized sampling
vec2 w12 = w1 + w2;
vec2 offset12 = w2 / (w1 + w2);
// Compute texture coordinates
vec2 texPos0 = (texPos1 - 1.0) / texSize;
vec2 texPos3 = (texPos1 + 2.0) / texSize;
vec2 texPos12 = (texPos1 + offset12) / texSize;
// 9 texture fetches (optimized from 16)
vec4 result = vec4(0.0);
result += texture2D(tex, vec2(texPos0.x, texPos0.y)) * w0.x * w0.y;
result += texture2D(tex, vec2(texPos12.x, texPos0.y)) * w12.x * w0.y;
result += texture2D(tex, vec2(texPos3.x, texPos0.y)) * w3.x * w0.y;
result += texture2D(tex, vec2(texPos0.x, texPos12.y)) * w0.x * w12.y;
result += texture2D(tex, vec2(texPos12.x, texPos12.y)) * w12.x * w12.y;
result += texture2D(tex, vec2(texPos3.x, texPos12.y)) * w3.x * w12.y;
result += texture2D(tex, vec2(texPos0.x, texPos3.y)) * w0.x * w3.y;
result += texture2D(tex, vec2(texPos12.x, texPos3.y)) * w12.x * w3.y;
result += texture2D(tex, vec2(texPos3.x, texPos3.y)) * w3.x * w3.y;
return result;
}
// RSRP to color mapping (cyan -> green -> yellow -> orange -> red)
vec3 rsrpToColor(float rsrp) {
// rsrp: normalized 0.0 (weak, -110dBm) to 1.0 (strong, -50dBm)
// Color stops: red -> orange -> yellow -> green -> cyan
vec3 c0 = vec3(1.0, 0.0, 0.0); // red (weak)
vec3 c1 = vec3(1.0, 0.5, 0.0); // orange
vec3 c2 = vec3(1.0, 1.0, 0.0); // yellow
vec3 c3 = vec3(0.0, 1.0, 0.0); // green
vec3 c4 = vec3(0.0, 1.0, 1.0); // cyan (strong)
float t = clamp(rsrp, 0.0, 1.0);
if (t < 0.25) {
return mix(c0, c1, t / 0.25);
} else if (t < 0.5) {
return mix(c1, c2, (t - 0.25) / 0.25);
} else if (t < 0.75) {
return mix(c2, c3, (t - 0.5) / 0.25);
} else {
return mix(c3, c4, (t - 0.75) / 0.25);
}
}
void main() {
// 1. Sample with Catmull-Rom interpolation (RAW value)
vec4 texColor = SampleTextureCatmullRom(u_texture, v_uv, u_textureSize);
float rsrpNormalized = texColor.r;
// 2. Discard if no coverage (validity check)
if (rsrpNormalized < 0.01) {
discard;
}
// 3. Apply colormap AFTER interpolation
vec3 color = rsrpToColor(rsrpNormalized);
// 4. Smooth boundary fading (optional)
float boundaryAlpha = smoothstep(0.01, 0.05, rsrpNormalized);
gl_FragColor = vec4(color, boundaryAlpha * u_opacity);
}
```
### JavaScript зміни
```javascript
// 1. Vertex shader (без змін)
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_uv;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_uv = a_texCoord;
}
`;
// 2. При створенні texture — зберегти розміри
const textureWidth = gridWidth;
const textureHeight = gridHeight;
// 3. Передати uniform
const textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize');
if (textureSizeLocation) {
gl.uniform2f(textureSizeLocation, textureWidth, textureHeight);
} else {
console.error('[WebGL] u_textureSize uniform NOT FOUND!');
}
// 4. Texture filtering — можна залишити LINEAR для fallback
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
```
---
## Етап 3: Texture Data Format
### Поточний формат (перевірити)
```javascript
// Normalized RSRP value (0-255 mapped to 0.0-1.0 in shader)
const normalized = (rsrp - minRsrp) / (maxRsrp - minRsrp);
const value = Math.round(normalized * 255);
// Store in R channel
textureData[idx] = value; // R = normalized RSRP
textureData[idx + 1] = value; // G (можна використати для validity mask)
textureData[idx + 2] = value; // B
textureData[idx + 3] = 255; // A = fully opaque
```
### Альтернатива: Float texture (краща точність)
```javascript
// Якщо браузер підтримує OES_texture_float
const ext = gl.getExtension('OES_texture_float');
if (ext) {
const floatData = new Float32Array(width * height);
for (const point of points) {
const normalized = (point.rsrp - minRsrp) / (maxRsrp - minRsrp);
floatData[gridY * width + gridX] = normalized;
}
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0,
gl.LUMINANCE, gl.FLOAT, floatData);
}
```
---
## Чеклист імплементації
### Phase 1: Quick Test (Smoothstep)
- [ ] Додати `u_textureSize` uniform
- [ ] Замінити `texture2D()` на `textureSmooth()`
- [ ] Тест на 50m і 200m
- [ ] Тест zoom in/out
### Phase 2: Production (Catmull-Rom)
- [ ] Імплементувати `SampleTextureCatmullRom()`
- [ ] Оновити colormap function
- [ ] Додати boundary fading
- [ ] Тест edge cases (краї текстури)
- [ ] Performance benchmark
### Phase 3: Polish
- [ ] Видалити старі CSS blur workarounds
- [ ] Видалити cellSize multiplication (не потрібно з Catmull-Rom)
- [ ] Cleanup debug logs
- [ ] Update version to v3.10.5
---
## Очікуваний результат
**До (GL_LINEAR):**
```
┌───┬───┬───┐
│ A │ B │ C │ ← Видимі краї між клітинками
├───┼───┼───┤ C0 continuity
│ D │ E │ F │
└───┴───┴───┘
```
**Після (Catmull-Rom):**
```
╭───────────────╮
│ ░░░▒▒▓▓██ │ ← Smooth gradient
│ ░░░▒▒▓▓██▓▓ │ C1 continuity
│ ░░▒▒▓▓██ │ Exact values at grid points
╰───────────────╯
```
---
## Референси
1. [TheRealMJP's 9-tap Catmull-Rom HLSL](https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1)
2. [Inigo Quilez - Better Texture Filtering](https://iquilezles.org/articles/texture/)
3. [2D Catmull-Rom in 4 samples - Shadertoy](https://www.shadertoy.com/view/4tyGDD)
4. [mapbox-gl-interpolate-heatmap](https://github.com/vinayakkulkarni/mapbox-gl-interpolate-heatmap)
5. [NVIDIA GPU Gems 2 - Fast Third-Order Texture Filtering](https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-20-fast-third-order-texture-filtering)