com.jme3.material.Material Maven / Gradle / Ivy
Show all versions of jme3-core Show documentation
/*
* Copyright (c) 2009-2021 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 com.jme3.material;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetManager;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.export.*;
import com.jme3.light.LightList;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.material.TechniqueDef.LightMode;
import com.jme3.math.*;
import com.jme3.renderer.Caps;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.TextureUnitException;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.shader.*;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.ListMap;
import com.jme3.util.SafeArrayList;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Material
describes the rendering style for a given
* {@link Geometry}.
* A material is essentially a list of {@link MatParam parameters},
* those parameters map to uniforms which are defined in a shader.
* Setting the parameters can modify the behavior of a
* shader.
*
*
* @author Kirill Vainer
*/
public class Material implements CloneableSmartAsset, Cloneable, Savable {
// Version #2: Fixed issue with RenderState.apply*** flags not getting exported
public static final int SAVABLE_VERSION = 2;
private static final Logger logger = Logger.getLogger(Material.class.getName());
private AssetKey key;
private String name;
private MaterialDef def;
private ListMap paramValues = new ListMap<>();
private Technique technique;
private HashMap techniques = new HashMap<>();
private RenderState additionalState = null;
final private RenderState mergedRenderState = new RenderState();
private boolean transparent = false;
private boolean receivesShadows = false;
private int sortingId = -1;
public Material(MaterialDef def) {
if (def == null) {
throw new IllegalArgumentException("Material definition cannot be null");
}
this.def = def;
// Load default values from definition (if any)
for (MatParam param : def.getMaterialParams()) {
if (param.getValue() != null) {
setParam(param.getName(), param.getVarType(), param.getValue());
}
}
}
public Material(AssetManager contentMan, String defName) {
this(contentMan.loadAsset(new AssetKey(defName)));
}
/**
* Do not use this constructor. Serialization purposes only.
*/
public Material() {
}
/**
* Returns the asset key name of the asset from which this material was loaded.
*
* This value will be null
unless this material was loaded
* from a .j3m file.
*
* @return Asset key name of the j3m file
*/
public String getAssetName() {
return key != null ? key.getName() : null;
}
/**
* @return the name of the material (not the same as the asset name), the returned value can be null
*/
public String getName() {
return name;
}
/**
* This method sets the name of the material.
* The name is not the same as the asset name.
* It can be null and there is no guarantee of its uniqueness.
* @param name the name of the material
*/
public void setName(String name) {
this.name = name;
}
@Override
public void setKey(AssetKey key) {
this.key = key;
}
@Override
public AssetKey getKey() {
return key;
}
/**
* Returns the sorting ID or sorting index for this material.
*
*
The sorting ID is used internally by the system to sort rendering
* of geometries. It sorted to reduce shader switches, if the shaders
* are equal, then it is sorted by textures.
*
* @return The sorting ID used for sorting geometries for rendering.
*/
public int getSortId() {
if (sortingId == -1 && technique != null) {
sortingId = technique.getSortId() << 16;
int texturesSortId = 17;
for (int i = 0; i < paramValues.size(); i++) {
MatParam param = paramValues.getValue(i);
if (!param.getVarType().isTextureType()) {
continue;
}
Texture texture = (Texture) param.getValue();
if (texture == null) {
continue;
}
Image image = texture.getImage();
if (image == null) {
continue;
}
int textureId = image.getId();
if (textureId == -1) {
textureId = 0;
}
texturesSortId = texturesSortId * 23 + textureId;
}
sortingId |= texturesSortId & 0xFFFF;
}
return sortingId;
}
/**
* Clones this material. The result is returned.
*/
@Override
public Material clone() {
try {
Material mat = (Material) super.clone();
if (additionalState != null) {
mat.additionalState = additionalState.clone();
}
mat.technique = null;
mat.techniques = new HashMap();
mat.paramValues = new ListMap();
for (int i = 0; i < paramValues.size(); i++) {
Map.Entry entry = paramValues.getEntry(i);
mat.paramValues.put(entry.getKey(), entry.getValue().clone());
}
mat.sortingId = -1;
return mat;
} catch (CloneNotSupportedException ex) {
throw new AssertionError(ex);
}
}
/**
* Compares two materials and returns true if they are equal.
* This methods compare definition, parameters, additional render states.
* Since materials are mutable objects, implementing equals() properly is not possible,
* hence the name contentEquals().
*
* @param otherObj the material to compare to this material
* @return true if the materials are equal.
*/
public boolean contentEquals(Object otherObj) {
if (!(otherObj instanceof Material)) {
return false;
}
Material other = (Material) otherObj;
// Early exit if the material are the same object
if (this == other) {
return true;
}
// Check material definition
if (this.getMaterialDef() != other.getMaterialDef()) {
return false;
}
// Early exit if the size of the params is different
if (this.paramValues.size() != other.paramValues.size()) {
return false;
}
// Checking technique
if (this.technique != null || other.technique != null) {
// Techniques are considered equal if their names are the same
// E.g. if user chose custom technique for one material but
// uses default technique for other material, the materials
// are not equal.
String thisDefName = this.technique != null
? this.technique.getDef().getName()
: TechniqueDef.DEFAULT_TECHNIQUE_NAME;
String otherDefName = other.technique != null
? other.technique.getDef().getName()
: TechniqueDef.DEFAULT_TECHNIQUE_NAME;
if (!thisDefName.equals(otherDefName)) {
return false;
}
}
// Comparing parameters
for (String paramKey : paramValues.keySet()) {
MatParam thisParam = this.getParam(paramKey);
MatParam otherParam = other.getParam(paramKey);
// This param does not exist in compared mat
if (otherParam == null) {
return false;
}
if (!otherParam.equals(thisParam)) {
return false;
}
}
// Comparing additional render states
if (additionalState == null) {
if (other.additionalState != null) {
return false;
}
} else {
if (!additionalState.equals(other.additionalState)) {
return false;
}
}
return true;
}
/**
* Works like {@link Object#hashCode() } except it may change together with the material as the material is mutable by definition.
*
* @return value for use in hashing
*/
public int contentHashCode() {
int hash = 7;
hash = 29 * hash + (this.def != null ? this.def.hashCode() : 0);
hash = 29 * hash + (this.paramValues != null ? this.paramValues.hashCode() : 0);
hash = 29 * hash + (this.technique != null ? this.technique.getDef().getName().hashCode() : 0);
hash = 29 * hash + (this.additionalState != null ? this.additionalState.contentHashCode() : 0);
return hash;
}
/**
* Returns the currently active technique.
*
* The technique is selected automatically by the {@link RenderManager}
* based on system capabilities. Users may select their own
* technique by using
* {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
*
* @return the currently active technique.
*
* @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
*/
public Technique getActiveTechnique() {
return technique;
}
/**
* Check if the transparent value marker is set on this material.
* @return True if the transparent value marker is set on this material.
* @see #setTransparent(boolean)
*/
public boolean isTransparent() {
return transparent;
}
/**
* Set the transparent value marker.
*
*
This value is merely a marker, by itself it does nothing.
* Generally model loaders will use this marker to indicate further
* up that the material is transparent and therefore any geometries
* using it should be put into the {@link Bucket#Transparent transparent
* bucket}.
*
* @param transparent the transparent value marker.
*/
public void setTransparent(boolean transparent) {
this.transparent = transparent;
}
/**
* Check if the material should receive shadows or not.
*
* @return True if the material should receive shadows.
*
* @see Material#setReceivesShadows(boolean)
*/
public boolean isReceivesShadows() {
return receivesShadows;
}
/**
* Set if the material should receive shadows or not.
*
*
This value is merely a marker, by itself it does nothing.
* Generally model loaders will use this marker to indicate
* the material should receive shadows and therefore any
* geometries using it should have {@link com.jme3.renderer.queue.RenderQueue.ShadowMode#Receive} set
* on them.
*
* @param receivesShadows if the material should receive shadows or not.
*/
public void setReceivesShadows(boolean receivesShadows) {
this.receivesShadows = receivesShadows;
}
/**
* Acquire the additional {@link RenderState render state} to apply
* for this material.
*
*
The first call to this method will create an additional render
* state which can be modified by the user to apply any render
* states in addition to the ones used by the renderer. Only render
* states which are modified in the additional render state will be applied.
*
* @return The additional render state.
*/
public RenderState getAdditionalRenderState() {
if (additionalState == null) {
additionalState = RenderState.ADDITIONAL.clone();
}
return additionalState;
}
/**
* Get the material definition (j3md file info) that this
* material is implementing.
*
* @return the material definition this material implements.
*/
public MaterialDef getMaterialDef() {
return def;
}
/**
* Returns the parameter set on this material with the given name,
* returns null
if the parameter is not set.
*
* @param name The parameter name to look up.
* @return The MatParam if set, or null if not set.
*/
public MatParam getParam(String name) {
return paramValues.get(name);
}
/**
* Returns the current parameter's value.
*
* @param the expected type of the parameter value
* @param name the parameter name to look up.
* @return current value or null if the parameter wasn't set.
*/
@SuppressWarnings("unchecked")
public T getParamValue(final String name) {
final MatParam param = paramValues.get(name);
return param == null ? null : (T) param.getValue();
}
/**
* Returns the texture parameter set on this material with the given name,
* returns null
if the parameter is not set.
*
* @param name The parameter name to look up.
* @return The MatParamTexture if set, or null if not set.
*/
public MatParamTexture getTextureParam(String name) {
MatParam param = paramValues.get(name);
if (param instanceof MatParamTexture) {
return (MatParamTexture) param;
}
return null;
}
/**
* Returns a collection of all parameters set on this material.
*
* @return a collection of all parameters set on this material.
*
* @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
*/
public Collection getParams() {
return paramValues.values();
}
/**
* Returns the ListMap of all parameters set on this material.
*
* @return a ListMap of all parameters set on this material.
*
* @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
*/
public ListMap getParamsMap() {
return paramValues;
}
/**
* Check if setting the parameter given the type and name is allowed.
* @param type The type that the "set" function is designed to set
* @param name The name of the parameter
*/
private void checkSetParam(VarType type, String name) {
MatParam paramDef = def.getMaterialParam(name);
if (paramDef == null) {
throw new IllegalArgumentException("Material parameter is not defined: " + name);
}
if (type != null && paramDef.getVarType() != type) {
logger.log(Level.WARNING, "Material parameter being set: {0} with "
+ "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()});
}
}
/**
* Pass a parameter to the material shader.
*
* @param name the name of the parameter defined in the material definition (j3md)
* @param type the type of the parameter {@link VarType}
* @param value the value of the parameter
*/
public void setParam(String name, VarType type, Object value) {
checkSetParam(type, name);
if (type.isTextureType()) {
setTextureParam(name, type, (Texture)value);
} else {
MatParam val = getParam(name);
if (val == null) {
MatParam paramDef = def.getMaterialParam(name);
paramValues.put(name, new MatParam(type, name, value));
} else {
val.setValue(value);
}
if (technique != null) {
technique.notifyParamChanged(name, type, value);
}
}
}
/**
* Clear a parameter from this material. The parameter must exist
* @param name the name of the parameter to clear
*/
public void clearParam(String name) {
checkSetParam(null, name);
MatParam matParam = getParam(name);
if (matParam == null) {
return;
}
paramValues.remove(name);
if (matParam instanceof MatParamTexture) {
sortingId = -1;
}
if (technique != null) {
technique.notifyParamChanged(name, null, null);
}
}
/**
* Set a texture parameter.
*
* @param name The name of the parameter
* @param type The variable type {@link VarType}
* @param value The texture value of the parameter.
*
* @throws IllegalArgumentException is value is null
*/
public void setTextureParam(String name, VarType type, Texture value) {
if (value == null) {
throw new IllegalArgumentException();
}
checkSetParam(type, name);
MatParamTexture val = getTextureParam(name);
if (val == null) {
checkTextureParamColorSpace(name, value);
paramValues.put(name, new MatParamTexture(type, name, value, value.getImage() != null ? value.getImage().getColorSpace() : null));
} else {
checkTextureParamColorSpace(name, value);
val.setTextureValue(value);
val.setColorSpace(value.getImage() != null ? value.getImage().getColorSpace() : null);
}
if (technique != null) {
technique.notifyParamChanged(name, type, value);
}
// need to recompute sort ID
sortingId = -1;
}
private void checkTextureParamColorSpace(String name, Texture value) {
MatParamTexture paramDef = (MatParamTexture) def.getMaterialParam(name);
if (paramDef.getColorSpace() != null && paramDef.getColorSpace() != value.getImage().getColorSpace()) {
value.getImage().setColorSpace(paramDef.getColorSpace());
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Material parameter {0} needs a {1} texture, "
+ "texture {2} was switched to {3} color space.",
new Object[]{name, paramDef.getColorSpace().toString(),
value.getName(),
value.getImage().getColorSpace().name()});
}
} else if (paramDef.getColorSpace() == null && value.getName() != null && value.getImage().getColorSpace() == ColorSpace.Linear) {
logger.log(Level.WARNING,
"The texture {0} has linear color space, but the material "
+ "parameter {2} specifies no color space requirement, this may "
+ "lead to unexpected behavior.\nCheck if the image "
+ "was not set to another material parameter with a linear "
+ "color space, or that you did not set the ColorSpace to "
+ "Linear using texture.getImage.setColorSpace().",
new Object[]{value.getName(), value.getImage().getColorSpace().name(), name});
}
}
/**
* Pass a texture to the material shader.
*
* @param name the name of the texture defined in the material definition
* (j3md) (for example Texture for Lighting.j3md)
* @param value the Texture object previously loaded by the asset manager
*/
public void setTexture(String name, Texture value) {
if (value == null) {
// clear it
clearParam(name);
return;
}
VarType paramType = null;
switch (value.getType()) {
case TwoDimensional:
paramType = VarType.Texture2D;
break;
case TwoDimensionalArray:
paramType = VarType.TextureArray;
break;
case ThreeDimensional:
paramType = VarType.Texture3D;
break;
case CubeMap:
paramType = VarType.TextureCubeMap;
break;
default:
throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
}
setTextureParam(name, paramType, value);
}
/**
* Pass a Matrix4f to the material shader.
*
* @param name the name of the matrix defined in the material definition (j3md)
* @param value the Matrix4f object
*/
public void setMatrix4(String name, Matrix4f value) {
setParam(name, VarType.Matrix4, value);
}
/**
* Pass a boolean to the material shader.
*
* @param name the name of the boolean defined in the material definition (j3md)
* @param value the boolean value
*/
public void setBoolean(String name, boolean value) {
setParam(name, VarType.Boolean, value);
}
/**
* Pass a float to the material shader.
*
* @param name the name of the float defined in the material definition (j3md)
* @param value the float value
*/
public void setFloat(String name, float value) {
setParam(name, VarType.Float, value);
}
/**
* Pass a float to the material shader. This version avoids auto-boxing
* if the value is already a Float.
*
* @param name the name of the float defined in the material definition (j3md)
* @param value the float value
*/
public void setFloat(String name, Float value) {
setParam(name, VarType.Float, value);
}
/**
* Pass an int to the material shader.
*
* @param name the name of the int defined in the material definition (j3md)
* @param value the int value
*/
public void setInt(String name, int value) {
setParam(name, VarType.Int, value);
}
/**
* Pass a Color to the material shader.
*
* @param name the name of the color defined in the material definition (j3md)
* @param value the ColorRGBA value
*/
public void setColor(String name, ColorRGBA value) {
setParam(name, VarType.Vector4, value);
}
/**
* Pass an uniform buffer object to the material shader.
*
* @param name the name of the buffer object defined in the material definition (j3md).
* @param value the buffer object.
*/
public void setUniformBufferObject(final String name, final BufferObject value) {
value.setBufferType(BufferObject.BufferType.UniformBufferObject);
setParam(name, VarType.BufferObject, value);
}
/**
* Pass a shader storage buffer object to the material shader.
*
* @param name the name of the buffer object defined in the material definition (j3md).
* @param value the buffer object.
*/
public void setShaderStorageBufferObject(final String name, final BufferObject value) {
value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject);
setParam(name, VarType.BufferObject, value);
}
/**
* Pass a Vector2f to the material shader.
*
* @param name the name of the Vector2f defined in the material definition (j3md)
* @param value the Vector2f value
*/
public void setVector2(String name, Vector2f value) {
setParam(name, VarType.Vector2, value);
}
/**
* Pass a Vector3f to the material shader.
*
* @param name the name of the Vector3f defined in the material definition (j3md)
* @param value the Vector3f value
*/
public void setVector3(String name, Vector3f value) {
setParam(name, VarType.Vector3, value);
}
/**
* Pass a Vector4f to the material shader.
*
* @param name the name of the Vector4f defined in the material definition (j3md)
* @param value the Vector4f value
*/
public void setVector4(String name, Vector4f value) {
setParam(name, VarType.Vector4, value);
}
/**
* Select the technique to use for rendering this material.
*
* Any candidate technique for selection (either default or named)
* must be verified to be compatible with the system, for that, the
* renderManager
is queried for capabilities.
*
* @param name The name of the technique to select, pass
* {@link TechniqueDef#DEFAULT_TECHNIQUE_NAME} to select one of the default
* techniques.
* @param renderManager The {@link RenderManager render manager}
* to query for capabilities.
*
* @throws IllegalArgumentException If no technique exists with the given
* name.
* @throws UnsupportedOperationException If no candidate technique supports
* the system capabilities.
*/
public void selectTechnique(String name, final RenderManager renderManager) {
// check if already created
Technique tech = techniques.get(name);
// When choosing technique, we choose one that
// supports all the caps.
if (tech == null) {
EnumSet rendererCaps = renderManager.getRenderer().getCaps();
List techDefs = def.getTechniqueDefs(name);
if (techDefs == null || techDefs.isEmpty()) {
throw new IllegalArgumentException(
String.format("The requested technique %s is not available on material %s", name, def.getName()));
}
TechniqueDef lastTech = null;
float weight = 0;
for (TechniqueDef techDef : techDefs) {
if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
float techWeight = techDef.getWeight() + (techDef.getLightMode() == renderManager.getPreferredLightMode() ? 10f : 0);
if (techWeight > weight) {
tech = new Technique(this, techDef);
techniques.put(name, tech);
weight = techWeight;
}
}
lastTech = techDef;
}
if (tech == null) {
throw new UnsupportedOperationException(
String.format("No technique '%s' on material "
+ "'%s' is supported by the video hardware. "
+ "The capabilities %s are required.",
name, def.getName(), lastTech.getRequiredCaps()));
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, this.getMaterialDef().getName() + " selected technique def " + tech.getDef());
}
} else if (technique == tech) {
// attempting to switch to an already
// active technique.
return;
}
technique = tech;
tech.notifyTechniqueSwitched();
// shader was changed
sortingId = -1;
}
private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList overrides, int unit) {
for (MatParamOverride override : overrides.getArray()) {
VarType type = override.getVarType();
MatParam paramDef = def.getMaterialParam(override.getName());
if (paramDef == null || paramDef.getVarType() != type || !override.isEnabled()) {
continue;
}
Uniform uniform = shader.getUniform(override.getPrefixedName());
if (override.getValue() != null) {
if (type.isTextureType()) {
try {
renderer.setTexture(unit, (Texture) override.getValue());
} catch (TextureUnitException exception) {
int numTexParams = unit + 1;
String message = "Too many texture parameters ("
+ numTexParams + ") assigned\n to " + toString();
throw new IllegalStateException(message);
}
uniform.setValue(VarType.Int, unit);
unit++;
} else {
uniform.setValue(type, override.getValue());
}
} else {
uniform.clearValue();
}
}
return unit;
}
private int updateShaderMaterialParameters(Renderer renderer, Shader shader,
SafeArrayList worldOverrides, SafeArrayList forcedOverrides) {
int unit = 0;
if (worldOverrides != null) {
unit = applyOverrides(renderer, shader, worldOverrides, unit);
}
if (forcedOverrides != null) {
unit = applyOverrides(renderer, shader, forcedOverrides, unit);
}
for (int i = 0; i < paramValues.size(); i++) {
MatParam param = paramValues.getValue(i);
VarType type = param.getVarType();
if (isBO(type)) {
final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName());
bufferBlock.setBufferObject((BufferObject) param.getValue());
} else {
Uniform uniform = shader.getUniform(param.getPrefixedName());
if (uniform.isSetByCurrentMaterial()) {
continue;
}
if (type.isTextureType()) {
try {
renderer.setTexture(unit, (Texture) param.getValue());
} catch (TextureUnitException exception) {
int numTexParams = unit + 1;
String message = "Too many texture parameters ("
+ numTexParams + ") assigned\n to " + toString();
throw new IllegalStateException(message);
}
uniform.setValue(VarType.Int, unit);
unit++;
} else {
uniform.setValue(type, param.getValue());
}
}
}
//TODO HACKY HACK remove this when texture unit is handled by the uniform.
return unit;
}
/**
* Returns true if the type is Buffer Object's type.
*
* @param type the material parameter type.
* @return true if the type is Buffer Object's type.
*/
private boolean isBO(final VarType type) {
return type == VarType.BufferObject;
}
private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) {
if (renderManager.getForcedRenderState() != null) {
renderer.applyRenderState(renderManager.getForcedRenderState());
} else {
if (techniqueDef.getRenderState() != null) {
renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
} else {
renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
}
}
}
/**
* Preloads this material for the given render manager.
*
* Preloading the material can ensure that when the material is first
* used for rendering, there won't be any delay since the material has
* been already been setup for rendering.
*
* @param renderManager The render manager to preload for
* @param geometry to determine the applicable parameter overrides, if any
*/
public void preload(RenderManager renderManager, Geometry geometry) {
if (technique == null) {
selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
}
TechniqueDef techniqueDef = technique.getDef();
Renderer renderer = renderManager.getRenderer();
EnumSet rendererCaps = renderer.getCaps();
if (techniqueDef.isNoRender()) {
return;
}
// Get world overrides
SafeArrayList overrides = geometry.getWorldMatParamOverrides();
Shader shader = technique.makeCurrent(renderManager, overrides, null, null, rendererCaps);
updateShaderMaterialParameters(renderer, shader, overrides, null);
renderManager.getRenderer().setShader(shader);
}
private void clearUniformsSetByCurrent(Shader shader) {
ListMap uniforms = shader.getUniformMap();
int size = uniforms.size();
for (int i = 0; i < size; i++) {
Uniform u = uniforms.getValue(i);
u.clearSetByCurrentMaterial();
}
}
private void resetUniformsNotSetByCurrent(Shader shader) {
ListMap uniforms = shader.getUniformMap();
int size = uniforms.size();
for (int i = 0; i < size; i++) {
Uniform u = uniforms.getValue(i);
if (!u.isSetByCurrentMaterial()) {
if (u.getName().charAt(0) != 'g') {
// Don't reset world globals!
// The benefits gained from this are very minimal
// and cause lots of matrix -> FloatBuffer conversions.
u.clearValue();
}
}
}
}
/**
* Called by {@link RenderManager} to render the geometry by
* using this material.
*
* The material is rendered as follows:
*
* - Determine which technique to use to render the material -
* either what the user selected via
* {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
* Material.selectTechnique()},
* or the first default technique that the renderer supports
* (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering capabilities})
* - If the technique has been changed since the last frame, then it is notified via
* {@link Technique#makeCurrent(com.jme3.renderer.RenderManager, com.jme3.util.SafeArrayList, com.jme3.util.SafeArrayList, com.jme3.light.LightList, java.util.EnumSet)
* Technique.makeCurrent()}.
* If the technique wants to use a shader to render the model, it should load it at this part -
* the shader should have all the proper defines as declared in the technique definition,
* including those that are bound to material parameters.
* The technique can re-use the shader from the last frame if
* no changes to the defines occurred.
* - Set the {@link RenderState} to use for rendering. The render states are
* applied in this order (later RenderStates override earlier RenderStates):
* - {@link TechniqueDef#getRenderState() Technique Definition's RenderState}
* - i.e. specific RenderState that is required for the shader.
* - {@link #getAdditionalRenderState() Material Instance Additional RenderState}
* - i.e. ad-hoc RenderState set per model
* - {@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState}
* - i.e. RenderState requested by a {@link com.jme3.post.SceneProcessor} or
* post-processing filter.
* - If the technique uses a shader, then the uniforms of the shader must be updated.
* - Uniforms bound to material parameters are updated based on the current material parameter values.
* - Uniforms bound to world parameters are updated from the RenderManager.
* Internally {@link UniformBindingManager} is used for this task.
* - Uniforms bound to textures will cause the texture to be uploaded as necessary.
* The uniform is set to the texture unit where the texture is bound.
* - If the technique uses a shader, the model is then rendered according
* to the lighting mode specified on the technique definition.
* - {@link LightMode#SinglePass single pass light mode} fills the shader's light uniform arrays
* with the first 4 lights and renders the model once.
* - {@link LightMode#MultiPass multi pass light mode} light mode renders the model multiple times,
* for the first light it is rendered opaque, on subsequent lights it is
* rendered with {@link BlendMode#AlphaAdditive alpha-additive} blending and depth writing disabled.
*
* - For techniques that do not use shaders,
* fixed function OpenGL is used to render the model (see {@link com.jme3.renderer.opengl.GLRenderer} interface):
* - OpenGL state that is bound to material parameters is updated.
* - The texture set on the material is uploaded and bound.
* Currently only 1 texture is supported for fixed function techniques.
* - If the technique uses lighting, then OpenGL lighting state is updated
* based on the light list on the geometry, otherwise OpenGL lighting is disabled.
* - The mesh is uploaded and rendered.
*
*
*
* @param geometry The geometry to render
* @param lights Presorted and filtered light list to use for rendering
* @param renderManager The render manager requesting the rendering
*/
public void render(Geometry geometry, LightList lights, RenderManager renderManager) {
if (technique == null) {
selectTechnique(TechniqueDef.DEFAULT_TECHNIQUE_NAME, renderManager);
}
TechniqueDef techniqueDef = technique.getDef();
Renderer renderer = renderManager.getRenderer();
EnumSet rendererCaps = renderer.getCaps();
if (techniqueDef.isNoRender()) {
return;
}
// Apply render state
updateRenderState(renderManager, renderer, techniqueDef);
// Get world overrides
SafeArrayList overrides = geometry.getWorldMatParamOverrides();
// Select shader to use
Shader shader = technique.makeCurrent(renderManager, overrides, renderManager.getForcedMatParams(), lights, rendererCaps);
// Begin tracking which uniforms were changed by material.
clearUniformsSetByCurrent(shader);
// Set uniform bindings
renderManager.updateUniformBindings(shader);
// Set material parameters
int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams());
// Clear any uniforms not changed by material.
resetUniformsNotSetByCurrent(shader);
// Delegate rendering to the technique
technique.render(renderManager, shader, geometry, lights, unit);
}
/**
* Called by {@link RenderManager} to render the geometry by
* using this material.
*
* Note that this version of the render method
* does not perform light filtering.
*
* @param geom The geometry to render
* @param rm The render manager requesting the rendering
*/
public void render(Geometry geom, RenderManager rm) {
render(geom, geom.getWorldLightList(), rm);
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule oc = ex.getCapsule(this);
oc.write(def.getAssetName(), "material_def", null);
oc.write(additionalState, "render_state", null);
oc.write(transparent, "is_transparent", false);
oc.write(name, "name", null);
oc.writeStringSavableMap(paramValues, "parameters", null);
}
@Override
public String toString() {
return "Material[name=" + name +
", def=" + (def != null ? def.getName() : null) +
", tech=" + (technique != null && technique.getDef() != null ? technique.getDef().getName() : null) +
"]";
}
@Override
@SuppressWarnings("unchecked")
public void read(JmeImporter im) throws IOException {
InputCapsule ic = im.getCapsule(this);
name = ic.readString("name", null);
additionalState = (RenderState) ic.readSavable("render_state", null);
transparent = ic.readBoolean("is_transparent", false);
// Load the material def
String defName = ic.readString("material_def", null);
HashMap params = (HashMap) ic.readStringSavableMap("parameters", null);
boolean enableVcolor = false;
boolean separateTexCoord = false;
boolean applyDefaultValues = false;
boolean guessRenderStateApply = false;
int ver = ic.getSavableVersion(Material.class);
if (ver < 1) {
applyDefaultValues = true;
}
if (ver < 2) {
guessRenderStateApply = true;
}
if (im.getFormatVersion() == 0) {
// Enable compatibility with old models
if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
// Using VertexColor, switch to Unshaded and set VertexColor=true
enableVcolor = true;
defName = "Common/MatDefs/Misc/Unshaded.j3md";
} else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
|| defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
// Using SimpleTextured/SolidColor, just switch to Unshaded
defName = "Common/MatDefs/Misc/Unshaded.j3md";
} else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
// Using WireColor, set wireframe render state = true and use Unshaded
getAdditionalRenderState().setWireframe(true);
defName = "Common/MatDefs/Misc/Unshaded.j3md";
} else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
// Uses unshaded, ensure that the proper param is set
MatParam value = params.get("SeperateTexCoord");
if (value != null && ((Boolean) value.getValue()) == true) {
params.remove("SeperateTexCoord");
separateTexCoord = true;
}
}
assert applyDefaultValues && guessRenderStateApply;
}
def = im.getAssetManager().loadAsset(new AssetKey(defName));
paramValues = new ListMap();
// load the textures and update nextTexUnit
for (Map.Entry entry : params.entrySet()) {
MatParam param = entry.getValue();
if (param instanceof MatParamTexture) {
MatParamTexture texVal = (MatParamTexture) param;
// the texture failed to load for this param
// do not add to param values
if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
continue;
}
checkTextureParamColorSpace(texVal.getName(), texVal.getTextureValue());
}
if (im.getFormatVersion() == 0 && param.getName().startsWith("m_")) {
// Ancient version of jME3 ...
param.setName(param.getName().substring(2));
}
if (def.getMaterialParam(param.getName()) == null) {
logger.log(Level.WARNING, "The material parameter is not defined: {0}. Ignoring..",
param.getName());
} else {
checkSetParam(param.getVarType(), param.getName());
paramValues.put(param.getName(), param);
}
}
if (applyDefaultValues) {
// compatability with old versions where default vars were
// not available
for (MatParam param : def.getMaterialParams()) {
if (param.getValue() != null && paramValues.get(param.getName()) == null) {
setParam(param.getName(), param.getVarType(), param.getValue());
}
}
}
if (guessRenderStateApply && additionalState != null) {
// Try to guess values of "apply" render state based on defaults
// if value != default then set apply to true
additionalState.applyPolyOffset = additionalState.offsetEnabled;
additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
additionalState.applyColorWrite = !additionalState.colorWrite;
additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
additionalState.applyDepthTest = !additionalState.depthTest;
additionalState.applyDepthWrite = !additionalState.depthWrite;
additionalState.applyStencilTest = additionalState.stencilTest;
additionalState.applyWireFrame = additionalState.wireframe;
}
if (enableVcolor) {
setBoolean("VertexColor", true);
}
if (separateTexCoord) {
setBoolean("SeparateTexCoord", true);
}
}
}