My approach to map species occurrences is: to download GBIF data for species and their synonyms, merge them and run a cleanup. Within R there are rgbif and CoordinateCleaner packages as a helpers. Then the process looks like below (sticking to vectorized data/maps)
As first step we have to prepare the grid, which be used as a occurrence counter:
sf::sf_use_s2(FALSE)
data(World, package = "tmap")
all <- World |>
dplyr::summarize() |>
terra::vect() |>
terra::fillHoles() |>
sf::st_as_sf()
grid <- sf::st_make_grid(all, cellsize = 3, what = "polygons", square = FALSE) |>
sf::st_intersection(all) |>
sf::st_as_sf()
grid |>
plot(lwd = 0.3)

Having the grid ready we can import species data:
p <- sf::st_read("/home/sapi/prunus_serotina_ehrh.gpkg",
layer = "prunus_serotina_ehrh")
#> Simple feature collection with 183634 features and 21 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: -123.3403 ymin: -43.66203 xmax: 177.7184 ymax: 60.67848
#> Geodetic CRS: WGS 84
Let's show them on the map:
tmap::tm_shape(grid) +
tmap::tm_polygons() +
tmap::tm_shape(p) +
tmap::tm_dots()

Now, we are counting how many occurrences are in particular grid. A small explanation: st_intersects() returns FALSE/TRUE, and lengths() of it counts how many TRUE values was there.
grid$num_points <- lengths(sf::st_intersects(grid, p))
And time to draw them on the map. As you are interested only to show presence/absence without counts it's enough to show them as gray polygons without any scale:
g <- grid |>
subset(num_points > 0)
tmap::tm_shape(all) +
tmap::tm_borders() +
tmap::tm_shape(g) +
tmap::tm_polygons()

If your data (species range) is in form of polygons, then the similar approach will work, as long as the polygons will intersects with grid. Just make sure both have the same CRS.
If your desired output is a raster, then create one, like:
r <- terra::rast(nrows=180, ncols=360, xmin=-180, xmax=180,
ymin=-90, ymax=90, crs = "EPSG:4326", vals = NA)
and rasterize your data:
s <- terra::rasterize(terra::vect(p), r)
terra::plot(s)

Created on 2024-01-21 with reprex v2.0.2
Rasterizing of polygons works the same way, just have a look on cover option in terra::rasterize() function. And maybe touches as well.
If there is 11000 species, create a list of input files with list.files() function and either create a function for lapply(), either a for() {} loop. Plot the resulting graphics with png() function + dev.off(). Or with tmap::save() or whatever you will use.