libgdx-3d-rendering — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited libgdx-3d-rendering (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick reference for the libGDX 3D pipeline (com.badlogic.gdx.graphics.g3d.*). Covers ModelBatch, Model/ModelInstance, model loading, ModelBuilder, Environment, lights, materials, attributes, PerspectiveCamera, AnimationController, and camera controllers.
gdx-gltf libraryDefaultShader.Config.numSpotLights defaults to 0DO NOT hallucinate PBR classes, shadow renderers, scene graphs, or built-in spot light shading.
NOT like SpriteBatch. No setProjectionMatrix() — camera is passed to begin().
ModelBatch modelBatch = new ModelBatch(); // uses DefaultShaderProvider, DefaultRenderableSorter
// In render():
modelBatch.begin(camera);
modelBatch.render(instance, environment); // RenderableProvider, not ModelInstance specifically
modelBatch.render(instances, environment); // Iterable<? extends RenderableProvider>
modelBatch.end(); // flushes, sorts, renders
// In dispose():
modelBatch.dispose(); // disposes ShaderProvider (cached shaders) onlyEnvironment passed to render() overrides whatever the RenderableProvider set. flush() can be called between begin()/end() to force intermediate draws. setCamera(Camera) switches cameras mid-batch (flushes first). DefaultRenderableSorter sorts opaque front-to-back, transparent back-to-front.
Gotchas:
dispose() only disposes the ShaderProvider's cached shaders — NOT the RenderContext, even if ModelBatch created it.begin() and end() — RenderContext manages it.ModelBatch.setProjectionMatrix() does NOT exist. The projection comes from the Camera passed to begin().Model implements Disposable. Fields: materials, nodes, animations, meshes, meshParts (all Array). Key methods: getAnimation("id"), getMaterial("id"), getNode("id") (all ignoreCase=true), calculateTransforms(), dispose().
Ownership: When loaded directly (not via AssetManager), Model owns all Meshes and Textures. dispose() destroys them. Multiple ModelInstances share the same Model — Model must outlive all its instances.
Implements RenderableProvider, NOT Disposable. The source Model owns GPU resources. Fields: model, transform (Matrix4, public non-final), materials (per-instance copies), nodes, animations, userData (Object).
Key constructors: new ModelInstance(model), (model, "nodeId1", "nodeId2") (selected root nodes), (model, x, y, z) (with translation), (copyFrom) (copies nodes/materials/animations).
Transform: instance.transform is a standard Matrix4 — use setToTranslation(), rotate(), scale(), setToTranslationAndScaling().
Per-instance materials — constructor deep-copies materials:
instance.getMaterial("mat_id").set(ColorAttribute.createDiffuse(Color.RED));
// Only affects this instance, not the Model or other instances.calculateTransforms(): Must be called manually if you modify Node translation/rotation/scale directly after construction. NOT needed if you only modify instance.transform.
G3dModelLoader — same class, different reader: new G3dModelLoader(new JsonReader()) for .g3dj, new G3dModelLoader(new UBJsonReader()) for .g3db. ObjLoader — for testing only, Javadoc warns "should not be used in production."
AssetManager (preferred) — .g3dj, .g3db, .obj loaders pre-registered:
assetManager.load("scene.g3db", Model.class);
assetManager.finishLoading();
Model model = assetManager.get("scene.g3db", Model.class);Texture ownership with AssetManager: AssetManager removes Textures from Model's managed disposables (manages them separately). When assetManager.unload("scene.g3db") is called, it disposes the Model (meshes) and tracks texture references independently. Default texture parameters: Linear filter, Repeat wrap.
Instance methods (not static). Every returned Model must be disposed.
ModelBuilder mb = new ModelBuilder();
long attr = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal;
Material mat = new Material(ColorAttribute.createDiffuse(Color.GREEN));
// Convenience methods: createBox, createSphere, createCylinder, createCone,
// createCapsule, createArrow, createLineGrid, createXYZCoordinates, createRect
Model box = mb.createBox(5f, 5f, 5f, mat, attr);Custom geometry via begin()/part()/end():
mb.begin();
MeshPartBuilder mpb = mb.part("partId", GL20.GL_TRIANGLES, attr, mat);
mpb.box(5f, 5f, 5f); // also: sphere, cylinder, cone, capsule, rect, line, triangle, vertex
Model model = mb.end();part() auto-creates a node if none exists. Only one part at a time. createRect requires explicit normal (4 corners + normal vector).
public class Environment extends AttributesEnvironment environment = new Environment();
environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
environment.add(new PointLight().set(1f, 1f, 1f, position, intensity));Lights are stored internally in DirectionalLightsAttribute, PointLightsAttribute, SpotLightsAttribute. Ambient light is a ColorAttribute with type ColorAttribute.AmbientLight.
`environment.add(light)` / `environment.remove(light)` — fluent API, returns this.
Environment.shadowMap field exists (type ShadowMap interface) but stock libGDX provides no implementation.
DirectionalLight (color, direction), PointLight (color, position, intensity), SpotLight (color, position, direction, intensity, cutoffAngle, exponent). All extend BaseLight<T>.
Gotchas:
set() overloads normalize direction. setDirection() does NOT normalize.set(float r, g, b, ...) overloads hardcode alpha to 1f.DefaultShader.Config.numSpotLights.Material extends Attributes (container). Attributes stores Attribute objects keyed by long type bitmasks.
Material mat = new Material(
ColorAttribute.createDiffuse(Color.BLUE),
ColorAttribute.createSpecular(Color.WHITE),
FloatAttribute.createShininess(16f)
);ColorAttribute types: Diffuse, Specular, Ambient, Emissive, Reflection (material), AmbientLight, Fog (environment). Critical: ColorAttribute.Ambient (material) and ColorAttribute.AmbientLight (environment) are different types.
TextureAttribute types: Diffuse, Specular, Bump, Normal, Ambient, Emissive, Reflection — factory methods accept Texture or TextureRegion.
Other attributes: BlendingAttribute(true, opacity), FloatAttribute.createShininess(), FloatAttribute.createAlphaTest(), IntAttribute.createCullFace() (0=no cull, GL_BACK=default), DepthTestAttribute.
PerspectiveCamera camera = new PerspectiveCamera(67f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.position.set(10f, 10f, 10f);
camera.lookAt(0, 0, 0);
camera.near = 1f; // default
camera.far = 300f; // default is only 100 — usually too small
camera.update(); // MUST call after any property changeFields: fieldOfView (67, vertical degrees), near (1), far (100 — increase for large scenes), position, direction, up. update() recomputes matrices and frustum — must call every frame if camera moves.
DefaultShader.Config config = new DefaultShader.Config();
config.numDirectionalLights = 2; // default
config.numPointLights = 5; // default
config.numSpotLights = 0; // default — spot lights DISABLED
config.numBones = 12; // default max bones for skeletal animation
ModelBatch modelBatch = new ModelBatch(new DefaultShaderProvider(config));Customize when:
numSpotLights > 0)CameraInputController (orbit) — extends GestureDetector. Left drag=orbit, right drag=pan, scroll/middle drag=zoom, WASD=move/rotate. target field is orbit center. Must call update() each frame.
FirstPersonCameraController — extends InputAdapter. WASD=move, QE=up/down, mouse drag=look. setVelocity(float), setDegreesPerPixel(float). Must call update() each frame.
Both require Gdx.input.setInputProcessor(controller).
AnimationController controller = new AnimationController(modelInstance);
controller.update(Gdx.graphics.getDeltaTime()); // MUST call every frameYou do NOT need to call `modelInstance.calculateTransforms()` — AnimationController calls it internally.
Key methods:
setAnimation(id) — immediate switch, plays once. (id, loopCount) — -1 = loop forever. (id, loopCount, speed, listener) — negative speed = reverse.animate(id, transitionTime) — crossfade. animate(id, loopCount, speed, listener, transitionTime) — full control.queue(...) — plays after current finishes (one slot). action(...) — one-shot overlay, resumes previous (throws if loopCount < 0).AnimationListener: onEnd(AnimationDesc) when loopCount reaches 0, onLoop(AnimationDesc) each loop boundary.
Key fields: current/queued/previous (AnimationDesc), paused (boolean), allowSameAnimation (default false — re-setting same animation preserves current time).
// create(): camera + modelBatch + environment + model/instance
camera = new PerspectiveCamera(67f, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
camera.position.set(10f, 10f, 10f); camera.lookAt(0,0,0); camera.far = 300f; camera.update();
modelBatch = new ModelBatch();
environment = new Environment();
environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
// render():
ScreenUtils.clear(0.3f, 0.3f, 0.3f, 1f, true); // 5th arg true = clear depth buffer (ESSENTIAL for 3D)
modelBatch.begin(camera);
modelBatch.render(instance, environment);
modelBatch.end();
// dispose(): modelBatch.dispose(); model.dispose(); // ModelInstance is NOT disposable| Resource | Owns | dispose() does |
|---|---|---|
Model (direct load) | Meshes + Textures | Disposes all managed disposables |
Model (AssetManager) | Meshes only | Textures managed by AssetManager |
ModelInstance | Nothing (NOT Disposable) | N/A — source Model owns GPU resources |
ModelBatch | Cached shaders | Disposes ShaderProvider only |
ModelBuilder | Nothing (NOT Disposable) | N/A — returned Models own resources |
begin(Camera). This is the #1 difference from SpriteBatch.ScreenUtils.clear(r, g, b, a, true) with the fifth boolean argument true for 3D scenes. Without depth clearing, 3D rendering produces artifacts.DefaultShader.Config.numSpotLights defaults to 0. SpotLights are silently ignored unless you increase this.Ambient is a per-material attribute. AmbientLight is an environment-level attribute. They have different type bitmasks.modelBatch.render().GdxRuntimeException("An action cannot be continuous"). Actions must have finite loop counts.Usage.Normal. Texturing requires Usage.TextureCoordinates. Missing flags produce black or untextured models.far is 100, which clips anything beyond that distance. Set to 300+ for typical scenes.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.