* Merge revision 11058 from experimental branch

- Add instanced geometry support. This is performed by uploading 4 vertex buffers each containing 4 floats. The top 3 rows are the world matrix and the bottom row is a quaternion representing the normal matrix. Hence, both unshaded and lit geometries can be rendered through instancing.
See Instancing.glsllib for more information as well as the comment in LwjglRenderer.
experimental
shadowislord 11 years ago
parent 6aeb694b71
commit 35cfae5ef0
  1. 2
      jme3-android/src/main/java/com/jme3/renderer/android/OGLESShaderRenderer.java
  2. 19
      jme3-core/src/main/java/com/jme3/material/Material.java
  3. 11
      jme3-core/src/main/java/com/jme3/renderer/Renderer.java
  4. 36
      jme3-core/src/main/java/com/jme3/scene/VertexBuffer.java
  5. 502
      jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java
  6. 2
      jme3-core/src/main/java/com/jme3/system/NullRenderer.java
  7. 10
      jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md
  8. 4
      jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.vert
  9. 5
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md
  10. 5
      jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.vert
  11. 87
      jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib
  12. 111
      jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancing.java
  13. 2
      jme3-ios/src/main/java/com/jme3/renderer/ios/IGLESShaderRenderer.java
  14. 2
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL1Renderer.java
  15. 2
      jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglRenderer.java
  16. 2
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL1Renderer.java
  17. 99
      jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglRenderer.java

@ -2362,7 +2362,7 @@ public class OGLESShaderRenderer implements Renderer {
clearTextureUnits();
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}

@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.shader.Shader;
import com.jme3.shader.Uniform;
import com.jme3.shader.UniformBindingManager;
@ -684,6 +686,17 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
return ambientLightColor;
}
private static void renderMeshFromGeometry(Renderer renderer, Geometry geom) {
Mesh mesh = geom.getMesh();
int lodLevel = geom.getLodLevel();
if (geom instanceof InstancedGeometry) {
InstancedGeometry instGeom = (InstancedGeometry) geom;
renderer.renderMesh(mesh, lodLevel, instGeom.getCurrentNumInstances(), instGeom.getAllInstanceData());
} else {
renderer.renderMesh(mesh, lodLevel, 1, null);
}
}
/**
* Uploads the lights in the light list as two uniform arrays.<br/><br/> *
* <p>
@ -858,7 +871,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
}
vars.release();
r.setShader(shader);
r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
renderMeshFromGeometry(r, g);
}
if (isFirstLight && lightList.size() > 0) {
@ -868,7 +881,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
lightPos.setValue(VarType.Vector4, nullDirLight);
r.setShader(shader);
r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
renderMeshFromGeometry(r, g);
}
}
@ -1139,7 +1152,7 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable {
r.setShader(shader);
}
r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
renderMeshFromGeometry(r, geom);
}
public void write(JmeExporter ex) throws IOException {

@ -267,18 +267,23 @@ public interface Renderer {
public void deleteBuffer(VertexBuffer vb);
/**
* Renders <code>count</code> meshes, with the geometry data supplied.
* Renders <code>count</code> meshes, with the geometry data supplied and
* per-instance data supplied.
* The shader which is currently set with <code>setShader</code> is
* responsible for transforming the input vertices into clip space
* and shading it based on the given vertex attributes.
* The int variable gl_InstanceID can be used to access the current
* The integer variable gl_InstanceID can be used to access the current
* instance of the mesh being rendered inside the vertex shader.
* If the instance data is non-null, then it is submitted as a
* per-instance vertex attribute to the shader.
*
* @param mesh The mesh to render
* @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
* @param count Number of mesh instances to render
* @param instanceData When count is greater than 1, these buffers provide
* the per-instance attributes.
*/
public void renderMesh(Mesh mesh, int lod, int count);
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData);
/**
* Resets all previously used {@link NativeObject Native Objects} on this Renderer.

@ -206,6 +206,14 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
* either an int or float buffer due to shader attribute types restrictions.
*/
HWBoneIndex,
/**
* Information about this instance.
*
* Format should be {@link Format#Float} and number of components
* should be 16.
*/
InstanceData
}
/**
@ -325,6 +333,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
protected Type bufType;
protected Format format;
protected boolean normalized = false;
protected transient boolean instanced = false;
protected transient boolean dataSizeChanged = false;
/**
@ -365,9 +374,13 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
throw new AssertionError();
}
// Are components between 1 and 4?
// Are components between 1 and 4 and not InstanceData?
if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) {
throw new AssertionError();
}
}
// Does usage comply with buffer directness?
//if (usage == Usage.CpuOnly && data.isDirect()) {
@ -531,6 +544,17 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return normalized;
}
/**
* TODO:
*/
public void setInstanced(boolean instanced) {
this.instanced = instanced;
}
public boolean isInstanced() {
return instanced;
}
/**
* @return The type of information that this buffer has.
*/
@ -585,8 +609,11 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
if (data.isReadOnly())
throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." );
if (components < 1 || components > 4)
if (bufType != Type.InstanceData) {
if (components < 1 || components > 4) {
throw new IllegalArgumentException("components must be between 1 and 4");
}
}
this.data = data;
this.components = components;
@ -982,6 +1009,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
vb.handleRef = new Object();
vb.id = -1;
vb.normalized = normalized;
vb.instanced = instanced;
vb.offset = offset;
vb.stride = stride;
vb.updateNeeded = true;
@ -1030,7 +1058,12 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id);
}
@Override
public void write(JmeExporter ex) throws IOException {
if (instanced) {
throw new IOException("Serialization of instanced data not allowed");
}
OutputCapsule oc = ex.getCapsule(this);
oc.write(components, "components", 0);
oc.write(usage, "usage", Usage.Dynamic);
@ -1064,6 +1097,7 @@ public class VertexBuffer extends NativeObject implements Savable, Cloneable {
}
}
@Override
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
components = ic.readInt("components", 0);

@ -0,0 +1,502 @@
package com.jme3.scene.instancing;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.export.Savable;
import com.jme3.math.Matrix3f;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Transform;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Format;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.control.AbstractControl;
import com.jme3.util.BufferUtils;
import java.io.IOException;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* <code>InstancedGeometry</code> allows rendering many similar
* geometries efficiently through a feature called geometry
* instancing.
*
* <p>
* All rendered geometries share material, mesh, and lod level
* but have different world transforms or possibly other parameters.
* The settings for all instances are inherited from this geometry's
* {@link #setMesh(com.jme3.scene.Mesh) mesh},
* {@link #setMaterial(com.jme3.material.Material) material} and
* {@link #setLodLevel(int) lod level} and cannot be changed per-instance.
* </p>
*
* <p>
* In order to receive any per-instance parameters, the material's shader
* must be changed to retrieve per-instance data via
* {@link VertexBuffer#setInstanced(boolean) instanced vertex attributes}
* or uniform arrays indexed with the GLSL built-in uniform
* <code>gl_InstanceID</code>. At the very least, they should use the
* functions specified in <code>Instancing.glsllib</code> shader library
* to transform vertex positions and normals instead of multiplying by the
* built-in matrix uniforms.
* </p>
*
* <p>
* This class can operate in two modes, {@link InstancedGeometry.Mode#Auto}
* and {@link InstancedGeometry.Mode#Manual}. See the respective enums
* for more information</p>
*
* <p>
* Prior to usage, the maximum number of instances must be set via
* {@link #setMaxNumInstances(int) } and the current number of instances set
* via {@link #setCurrentNumInstances(int) }. The user is then
* expected to provide transforms for all instances up to the number
* of current instances.
* </p>
*
* @author Kirill Vainer
*/
public class InstancedGeometry extends Geometry {
/**
* Indicates how the per-instance data is to be specified.
*/
public static enum Mode {
/**
* The user must specify all per-instance transforms and
* parameters manually via
* {@link InstancedGeometry#setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }
* or
* {@link InstancedGeometry#setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }.
*/
Manual,
/**
* The user
* {@link InstancedGeometry#setInstanceTransform(int, com.jme3.math.Transform) provides world transforms}
* and then uses the <code>Instancing.glsllib</code> transform functions in the
* shader to transform vertex attributes to the respective spaces.
* Additional per-instance data can be specified via
* {@link InstancedGeometry#setManualGlobalInstanceData(com.jme3.scene.VertexBuffer[]) }.
* {@link #setManualCameraInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer) }
* cannot be used at this mode since it is computed automatically.
*/
Auto
}
private static class InstancedGeometryControl extends AbstractControl {
private InstancedGeometry geom;
public InstancedGeometryControl() {
}
public InstancedGeometryControl(InstancedGeometry geom) {
this.geom = geom;
}
@Override
protected void controlUpdate(float tpf) {
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
geom.renderFromControl(vp.getCamera());
}
}
private static final int INSTANCE_SIZE = 16;
private InstancedGeometry.Mode mode;
private InstancedGeometryControl control;
private int currentNumInstances = 1;
private Camera lastCamera = null;
private Matrix4f[] worldMatrices = new Matrix4f[1];
private VertexBuffer[] globalInstanceData;
private final HashMap<Camera, VertexBuffer> instanceDataPerCam
= new HashMap<Camera, VertexBuffer>();
// TODO: determine if perhaps its better to use TempVars here.
private final Matrix4f tempMat4 = new Matrix4f();
private final Matrix4f tempMat4_2 = new Matrix4f();
private final Matrix3f tempMat3 = new Matrix3f();
private final Quaternion tempQuat = new Quaternion();
private final float[] tempFloatArray = new float[16];
/**
* Serialization only. Do not use.
*/
public InstancedGeometry() {
super();
setIgnoreTransform(true);
}
/**
* Creates instanced geometry with the specified mode and name.
*
* @param mode The {@link Mode} at which the instanced geometry operates at.
* @param name The name of the spatial.
*
* @see Mode
* @see Spatial#Spatial(java.lang.String)
*/
public InstancedGeometry(InstancedGeometry.Mode mode, String name) {
super(name);
this.mode = mode;
setIgnoreTransform(true);
if (mode == InstancedGeometry.Mode.Auto) {
control = new InstancedGeometryControl(this);
addControl(control);
}
}
/**
* The mode with which this instanced geometry was initialized
* with. Cannot be changed after initialization.
*
* @return instanced geometry mode.
*/
public InstancedGeometry.Mode getMode() {
return mode;
}
/**
* Global user specified per-instance data.
*
* By default set to <code>null</code>, specify an array of VertexBuffers
* via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }.
*
* @return global user specified per-instance data.
* @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[])
*/
public VertexBuffer[] getGlobalUserInstanceData() {
return globalInstanceData;
}
/**
* Specify global user per-instance data.
*
* By default set to <code>null</code>, specify an array of VertexBuffers
* that contain per-instance vertex attributes.
*
* @param globalInstanceData global user per-instance data.
*
* @throws IllegalArgumentException If one of the VertexBuffers is not
* {@link VertexBuffer#setInstanced(boolean) instanced}.
*/
public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) {
this.globalInstanceData = globalInstanceData;
}
/**
* Specify camera specific user per-instance data.
*
* Only applies when operating in {@link Mode#Manual}.
* When operating in {@link Mode#Auto}, this data is computed automatically,
* and using this method is not allowed.
*
* @param camera The camera for which per-instance data is to be set.
* @param cameraInstanceData The camera's per-instance data.
*
* @throws IllegalArgumentException If camera is null.
* @throws IllegalStateException If {@link #getMode() mode} is set to
* {@link Mode#Auto}.
*
* @see Mode
* @see #getCameraUserInstanceData(com.jme3.renderer.Camera)
*/
public void setCameraUserInstanceData(Camera camera, VertexBuffer cameraInstanceData) {
if (mode == Mode.Auto) {
throw new IllegalStateException("Not allowed in auto mode");
}
if (camera == null) {
throw new IllegalArgumentException("camera cannot be null");
}
instanceDataPerCam.put(camera, cameraInstanceData);
}
/**
* Return camera specific user per-instance data.
*
* Only applies when operating in {@link Mode#Manual}.
* When operating in {@link Mode#Auto}, this data is computed automatically,
* and using this method is not allowed.
*
* @param camera The camera to look up the per-instance data for.
* @return The per-instance data, or <code>null</code> if none was specified
* for the given camera.
*
* @throws IllegalArgumentException If camera is null.
* @throws IllegalStateException If {@link #getMode() mode} is set to
* {@link Mode#Auto}.
*
* @see Mode
* @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
*/
public VertexBuffer getCameraUserInstanceData(Camera camera) {
if (mode == Mode.Auto) {
throw new IllegalStateException("Not allowed in auto mode");
}
if (camera == null) {
throw new IllegalArgumentException("camera cannot be null");
}
return instanceDataPerCam.get(camera);
}
/**
* Return a read only map with the mappings between cameras and camera
* specific per-instance data.
*
* Only applies when operating in {@link Mode#Manual}.
* When operating in {@link Mode#Auto}, this data is computed automatically,
* and using this method is not allowed.
*
* @return read only map with the mappings between cameras and camera
* specific per-instance data.
*
* @throws IllegalStateException If {@link #getMode() mode} is set to
* {@link Mode#Auto}.
*
* @see Mode
* @see #setCameraUserInstanceData(com.jme3.renderer.Camera, com.jme3.scene.VertexBuffer)
*/
public Map<Camera, VertexBuffer> getAllCameraUserInstanceData() {
if (mode == Mode.Auto) {
throw new IllegalStateException("Not allowed in auto mode");
}
return Collections.unmodifiableMap(instanceDataPerCam);
}
private void updateInstance(Matrix4f viewMatrix, Matrix4f worldMatrix, float[] store, int offset) {
viewMatrix.mult(worldMatrix, tempMat4);
tempMat4.toRotationMatrix(tempMat3);
tempMat3.invertLocal();
// NOTE: No need to take the transpose in order to encode
// into quaternion, the multiplication in the shader is vec * quat
// apparently...
tempQuat.fromRotationMatrix(tempMat3);
// Column-major encoding. The "W" field in each of the encoded
// vectors represents the quaternion.
store[offset + 0] = tempMat4.m00;
store[offset + 1] = tempMat4.m10;
store[offset + 2] = tempMat4.m20;
store[offset + 3] = tempQuat.getX();
store[offset + 4] = tempMat4.m01;
store[offset + 5] = tempMat4.m11;
store[offset + 6] = tempMat4.m21;
store[offset + 7] = tempQuat.getY();
store[offset + 8] = tempMat4.m02;
store[offset + 9] = tempMat4.m12;
store[offset + 10] = tempMat4.m22;
store[offset + 11] = tempQuat.getZ();
store[offset + 12] = tempMat4.m03;
store[offset + 13] = tempMat4.m13;
store[offset + 14] = tempMat4.m23;
store[offset + 15] = tempQuat.getW();
}
private void renderFromControl(Camera cam) {
if (mode != Mode.Auto) {
return;
}
// Get the instance data VBO for this camera.
VertexBuffer instanceDataVB = instanceDataPerCam.get(cam);
FloatBuffer instanceData;
if (instanceDataVB == null) {
// This is a new camera, create instance data VBO for it.
instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
instanceDataVB = new VertexBuffer(Type.InstanceData);
instanceDataVB.setInstanced(true);
instanceDataVB.setupData(Usage.Stream, INSTANCE_SIZE, Format.Float, instanceData);
instanceDataPerCam.put(cam, instanceDataVB);
} else {
// Retrieve the current instance data buffer.
instanceData = (FloatBuffer) instanceDataVB.getData();
}
Matrix4f viewMatrix = cam.getViewMatrix();
instanceData.limit(instanceData.capacity());
instanceData.position(0);
assert currentNumInstances <= worldMatrices.length;
for (int i = 0; i < currentNumInstances; i++) {
Matrix4f worldMatrix = worldMatrices[i];
if (worldMatrix == null) {
worldMatrix = Matrix4f.IDENTITY;
}
updateInstance(viewMatrix, worldMatrix, tempFloatArray, 0);
instanceData.put(tempFloatArray);
}
instanceData.flip();
this.lastCamera = cam;
instanceDataVB.updateData(instanceDataVB.getData());
}
/**
* Set the current number of instances to be rendered.
*
* @param currentNumInstances the current number of instances to be rendered.
*
* @throws IllegalArgumentException If current number of instances is
* greater than the maximum number of instances.
*/
public void setCurrentNumInstances(int currentNumInstances) {
if (currentNumInstances > worldMatrices.length) {
throw new IllegalArgumentException("currentNumInstances cannot be larger than maxNumInstances");
}
this.currentNumInstances = currentNumInstances;
}
/**
* Set the maximum amount of instances that can be rendered by this
* instanced geometry when mode is set to auto.
*
* This re-allocates internal structures and therefore should be called
* only when necessary.
*
* @param maxNumInstances The maximum number of instances that can be
* rendered.
*
* @throws IllegalStateException If mode is set to manual.
* @throws IllegalArgumentException If maxNumInstances is zero or negative
*/
public void setMaxNumInstances(int maxNumInstances) {
if (mode == Mode.Manual) {
throw new IllegalStateException("Not allowed in manual mode");
}
if (maxNumInstances < 1) {
throw new IllegalArgumentException("maxNumInstances must be 1 or higher");
}
this.worldMatrices = new Matrix4f[maxNumInstances];
if (currentNumInstances > maxNumInstances) {
currentNumInstances = maxNumInstances;
}
// Resize instance data for each of the cameras.
for (VertexBuffer instanceDataVB : instanceDataPerCam.values()) {
FloatBuffer instanceData = (FloatBuffer) instanceDataVB.getData();
if (instanceData.capacity() / INSTANCE_SIZE != worldMatrices.length) {
// Delete old data.
BufferUtils.destroyDirectBuffer(instanceData);
// Resize instance data for this camera.
// Create new data with new length.
instanceData = BufferUtils.createFloatBuffer(worldMatrices.length * INSTANCE_SIZE);
instanceDataVB.updateData(instanceData);
}
}
}
public int getMaxNumInstances() {
return worldMatrices.length;
}
public int getCurrentNumInstances() {
return currentNumInstances;
}
public void setInstanceTransform(int instanceIndex, Matrix4f worldTransform) {
if (mode == Mode.Manual) {
throw new IllegalStateException("Not allowed in manual mode");
}
if (worldTransform == null) {
throw new IllegalArgumentException("worldTransform cannot be null");
}
if (instanceIndex < 0) {
throw new IllegalArgumentException("instanceIndex cannot be smaller than zero");
}
if (instanceIndex >= currentNumInstances) {
throw new IllegalArgumentException("instanceIndex cannot be larger than currentNumInstances");
}
// TODO: Determine if need to make a copy of matrix or just doing this
// is fine.
worldMatrices[instanceIndex] = worldTransform;
}
public void setInstanceTransform(int instanceIndex, Transform worldTransform) {
if (worldTransform == null) {
throw new IllegalArgumentException("worldTransform cannot be null");
}
// Compute the world transform matrix.
tempMat4.loadIdentity();
tempMat4.setRotationQuaternion(worldTransform.getRotation());
tempMat4.setTranslation(worldTransform.getTranslation());
tempMat4_2.loadIdentity();
tempMat4_2.scale(worldTransform.getScale());
tempMat4.multLocal(tempMat4_2);
setInstanceTransform(instanceIndex, tempMat4.clone());
}
public VertexBuffer[] getAllInstanceData() {
VertexBuffer instanceDataForCam = instanceDataPerCam.get(lastCamera);
ArrayList<VertexBuffer> allData = new ArrayList();
if (instanceDataForCam != null) {
allData.add(instanceDataForCam);
}
if (globalInstanceData != null) {
allData.addAll(Arrays.asList(globalInstanceData));
}
return allData.toArray(new VertexBuffer[allData.size()]);
}
@Override
public void write(JmeExporter exporter) throws IOException {
super.write(exporter);
OutputCapsule capsule = exporter.getCapsule(this);
capsule.write(currentNumInstances, "cur_num_instances", 1);
capsule.write(mode, "instancing_mode", InstancedGeometry.Mode.Auto);
if (mode == Mode.Auto) {
capsule.write(worldMatrices, "world_matrices", null);
}
}
@Override
public void read(JmeImporter importer) throws IOException {
super.read(importer);
InputCapsule capsule = importer.getCapsule(this);
currentNumInstances = capsule.readInt("cur_num_instances", 1);
mode = capsule.readEnum("instancing_mode", InstancedGeometry.Mode.class,
InstancedGeometry.Mode.Auto);
if (mode == Mode.Auto) {
Savable[] matrixSavables = capsule.readSavableArray("world_matrices", null);
worldMatrices = new Matrix4f[matrixSavables.length];
for (int i = 0; i < worldMatrices.length; i++) {
worldMatrices[i] = (Matrix4f) matrixSavables[i];
}
control = getControl(InstancedGeometryControl.class);
control.geom = this;
}
}
}

@ -137,7 +137,7 @@ public class NullRenderer implements Renderer {
public void deleteBuffer(VertexBuffer vb) {
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
}
public void resetGLObjects() {

@ -1,10 +1,20 @@
MaterialDef Debug Normals {
MaterialParameters {
// For instancing
Boolean UseInstancing
}
Technique {
VertexShader GLSL100: Common/MatDefs/Misc/ShowNormals.vert
FragmentShader GLSL100: Common/MatDefs/Misc/ShowNormals.frag
WorldParameters {
WorldViewProjectionMatrix
ProjectionMatrix
}
Defines {
INSTANCING : UseInstancing
}
}
}

@ -1,4 +1,4 @@
uniform mat4 g_WorldViewProjectionMatrix;
#import "Common/ShaderLib/Instancing.glsllib"
attribute vec3 inPosition;
attribute vec3 inNormal;
@ -6,6 +6,6 @@ attribute vec3 inNormal;
varying vec3 normal;
void main(){
gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0);
gl_Position = TransformWorldViewProjection(vec4(inPosition,1.0));
normal = inNormal;
}

@ -12,6 +12,9 @@ MaterialDef Unshaded {
// The glow color of the object
Color GlowColor
// For instancing
Boolean UseInstancing
// For hardware skinning
Int NumberOfBones
Matrix4Array BoneMatrices
@ -56,9 +59,11 @@ MaterialDef Unshaded {
WorldParameters {
WorldViewProjectionMatrix
ProjectionMatrix
}
Defines {
INSTANCING : UseInstancing
SEPARATE_TEXCOORD : SeparateTexCoord
HAS_COLORMAP : ColorMap
HAS_LIGHTMAP : LightMap

@ -1,6 +1,6 @@
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Instancing.glsllib"
uniform mat4 g_WorldViewProjectionMatrix;
attribute vec3 inPosition;
#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
@ -33,5 +33,6 @@ void main(){
#ifdef NUM_BONES
Skinning_Compute(modelSpacePos);
#endif
gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
gl_Position = TransformWorldViewProjection(modelSpacePos);
}

@ -0,0 +1,87 @@
// Instancing GLSL library.
//
// When the INSTANCING define is set in the shader,
// all global matrices are replaced with "instanced" versions.
// One exception is g_NormalMatrix which becomes unusable,
// instead the function ApplyNormalTransform is used to transform
// the normal and tangent vectors into world view space.
// The world matrix and normal transform quaternion need to be passed
// as vertex attributes "inWorldMatrix" and "inNormalRotationQuaternion"
// respectively.
// The VertexBuffers for those two attributes
// need to be configured into instanced mode (VertexBuffer.setInstanced(true)).
// - inWorldMatrix should have 12 * numInstances floats.
// - inNormalRotationQuaternion should have 4 * numInstances.
// Thus, instancing data occupies 4 vertex attributes (16 / 4 = 4).
//
// The GL_ARB_draw_instanced and GL_ARB_instanced_arrays extensions
// are required (OGL 3.3).
uniform mat4 g_WorldMatrix;
uniform mat4 g_ViewMatrix;
uniform mat4 g_ProjectionMatrix;
uniform mat4 g_WorldViewMatrix;
uniform mat4 g_WorldViewProjectionMatrix;
uniform mat3 g_NormalMatrix;
#if defined INSTANCING
// World Matrix + Normal Rotation Quaternion.
// The World Matrix is the top 3 rows -
// since the bottom row is always 0,0,0,1 for this transform.
// The bottom row is the transpose of the inverse of WorldView Transform
// as a quaternion. i.e. g_NormalMatrix converted to a quaternion.
//
// Using a quaternion instead of a matrix here allows saving approximately
// 2 vertex attributes which now can be used for additional per-vertex data.
attribute mat4 inInstanceData;
// Extract the world view matrix out of the instance data, leaving out the
// quaternion at the end.
mat4 worldViewMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0),
vec4(inInstanceData[1].xyz, 0.0),
vec4(inInstanceData[2].xyz, 0.0),
vec4(inInstanceData[3].xyz, 1.0));
vec4 TransformWorldView(vec4 position)
{
return worldViewMatrix * position;
}
vec4 TransformWorldViewProjection(vec4 position)
{
return g_ProjectionMatrix * TransformWorldView(position);
}
vec3 TransformNormal(vec3 vec)
{
vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w,
inInstanceData[2].w, inInstanceData[3].w);
return vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz);
}
// Prevent user from using g_** matrices which will have invalid data in this case.
#define g_WorldMatrix Use_the_instancing_functions_for_this
#define g_WorldViewMatrix Use_the_instancing_functions_for_this
#define g_WorldViewProjectionMatrix Use_the_instancing_functions_for_this
#define g_NormalMatrix Use_the_instancing_functions_for_this
#else
vec4 TransformWorldView(vec4 position)
{
return g_WorldViewMatrix * position;
}
vec4 TransformWorldViewProjection(vec4 position)
{
return g_WorldViewProjectionMatrix * position;
}
vec3 TransformNormal(vec3 normal) {
return g_NormalMatrix * normal;
}
#endif

@ -0,0 +1,111 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package jme3test.scene.instancing;
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.instancing.InstancedGeometry;
import com.jme3.scene.shape.Sphere;
public class TestInstancing extends SimpleApplication {
private InstancedGeometry instancedGeometry;
public static void main(String[] args){
TestInstancing app = new TestInstancing();
//app.setShowSettings(false);
//app.setDisplayFps(false);
//app.setDisplayStatView(false);
app.start();
}
private Geometry createInstance(float x, float z) {
// Note: it doesn't matter what mesh or material we set here.
Geometry geometry = new Geometry("randomGeom", instancedGeometry.getMesh());
geometry.setMaterial(instancedGeometry.getMaterial());
geometry.setLocalTranslation(x, 0, z);
return geometry;
}
@Override
public void simpleInitApp() {
Sphere sphere = new Sphere(10, 10, 0.5f, true, false);
Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
material.setBoolean("UseInstancing", true);
instancedGeometry = new InstancedGeometry(InstancedGeometry.Mode.Auto, "instanced_geom");
instancedGeometry.setMaxNumInstances(60 * 60);
instancedGeometry.setCurrentNumInstances(60 * 60);
instancedGeometry.setCullHint(CullHint.Never);
instancedGeometry.setMesh(sphere);
instancedGeometry.setMaterial(material);
rootNode.attachChild(instancedGeometry);
Node instancedGeoms = new Node("instances_node");
// Important: Do not render these geometries, only
// use their world transforms to instance them via
// InstancedGeometry.
instancedGeoms.setCullHint(CullHint.Always);
for (int y = -30; y < 30; y++) {
for (int x = -30; x < 30; x++) {
Geometry instance = createInstance(x, y);
instancedGeoms.attachChild(instance);
}
}
rootNode.attachChild(instancedGeoms);
rootNode.setCullHint(CullHint.Never);
int instanceIndex = 0;
for (Spatial child : instancedGeoms.getChildren()) {
if (instanceIndex < instancedGeometry.getMaxNumInstances()) {
instancedGeometry.setInstanceTransform(instanceIndex++, child.getWorldTransform());
}
}
instancedGeometry.setCurrentNumInstances(instanceIndex);
cam.setLocation(new Vector3f(38.373516f, 6.689055f, 38.482082f));
cam.setRotation(new Quaternion(-0.04004206f, 0.918326f, -0.096310444f, -0.38183528f));
flyCam.setMoveSpeed(15);
}
}

@ -936,7 +936,7 @@ public class IGLESShaderRenderer implements Renderer {
* @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }.
* @param count Number of mesh instances to render
*/
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
logger.log(Level.FINE, "IGLESShaderRenderer renderMesh");
if (mesh.getVertexCount() == 0) {
return;

@ -1181,7 +1181,7 @@ public class JoglGL1Renderer implements GL1Renderer {
resetFixedFuncBindings();
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}

@ -2554,7 +2554,7 @@ public class JoglRenderer implements Renderer {
clearTextureUnits();
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}

@ -1121,7 +1121,7 @@ public class LwjglGL1Renderer implements GL1Renderer {
resetFixedFuncBindings();
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}

@ -281,7 +281,7 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.PackedDepthStencilBuffer);
}
if (ctxCaps.GL_ARB_draw_instanced) {
if (ctxCaps.GL_ARB_draw_instanced && ctxCaps.GL_ARB_instanced_arrays) {
caps.add(Caps.MeshInstancing);
}
@ -316,7 +316,8 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.TextureCompressionLATC);
}
if (ctxCaps.GL_EXT_packed_float) {
if (ctxCaps.GL_EXT_packed_float || ctxCaps.OpenGL30) {
// This format is part of the OGL3 specification
caps.add(Caps.PackedFloatColorBuffer);
if (ctxCaps.GL_ARB_half_float_pixel) {
// because textures are usually uploaded as RGB16F
@ -329,7 +330,7 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.TextureArray);
}
if (ctxCaps.GL_EXT_texture_shared_exponent) {
if (ctxCaps.GL_EXT_texture_shared_exponent || ctxCaps.OpenGL30) {
caps.add(Caps.SharedExponentTexture);
}
@ -392,8 +393,8 @@ public class LwjglRenderer implements Renderer {
caps.add(Caps.Multisample);
}
//supports sRGB pipeline
if (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB){
// Supports sRGB pipeline.
if ( (ctxCaps.GL_ARB_framebuffer_sRGB && ctxCaps.GL_EXT_texture_sRGB ) || ctxCaps.OpenGL30 ) {
caps.add(Caps.Srgb);
}
@ -2168,6 +2169,9 @@ public class LwjglRenderer implements Renderer {
for (int i = 0; i < attribList.oldLen; i++) {
int idx = attribList.oldList[i];
glDisableVertexAttribArray(idx);
if (context.boundAttribs[idx].isInstanced()) {
ARBInstancedArrays.glVertexAttribDivisorARB(idx, 0);
}
context.boundAttribs[idx] = null;
}
context.attribIndexList.copyNewToOld();
@ -2201,15 +2205,32 @@ public class LwjglRenderer implements Renderer {
}
}
int slotsRequired = 1;
if (vb.isInstanced()) {
if (!GLContext.getCapabilities().GL_ARB_instanced_arrays
|| !GLContext.getCapabilities().GL_ARB_draw_instanced) {
throw new RendererException("Instancing is required, "
+ "but not supported by the "
+ "graphics hardware");
}
if (vb.getNumComponents() > 4 && vb.getNumComponents() % 4 != 0) {
throw new RendererException("Number of components in multi-slot "
+ "buffers must be divisible by 4");
}
slotsRequired = vb.getNumComponents() / 4;
}
if (vb.isUpdateNeeded() && idb == null) {
updateBufferData(vb);
}
VertexBuffer[] attribs = context.boundAttribs;
if (!context.attribIndexList.moveToNew(loc)) {
glEnableVertexAttribArray(loc);
for (int i = 0; i < slotsRequired; i++) {
if (!context.attribIndexList.moveToNew(loc + i)) {
glEnableVertexAttribArray(loc + i);
//System.out.println("Enabled ATTRIB IDX: "+loc);
}
}
if (attribs[loc] != vb) {
// NOTE: Use id from interleaved buffer if specified
int bufId = idb != null ? idb.getId() : vb.getId();
@ -2222,14 +2243,43 @@ public class LwjglRenderer implements Renderer {
//statistics.onVertexBufferUse(vb, false);
}
if (slotsRequired == 1) {
glVertexAttribPointer(loc,
vb.getNumComponents(),
convertFormat(vb.getFormat()),
vb.isNormalized(),
vb.getStride(),
vb.getOffset());
} else {
for (int i = 0; i < slotsRequired; i++) {
// The pointer maps the next 4 floats in the slot.
// E.g.
// P1: XXXX____________XXXX____________
// P2: ____XXXX____________XXXX________
// P3: ________XXXX____________XXXX____
// P4: ____________XXXX____________XXXX
// stride = 4 bytes in float * 4 floats in slot * num slots
// offset = 4 bytes in float * 4 floats in slot * slot index
glVertexAttribPointer(loc + i,
4,
convertFormat(vb.getFormat()),
vb.isNormalized(),
4 * 4 * slotsRequired,
4 * 4 * i);
}
}
attribs[loc] = vb;
for (int i = 0; i < slotsRequired; i++) {
int slot = loc + i;
if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) {
// non-instanced -> instanced
ARBInstancedArrays.glVertexAttribDivisorARB(slot, 1);
} else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) {
// instanced -> non-instanced
ARBInstancedArrays.glVertexAttribDivisorARB(slot, 0);
}
attribs[slot] = vb;
}
}
} else {
throw new IllegalStateException("Cannot render mesh without shader bound");
@ -2241,7 +2291,8 @@ public class LwjglRenderer implements Renderer {
}
public void drawTriangleArray(Mesh.Mode mode, int count, int vertCount) {
if (count > 1) {
boolean useInstancing = count > 1 && caps.contains(Caps.MeshInstancing);
if (useInstancing) {
ARBDrawInstanced.glDrawArraysInstancedARB(convertElementMode(mode), 0,
vertCount, count);
} else {
@ -2350,7 +2401,7 @@ public class LwjglRenderer implements Renderer {
}
}
public void updateVertexArray(Mesh mesh) {
public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) {
int id = mesh.getId();
if (id == -1) {
IntBuffer temp = intBuf1;
@ -2369,6 +2420,10 @@ public class LwjglRenderer implements Renderer {
updateBufferData(interleavedData);
}
if (instanceData != null) {
setVertexAttrib(instanceData, null);
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@ -2386,9 +2441,9 @@ public class LwjglRenderer implements Renderer {
}
}
private void renderMeshVertexArray(Mesh mesh, int lod, int count) {
private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) {
if (mesh.getId() == -1) {
updateVertexArray(mesh);
updateVertexArray(mesh, instanceData);
} else {
// TODO: Check if it was updated
}
@ -2414,25 +2469,25 @@ public class LwjglRenderer implements Renderer {
clearTextureUnits();
}
private void renderMeshDefault(Mesh mesh, int lod, int count) {
VertexBuffer indices;
private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData);
if (interleavedData != null && interleavedData.isUpdateNeeded()) {
updateBufferData(interleavedData);
}
// IntMap<VertexBuffer> buffers = mesh.getBuffers();
// SafeArrayList<VertexBuffer> buffersList = mesh.getBufferList();
VertexBuffer indices;
if (mesh.getNumLodLevels() > 0) {
indices = mesh.getLodLevel(lod);
} else {
indices = mesh.getBuffer(Type.Index);
}
// for (Entry<VertexBuffer> entry : buffers) {
// VertexBuffer vb = entry.getValue();
if (instanceData != null) {
for (VertexBuffer vb : instanceData) {
setVertexAttrib(vb, null);
}
}
for (VertexBuffer vb : mesh.getBufferList().getArray()) {
if (vb.getBufferType() == Type.InterleavedData
|| vb.getUsage() == Usage.CpuOnly // ignore cpu-only buffers
@ -2458,7 +2513,7 @@ public class LwjglRenderer implements Renderer {
clearTextureUnits();
}
public void renderMesh(Mesh mesh, int lod, int count) {
public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) {
if (mesh.getVertexCount() == 0) {
return;
}
@ -2489,7 +2544,7 @@ public class LwjglRenderer implements Renderer {
// if (GLContext.getCapabilities().GL_ARB_vertex_array_object){
// renderMeshVertexArray(mesh, lod, count);
// }else{
renderMeshDefault(mesh, lod, count);
renderMeshDefault(mesh, lod, count, instanceData);
// }
}

Loading…
Cancel
Save