/** * 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) { int texUnit = ((MatParamTexture) matParam).getUnit(); nextTexUnit--; for (MatParam param : paramValues.values()) { if (param instanceof MatParamTexture) { MatParamTexture texParam = (MatParamTexture) param; if (texParam.getUnit() > texUnit) { texParam.setUnit(texParam.getUnit() - 1); } } } sortingId = -1; } if (technique != null) { technique.notifyParamChanged(name, null, null); } }
private void clearUniformsSetByCurrent(Shader shader) { ListMap<String, Uniform> uniforms = shader.getUniformMap(); int size = uniforms.size(); for (int i = 0; i < size; i++) { Uniform u = uniforms.getValue(i); u.clearSetByCurrentMaterial(); } }
/** * Preloads this material for the given render manager. * * <p>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 rm The render manager to preload for */ public void preload(RenderManager rm) { autoSelectTechnique(rm); Renderer r = rm.getRenderer(); TechniqueDef techDef = technique.getDef(); Collection<MatParam> params = paramValues.values(); for (MatParam param : params) { if (param instanceof MatParamTexture) { MatParamTexture texParam = (MatParamTexture) param; r.setTexture(0, texParam.getTextureValue()); } else { if (!techDef.isUsingShaders()) { continue; } technique.updateUniformParam(param.getName(), param.getVarType(), param.getValue()); } } Shader shader = technique.getShader(); if (techDef.isUsingShaders()) { r.setShader(shader); } }
/** * Returns the texture parameter set on this material with the given name, returns <code>null * </code> 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 the sorting ID or sorting index for this material. * * <p>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() { Technique t = getActiveTechnique(); if (sortingId == -1 && t != null && t.getShader() != null) { int texId = -1; for (int i = 0; i < paramValues.size(); i++) { MatParam param = paramValues.getValue(i); if (param instanceof MatParamTexture) { MatParamTexture tex = (MatParamTexture) param; if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) { if (texId == -1) { texId = 0; } texId += tex.getTextureValue().getImage().getId() % 0xff; } } } sortingId = texId + t.getShader().getId() * 1000; } 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<String, Technique>(); mat.paramValues = new ListMap<String, MatParam>(); for (int i = 0; i < paramValues.size(); i++) { Map.Entry<String, MatParam> entry = paramValues.getEntry(i); mat.paramValues.put(entry.getKey(), entry.getValue().clone()); } return mat; } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); } }
/** * 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, paramDef.getFixedFuncBinding())); } else { val.setValue(value); } if (technique != null) { technique.notifyParamChanged(name, type, value); } } }
/** * 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) { paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++)); } else { val.setTextureValue(value); } if (technique != null) { technique.notifyParamChanged(name, type, nextTexUnit - 1); } // need to recompute sort ID sortingId = -1; }
/** * 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<MatParam> getParams() { return paramValues.values(); }
/** * Returns the parameter set on this material with the given name, returns <code>null</code> 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); }
/** * 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() : "Default"; String otherDefName = other.technique != null ? other.technique.getDef().getName() : "Default"; 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; }
public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); 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<String, MatParam> params = (HashMap<String, MatParam>) 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 renderstate = 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 = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName)); paramValues = new ListMap<String, MatParam>(); // load the textures and update nextTexUnit for (Map.Entry<String, MatParam> entry : params.entrySet()) { MatParam param = entry.getValue(); if (param instanceof MatParamTexture) { MatParamTexture texVal = (MatParamTexture) param; if (nextTexUnit < texVal.getUnit() + 1) { nextTexUnit = texVal.getUnit() + 1; } // the texture failed to load for this param // do not add to param values if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) { continue; } } if (im.getFormatVersion() == 0 && param.getName().startsWith("m_")) { // Ancient version of jME3 ... param.setName(param.getName().substring(2)); } 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.applyAlphaFallOff = additionalState.alphaTest; additionalState.applyAlphaTest = additionalState.alphaTest; 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.applyPointSprite = additionalState.pointSprite; additionalState.applyStencilTest = additionalState.stencilTest; additionalState.applyWireFrame = additionalState.wireframe; } if (enableVcolor) { setBoolean("VertexColor", true); } if (separateTexCoord) { setBoolean("SeparateTexCoord", true); } }
/** * Called by {@link RenderManager} to render the geometry by using this material. * * <p>The material is rendered as follows: * * <ul> * <li>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}) * <ul> * <li>If the technique has been changed since the last frame, then it is notified via * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, * 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. * </ul> * <li>Set the {@link RenderState} to use for rendering. The render states are applied in this * order (later RenderStates override earlier RenderStates): * <ol> * <li>{@link TechniqueDef#getRenderState() Technique Definition's RenderState} - i.e. * specific renderstate that is required for the shader. * <li>{@link #getAdditionalRenderState() Material Instance Additional RenderState} - i.e. * ad-hoc renderstate set per model * <li>{@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} - * i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or * post-processing filter. * </ol> * <li>If the technique {@link TechniqueDef#isUsingShaders() uses a shader}, then the uniforms * of the shader must be updated. * <ul> * <li>Uniforms bound to material parameters are updated based on the current material * parameter values. * <li>Uniforms bound to world parameters are updated from the RenderManager. Internally * {@link UniformBindingManager} is used for this task. * <li>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. * </ul> * <li>If the technique uses a shader, the model is then rendered according to the lighting mode * specified on the technique definition. * <ul> * <li>{@link LightMode#SinglePass single pass light mode} fills the shader's light * uniform arrays with the first 4 lights and renders the model once. * <li>{@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. * </ul> * <li>For techniques that do not use shaders, fixed function OpenGL is used to render the model * (see {@link GL1Renderer} interface): * <ul> * <li>OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is * updated. * <li>The texture set on the material is uploaded and bound. Currently only 1 texture is * supported for fixed function techniques. * <li>If the technique uses lighting, then OpenGL lighting state is updated based on the * light list on the geometry, otherwise OpenGL lighting is disabled. * <li>The mesh is uploaded and rendered. * </ul> * </ul> * * @param geom The geometry to render * @param rm The render manager requesting the rendering */ public void render(Geometry geom, RenderManager rm) { autoSelectTechnique(rm); Renderer r = rm.getRenderer(); TechniqueDef techDef = technique.getDef(); if (techDef.getLightMode() == LightMode.MultiPass && geom.getWorldLightList().size() == 0) { return; } if (rm.getForcedRenderState() != null) { r.applyRenderState(rm.getForcedRenderState()); } else { if (techDef.getRenderState() != null) { r.applyRenderState( techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); } else { r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); } } // update camera and world matrices // NOTE: setWorldTransform should have been called already if (techDef.isUsingShaders()) { // reset unchanged uniform flag clearUniformsSetByCurrent(technique.getShader()); rm.updateUniformBindings(technique.getWorldBindUniforms()); } // setup textures and uniforms for (int i = 0; i < paramValues.size(); i++) { MatParam param = paramValues.getValue(i); param.apply(r, technique); } Shader shader = technique.getShader(); // send lighting information, if needed switch (techDef.getLightMode()) { case Disable: r.setLighting(null); break; case SinglePass: updateLightListUniforms(shader, geom, 4); break; case FixedPipeline: r.setLighting(geom.getWorldLightList()); break; case MultiPass: // NOTE: Special case! resetUniformsNotSetByCurrent(shader); renderMultipassLighting(shader, geom, rm); // very important, notice the return statement! return; } // upload and bind shader if (techDef.isUsingShaders()) { // any unset uniforms will be set to 0 resetUniformsNotSetByCurrent(shader); r.setShader(shader); } r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1); }