- 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
parent
6aeb694b71
commit
35cfae5ef0
@ -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; |
||||
} |
||||
} |
||||
} |
@ -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 |
||||
} |
||||
} |
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue