Can't seem to get shaders to work.
-
Hello Everyone
For the last two weeks I have been trying my hand at using shaders to create a sort of heatmap overlaying the map QML.
However, no matter what it keeps refusing to show me any coloring.
I am stumped, so does anyone have any clue as to why this is happening? I have added my .vert and .frag and the main.qml file for reference.main.qml:
import QtQuick import QtLocation import QtPositioning import QtQuick.Controls ApplicationWindow { visible: true width: 1024 height: 768 title: "Heat Map Overlay - Working Version" ListModel { id: dataModel ListElement { latitude: 40.7128; longitude: -74.0060; value: 0.9 } // New York ListElement { latitude: 51.5074; longitude: -0.1278; value: 0.7 } // London ListElement { latitude: 35.6762; longitude: 139.6503; value: 0.8 } // Tokyo ListElement { latitude: -33.8688; longitude: 151.2093; value: 0.6 } // Sydney ListElement { latitude: 48.8566; longitude: 2.3522; value: 0.85 } // Paris ListElement { latitude: 55.7558; longitude: 37.6173; value: 0.4 } // Moscow ListElement { latitude: -23.5505; longitude: -46.6333; value: 0.5 } // São Paulo ListElement { latitude: 19.4326; longitude: -99.1332; value: 0.65 } // Mexico City ListElement { latitude: 28.6139; longitude: 77.2090; value: 0.3 } // New Delhi ListElement { latitude: 1.3521; longitude: 103.8198; value: 0.75 } // Singapore } Map { id: map anchors.fill: parent plugin: Plugin { name: "osm" } center: QtPositioning.coordinate(30, 0) zoomLevel: 3 onCenterChanged: updateTimer.restart() onZoomLevelChanged: updateTimer.restart() // Debug markers MapItemView { model: showMarkersCheck.checked ? dataModel : null delegate: MapCircle { center: QtPositioning.coordinate(model.latitude, model.longitude) radius: 50000 color: Qt.rgba(1, 0, 0, 0.3) border.width: 2 border.color: "red" } } } Timer { id: updateTimer interval: 100 onTriggered: heatmapOverlay.updatePoints() } ShaderEffect { id: heatmapOverlay anchors.fill: map blending: true opacity: opacitySlider.value // Individual point properties (Qt 6.8 doesn't handle arrays well) property vector4d point0: Qt.vector4d(-1, -1, 0, 0) property vector4d point1: Qt.vector4d(-1, -1, 0, 0) property vector4d point2: Qt.vector4d(-1, -1, 0, 0) property vector4d point3: Qt.vector4d(-1, -1, 0, 0) property vector4d point4: Qt.vector4d(-1, -1, 0, 0) property vector4d point5: Qt.vector4d(-1, -1, 0, 0) property vector4d point6: Qt.vector4d(-1, -1, 0, 0) property vector4d point7: Qt.vector4d(-1, -1, 0, 0) property vector4d point8: Qt.vector4d(-1, -1, 0, 0) property vector4d point9: Qt.vector4d(-1, -1, 0, 0) property int pointCount: 0 property real influenceRadius: 0.2 // In normalized coordinates function updatePoints() { var count = Math.min(dataModel.count, 10) var points = [] for (var i = 0; i < count; i++) { var item = dataModel.get(i) var coord = QtPositioning.coordinate(item.latitude, item.longitude) var screenPos = map.fromCoordinate(coord) // Normalize to 0-1 range var normalizedX = screenPos.x / width var normalizedY = screenPos.y / height // Store in temporary array points.push(Qt.vector4d(normalizedX, normalizedY, item.value, 1.0)) } // Assign to individual properties point0 = points[0] || Qt.vector4d(-1, -1, 0, 0) point1 = points[1] || Qt.vector4d(-1, -1, 0, 0) point2 = points[2] || Qt.vector4d(-1, -1, 0, 0) point3 = points[3] || Qt.vector4d(-1, -1, 0, 0) point4 = points[4] || Qt.vector4d(-1, -1, 0, 0) point5 = points[5] || Qt.vector4d(-1, -1, 0, 0) point6 = points[6] || Qt.vector4d(-1, -1, 0, 0) point7 = points[7] || Qt.vector4d(-1, -1, 0, 0) point8 = points[8] || Qt.vector4d(-1, -1, 0, 0) point9 = points[9] || Qt.vector4d(-1, -1, 0, 0) pointCount = count console.log("Updated", count, "points for shader") } Component.onCompleted: updatePoints() vertexShader: "qrc:/shaders/heatmap.vert.qsb" fragmentShader: "qrc:/shaders/heatmap_simple.frag.qsb" } // Controls Rectangle { anchors.right: parent.right anchors.top: parent.top anchors.margins: 10 width: 200 height: controlCol.height + 20 color: Qt.rgba(0.1, 0.1, 0.1, 0.9) radius: 10 Column { id: controlCol anchors.centerIn: parent spacing: 10 Text { text: "Controls" color: "white" font.bold: true } Row { spacing: 10 Text { text: "Opacity:" color: "white" width: 60 } Slider { id: opacitySlider width: 120 from: 0 to: 1 value: 0.7 } } CheckBox { id: showMarkersCheck text: "Show Markers" palette.windowText: "white" } Text { text: "Zoom: " + map.zoomLevel.toFixed(2) color: "white" } } } }heatmap.vert:
#version 440 layout(location = 0) in vec4 qt_Vertex; layout(location = 1) in vec2 qt_MultiTexCoord0; layout(location = 0) out vec2 texCoord; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; }; void main() { texCoord = qt_MultiTexCoord0; gl_Position = qt_Matrix * qt_Vertex; }heatmap.frag
#version 440 layout(location = 0) in vec2 qt_TexCoord0; layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; // Custom uniforms vec4 dataPoints[10]; // x, y (in pixels), value, padding int actualPointCount; // Number of valid points float mapZoom; // Current map zoom level vec2 mapCenter; // Map center (lon, lat) float influenceRadius; // Influence radius in pixels } ubuf; // Smooth heat map gradient vec3 getHeatMapColor(float value) { value = clamp(value, 0.0, 1.0); // Create smooth gradient with more color stops vec3 color; if (value < 0.2) { // Dark blue to blue float t = value * 5.0; color = mix(vec3(0.0, 0.0, 0.3), vec3(0.0, 0.0, 1.0), t); } else if (value < 0.35) { // Blue to cyan float t = (value - 0.2) * 6.67; color = mix(vec3(0.0, 0.0, 1.0), vec3(0.0, 0.7, 1.0), t); } else if (value < 0.5) { // Cyan to green float t = (value - 0.35) * 6.67; color = mix(vec3(0.0, 0.7, 1.0), vec3(0.0, 1.0, 0.0), t); } else if (value < 0.65) { // Green to yellow-green float t = (value - 0.5) * 6.67; color = mix(vec3(0.0, 1.0, 0.0), vec3(0.5, 1.0, 0.0), t); } else if (value < 0.8) { // Yellow-green to yellow float t = (value - 0.65) * 6.67; color = mix(vec3(0.5, 1.0, 0.0), vec3(1.0, 1.0, 0.0), t); } else if (value < 0.9) { // Yellow to orange float t = (value - 0.8) * 10.0; color = mix(vec3(1.0, 1.0, 0.0), vec3(1.0, 0.5, 0.0), t); } else { // Orange to red float t = (value - 0.9) * 10.0; color = mix(vec3(1.0, 0.5, 0.0), vec3(1.0, 0.0, 0.0), t); } return color; } // Improved IDW with Gaussian falloff float getInterpolatedValue(vec2 pixelCoord) { if (ubuf.actualPointCount == 0) { return 0.0; } float totalWeight = 0.0; float totalValue = 0.0; // Parameters for interpolation float power = 2.5; // IDW power parameter float minDist = 0.5; // Minimum distance to avoid singularity // Check if we're very close to any data point float closestDist = 1e10; float closestValue = 0.0; for (int i = 0; i < ubuf.actualPointCount; i++) { vec2 pointPos = ubuf.dataPoints[i].xy; float pointValue = ubuf.dataPoints[i].z; // Skip invalid points if (pointPos.x < -100.0) continue; float dist = distance(pixelCoord, pointPos); if (dist < closestDist) { closestDist = dist; closestValue = pointValue; } // If very close to a point, return its value if (dist < minDist) { return pointValue; } // Calculate influence based on distance and radius float normalizedDist = dist / ubuf.influenceRadius; // Only consider points within influence radius if (normalizedDist < 1.0) { // Combine IDW with Gaussian falloff for smoother results float idwWeight = 1.0 / pow(dist, power); float gaussianWeight = exp(-normalizedDist * normalizedDist * 2.0); float weight = idwWeight * gaussianWeight; totalWeight += weight; totalValue += weight * pointValue; } } // If we have accumulated weight, return weighted average if (totalWeight > 0.0) { return totalValue / totalWeight; } // If no points in influence radius, use closest point with strong falloff if (closestDist < ubuf.influenceRadius * 2.0) { float falloff = 1.0 - (closestDist / (ubuf.influenceRadius * 2.0)); return closestValue * falloff * falloff; } return 0.0; } void main() { // Convert normalized texture coordinates to pixel coordinates vec2 pixelCoord = qt_TexCoord0 * vec2(textureSize(qt_Matrix, 0)); // Get interpolated value float value = getInterpolatedValue(pixelCoord); // Only render if we have a meaningful value if (value > 0.01) { vec3 color = getHeatMapColor(value); // Fade out at edges of influence float alpha = smoothstep(0.0, 0.1, value) * 0.9; fragColor = vec4(color, alpha) * ubuf.qt_Opacity; } else { fragColor = vec4(0.0, 0.0, 0.0, 0.0); } }Any help would be very appreciated
Kind regards
Olivier -
Hey! It seems like it is impossible to pass javascript arrays to ShaderEffect shader parameters. I have been looking for a solution for this in pure QML as well. The best workaround is to pass an image and use the channels as info, this is prone to floating point precision issues though.