Your three examples are not all exactly alike.
In the last two:
if [ "${a}" == 0 ]; then ffmpeg -framerate 2 -pattern_type glob -i "*.png" -pix_fmt yuv420p output.mp4
If $a was not quoted, and its value contained characters of $IFS (by default space, tab and newline) or wildcard characters, this would cause [ to receive more than three arguments (beside the [ and ] ones), resulting in an error; similarly, if the value of $a was the empty string, this would cause [ to receive too few arguments:
$ (a=0; [ $a == 0 ] && echo OK) OK
(but only if $IFS currently happens not to contain 0)
$ (a='foo bar'; [ $a == 0 ] && echo OK) bash: [: too many arguments
(with the default value of $IFS)
$ (a=; [ $a == 0 ] && echo OK) bash: [: ==: unary operator expected
(even with an empty $IFS or with zsh (that otherwise doesn't implement that implicit split+glob operator upon unquoted expansions))
$ (a='*'; [ $a == 0 ] && echo OK) bash: [: too many arguments
(when run in a directory that contain at least 2 non-hidden files).
With quoting, no error:
$ (a='foo bar'; [ "$a" == 0 ] && echo OK) $ (a=; [ "$a" == 0 ] && echo OK)
Your first example is different. The rules about expansion within double quotes are special when arrays are involved; if a denotes an array, then:
$a is the first element of the array (sticktly speaking it's ${a[0]} even if the element at indice 0 is not defined);
${a[*]} or ${a[@]} are the elements of the array, additionally split at $IFS (space, tab, newline by default);
"${a[@]}" is the elements of the array, not split at $IFS.
So your loop for i in "${indices[@]}"; do ... does not actually work the same, depending on the contents of the array. For example:
$ (declare -a a=(a b c); printf '%s\n' $a) a $ (declare -a a=(a b c); printf '%s\n' ${a[*]}) a b c $ (declare -a a=(a 'b c'); printf '%s\n' ${a[*]}) a b c $ (declare -a a=(a 'b c'); printf '%s\n' ${a[@]}) a b c $ (declare -a a=(a 'b c'); printf '%s\n' "${a[*]}") a b c $ (declare -a a=(a 'b c'); printf '%s\n' "${a[@]}") a b c