Godot Vision Plugin
A Godot plugin that provides a unified GDScript interface for ML Kit face mesh detection on Android and iOS. Pass any Image to the Vision node and receive rich per-face data including 468 3-D landmark points, triangle mesh indices, and named facial contours — all in a single async call.
Key Features:
- Detect one or more faces in any
Imageusing ML Kit's Face Mesh API - Receive 468 normalised 3-D landmark points per face (
x/y∈ [0, 1],z= relative depth) - Access the full triangle mesh (indices into the landmark array) for each detected face
- Query individual named contours — face oval, eyes, eyebrows, lips, and nose bridge — via
FaceMeshInfoconstants - Built-in wireframe drawing utilities: overlay a mesh on an existing image or generate a transparent mesh layer for compositing
- Typed result and error classes (
FaceScanResult,FaceMeshInfo,ScanError) for clean, idiomatic GDScript
Table of Contents
- Installation
- Usage
- Signals
- Methods
- Classes
- Platform-Specific Notes
- Links
- All Plugins
- Credits
- Contributing
Installation
_Before installing this plugin, make sure to uninstall any previous versions of the same plugin._
_If installing both Android and iOS versions of the plugin in the same project, then make sure that both versions use the same addon interface version._
There are 2 ways to install the Vision plugin into your project:
- Through the Godot Editor's AssetLib
- Manually by downloading archives from Github
Installing via AssetLib
Steps:
- search for and select the
Visionplugin in Godot Editor - click
Downloadbutton - on the installation dialog...
- keep
Change Install Foldersetting pointing to your project's root directory - keep
Ignore asset rootcheckbox checked - click
Installbutton
- keep
- enable the plugin via the
Pluginstab ofProject->Project Settings...menu, in the Godot Editor
When installing via AssetLib, the installer may display a warning that states "_[x number of]_ files conflict with your project and won't be installed." You can ignore this warning since both versions use the same addon code.
Installing manually
Steps:
- download release archive from Github
- unzip the release archive
- copy to your Godot project's root directory
- enable the plugin via the
Pluginstab ofProject->Project Settings...menu, in the Godot Editor
Usage
Add a Vision node to your main scene or an autoload global scene.
- connect
Visionnode signals before callingscan_face()face_mesh_ready(result: FaceScanResult)— emitted when detection succeedsface_mesh_failed(error: ScanError)— emitted when detection fails
- call
vision.scan_face(image)with anyImage; the image is automatically converted toFORMAT_RGBA8if needed - in the
face_mesh_readycallback, iterate overresult.get_faces()to access per-face landmark data - optionally use the static drawing helpers to render the mesh directly onto an image
Basic face scan example:
@onready var vision := $Vision
func _ready() -> void:
vision.face_mesh_ready.connect(_on_face_mesh_ready)
vision.face_mesh_failed.connect(_on_face_mesh_failed)
func scan(image: Image) -> void:
vision.scan_face(image)
func _on_face_mesh_ready(result: FaceScanResult) -> void:
print("Detected %d face(s)" % result.get_face_count())
for face in result.get_faces():
print(" Points: %d, Triangles: %d" % [face.get_points().size(), face.get_triangles().size()])
func _on_face_mesh_failed(error: ScanError) -> void:
print("Scan failed [%s]: %s" % [error.get_code(), error.get_description()])
Drawing the mesh overlay onto an image:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
# Returns a new Image with the wireframe painted on top
var annotated: Image = Vision.draw_face_mesh_on_image(original_image, result, Color.CYAN)
$TextureRect.texture = ImageTexture.create_from_image(annotated)
Generating a transparent mesh layer for compositing:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
var mesh_layer: Image = Vision.generate_face_mesh_image(
result, original_image.get_width(), original_image.get_height(), Color.LIME)
$MeshOverlay.texture = ImageTexture.create_from_image(mesh_layer)
Accessing individual contours:
func _on_face_mesh_ready(result: FaceScanResult) -> void:
var face: FaceMeshInfo = result.get_face(0)
if face:
var oval: Array = face.get_contour(FaceMeshInfo.CONTOUR_FACE_OVAL)
for point in oval:
print("Oval point: ", point) # Vector3 — x/y normalised, z = depth
Signals
Register listeners to the following signals of the Vision node:
| Signal | Description |
|---|---|
face_mesh_ready(result: FaceScanResult) |
Emitted when face mesh detection succeeds. result contains the image dimensions and an array of FaceMeshInfo objects, one per detected face. |
face_mesh_failed(error: ScanError) |
Emitted when detection fails. error contains a ScanError.Code enum value and a human-readable description. |
Methods
Vision node methods
| Method | Description |
|---|---|
scan_face(a_image: Image) -> void |
Sends a_image to the native plugin for ML Kit face mesh detection. The image is automatically converted to FORMAT_RGBA8 internally. Results are delivered asynchronously via the face_mesh_ready or face_mesh_failed signals. |
Static drawing utilities
These are pure-GDScript helpers that operate on a FaceScanResult returned by the face_mesh_ready signal. They require no scene nodes or rendering server calls.
| Method | Description |
|---|---|
Vision.generate_face_mesh_image(a_result: FaceScanResult, a_width: int, a_height: int, a_color: Color) -> Image |
Creates a new FORMAT_RGBA8 image of the given dimensions with a fully transparent background, then draws the face mesh wireframe in a_color. Use this to composite the mesh as a separate layer on top of your camera frame. |
Vision.draw_face_mesh_on_image(a_original: Image, a_result: FaceScanResult, a_color: Color) -> Image |
Returns a new FORMAT_RGBA8 copy of a_original with the face mesh wireframe painted on top in a_color. The original image is not modified. |
Classes
FaceScanResult
Encapsulates the full result of a scan_face() call. Received via the face_mesh_ready signal.
| Method | Return type | Description |
|---|---|---|
get_image_width() |
int |
Width (px) of the image that was scanned. |
get_image_height() |
int |
Height (px) of the image that was scanned. |
get_face_count() |
int |
Number of faces detected in the image. |
get_face(a_index: int) |
FaceMeshInfo |
Returns the FaceMeshInfo at the given index, or null if out of range. |
get_faces() |
Array |
Returns all detected faces as an Array of FaceMeshInfo objects. |
is_valid() |
bool |
Returns true when all required fields are present in the result. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
FaceMeshInfo
Encapsulates the mesh data for a single detected face.
| Method | Return type | Description |
|---|---|---|
get_points() |
Array |
All 468 landmark points as Vector3. x and y are normalised to [0, 1]; z is relative depth. |
get_triangles() |
Array |
All mesh triangles as Vector3i. Each component (x, y, z) is an index into the get_points() array. |
get_contours() |
Dictionary |
Raw contours dictionary keyed by contour name. Values are arrays of [x, y, z] sub-arrays. |
get_contour(a_contour_name: String) |
Array |
Points of a named contour as Array of Vector3. Use the CONTOUR_* constants as the name argument. |
is_valid() |
bool |
Returns true when points, triangles, and contours are all present. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
Contour name constants (pass to get_contour()):
| Constant | Facial region |
|---|---|
CONTOUR_FACE_OVAL |
Outer boundary of the face |
CONTOUR_LEFT_EYE |
Left eye outline |
CONTOUR_LEFT_EYEBROW_BOTTOM |
Bottom edge of the left eyebrow |
CONTOUR_LEFT_EYEBROW_TOP |
Top edge of the left eyebrow |
CONTOUR_LOWER_LIP_BOTTOM |
Outer bottom edge of the lower lip |
CONTOUR_LOWER_LIP_TOP |
Inner top edge of the lower lip |
CONTOUR_NOSE_BRIDGE |
Bridge of the nose |
CONTOUR_RIGHT_EYE |
Right eye outline |
CONTOUR_RIGHT_EYEBROW_BOTTOM |
Bottom edge of the right eyebrow |
CONTOUR_RIGHT_EYEBROW_TOP |
Top edge of the right eyebrow |
CONTOUR_UPPER_LIP_BOTTOM |
Inner bottom edge of the upper lip |
CONTOUR_UPPER_LIP_TOP |
Outer top edge of the upper lip |
ScanError
Encapsulates error information delivered via the face_mesh_failed signal.
| Method | Return type | Description |
|---|---|---|
get_code() |
ScanError.Code |
One of the Code enum values below. |
get_description() |
String |
Human-readable description of the error. |
ScanError.Code enum values:
| Value | Meaning |
|---|---|
NONE |
No error. |
INVALID_IMAGE |
The supplied image could not be processed. |
NO_CODE_DETECTED |
No face was detected in the image. |
SCANNER_FAILURE |
The underlying ML Kit scanner returned a failure. |
INTERNAL_ERROR |
An unexpected internal error occurred. |
ImageInfo
Wraps a Godot Image as a serialisable dictionary for transfer to the native plugin. Used internally by scan_face() — you do not normally need to use this class directly.
| Method | Return type | Description |
|---|---|---|
ImageInfo.create_from_image(a_image: Image) (static) |
ImageInfo |
Creates an ImageInfo from a Godot Image. |
get_buffer() |
PackedByteArray |
Raw pixel bytes of the image. |
get_width() |
int |
Image width in pixels. |
get_height() |
int |
Image height in pixels. |
get_format() |
Image.Format |
Pixel format of the image. |
has_mipmaps() |
bool |
Whether the image has mipmaps. |
is_valid() |
bool |
Returns true when width, height, and buffer are all present. |
get_raw_data() |
Dictionary |
Returns the underlying raw data dictionary. |
Platform-Specific Notes
Android
- Download Android export template and enable gradle build from export settings
- Troubleshooting:
- Logs:
adb logcat | grep 'godot'(Linux),adb.exe logcat | select-string "godot"(Windows) - You may find the following resources helpful:
- https://docs.godotengine.org/en/stable/tutorials/export/exporting_for_android.html
- https://developer.android.com/tools/adb
- https://developer.android.com/studio/debug
- https://developer.android.com/courses
iOS
- Follow instructions on Exporting for iOS
- View XCode logs while running the game for troubleshooting.
- See Godot iOS Export Troubleshooting.
Links
Changelog for version v1.0-Multi
No changelog provided for this version.