Highlighted Changes in Ardor3D 1.5 - Renanse/Ardor3D GitHub Wiki
The goal of version "1.5" of Ardor3D is to provide an OpenGL 3.3+ forward-compatible rendering engine, porting as many features of the “1.0” engine as possible.
The MeshData class in Ardor3D provides a way to package together per-vertex data for a Mesh. In the past, this data was stored as individual, hardcoded buffer fields that mapped to vertex properties handled in fixed-function OpenGL (position, normal, texture coordinates, etc.) This hard-coded approach meant that user-defined vertex properties (for example, per-vertex bone weights for skeletal animation) had to be handled separately.
In addition, previously a DataMode scene hint instructed Ardor3D on how this data was sent to the card - either as Arrays or as Vertex Buffer Objects (VBO). Arrays send data through on each render whereas VBO data would be sent once to the card and referenced by id on subsequent renders, updating only if flagged that the data was dirty.
Modern OpenGL does away with Arrays, so all data in Ardor3D is now handled via VBO. Per-vertex data is still kept in MeshData but is now stored in a map that is keyed by a String. Old methods for referencing vertex information (getVertexCoords
, getNormalCoords
, etc.) are maintained for convenience but internally access the map using keys predefined as constants in MeshData.
In addition, MeshData sent to the card is now wrapped up in a Vertex Array Object during rendering. This approach allows referencing all contained data with a single id and can reduce the amount of data sent to the card each frame, improving performance. The side effect, however, is that code modifying vertex data must flag the buffer (and MeshData itself) as being dirty.
You can mark individual AbstractBufferData objects dirty via their markDirty()
method. Keep in mind that you will need to also mark the MeshData as dirty via markBuffersDirty()
.
meshData.getVertexCoords().markDirty();
meshData.markBuffersDirty(); // tells Ardor3D that one or more buffers need updates.
Alternatively, you can do both at once by calling markBufferDirty(key)
on MeshData, providing the key used for the buffer you are changing. This is the recommended approach.
meshData.markBufferDirty(MeshData.KEY_VertexCoords);
Previous versions of Ardor3D allowed storing a single Object on a Spatial via getUserData(Object)
. Similar to MeshData’s new per-vertex properties, this user data field has been replaced with a Map<String, Object>
. The old methods currently remain for convenience, making use of the constant Spatial.KEY_UserData, but are marked as deprecated.
Properties are set on a Spatial using the setProperty(String, Object)
method. Properties can be retrieved from a Spatial using either getLocalProperty(String)
or getProperty(String)
. The former will return only properties locally set on the given Spatial whereas the latter will use a new scene hint called PropertyMode to determine if and how it should look at parent properties in the Scene when determining how to retrieve a property value.
Moving to a Map of properties allows us to store useful data locally to the scenegraph and provides one possible source of values for shader uniforms during rendering. See the Material System section below for more on that.
Currently, properties are not stored during Savable export, but the intention is to do so in the future.
Ardor3D’s RenderState classes were originally designed to mirror the state of OpenGL itself and assist in reducing the number of calls made by the engine to change that state. Modern OpenGL removes a large amount of this state information, expecting relevant information to be passed directly to the shaders in use. Therefore, we are removing several RenderState classes to mirror this change.
State | Replacement Strategy |
---|---|
Material | Supply material face color and shininess to an appropriate shader as a ColorSurface uniform. |
Shading | Turn on Flat shading via the use of the flat keyword on in/out shader variables. |
Fog | Supply fog properties to the shader via a FogParams uniform supplier. |
Clip | Supply clip planes to the shader as uniform and test fragments + discard. |
FragmentProgram / VertexProgram | The old ARB assembly language style shader programs are obsolete and have no direct replacement in Ardor3D 1.5. They will need to be rewritten in GLSL and accessed via the Material System. |
GLSLShader | Use the Material System. |
Light | Lights are now Spatial objects and can be added directly to the scene. Other Spatials in the scene can be marked as capable of receiving light via the SpatialProperty "_receiveLight". (See convenience method LightProperties.setLightReceiver(boolean) .) By default, this is true unless specifically set to false. The flag indicates to the engine that it should spend time determining which lights would have the most effect on a given Spatial. Note that the material for a mesh must also integrate light data for the lighting to have any effect during rendering. |
State | Notes |
---|---|
Texture | 1. Adding a texture to the TextureState will no longer automatically make use of it - this depends on the Material used and how it accesses Textures. 2. Textures no longer have EnvironmentalMapMode , ApplyMode , or any fields related to ApplyMode - these types of operations should be done by the shader itself.3. Textures no longer have a TextureMatrix . Use uniform values to supply desired matrices to the Material.4. "Disabling" the Texture state does not automatically remove texturing - instead, it means the associated Texture(s) will not be sent to the card. This could result in bleeding of other Textures in the scene or other unexpected behavior if the current Material and Shader do not take this into account. |
The following RenderStates should work as expected during rendering: Blend, Wireframe, ZBuffer, Cull, Stencil, ColorMask and Offset.
With the move to a Shader-based modern OpenGL pipeline, there was a need to greatly improve the handling of OpenGL shaders and how data are supplied to shaders. In Ardor3D 1.5, this is now done with the new classes: RenderMaterial, MaterialTechnique, and TechniquePass.
RenderMaterial represents a style of drawing a renderable in Ardor3D and consists of one or more techniques.
MaterialTechnique represents one method of rendering the given material style. The technique used is decided based on a scoring system. This scoring system is not currently implemented so the first technique is always used. The future intent is that the technique will provide scoring logic.
Techniques consist of one or more passes. These passes are executed in the order they are listed in the technique.
TechniquePass contains the render information necessary to draw a renderable once. This information includes shaders, attributes, and uniforms.
A pass must at least have shaders assigned for ShaderType.Vertex and ShaderType.Fragment. It can currently have up to five shaders - one for each shader type. Shaders can be set on passes as a String, a List of Strings (concatenated by the driver) or an InputStream.
Attributes are per-vertex data provided via information stored in a MeshData object and referenced by a String key. This relationship is described via the VertexAttributeRef class, which maps either a specific shader variable by name or a specific attribute location, to an AbstractBufferData object stored in MeshData by a String key. This data may be traditional information familiar to fixed function rendering such as vertex positions, per-vertex normal vectors, and texture coordinates, or less traditional information such as per-vertex weights, particle influences, and much more.
Uniforms are data provided to the shader once per render pass. This data can come from a variety of sources, including static values, Spatial properties, Render matrices, engine state values, or even a user-provided function. Uniforms can also provide default values. This relationship is described via the UniformRef class.
While a bit laborious and less reusable than Material Files (see below), you can build shaders directly in Java code. Below is a simple example of a material that draws a Mesh in solid white. We provide a single attribute, vertex position, and three matrix uniforms for the mesh’s world transform, camera transform and screen space transform. The vertex shader simply transforms the coordinate into the screen. The fragment shader simply returns a white pixel.
RenderMaterial material = new RenderMaterial();
MaterialTechnique technique = new MaterialTechnique();
material.addTechnique(technique);
TechniquePass pass = new TechniquePass();
technique.addPass(pass);
pass.addAttribute(new VertexAttributeRef(MeshData.KEY_VertexCoords));
pass.addUniform(new UniformRef("model", UniformType.Matrix4x4,
UniformSource.RendererMatrix, RenderMatrixType.Model));
pass.addUniform(new UniformRef("view", UniformType.Matrix4x4,
UniformSource.RendererMatrix, RenderMatrixType.View));
pass.addUniform(new UniformRef("projection", UniformType.Matrix4x4,
UniformSource.RendererMatrix, RenderMatrixType.Projection));
final String vertexShader = //
"#version 330 core\n" + //
"in vec3 vertex;\n" + //
"uniform mat4 model, view, projection;\n" + //
"void main() {\n" + //
" gl_Position = projection * view * model * vec4(vertex, 1.0);\n" + //
"}";
pass.setShader(ShaderType.Vertex, vertexShader);
final String fragmentShader = //
"#version 330 core\n" + //
"out vec4 FragColor;\n" + //
"void main() {\n" + //
" FragColor = vec4(1.0, 1.0, 1.0, 1.0);\r\n" + //
"}";
pass.setShader(ShaderType.Fragment, fragmentShader);
Here is that same simple material as a YAML file. We go into more detail on the format below.
---
techniques:
passes:
-
attributes:
- key: vertex
uniforms:
- builtIn: [model, view, projection]
shaders:
Vertex:
program: |
#version 330 core
in vec3 vertex;
uniform mat4 model, view, projection;
void main() {
gl_Position = projection * view * model * vec4(vertex, 1.0);
}
Fragment:
program: |
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}</td>
</tr>
</table>
The YAML-based file format Ardor3D uses to describe render materials has several features designed to make things easier and more extensible. We go into these below. How YAML works, in general, is not covered here, but there are excellent resources online such as yaml.org.
Reading in a .yaml file is done via the YamlMaterialReader
class in ardor3d-core. Ardor3d’s included material files currently only supply one material per file, however, each file can contain more than one if desired. You can then read the file with the reader and pull out the material by index or by looking for a particular name.
It is important to note that when supplying the name of a .yaml file to Spatial.setRenderMaterial
, only the first material in the file is used.
Each material in the material file is laid out in as a list of 1 or more techniques, each with 1 or more passes. Each of these passes is laid out in 3 major sections: attributes, uniforms, and shaders.
The attributes
section of the YAML file provides information about the connection of MeshData buffers to vertex shader inputs. This section is a sequence of mapping nodes, each describing a single shader input.
The following keys are available in each mapping node:
Key | Purpose |
---|---|
key | String: Used for both meshKey and shaderKey, if either, or both, are not specified. |
location | Integer: The specific vertex shader stage input location to map to, regardless of the variable name used in the shader. If this is specified, shaderKey should not be. The vertex shader should have a matching layout (location=X) where X is the same integer supplied here in the yaml. |
meshKey | String: The key used to pull the desired AbstractDataBuffer from our MeshData. Defaults to any value supplied to key (above) if not supplied. |
shaderKey | String: The variable name to match to in the vertex shader. Ignored if location is supplied. |
span | Integer: Number of tuples that combine to form a single attribute. Default is 1. |
stride | Integer: Specifies the offset between consecutive generic vertex attributes. This value will be converted to bytes by the engine. If stride is 0, the generic vertex attributes are understood to be tightly packed in the array. See also glVertexAttribPointer. Default is 0. |
offset | Integer: Specifies the offset into the buffer, in tuples, to find the first value for this attribute. This value will be converted to bytes by the engine. Default is 0. |
divisor | Integer: Used when drawing multiple instances of a mesh in a single draw call. See also glVertexAttribDivisor. Default is 0. |
normalized | Boolean: Specifies whether fixed-point data values should be normalized (true) or converted directly as fixed-point values (false). See also glVertexAttribPointer. Default is false. |
attributes:
- key: vertex
-
meshKey: normal
shaderKey: in_normal
-
meshKey: color
location: 2
-
key: uv0
location: 4
#version 330 core
in vec3 vertex;
in vec3 in_normal;
layout (location = 2) in vec4 in_color;
layout (location = 4) in vec2 texCoord;
The uniforms
section of the YAML file provides information on how to provide data to shader uniform variables. This section is a sequence of mapping nodes, each describing a single shader uniform. The shader uniforms can be in any of the shaders provided to the material.
The following keys are available in each mapping node:
Key | Purpose | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
shaderKey | String: The variable name to match to in the vertex shader. Ignored if location is supplied. | ||||||||||||
location | Integer: The specific uniform buffer location to map to, regardless of the variable name used in the shader. If this is specified, shaderKey should not be. (note, this feature is not fully tested.) | ||||||||||||
type | Enum: The data type of the uniform. Defaults to `Int1`
One of: |
||||||||||||
source | Enum: The source of data for the uniform. Defaults to `Value`
One of:
|
||||||||||||
value | The value of the data. Appropriate content depends on the value of source. See source table for additional information. | ||||||||||||
extra | Some combinations of source and value require an additional parameter. For example, Ardor3dState + Light needs to know the index of the light in question. Extra is used to provide this information. | ||||||||||||
defaultValue | The numeric value to use if a particular data source does not exist to read from - for example, if a SpatialProperty is missing. |
Source: Value Note: "source" key is optional since the default is “Value”.
uniforms:
-
shaderKey: cubeMap
type: Int1
value: 0
-
shaderKey: reflectMat
type: Matrix4x4
value: [[-1, 0, 0, 0],
[ 0, 1, 0, 0],
[ 0, 0, 1, 0],
[ 1, 0, 0, 1]]
Source: SpatialProperty Note: shaderKey is used for the spatial property key as well, since value is not specified.
uniforms:
-
shaderKey: roughness
type: Float1
source: SpatialProperty
Source = RendererMatrix Note: See "builtin" section below for a more compact way to specify these common uniforms.
uniforms:
-
shaderKey: model
type: Matrix4x4
source: RendererMatrix
value: Model
Source = Ardor3dState Note: See Ardor3dStateProperty enum for possible values and their appropriate type.
uniforms:
-
shaderKey: defaultColor
type: Float4
source: Ardor3dState
value: MeshDefaultColor
We have added some optional shortcuts for often-used uniform settings such as render matrices. These shortcuts are accessed by adding the key "builtIn" to the uniforms section, followed by a list of shortcut values. These are shorthand for fully expressed uniform nodes.
Example
uniforms:
- builtIn: [model, view, projection]
List of current available built in values:
Shortcut Value | Fully Expanded Form |
---|---|
view | -
shaderKey: view
type: Matrix4x4
source: RendererMatrix
value: View |
model |
-
shaderKey: model
type: Matrix4x4
source: RendererMatrix
value: Model |
projection | -
shaderKey: projection
type: Matrix4x4
source: RendererMatrix
value: Projection |
normalMat | -
shaderKey: normalMat
type: Matrix3x3
source: RendererMatrix
value: Normal |
modelViewProj | -
shaderKey: modelViewProj
type: Matrix4x4
source: RendererMatrix
value: ModelViewProjection |
modelViewProjection | -
shaderKey: modelViewProjection
type: Matrix4x4
source: RendererMatrix
value: ModelViewProjection |
textureMatrix0 | -
shaderKey: textureMatrix0
type: Matrix4x4
source: SpatialProperty
value: textureMatrix0
defaultValue: [[1, 0, 0, 0][0, 1, 0, 0][0, 0, 1, 0][0, 0, 0, 1]] |
textureMatrix1 | -
shaderKey: textureMatrix1
type: Matrix4x4
source: SpatialProperty
value: textureMatrix1
defaultValue: [[1, 0, 0, 0][0, 1, 0, 0][0, 0, 1, 0][0, 0, 0, 1]] |
textureMatrix2 | -
shaderKey: textureMatrix2
type: Matrix4x4
source: SpatialProperty
value: textureMatrix2
defaultValue: [[1, 0, 0, 0][0, 1, 0, 0][0, 0, 1, 0][0, 0, 0, 1]] |
textureMatrix3 | -
shaderKey: textureMatrix3
type: Matrix4x4
source: SpatialProperty
value: textureMatrix3
defaultValue: [[1, 0, 0, 0][0, 1, 0, 0][0, 0, 1, 0][0, 0, 0, 1]] |
viewSize | -
shaderKey: viewSize
type: Float2
source: Ardor3dState
value: CurrentViewportSizePixels |
viewOffset | -
shaderKey: viewOffset
type: Float2
source: Ardor3dState
value: CurrentViewportOffsetPixels |
cameraLoc | -
shaderKey: cameraLoc
type: Float3
source: Ardor3dState
value: CurrentCameraLocation |
defaultColor | -
shaderKey: defaultColor
type: Float4
source: Ardor3dState
value: MeshDefaultColorRGBA |
defaultColorRGB | -
shaderKey: defaultColorRGB
type: Float3
source: Ardor3dState
value: MeshDefaultColorRGB |
colorSurface | -
shaderKey: surface
type: UniformSupplier
source: SpatialProperty
extra: com.ardor3d.surface.ColorSurface |
pbrSurface | -
shaderKey: surface
type: UniformSupplier
source: SpatialProperty
extra: com.ardor3d.surface.PbrSurface |
pbrTexturedSurface | -
shaderKey: surface
type: UniformSupplier
source: SpatialProperty
extra: com.ardor3d.surface.PbrTexturedSurface |
fogParams | -
shaderKey: fogParams
type: UniformSupplier
source: SpatialProperty
extra: com.ardor3d.renderer.material.fog.FogParams |
lights | -
shaderKey: lightProps
type: UniformSupplier
source: Ardor3dState
value: LightProperties |
alphaTest | -
shaderKey: alphaTestType
type: Int1
source: SpatialProperty
defaultValue: 7
-
shaderKey: alphaReference
type: Float1
source: SpatialProperty
defaultValue: 0 |
Oftentimes information we wish to send to the shader may be grouped logically into a class on the Java side. Enabling that class to directly provide uniform information to the shader can reduce the verbosity of our material files or code. In Ardor3d 1.5, this can be done via implementing the IUniformSupplier
interface and sending the class as a single uniform using the UniformType, UniformSupplier
.
Ardor3d takes these classes and calls the interface method, getUniforms, to expand it into multiple uniform refs. During rendering, the original name of the supplier uniform is prepended to the name of its supplied uniforms, so you will need to provide a matching struct and variable in the shader code to receive the value.
In addition, if no instance of the object is found by the material to supply values, a new instance of the class is created, cached at the uniform instance level, and used to provide default values to the shader. The instance will have the method applyDefaultUniformValues
called after creation to set it up for this use.
Below is an example of a class and material using the IUniformSupplier. Imagine newing up an instance of the class ColorSurface below and adding it as a spatial property under the key "surface". The single “surface” uniform in the material below will get expanded into 4 uniforms, pulling their data directly from the class instance methods.
Class implementing IUniformSupplier
public class ColorSurface implements IUniformSupplier {
public final ColorRGBA ambient = new ColorRGBA(0.1f, 0.1f, 0.1f, 1f);
public final ColorRGBA diffuse = new ColorRGBA(.5f, .5f, .5f, 1f);
public final ColorRGBA specular = new ColorRGBA(1f, 1f, 1f, 1f);
public float shininess = 32f;
protected final List<UniformRef> cachedUniforms = new ArrayList<>();
public ColorSurface() {
cachedUniforms.add(new UniformRef("ambient", UniformType.Float3, UniformSource.Supplier, (Supplier<ReadOnlyColorRGBA>) this::getAmbient));
cachedUniforms.add(new UniformRef("diffuse", UniformType.Float3, UniformSource.Supplier, (Supplier<ReadOnlyColorRGBA>) this::getDiffuse));
cachedUniforms.add(new UniformRef("specular", UniformType.Float3, UniformSource.Supplier, (Supplier<ReadOnlyColorRGBA>) this::getSpecular));
cachedUniforms.add(new UniformRef("shininess", UniformType.Float1, UniformSource.Supplier, (Supplier<Float>) this::getShininess));
}
@Override
public void applyDefaultUniformValues() {}
public ColorRGBA getAmbient() {
return ambient;
}
public ColorRGBA getDiffuse() {
return diffuse;
}
public ColorRGBA getSpecular() {
return specular;
}
public float getShininess() {
return shininess;
}
@Override
public List<UniformRef> getUniforms() {
return cachedUniforms;
}
}
Material using the class
---
techniques:
passes:
-
attributes:
- key: vertex
- key: normal
uniforms:
- builtIn: [model, view, projection, normalMat, cameraLoc, defaultColor, lights, alphaTest]
-
shaderKey: surface
type: UniformSupplier
source: SpatialProperty
extra: com.ardor3d.surface.ColorSurface
shaders:
Vertex:
source: phong/phong.vert
Fragment:
source: phong/phong_modulate.frag
The shaders
section of the YAML file provides the source of the shader code to be sent to the OpenGL driver. Shader code is provided under a node named for the type of shader and can either be directly provided as text in the YAML file, or referenced as an external file.
Shader code is provided directly in the YAML via the use of the program key. An external file can be used instead by the use of the source or sources key (the latter taking a list of source files.) External files are located by Ardor3D via the standard lookup mechanism provided by ResourceLocator, using the resource type ResourceLocatorTool.TYPE_SHADER
.
Example
shaders:
Vertex:
source: pbr/pbr.vert
Fragment:
program: |
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
Additional mechanisms are available in this section as well that provide greater flexibility and allow you to create libraries of reusable shader code. These are described below.
The first mechanism is the ability to provide one or more chunks of code for injection into the associated shader. This injection is done via the keywords inject(s) and define(s).
inject
takes a string or list of strings (injects) and includes them as new lines after the #version line in the shader. The lines are included in the order provided.
define
is the same as inject except that it automatically places #define before the text provided. This keyword is provided purely as a way to allow the YAML files to be terser.
Example
...
shaders:
Vertex:
source: ui/ui_textured.vert
define: VERTEX_COLORS
Fragment:
source: ui/ui_textured.frag
define: VERTEX_COLORS
Shader output: note that the 2nd line is injected by Ardor3D
#version 330 core
#define VERTEX_COLORS
in vec3 vertex;
in vec2 uv0;
#ifdef VERTEX_COLORS
in vec4 color;
#endif
...
The other mechanism provided is the import directive. This directive allows injecting source code from other shader files into an arbitrary position of other shader source and is done via a keyword and relative path. The keyword is defined in Ardor3D in the static field MaterialManager.ImportMarker and is "@import"
by default.
MaterialManager provides the method inflateShaderImports(String) to properly inflate any imports found in a given chunk of shader code, thus allowing this mechanism to be used outside of YAML as well.
Example
#version 400 core
@import include/alpha_test.glsl
@import include/phong_lighting.glsl
#ifdef USE_FOG
@import include/fog.glsl
#endif
...
Previously, Ardor3D’s TextureRenderer
supported two different implementations: FBO and Pbuffer. The actual choice between these implementations was abstracted away via Ardor3D’s TextureRendererFactory
. This approach meant priming the factory with the correct TextureRenderer implementation prior to usage and also resulted in an odd situation where you could technically provide different OpenGL bindings for the creation of the TextureRenderer target versus rendering of content to that target.
With the move to OpenGL 3.3+ and forward compatibility, Pbuffer is no longer supported, simplifying TextureRenderer implementation and creation. Now, TextureRenderers are created directly from a Renderer. There is only a single method for doing this in Renderer, reducing confusion over which settings provided actually have an effect. Methods that only had a material effect when the backend was a Pbuffer have also been removed as has the TextureRendererFactory class.
Old code:
TextureRendererFactory.INSTANCE.setProvider(new LwjglTextureRendererProvider());
DisplaySettings settings = new DisplaySettings(512, 512, 24, 0, 0, 24, 0, 0, false, false);
TextureRenderer texRend = TextureRendererFactory.INSTANCE
.createTextureRenderer(settings, false, renderer,
ContextManager.getCurrentContext().getCapabilities());
New code:
TextureRenderer texRend = renderer.createTextureRenderer(512, 512, 24, 0);
In addition, it is now possible to resize an existing TextureRenderer on the fly, allowing for reuse. This functionality is best used in situations where you are doing one-off renders, such as generating diffuse maps, etc. as it would be expensive to resize every frame.
There are two Texture related changes worth noting here. Textures now have a field for specifying the mip level to render into during Render to Texture operations. This can be set via the method setTexRenderMipLevel(int)
. Also, in the past, any time we rendered to a Texture using a TextureRenderer and the Texture used a minification filter that used mipmaps, we would automatically generate new mipmaps. When rendering to a specific mipmap, however, this functionality may not be desired. You can set the behavior you desire on the TextureRenderer using the method setEnableMipGeneration(boolean)
. The default is true.
The LWJGL2 based renderer has been replaced with an LWJGL3 renderer under the subproject ardor3d-lwjgl3. Right now this includes:
- A GLFW based implementation of Ardor3D’s
NativeCanvas
. - A headless version of the above.
- A GLFW based implementation of Ardor3D’s
KeyboardWrapper
,MouseWrapper
, andMouseManager
. - An LWJGL3 based implementation of Ardor3D’s
Renderer
andTextureRenderer
.
Additional Canvas Types:
- A
java.awt.Canvas
based Lwjgl canvas is found in subproject ardor3d-lwjgl3-awt. - An SWT based Lwjgl canvas is found in subproject ardor3d-lwjgl3-swt.
Intended future support here includes:
- Android support
The JOGL renderer has been dropped in Ardor3D 1.5, though the core of the engine has been left renderer-agnostic so as to allow re-adding JOGL (or some other binding) in the future. The main reasons for dropping JOGL support include:
- Lack of customer usage - current users of the original Ardor3D repo have decided to focus on LWJGL.
- JOGL Ardor3D community/fork - folks using Ardor3D and JOGL decided several years ago to fork and continue the development of Ardor3D within their own community. Thus someone interested in using JOGL and Ardor3D already has a community-supported means of doing so.