2 * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
15 * may be used to endorse or promote products derived from this software
16 * without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
30 package com.jme3.material;
32 import com.jme3.asset.Asset;
33 import com.jme3.asset.AssetKey;
34 import com.jme3.math.ColorRGBA;
35 import com.jme3.math.Matrix4f;
36 import com.jme3.math.Vector2f;
37 import com.jme3.asset.AssetManager;
38 import com.jme3.export.JmeExporter;
39 import com.jme3.export.JmeImporter;
40 import com.jme3.export.InputCapsule;
41 import com.jme3.export.OutputCapsule;
42 import com.jme3.export.Savable;
43 import com.jme3.light.AmbientLight;
44 import com.jme3.light.DirectionalLight;
45 import com.jme3.light.Light;
46 import com.jme3.light.LightList;
47 import com.jme3.light.PointLight;
48 import com.jme3.light.SpotLight;
49 import com.jme3.material.RenderState.BlendMode;
50 import com.jme3.material.RenderState.FaceCullMode;
51 import com.jme3.material.TechniqueDef.LightMode;
52 import com.jme3.material.TechniqueDef.ShadowMode;
53 import com.jme3.math.Quaternion;
54 import com.jme3.math.Vector3f;
55 import com.jme3.math.Vector4f;
56 import com.jme3.renderer.Caps;
57 import com.jme3.renderer.RenderManager;
58 import com.jme3.renderer.Renderer;
59 import com.jme3.renderer.queue.RenderQueue.Bucket;
60 import com.jme3.scene.Geometry;
61 import com.jme3.shader.Shader;
62 import com.jme3.shader.Uniform;
63 import com.jme3.shader.VarType;
64 import com.jme3.texture.Texture;
65 import com.jme3.util.ListMap;
66 import com.jme3.util.TempVars;
67 import java.io.IOException;
68 import java.nio.FloatBuffer;
69 import java.util.Collection;
70 import java.util.EnumSet;
71 import java.util.HashMap;
72 import java.util.List;
74 import java.util.logging.Level;
75 import java.util.logging.Logger;
78 * <code>Material</code> describes the rendering style for a given
80 * <p>A material is essentially a list of {@link MatParam parameters},
81 * those parameters map to uniforms which are defined in a shader.
82 * Setting the parameters can modify the behavior of a
85 * @author Kirill Vainer
87 public class Material implements Asset, Cloneable, Savable, Comparable<Material> {
89 // Version #2: Fixed issue with RenderState.apply*** flags not getting exported
90 public static final int SAVABLE_VERSION = 2;
92 private static final Logger logger = Logger.getLogger(Material.class.getName());
93 private static final RenderState additiveLight = new RenderState();
94 private static final RenderState depthOnly = new RenderState();
95 private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1);
98 depthOnly.setDepthTest(true);
99 depthOnly.setDepthWrite(true);
100 depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back);
101 depthOnly.setColorWrite(false);
103 additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive);
104 additiveLight.setDepthWrite(false);
106 private AssetKey key;
107 private MaterialDef def;
108 private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>();
109 private Technique technique;
110 private HashMap<String, Technique> techniques = new HashMap<String, Technique>();
111 private Technique[] techniqueArray = null;
112 private int nextTexUnit = 0;
113 private RenderState additionalState = null;
114 private RenderState mergedRenderState = new RenderState();
115 private boolean transparent = false;
116 private boolean receivesShadows = false;
117 private int sortingId = -1;
118 private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1);
120 public Material(MaterialDef def) {
122 throw new NullPointerException("Material definition cannot be null");
126 // Load default values from definition (if any)
127 for (MatParam param : def.getMaterialParams()){
128 if (param.getValue() != null){
129 setParam(param.getName(), param.getVarType(), param.getValue());
134 public Material(AssetManager contentMan, String defName) {
135 this((MaterialDef) contentMan.loadAsset(new AssetKey(defName)));
139 * Do not use this constructor. Serialization purposes only.
145 * Returns the asset key name of the asset from which this material was loaded.
147 * <p>This value will be <code>null</code> unless this material was loaded
150 * @return Asset key name of the j3m file
152 public String getAssetName() {
153 return key != null ? key.getName() : null;
156 public void setKey(AssetKey key) {
160 public AssetKey getKey() {
165 * Returns the sorting ID or sorting index for this material.
167 * <p>The sorting ID is used internally by the system to sort rendering
168 * of geometries. It sorted to reduce shader switches, if the shaders
169 * are equal, then it is sorted by textures.
171 * @return The sorting ID used for sorting geometries for rendering.
173 public int getSortId() {
174 Technique t = getActiveTechnique();
175 if (sortingId == -1 && t != null && t.getShader() != null) {
177 for (int i = 0; i < paramValues.size(); i++) {
178 MatParam param = paramValues.getValue(i);
179 if (param instanceof MatParamTexture) {
180 MatParamTexture tex = (MatParamTexture) param;
181 if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) {
185 texId += tex.getTextureValue().getImage().getId() % 0xff;
189 sortingId = texId + t.getShader().getId() * 1000;
195 * Uses the sorting ID for each material to compare them.
197 * @param m The other material to compare to.
199 * @return zero if the materials are equal, returns a negative value
200 * if <code>this</code> has a lower sorting ID than <code>m</code>,
201 * otherwise returns a positive value.
203 public int compareTo(Material m) {
204 return m.getSortId() - getSortId();
208 * Clones this material. The result is returned.
211 public Material clone() {
213 Material mat = (Material) super.clone();
215 if (additionalState != null) {
216 mat.additionalState = additionalState.clone();
218 mat.technique = null;
219 mat.techniques = new HashMap<String, Technique>();
220 mat.techniqueArray = null;
222 mat.paramValues = new ListMap<String, MatParam>();
223 for (int i = 0; i < paramValues.size(); i++) {
224 Map.Entry<String, MatParam> entry = paramValues.getEntry(i);
225 mat.paramValues.put(entry.getKey(), entry.getValue().clone());
229 } catch (CloneNotSupportedException ex) {
230 throw new AssertionError();
235 * Returns the currently active technique.
237 * The technique is selected automatically by the {@link RenderManager}
238 * based on system capabilities. Users may select their own
240 * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }.
242 * @return the currently active technique.
244 * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager)
246 public Technique getActiveTechnique() {
251 * Check if the transparent value marker is set on this material.
252 * @return True if the transparent value marker is set on this material.
253 * @see #setTransparent(boolean)
255 public boolean isTransparent() {
260 * Set the transparent value marker.
262 * <p>This value is merely a marker, by itself it does nothing.
263 * Generally model loaders will use this marker to indicate further
264 * up that the material is transparent and therefore any geometries
265 * using it should be put into the {@link Bucket#Transparent transparent
268 * @param transparent the transparent value marker.
270 public void setTransparent(boolean transparent) {
271 this.transparent = transparent;
275 * Check if the material should receive shadows or not.
277 * @return True if the material should receive shadows.
279 * @see Material#setReceivesShadows(boolean)
281 public boolean isReceivesShadows() {
282 return receivesShadows;
286 * Set if the material should receive shadows or not.
288 * <p>This value is merely a marker, by itself it does nothing.
289 * Generally model loaders will use this marker to indicate
290 * the material should receive shadows and therefore any
291 * geometries using it should have the {@link ShadowMode#Receive} set
294 * @param receivesShadows if the material should receive shadows or not.
296 public void setReceivesShadows(boolean receivesShadows) {
297 this.receivesShadows = receivesShadows;
301 * Acquire the additional {@link RenderState render state} to apply
304 * <p>The first call to this method will create an additional render
305 * state which can be modified by the user to apply any render
306 * states in addition to the ones used by the renderer. Only render
307 * states which are modified in the additional render state will be applied.
309 * @return The additional render state.
311 public RenderState getAdditionalRenderState() {
312 if (additionalState == null) {
313 additionalState = RenderState.ADDITIONAL.clone();
315 return additionalState;
319 * Get the material definition (j3md file info) that <code>this</code>
320 * material is implementing.
322 * @return the material definition this material implements.
324 public MaterialDef getMaterialDef() {
329 * Returns the parameter set on this material with the given name,
330 * returns <code>null</code> if the parameter is not set.
332 * @param name The parameter name to look up.
333 * @return The MatParam if set, or null if not set.
335 public MatParam getParam(String name) {
336 MatParam param = paramValues.get(name);
337 if (param instanceof MatParam) {
338 return (MatParam) param;
344 * Returns the texture parameter set on this material with the given name,
345 * returns <code>null</code> if the parameter is not set.
347 * @param name The parameter name to look up.
348 * @return The MatParamTexture if set, or null if not set.
350 public MatParamTexture getTextureParam(String name) {
351 MatParam param = paramValues.get(name);
352 if (param instanceof MatParamTexture) {
353 return (MatParamTexture) param;
359 * Returns a collection of all parameters set on this material.
361 * @return a collection of all parameters set on this material.
363 * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object)
365 public Collection<MatParam> getParams() {
366 return paramValues.values();
369 private String checkSetParam(VarType type, String name) {
370 MatParam paramDef = def.getMaterialParam(name);
371 String newName = name;
373 if (paramDef == null && name.startsWith("m_")) {
374 newName = name.substring(2);
375 paramDef = def.getMaterialParam(newName);
376 if (paramDef == null) {
377 throw new IllegalArgumentException("Material parameter is not defined: " + name);
379 logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName});
381 } else if (paramDef == null) {
382 throw new IllegalArgumentException("Material parameter is not defined: " + name);
385 if (type != null && paramDef.getVarType() != type) {
386 logger.log(Level.WARNING, "Material parameter being set: {0} with "
387 + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} );
392 public int getParamIndex(String name) {
393 for(int i=paramValues.size()-1;i>=0;i--) {
394 if (name.equals(paramValues.getKey(i))) {
401 * Pass a parameter to the material shader.
403 * @param name the name of the parameter defined in the material definition (j3md)
404 * @param type the type of the parameter {@link VarType}
405 * @param value the value of the parameter
407 public void setParam(String name, VarType type, Object value) {
408 name = checkSetParam(type, name);
409 boolean techniqueUpdateNeeded = false;
410 MatParam val = getParam(name);
411 // if (technique != null) {
412 // technique.notifySetParam(name, type, value);
416 MatParam paramDef = def.getMaterialParam(name);
417 val = new MatParam(type, name, value, paramDef.getFixedFuncBinding());
419 paramValues.put(name, val);
420 techniqueUpdateNeeded = true;
424 Object value2 = val.multiData != null ? val.multiData : value;
425 if (techniqueArray == null) {
428 for(Technique tech : techniqueArray) {
429 tech.notifySetParam(name, type, value2);
431 if (techniqueUpdateNeeded) {
432 for(Technique tech : techniqueArray) {
433 tech.setNeedReload(true);
437 public void setParam(int paramIndex, VarType type, Object value) {
438 MatParam val = paramValues.getValue(paramIndex);
439 // if (technique != null) {
440 // technique.notifySetParam(name, type, value);
444 Object value2 = val.multiData != null ? val.multiData : value;
445 if (techniqueArray == null) {
448 paramValues.getValue(0);
449 for(Technique tech : techniqueArray) {
450 tech.notifySetParam(paramIndex, type, value2);
454 * Clear a parameter from this material. The parameter must exist
455 * @param name the name of the parameter to clear
457 public void clearParam(String name) {
458 //On removal, we don't check if the param exists in the paramDef, and just go on with the process.
459 // name = checkSetParam(null, name);
461 MatParam matParam = getParam(name);
462 if (matParam != null) {
463 // paramValues.remove(name);
464 paramValues.put(name, null);
465 // if (technique != null) {
466 // technique.notifyClearParam(name);
468 if (techniqueArray == null) {
471 for(Technique tech : techniqueArray) {
472 tech.notifyClearParam(name);
474 if (matParam instanceof MatParamTexture) {
475 int texUnit = ((MatParamTexture) matParam).getUnit();
477 for (MatParam param : paramValues.values()) {
478 if (param instanceof MatParamTexture) {
479 MatParamTexture texParam = (MatParamTexture) param;
480 if (texParam.getUnit() > texUnit) {
481 texParam.setUnit(texParam.getUnit() - 1);
488 // throw new IllegalArgumentException("The given parameter is not set.");
492 private void clearTextureParam(String name) {
493 name = checkSetParam(null, name);
495 MatParamTexture val = getTextureParam(name);
497 throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null.");
500 int texUnit = val.getUnit();
501 // paramValues.remove(name);
502 paramValues.put(name, null);
504 for (MatParam param : paramValues.values()) {
505 if (param instanceof MatParamTexture) {
506 MatParamTexture texParam = (MatParamTexture) param;
507 if (texParam.getUnit() > texUnit) {
508 texParam.setUnit(texParam.getUnit() - 1);
517 * Set a texture parameter.
519 * @param name The name of the parameter
520 * @param type The variable type {@link VarType}
521 * @param value The texture value of the parameter.
523 * @throws IllegalArgumentException is value is null
525 public void setTextureParam(String name, VarType type, Texture value) {
527 throw new IllegalArgumentException();
529 boolean techniqueUpdateNeeded = false;
530 name = checkSetParam(type, name);
531 MatParamTexture val = getTextureParam(name);
533 paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++));
534 techniqueUpdateNeeded = true;
536 val.setTextureValue(value);
539 // if (technique != null) {
540 // technique.notifySetParam(name, type, nextTexUnit - 1);
542 if (techniqueArray == null) {
545 for(Technique tech : techniqueArray) {
546 tech.notifySetParam(name, type, nextTexUnit - 1);
548 if (techniqueUpdateNeeded) {
549 for(Technique tech : techniqueArray) {
550 tech.setNeedReload(true);
554 // need to recompute sort ID
559 * Pass a texture to the material shader.
561 * @param name the name of the texture defined in the material definition
562 * (j3md) (for example Texture for Lighting.j3md)
563 * @param value the Texture object previously loaded by the asset manager
565 public void setTexture(String name, Texture value) {
568 clearTextureParam(name);
572 VarType paramType = null;
573 switch (value.getType()) {
575 paramType = VarType.Texture2D;
577 case TwoDimensionalArray:
578 paramType = VarType.TextureArray;
580 case ThreeDimensional:
581 paramType = VarType.Texture3D;
584 paramType = VarType.TextureCubeMap;
587 throw new UnsupportedOperationException("Unknown texture type: " + value.getType());
590 setTextureParam(name, paramType, value);
594 * Pass a Matrix4f to the material shader.
596 * @param name the name of the matrix defined in the material definition (j3md)
597 * @param value the Matrix4f object
599 public void setMatrix4(String name, Matrix4f value) {
600 setParam(name, VarType.Matrix4, value);
604 * Pass a boolean to the material shader.
606 * @param name the name of the boolean defined in the material definition (j3md)
607 * @param value the boolean value
609 public void setBoolean(String name, boolean value) {
610 setParam(name, VarType.Boolean, value);
614 * Pass a float to the material shader.
616 * @param name the name of the float defined in the material definition (j3md)
617 * @param value the float value
619 public void setFloat(String name, float value) {
620 setParam(name, VarType.Float, value);
624 * Pass an int to the material shader.
626 * @param name the name of the int defined in the material definition (j3md)
627 * @param value the int value
629 public void setInt(String name, int value) {
630 setParam(name, VarType.Int, value);
634 * Pass a Color to the material shader.
636 * @param name the name of the color defined in the material definition (j3md)
637 * @param value the ColorRGBA value
639 public void setColor(String name, ColorRGBA value) {
640 setParam(name, VarType.Vector4, value);
644 * Pass a Vector2f to the material shader.
646 * @param name the name of the Vector2f defined in the material definition (j3md)
647 * @param value the Vector2f value
649 public void setVector2(String name, Vector2f value) {
650 setParam(name, VarType.Vector2, value);
654 * Pass a Vector3f to the material shader.
656 * @param name the name of the Vector3f defined in the material definition (j3md)
657 * @param value the Vector3f value
659 public void setVector3(String name, Vector3f value) {
660 setParam(name, VarType.Vector3, value);
664 * Pass a Vector4f to the material shader.
666 * @param name the name of the Vector4f defined in the material definition (j3md)
667 * @param value the Vector4f value
669 public void setVector4(String name, Vector4f value) {
670 setParam(name, VarType.Vector4, value);
673 private ColorRGBA getAmbientColor(LightList lightList) {
674 ambientLightColor.set(0, 0, 0, 1);
675 for (int j = 0; j < lightList.size(); j++) {
676 Light l = lightList.get(j);
677 if (l instanceof AmbientLight) {
678 ambientLightColor.addLocal(l.getColor());
681 ambientLightColor.a = 1.0f;
682 return ambientLightColor;
686 * Uploads the lights in the light list as two uniform arrays.<br/><br/>
688 * <code>uniform vec4 g_LightColor[numLights];</code><br/>
689 * // g_LightColor.rgb is the diffuse/specular color of the light.<br/>
690 * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/>
693 * <code>uniform vec4 g_LightPosition[numLights];</code><br/>
694 * // g_LightPosition.xyz is the position of the light (for point lights)<br/>
695 * // or the direction of the light (for directional lights).<br/>
696 * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/>
699 protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) {
700 if (numLights == 0) { // this shader does not do lighting, ignore.
704 LightList lightList = g.getWorldLightList();
705 Uniform lightColor = shader.getUniform("g_LightColor");
706 Uniform lightPos = shader.getUniform("g_LightPosition");
707 Uniform lightDir = shader.getUniform("g_LightDirection");
708 lightColor.setVector4Length(numLights);
709 lightPos.setVector4Length(numLights);
710 lightDir.setVector4Length(numLights);
712 Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
713 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
717 for (int i = 0; i < numLights; i++) {
718 if (lightList.size() <= i) {
719 lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
720 lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
722 Light l = lightList.get(i);
723 ColorRGBA color = l.getColor();
724 lightColor.setVector4InArray(color.getRed(),
730 switch (l.getType()) {
732 DirectionalLight dl = (DirectionalLight) l;
733 Vector3f dir = dl.getDirection();
734 lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex);
737 PointLight pl = (PointLight) l;
738 Vector3f pos = pl.getPosition();
739 float invRadius = pl.getInvRadius();
740 lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex);
743 SpotLight sl = (SpotLight) l;
744 Vector3f pos2 = sl.getPosition();
745 Vector3f dir2 = sl.getDirection();
746 float invRange = sl.getInvSpotRange();
747 float spotAngleCos = sl.getPackedAngleCos();
749 lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex);
750 lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex);
753 // skip this light. Does not increase lightIndex
756 throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
763 while (lightIndex < numLights) {
764 lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
765 lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex);
771 protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) {
773 Renderer r = rm.getRenderer();
774 LightList lightList = g.getWorldLightList();
775 Uniform lightDir = shader.getUniform("g_LightDirection");
776 Uniform lightColor = shader.getUniform("g_LightColor");
777 Uniform lightPos = shader.getUniform("g_LightPosition");
778 Uniform ambientColor = shader.getUniform("g_AmbientLightColor");
779 boolean isFirstLight = true;
780 boolean isSecondLight = false;
782 for (int i = 0; i < lightList.size(); i++) {
783 Light l = lightList.get(i);
784 if (l instanceof AmbientLight) {
789 // set ambient color for first light only
790 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
791 isFirstLight = false;
792 isSecondLight = true;
793 } else if (isSecondLight) {
794 ambientColor.setValue(VarType.Vector4, ColorRGBA.Black);
795 // apply additive blending for 2nd and future lights
796 r.applyRenderState(additiveLight);
797 isSecondLight = false;
800 TempVars vars = TempVars.get();
801 Quaternion tmpLightDirection = vars.quat1;
802 Quaternion tmpLightPosition = vars.quat2;
803 ColorRGBA tmpLightColor = vars.color;
804 Vector4f tmpVec = vars.vect4f;
806 ColorRGBA color = l.getColor();
807 tmpLightColor.set(color);
808 tmpLightColor.a = l.getType().getId();
809 lightColor.setValue(VarType.Vector4, tmpLightColor);
811 switch (l.getType()) {
813 DirectionalLight dl = (DirectionalLight) l;
814 Vector3f dir = dl.getDirection();
816 tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1);
817 lightPos.setValue(VarType.Vector4, tmpLightPosition);
818 tmpLightDirection.set(0, 0, 0, 0);
819 lightDir.setValue(VarType.Vector4, tmpLightDirection);
822 PointLight pl = (PointLight) l;
823 Vector3f pos = pl.getPosition();
824 float invRadius = pl.getInvRadius();
826 tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius);
827 lightPos.setValue(VarType.Vector4, tmpLightPosition);
828 tmpLightDirection.set(0, 0, 0, 0);
829 lightDir.setValue(VarType.Vector4, tmpLightDirection);
832 SpotLight sl = (SpotLight) l;
833 Vector3f pos2 = sl.getPosition();
834 Vector3f dir2 = sl.getDirection();
835 float invRange = sl.getInvSpotRange();
836 float spotAngleCos = sl.getPackedAngleCos();
838 tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange);
839 lightPos.setValue(VarType.Vector4, tmpLightPosition);
841 //We transform the spot directoin in view space here to save 5 varying later in the lighting shader
842 //one vec4 less and a vec4 that becomes a vec3
843 //the downside is that spotAngleCos decoding happen now in the frag shader.
844 tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0);
845 rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec);
846 tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos);
848 lightDir.setValue(VarType.Vector4, tmpLightDirection);
852 throw new UnsupportedOperationException("Unknown type of light: " + l.getType());
856 r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
859 if (isFirstLight && lightList.size() > 0) {
860 // There are only ambient lights in the scene. Render
861 // a dummy "normal light" so we can see the ambient
862 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList));
863 lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha);
864 lightPos.setValue(VarType.Vector4, nullDirLight);
866 r.renderMesh(g.getMesh(), g.getLodLevel(), 1);
871 * Select the technique to use for rendering this material.
873 * If <code>name</code> is "Default", then one of the
874 * {@link MaterialDef#getDefaultTechniques() default techniques}
875 * on the material will be selected. Otherwise, the named technique
876 * will be found in the material definition.
878 * Any candidate technique for selection (either default or named)
879 * must be verified to be compatible with the system, for that, the
880 * <code>renderManager</code> is queried for capabilities.
882 * @param name The name of the technique to select, pass "Default" to
883 * select one of the default techniques.
884 * @param renderManager The {@link RenderManager render manager}
885 * to query for capabilities.
887 * @throws IllegalArgumentException If "Default" is passed and no default
888 * techniques are available on the material definition, or if a name
889 * is passed but there's no technique by that name.
890 * @throws UnsupportedOperationException If no candidate technique supports
891 * the system capabilities.
893 public void selectTechnique(String name, RenderManager renderManager) {
894 // check if already created
895 Technique tech = techniques.get(name);
897 // When choosing technique, we choose one that
898 // supports all the caps.
899 EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps();
901 if (name.equals("Default")) {
902 List<TechniqueDef> techDefs = def.getDefaultTechniques();
903 if (techDefs == null || techDefs.isEmpty()) {
904 throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'");
907 TechniqueDef lastTech = null;
908 for (TechniqueDef techDef : techDefs) {
909 if (rendererCaps.containsAll(techDef.getRequiredCaps())) {
910 // use the first one that supports all the caps
911 tech = new Technique(this, techDef);
912 putTechnique(name, tech);
918 throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n"
919 + " is supported by the video hardware. The caps "
920 + lastTech.getRequiredCaps() + " are required.");
924 // create "special" technique instance
925 TechniqueDef techDef = def.getTechniqueDef(name);
926 if (techDef == null) {
927 throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name);
930 if (!rendererCaps.containsAll(techDef.getRequiredCaps())) {
931 throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n"
932 + "requires caps " + techDef.getRequiredCaps() + " which are not "
933 + "supported by the video renderer");
936 tech = new Technique(this, techDef);
937 putTechnique(name, tech);
939 } else if (technique == tech) {
940 // attempting to switch to an already
946 if (technique.isNeedReload()) {
947 tech.makeCurrent(def.getAssetManager(), paramValues);
950 // shader was changed
954 private void autoSelectTechnique(RenderManager rm) {
955 if (technique == null) {
956 // NOTE: Not really needed anymore since we have technique
957 // selection by caps. Rename all "FixedFunc" techniques to "Default"
958 // and remove this hack.
959 if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) {
960 selectTechnique("FixedFunc", rm);
962 selectTechnique("Default", rm);
964 } else if (technique.isNeedReload()) {
965 technique.makeCurrent(def.getAssetManager(), paramValues);
970 * Preloads this material for the given render manager.
972 * Preloading the material can ensure that when the material is first
973 * used for rendering, there won't be any delay since the material has
974 * been already been setup for rendering.
976 * @param rm The render manager to preload for
978 public void preload(RenderManager rm) {
979 autoSelectTechnique(rm);
981 Renderer r = rm.getRenderer();
982 TechniqueDef techDef = technique.getDef();
984 Collection<MatParam> params = paramValues.values();
985 for (MatParam param : params) {
986 if (param instanceof MatParamTexture) {
987 MatParamTexture texParam = (MatParamTexture) param;
988 r.setTexture(0, texParam.getTextureValue());
990 if (!techDef.isUsingShaders()) {
994 technique.updateUniformParam(param.getName(),
996 param.getValue(), true);
1000 Shader shader = technique.getShader();
1001 if (techDef.isUsingShaders()) {
1002 r.setShader(shader);
1006 private void clearUniformsSetByCurrent(Shader shader) {
1007 ListMap<String, Uniform> uniforms = shader.getUniformMap();
1008 for (int i = 0; i < uniforms.size(); i++) {
1009 Uniform u = uniforms.getValue(i);
1010 u.clearSetByCurrentMaterial();
1014 private void resetUniformsNotSetByCurrent(Shader shader) {
1015 ListMap<String, Uniform> uniforms = shader.getUniformMap();
1016 for (int i = 0; i < uniforms.size(); i++) {
1017 Uniform u = uniforms.getValue(i);
1018 if (!u.isSetByCurrentMaterial()) {
1025 * Called by {@link RenderManager} to render the geometry by
1026 * using this material.
1028 * @param geom The geometry to render
1029 * @param rm The render manager requesting the rendering
1031 public void render(Geometry geom, RenderManager rm) {
1032 autoSelectTechnique(rm);
1034 Renderer r = rm.getRenderer();
1036 TechniqueDef techDef = technique.getDef();
1038 if (techDef.getLightMode() == LightMode.MultiPass
1039 && geom.getWorldLightList().size() == 0) {
1043 if (rm.getForcedRenderState() != null) {
1044 r.applyRenderState(rm.getForcedRenderState());
1046 if (techDef.getRenderState() != null) {
1047 r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState));
1049 r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState));
1053 boolean needReloadParams = false;
1054 if (technique.isNeedReload()) {
1055 technique.makeCurrent(def.getAssetManager(), paramValues);
1056 needReloadParams = true;
1058 // update camera and world matrices
1059 // NOTE: setWorldTransform should have been called already
1060 if (techDef.isUsingShaders()) {
1061 // reset unchanged uniform flag
1062 clearUniformsSetByCurrent(technique.getShader());
1063 rm.updateUniformBindings(technique.getWorldBindUniforms());
1066 // setup textures and uniforms
1067 for (int i = paramValues.size()-1; i >=0 ; i--) {
1068 MatParam param = paramValues.getValue(i);
1069 if (param != null) {
1070 param.apply(r, technique, i);
1074 Shader shader = technique.getShader();
1076 // send lighting information, if needed
1077 switch (techDef.getLightMode()) {
1079 r.setLighting(null);
1082 updateLightListUniforms(shader, geom, 4);
1085 r.setLighting(geom.getWorldLightList());
1088 // NOTE: Special case!
1089 resetUniformsNotSetByCurrent(shader);
1090 renderMultipassLighting(shader, geom, rm);
1091 // very important, notice the return statement!
1095 // upload and bind shader
1096 if (techDef.isUsingShaders()) {
1097 // any unset uniforms will be set to 0
1098 resetUniformsNotSetByCurrent(shader);
1099 r.setShader(shader);
1102 r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1);
1105 public void write(JmeExporter ex) throws IOException {
1106 OutputCapsule oc = ex.getCapsule(this);
1107 oc.write(def.getAssetName(), "material_def", null);
1108 oc.write(additionalState, "render_state", null);
1109 oc.write(transparent, "is_transparent", false);
1110 oc.writeStringSavableMap(paramValues, "parameters", null);
1113 public void read(JmeImporter im) throws IOException {
1114 InputCapsule ic = im.getCapsule(this);
1116 additionalState = (RenderState) ic.readSavable("render_state", null);
1117 transparent = ic.readBoolean("is_transparent", false);
1119 // Load the material def
1120 String defName = ic.readString("material_def", null);
1121 HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null);
1123 boolean enableVcolor = false;
1124 boolean separateTexCoord = false;
1125 boolean applyDefaultValues = false;
1126 boolean guessRenderStateApply = false;
1128 int ver = ic.getSavableVersion(Material.class);
1130 applyDefaultValues = true;
1133 guessRenderStateApply = true;
1135 if (im.getFormatVersion() == 0) {
1136 // Enable compatibility with old models
1137 if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) {
1138 // Using VertexColor, switch to Unshaded and set VertexColor=true
1139 enableVcolor = true;
1140 defName = "Common/MatDefs/Misc/Unshaded.j3md";
1141 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md")
1142 || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) {
1143 // Using SimpleTextured/SolidColor, just switch to Unshaded
1144 defName = "Common/MatDefs/Misc/Unshaded.j3md";
1145 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) {
1146 // Using WireColor, set wireframe renderstate = true and use Unshaded
1147 getAdditionalRenderState().setWireframe(true);
1148 defName = "Common/MatDefs/Misc/Unshaded.j3md";
1149 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) {
1150 // Uses unshaded, ensure that the proper param is set
1151 MatParam value = params.get("SeperateTexCoord");
1152 if (value != null && ((Boolean) value.getValue()) == true) {
1153 params.remove("SeperateTexCoord");
1154 separateTexCoord = true;
1157 assert applyDefaultValues && guessRenderStateApply;
1160 def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName));
1161 paramValues = new ListMap<String, MatParam>();
1163 // load the textures and update nextTexUnit
1164 for (Map.Entry<String, MatParam> entry : params.entrySet()) {
1165 MatParam param = entry.getValue();
1166 if (param instanceof MatParamTexture) {
1167 MatParamTexture texVal = (MatParamTexture) param;
1169 if (nextTexUnit < texVal.getUnit() + 1) {
1170 nextTexUnit = texVal.getUnit() + 1;
1173 // the texture failed to load for this param
1174 // do not add to param values
1175 if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) {
1179 param.setName(checkSetParam(param.getVarType(), param.getName()));
1180 paramValues.put(param.getName(), param);
1183 if (applyDefaultValues){
1184 // compatability with old versions where default vars were
1186 for (MatParam param : def.getMaterialParams()){
1187 if (param.getValue() != null && paramValues.get(param.getName()) == null){
1188 setParam(param.getPrefixedName(), param.getVarType(), param.getValue());
1192 if (guessRenderStateApply && additionalState != null){
1193 // Try to guess values of "apply" render state based on defaults
1194 // if value != default then set apply to true
1195 additionalState.applyPolyOffset = additionalState.offsetEnabled;
1196 additionalState.applyAlphaFallOff = additionalState.alphaTest;
1197 additionalState.applyAlphaTest = additionalState.alphaTest;
1198 additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off;
1199 additionalState.applyColorWrite = !additionalState.colorWrite;
1200 additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back;
1201 additionalState.applyDepthTest = !additionalState.depthTest;
1202 additionalState.applyDepthWrite = !additionalState.depthWrite;
1203 additionalState.applyPointSprite = additionalState.pointSprite;
1204 additionalState.applyStencilTest = additionalState.stencilTest;
1205 additionalState.applyWireFrame = additionalState.wireframe;
1208 setBoolean("VertexColor", true);
1210 if (separateTexCoord) {
1211 setBoolean("SeparateTexCoord", true);
1214 void putTechnique(String name, Technique tech) {
1215 techniques.put(name, tech);
1216 techniqueArray = null;
1218 void setTechniqueArray() {
1219 techniqueArray = techniques.values().toArray(new Technique[techniques.size()]);