@@ -445,26 +445,50 @@ void VROMaterial::applySemanticMaskModifier() {
445445 " uniform highp float semantic_label_mask;" ,
446446 // Compute shared UV once. No Y-flip: gl_FragCoord and the transform are both GL-convention.
447447 " highp vec2 semUV = (ar_semantic_texture_transform * vec4(gl_FragCoord.xy / ar_viewport_size.xy, 0.0, 1.0)).xy;" ,
448- " highp float label_raw = texture(semantic_texture, semUV).r * 255.0;" ,
449- " int semLabel = int(floor(label_raw + 0.5));" ,
450- " bool semMatches = (semLabel >= 1 && semLabel <= 11 && (int(semantic_label_mask) & (1 << semLabel)) != 0);" ,
448+ // Clamp inward by a small margin to skip the 1-2 pixel unlabeled border that ARCore's
449+ // neural network outputs at the left/right edges of the landscape image. In portrait mode
450+ // those edges map to thin horizontal lines at the top/bottom of the viewport. Without the
451+ // clamp those border pixels (label=0) cut the sky effect with a transparent stripe.
452+ // 0.005 β half a pixel on a 192-px-tall semantic texture; harmless for center content.
453+ " semUV = clamp(semUV, vec2(0.005), vec2(0.995));" ,
454+ " bool semInBounds = (semUV.x >= 0.0 && semUV.x <= 1.0 && semUV.y >= 0.0 && semUV.y <= 1.0);" ,
455+ " if (semInBounds) {" ,
456+ " highp float label_raw = texture(semantic_texture, semUV).r * 255.0;" ,
457+ " int semLabel = int(floor(label_raw + 0.5));" ,
458+ " bool semMatches = (semLabel >= 1 && semLabel <= 11 && (int(semantic_label_mask) & (1 << semLabel)) != 0);" ,
451459 // Debug mode (semantic_mask_mode == 2): colorise by label for overlay visualisation.
452460 // Blue = unlabeled, tealβorange gradient = classified pixels (fully opaque).
453- " if (semantic_mask_mode >= 1.5) {" ,
454- " highp float t = label_raw / 11.0;" ,
455- " _output_color = vec4(t, float(semLabel > 0) * 0.8, 1.0 - t, 1.0);" ,
456- " } else {" ,
461+ " if (semantic_mask_mode >= 1.5 && semantic_mask_mode < 2 .5) {" ,
462+ " highp float t = label_raw / 11.0;" ,
463+ " _output_color = vec4(t, float(semLabel > 0) * 0.8, 1.0 - t, 1.0);" ,
464+ " } else {" ,
457465 // Confidence value from texture [0,1]. Used as alpha weight for soft edges.
458466 // On iOS (no platform confidence) the texture is 1x1 white β conf = 1.0 β hard edges.
459- " highp float conf = texture(semantic_confidence_texture, semUV).r;" ,
467+ " highp float conf = texture(semantic_confidence_texture, semUV).r;" ,
460468 // ShowOnly (mode=0): show matched pixels with weight = conf; hide everything else.
461469 // Hide (mode=1): hide matched pixels; show others at full opacity.
462- " if (semantic_mask_mode < 0.5) {" ,
463- " _output_color.a *= semMatches ? conf : 0.0;" ,
464- " } else {" ,
465- " _output_color.a *= semMatches ? (1.0 - conf) : 1.0;" ,
470+ // ShowOnlySky (mode=3): like ShowOnly, but unlabeled pixels use viewport Y as a proxy
471+ // for sky context β upper half of screen shows sphere, lower half hides it. This
472+ // handles the ~10-15% border of the ARCore semantic image where the neural network
473+ // returns label=0 even though the camera sees real sky (or real ground). Regular
474+ // ShowOnly (mode=0) is unchanged so other semantic mask uses are not affected.
475+ " if (semantic_mask_mode < 0.5) {" ,
476+ " _output_color.a *= semMatches ? conf : 0.0;" ,
477+ " } else if (semantic_mask_mode > 2.5) {" ,
478+ " if (semMatches) {" ,
479+ " _output_color.a *= conf;" ,
480+ " } else if (semLabel == 0) {" ,
481+ " highp float normY = gl_FragCoord.y / ar_viewport_size.y;" ,
482+ " _output_color.a *= (normY > 0.5) ? 1.0 : 0.0;" ,
483+ " } else {" ,
484+ " _output_color.a *= 0.0;" ,
485+ " }" ,
486+ " } else {" ,
487+ " _output_color.a *= semMatches ? (1.0 - conf) : 1.0;" ,
488+ " }" ,
466489 " }" ,
467490 " }"
491+ // semInBounds == false: no semantic data available for this pixel β pass through unchanged.
468492 };
469493
470494 _semanticMaskModifier = std::make_shared<VROShaderModifier>(
0 commit comments