1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine All rights reserved. 3 * <p/> 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are met: 6 * 7 * * Redistributions of source code must retain the above copyright notice, 8 * this list of conditions and the following disclaimer. 9 * <p/> 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. 13 * <p/> 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. 17 * <p/> 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. 29 */ 30 package com.jme3.material; 31 32 import com.jme3.asset.Asset; 33 import com.jme3.asset.AssetKey; 34 import com.jme3.asset.AssetManager; 35 import com.jme3.export.*; 36 import com.jme3.light.*; 37 import com.jme3.material.RenderState.BlendMode; 38 import com.jme3.material.RenderState.FaceCullMode; 39 import com.jme3.material.TechniqueDef.LightMode; 40 import com.jme3.material.TechniqueDef.ShadowMode; 41 import com.jme3.math.*; 42 import com.jme3.renderer.Caps; 43 import com.jme3.renderer.RenderManager; 44 import com.jme3.renderer.Renderer; 45 import com.jme3.renderer.queue.RenderQueue.Bucket; 46 import com.jme3.scene.Geometry; 47 import com.jme3.shader.Shader; 48 import com.jme3.shader.Uniform; 49 import com.jme3.shader.VarType; 50 import com.jme3.texture.Texture; 51 import com.jme3.util.ListMap; 52 import com.jme3.util.TempVars; 53 import java.io.IOException; 54 import java.util.*; 55 import java.util.logging.Level; 56 import java.util.logging.Logger; 57 58 /** 59 * <code>Material</code> describes the rendering style for a given 60 * {@link Geometry}. 61 * <p>A material is essentially a list of {@link MatParam parameters}, 62 * those parameters map to uniforms which are defined in a shader. 63 * Setting the parameters can modify the behavior of a 64 * shader. 65 * <p/> 66 * @author Kirill Vainer 67 */ 68 public class Material implements Asset, Cloneable, Savable, Comparable<Material> { 69 70 // Version #2: Fixed issue with RenderState.apply*** flags not getting exported 71 public static final int SAVABLE_VERSION = 2; 72 73 private static final Logger logger = Logger.getLogger(Material.class.getName()); 74 private static final RenderState additiveLight = new RenderState(); 75 private static final RenderState depthOnly = new RenderState(); 76 private static final Quaternion nullDirLight = new Quaternion(0, -1, 0, -1); 77 78 static { 79 depthOnly.setDepthTest(true); 80 depthOnly.setDepthWrite(true); 81 depthOnly.setFaceCullMode(RenderState.FaceCullMode.Back); 82 depthOnly.setColorWrite(false); 83 84 additiveLight.setBlendMode(RenderState.BlendMode.AlphaAdditive); 85 additiveLight.setDepthWrite(false); 86 } 87 private AssetKey key; 88 private String name; 89 private MaterialDef def; 90 private ListMap<String, MatParam> paramValues = new ListMap<String, MatParam>(); 91 private Technique technique; 92 private HashMap<String, Technique> techniques = new HashMap<String, Technique>(); 93 private int nextTexUnit = 0; 94 private RenderState additionalState = null; 95 private RenderState mergedRenderState = new RenderState(); 96 private boolean transparent = false; 97 private boolean receivesShadows = false; 98 private int sortingId = -1; 99 private transient ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); 100 101 public Material(MaterialDef def) { 102 if (def == null) { 103 throw new NullPointerException("Material definition cannot be null"); 104 } 105 this.def = def; 106 107 // Load default values from definition (if any) 108 for (MatParam param : def.getMaterialParams()){ 109 if (param.getValue() != null){ 110 setParam(param.getName(), param.getVarType(), param.getValue()); 111 } 112 } 113 } 114 115 public Material(AssetManager contentMan, String defName) { 116 this((MaterialDef) contentMan.loadAsset(new AssetKey(defName))); 117 } 118 119 /** 120 * Do not use this constructor. Serialization purposes only. 121 */ 122 public Material() { 123 } 124 125 /** 126 * Returns the asset key name of the asset from which this material was loaded. 127 * 128 * <p>This value will be <code>null</code> unless this material was loaded 129 * from a .j3m file. 130 * 131 * @return Asset key name of the j3m file 132 */ 133 public String getAssetName() { 134 return key != null ? key.getName() : null; 135 } 136 137 /** 138 * @return the name of the material (not the same as the asset name), the returned value can be null 139 */ 140 public String getName() { 141 return name; 142 } 143 144 /** 145 * This method sets the name of the material. 146 * The name is not the same as the asset name. 147 * It can be null and there is no guarantee of its uniqness. 148 * @param name the name of the material 149 */ 150 public void setName(String name) { 151 this.name = name; 152 } 153 154 public void setKey(AssetKey key) { 155 this.key = key; 156 } 157 158 public AssetKey getKey() { 159 return key; 160 } 161 162 /** 163 * Returns the sorting ID or sorting index for this material. 164 * 165 * <p>The sorting ID is used internally by the system to sort rendering 166 * of geometries. It sorted to reduce shader switches, if the shaders 167 * are equal, then it is sorted by textures. 168 * 169 * @return The sorting ID used for sorting geometries for rendering. 170 */ 171 public int getSortId() { 172 Technique t = getActiveTechnique(); 173 if (sortingId == -1 && t != null && t.getShader() != null) { 174 int texId = -1; 175 for (int i = 0; i < paramValues.size(); i++) { 176 MatParam param = paramValues.getValue(i); 177 if (param instanceof MatParamTexture) { 178 MatParamTexture tex = (MatParamTexture) param; 179 if (tex.getTextureValue() != null && tex.getTextureValue().getImage() != null) { 180 if (texId == -1) { 181 texId = 0; 182 } 183 texId += tex.getTextureValue().getImage().getId() % 0xff; 184 } 185 } 186 } 187 sortingId = texId + t.getShader().getId() * 1000; 188 } 189 return sortingId; 190 } 191 192 /** 193 * Uses the sorting ID for each material to compare them. 194 * 195 * @param m The other material to compare to. 196 * 197 * @return zero if the materials are equal, returns a negative value 198 * if <code>this</code> has a lower sorting ID than <code>m</code>, 199 * otherwise returns a positive value. 200 */ 201 public int compareTo(Material m) { 202 return m.getSortId() - getSortId(); 203 } 204 205 @Override 206 public boolean equals(Object obj) { 207 if(obj instanceof Material){ 208 return ((Material)obj).compareTo(this) == 0; 209 } 210 return super.equals(obj); 211 } 212 213 /** 214 * Clones this material. The result is returned. 215 */ 216 @Override 217 public Material clone() { 218 try { 219 Material mat = (Material) super.clone(); 220 221 if (additionalState != null) { 222 mat.additionalState = additionalState.clone(); 223 } 224 mat.technique = null; 225 mat.techniques = new HashMap<String, Technique>(); 226 227 mat.paramValues = new ListMap<String, MatParam>(); 228 for (int i = 0; i < paramValues.size(); i++) { 229 Map.Entry<String, MatParam> entry = paramValues.getEntry(i); 230 mat.paramValues.put(entry.getKey(), entry.getValue().clone()); 231 } 232 233 return mat; 234 } catch (CloneNotSupportedException ex) { 235 throw new AssertionError(); 236 } 237 } 238 239 /** 240 * Returns the currently active technique. 241 * <p> 242 * The technique is selected automatically by the {@link RenderManager} 243 * based on system capabilities. Users may select their own 244 * technique by using 245 * {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) }. 246 * 247 * @return the currently active technique. 248 * 249 * @see #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) 250 */ 251 public Technique getActiveTechnique() { 252 return technique; 253 } 254 255 /** 256 * Check if the transparent value marker is set on this material. 257 * @return True if the transparent value marker is set on this material. 258 * @see #setTransparent(boolean) 259 */ 260 public boolean isTransparent() { 261 return transparent; 262 } 263 264 /** 265 * Set the transparent value marker. 266 * 267 * <p>This value is merely a marker, by itself it does nothing. 268 * Generally model loaders will use this marker to indicate further 269 * up that the material is transparent and therefore any geometries 270 * using it should be put into the {@link Bucket#Transparent transparent 271 * bucket}. 272 * 273 * @param transparent the transparent value marker. 274 */ 275 public void setTransparent(boolean transparent) { 276 this.transparent = transparent; 277 } 278 279 /** 280 * Check if the material should receive shadows or not. 281 * 282 * @return True if the material should receive shadows. 283 * 284 * @see Material#setReceivesShadows(boolean) 285 */ 286 public boolean isReceivesShadows() { 287 return receivesShadows; 288 } 289 290 /** 291 * Set if the material should receive shadows or not. 292 * 293 * <p>This value is merely a marker, by itself it does nothing. 294 * Generally model loaders will use this marker to indicate 295 * the material should receive shadows and therefore any 296 * geometries using it should have the {@link ShadowMode#Receive} set 297 * on them. 298 * 299 * @param receivesShadows if the material should receive shadows or not. 300 */ 301 public void setReceivesShadows(boolean receivesShadows) { 302 this.receivesShadows = receivesShadows; 303 } 304 305 /** 306 * Acquire the additional {@link RenderState render state} to apply 307 * for this material. 308 * 309 * <p>The first call to this method will create an additional render 310 * state which can be modified by the user to apply any render 311 * states in addition to the ones used by the renderer. Only render 312 * states which are modified in the additional render state will be applied. 313 * 314 * @return The additional render state. 315 */ 316 public RenderState getAdditionalRenderState() { 317 if (additionalState == null) { 318 additionalState = RenderState.ADDITIONAL.clone(); 319 } 320 return additionalState; 321 } 322 323 /** 324 * Get the material definition (j3md file info) that <code>this</code> 325 * material is implementing. 326 * 327 * @return the material definition this material implements. 328 */ 329 public MaterialDef getMaterialDef() { 330 return def; 331 } 332 333 /** 334 * Returns the parameter set on this material with the given name, 335 * returns <code>null</code> if the parameter is not set. 336 * 337 * @param name The parameter name to look up. 338 * @return The MatParam if set, or null if not set. 339 */ 340 public MatParam getParam(String name) { 341 MatParam param = paramValues.get(name); 342 return param; 343 } 344 345 /** 346 * Returns the texture parameter set on this material with the given name, 347 * returns <code>null</code> if the parameter is not set. 348 * 349 * @param name The parameter name to look up. 350 * @return The MatParamTexture if set, or null if not set. 351 */ 352 public MatParamTexture getTextureParam(String name) { 353 MatParam param = paramValues.get(name); 354 if (param instanceof MatParamTexture) { 355 return (MatParamTexture) param; 356 } 357 return null; 358 } 359 360 /** 361 * Returns a collection of all parameters set on this material. 362 * 363 * @return a collection of all parameters set on this material. 364 * 365 * @see #setParam(java.lang.String, com.jme3.shader.VarType, java.lang.Object) 366 */ 367 public Collection<MatParam> getParams() { 368 return paramValues.values(); 369 } 370 371 private String checkSetParam(VarType type, String name) { 372 MatParam paramDef = def.getMaterialParam(name); 373 String newName = name; 374 375 if (paramDef == null && name.startsWith("m_")) { 376 newName = name.substring(2); 377 paramDef = def.getMaterialParam(newName); 378 if (paramDef == null) { 379 throw new IllegalArgumentException("Material parameter is not defined: " + name); 380 } else { 381 logger.log(Level.WARNING, "Material parameter {0} uses a deprecated naming convention use {1} instead ", new Object[]{name, newName}); 382 } 383 } else if (paramDef == null) { 384 throw new IllegalArgumentException("Material parameter is not defined: " + name); 385 } 386 387 if (type != null && paramDef.getVarType() != type) { 388 logger.log(Level.WARNING, "Material parameter being set: {0} with " 389 + "type {1} doesn''t match definition types {2}", new Object[]{name, type.name(), paramDef.getVarType()} ); 390 } 391 392 return newName; 393 } 394 395 /** 396 * Pass a parameter to the material shader. 397 * 398 * @param name the name of the parameter defined in the material definition (j3md) 399 * @param type the type of the parameter {@link VarType} 400 * @param value the value of the parameter 401 */ 402 public void setParam(String name, VarType type, Object value) { 403 name = checkSetParam(type, name); 404 405 MatParam val = getParam(name); 406 if (technique != null) { 407 technique.notifySetParam(name, type, value); 408 } 409 if (val == null) { 410 MatParam paramDef = def.getMaterialParam(name); 411 paramValues.put(name, new MatParam(type, name, value, paramDef.getFixedFuncBinding())); 412 } else { 413 val.setValue(value); 414 } 415 } 416 417 /** 418 * Clear a parameter from this material. The parameter must exist 419 * @param name the name of the parameter to clear 420 */ 421 public void clearParam(String name) { 422 //On removal, we don't check if the param exists in the paramDef, and just go on with the process. 423 // name = checkSetParam(null, name); 424 425 MatParam matParam = getParam(name); 426 if (matParam != null) { 427 paramValues.remove(name); 428 if (technique != null) { 429 technique.notifyClearParam(name); 430 } 431 if (matParam instanceof MatParamTexture) { 432 int texUnit = ((MatParamTexture) matParam).getUnit(); 433 nextTexUnit--; 434 for (MatParam param : paramValues.values()) { 435 if (param instanceof MatParamTexture) { 436 MatParamTexture texParam = (MatParamTexture) param; 437 if (texParam.getUnit() > texUnit) { 438 texParam.setUnit(texParam.getUnit() - 1); 439 } 440 } 441 } 442 } 443 } 444 // else { 445 // throw new IllegalArgumentException("The given parameter is not set."); 446 // } 447 } 448 449 private void clearTextureParam(String name) { 450 name = checkSetParam(null, name); 451 452 MatParamTexture val = getTextureParam(name); 453 if (val == null) { 454 throw new IllegalArgumentException("The given texture for parameter \"" + name + "\" is null."); 455 } 456 457 int texUnit = val.getUnit(); 458 paramValues.remove(name); 459 nextTexUnit--; 460 for (MatParam param : paramValues.values()) { 461 if (param instanceof MatParamTexture) { 462 MatParamTexture texParam = (MatParamTexture) param; 463 if (texParam.getUnit() > texUnit) { 464 texParam.setUnit(texParam.getUnit() - 1); 465 } 466 } 467 } 468 469 sortingId = -1; 470 } 471 472 /** 473 * Set a texture parameter. 474 * 475 * @param name The name of the parameter 476 * @param type The variable type {@link VarType} 477 * @param value The texture value of the parameter. 478 * 479 * @throws IllegalArgumentException is value is null 480 */ 481 public void setTextureParam(String name, VarType type, Texture value) { 482 if (value == null) { 483 throw new IllegalArgumentException(); 484 } 485 486 name = checkSetParam(type, name); 487 MatParamTexture val = getTextureParam(name); 488 if (val == null) { 489 paramValues.put(name, new MatParamTexture(type, name, value, nextTexUnit++)); 490 } else { 491 val.setTextureValue(value); 492 } 493 494 if (technique != null) { 495 technique.notifySetParam(name, type, nextTexUnit - 1); 496 } 497 498 // need to recompute sort ID 499 sortingId = -1; 500 } 501 502 /** 503 * Pass a texture to the material shader. 504 * 505 * @param name the name of the texture defined in the material definition 506 * (j3md) (for example Texture for Lighting.j3md) 507 * @param value the Texture object previously loaded by the asset manager 508 */ 509 public void setTexture(String name, Texture value) { 510 if (value == null) { 511 // clear it 512 clearTextureParam(name); 513 return; 514 } 515 516 VarType paramType = null; 517 switch (value.getType()) { 518 case TwoDimensional: 519 paramType = VarType.Texture2D; 520 break; 521 case TwoDimensionalArray: 522 paramType = VarType.TextureArray; 523 break; 524 case ThreeDimensional: 525 paramType = VarType.Texture3D; 526 break; 527 case CubeMap: 528 paramType = VarType.TextureCubeMap; 529 break; 530 default: 531 throw new UnsupportedOperationException("Unknown texture type: " + value.getType()); 532 } 533 534 setTextureParam(name, paramType, value); 535 } 536 537 /** 538 * Pass a Matrix4f to the material shader. 539 * 540 * @param name the name of the matrix defined in the material definition (j3md) 541 * @param value the Matrix4f object 542 */ 543 public void setMatrix4(String name, Matrix4f value) { 544 setParam(name, VarType.Matrix4, value); 545 } 546 547 /** 548 * Pass a boolean to the material shader. 549 * 550 * @param name the name of the boolean defined in the material definition (j3md) 551 * @param value the boolean value 552 */ 553 public void setBoolean(String name, boolean value) { 554 setParam(name, VarType.Boolean, value); 555 } 556 557 /** 558 * Pass a float to the material shader. 559 * 560 * @param name the name of the float defined in the material definition (j3md) 561 * @param value the float value 562 */ 563 public void setFloat(String name, float value) { 564 setParam(name, VarType.Float, value); 565 } 566 567 /** 568 * Pass an int to the material shader. 569 * 570 * @param name the name of the int defined in the material definition (j3md) 571 * @param value the int value 572 */ 573 public void setInt(String name, int value) { 574 setParam(name, VarType.Int, value); 575 } 576 577 /** 578 * Pass a Color to the material shader. 579 * 580 * @param name the name of the color defined in the material definition (j3md) 581 * @param value the ColorRGBA value 582 */ 583 public void setColor(String name, ColorRGBA value) { 584 setParam(name, VarType.Vector4, value); 585 } 586 587 /** 588 * Pass a Vector2f to the material shader. 589 * 590 * @param name the name of the Vector2f defined in the material definition (j3md) 591 * @param value the Vector2f value 592 */ 593 public void setVector2(String name, Vector2f value) { 594 setParam(name, VarType.Vector2, value); 595 } 596 597 /** 598 * Pass a Vector3f to the material shader. 599 * 600 * @param name the name of the Vector3f defined in the material definition (j3md) 601 * @param value the Vector3f value 602 */ 603 public void setVector3(String name, Vector3f value) { 604 setParam(name, VarType.Vector3, value); 605 } 606 607 /** 608 * Pass a Vector4f to the material shader. 609 * 610 * @param name the name of the Vector4f defined in the material definition (j3md) 611 * @param value the Vector4f value 612 */ 613 public void setVector4(String name, Vector4f value) { 614 setParam(name, VarType.Vector4, value); 615 } 616 617 private ColorRGBA getAmbientColor(LightList lightList) { 618 ambientLightColor.set(0, 0, 0, 1); 619 for (int j = 0; j < lightList.size(); j++) { 620 Light l = lightList.get(j); 621 if (l instanceof AmbientLight) { 622 ambientLightColor.addLocal(l.getColor()); 623 } 624 } 625 ambientLightColor.a = 1.0f; 626 return ambientLightColor; 627 } 628 629 /** 630 * Uploads the lights in the light list as two uniform arrays.<br/><br/> 631 * * <p> 632 * <code>uniform vec4 g_LightColor[numLights];</code><br/> 633 * // g_LightColor.rgb is the diffuse/specular color of the light.<br/> 634 * // g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point, <br/> 635 * // 2 = Spot. <br/> 636 * <br/> 637 * <code>uniform vec4 g_LightPosition[numLights];</code><br/> 638 * // g_LightPosition.xyz is the position of the light (for point lights)<br/> 639 * // or the direction of the light (for directional lights).<br/> 640 * // g_LightPosition.w is the inverse radius (1/r) of the light (for attenuation) <br/> 641 * </p> 642 */ 643 protected void updateLightListUniforms(Shader shader, Geometry g, int numLights) { 644 if (numLights == 0) { // this shader does not do lighting, ignore. 645 return; 646 } 647 648 LightList lightList = g.getWorldLightList(); 649 Uniform lightColor = shader.getUniform("g_LightColor"); 650 Uniform lightPos = shader.getUniform("g_LightPosition"); 651 Uniform lightDir = shader.getUniform("g_LightDirection"); 652 lightColor.setVector4Length(numLights); 653 lightPos.setVector4Length(numLights); 654 lightDir.setVector4Length(numLights); 655 656 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); 657 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); 658 659 int lightIndex = 0; 660 661 for (int i = 0; i < numLights; i++) { 662 if (lightList.size() <= i) { 663 lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); 664 lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); 665 } else { 666 Light l = lightList.get(i); 667 ColorRGBA color = l.getColor(); 668 lightColor.setVector4InArray(color.getRed(), 669 color.getGreen(), 670 color.getBlue(), 671 l.getType().getId(), 672 i); 673 674 switch (l.getType()) { 675 case Directional: 676 DirectionalLight dl = (DirectionalLight) l; 677 Vector3f dir = dl.getDirection(); 678 lightPos.setVector4InArray(dir.getX(), dir.getY(), dir.getZ(), -1, lightIndex); 679 break; 680 case Point: 681 PointLight pl = (PointLight) l; 682 Vector3f pos = pl.getPosition(); 683 float invRadius = pl.getInvRadius(); 684 lightPos.setVector4InArray(pos.getX(), pos.getY(), pos.getZ(), invRadius, lightIndex); 685 break; 686 case Spot: 687 SpotLight sl = (SpotLight) l; 688 Vector3f pos2 = sl.getPosition(); 689 Vector3f dir2 = sl.getDirection(); 690 float invRange = sl.getInvSpotRange(); 691 float spotAngleCos = sl.getPackedAngleCos(); 692 693 lightPos.setVector4InArray(pos2.getX(), pos2.getY(), pos2.getZ(), invRange, lightIndex); 694 lightDir.setVector4InArray(dir2.getX(), dir2.getY(), dir2.getZ(), spotAngleCos, lightIndex); 695 break; 696 case Ambient: 697 // skip this light. Does not increase lightIndex 698 continue; 699 default: 700 throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); 701 } 702 } 703 704 lightIndex++; 705 } 706 707 while (lightIndex < numLights) { 708 lightColor.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); 709 lightPos.setVector4InArray(0f, 0f, 0f, 0f, lightIndex); 710 711 lightIndex++; 712 } 713 } 714 715 protected void renderMultipassLighting(Shader shader, Geometry g, RenderManager rm) { 716 717 Renderer r = rm.getRenderer(); 718 LightList lightList = g.getWorldLightList(); 719 Uniform lightDir = shader.getUniform("g_LightDirection"); 720 Uniform lightColor = shader.getUniform("g_LightColor"); 721 Uniform lightPos = shader.getUniform("g_LightPosition"); 722 Uniform ambientColor = shader.getUniform("g_AmbientLightColor"); 723 boolean isFirstLight = true; 724 boolean isSecondLight = false; 725 726 for (int i = 0; i < lightList.size(); i++) { 727 Light l = lightList.get(i); 728 if (l instanceof AmbientLight) { 729 continue; 730 } 731 732 if (isFirstLight) { 733 // set ambient color for first light only 734 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); 735 isFirstLight = false; 736 isSecondLight = true; 737 } else if (isSecondLight) { 738 ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); 739 // apply additive blending for 2nd and future lights 740 r.applyRenderState(additiveLight); 741 isSecondLight = false; 742 } 743 744 TempVars vars = TempVars.get(); 745 Quaternion tmpLightDirection = vars.quat1; 746 Quaternion tmpLightPosition = vars.quat2; 747 ColorRGBA tmpLightColor = vars.color; 748 Vector4f tmpVec = vars.vect4f; 749 750 ColorRGBA color = l.getColor(); 751 tmpLightColor.set(color); 752 tmpLightColor.a = l.getType().getId(); 753 lightColor.setValue(VarType.Vector4, tmpLightColor); 754 755 switch (l.getType()) { 756 case Directional: 757 DirectionalLight dl = (DirectionalLight) l; 758 Vector3f dir = dl.getDirection(); 759 760 tmpLightPosition.set(dir.getX(), dir.getY(), dir.getZ(), -1); 761 lightPos.setValue(VarType.Vector4, tmpLightPosition); 762 tmpLightDirection.set(0, 0, 0, 0); 763 lightDir.setValue(VarType.Vector4, tmpLightDirection); 764 break; 765 case Point: 766 PointLight pl = (PointLight) l; 767 Vector3f pos = pl.getPosition(); 768 float invRadius = pl.getInvRadius(); 769 770 tmpLightPosition.set(pos.getX(), pos.getY(), pos.getZ(), invRadius); 771 lightPos.setValue(VarType.Vector4, tmpLightPosition); 772 tmpLightDirection.set(0, 0, 0, 0); 773 lightDir.setValue(VarType.Vector4, tmpLightDirection); 774 break; 775 case Spot: 776 SpotLight sl = (SpotLight) l; 777 Vector3f pos2 = sl.getPosition(); 778 Vector3f dir2 = sl.getDirection(); 779 float invRange = sl.getInvSpotRange(); 780 float spotAngleCos = sl.getPackedAngleCos(); 781 782 tmpLightPosition.set(pos2.getX(), pos2.getY(), pos2.getZ(), invRange); 783 lightPos.setValue(VarType.Vector4, tmpLightPosition); 784 785 //We transform the spot directoin in view space here to save 5 varying later in the lighting shader 786 //one vec4 less and a vec4 that becomes a vec3 787 //the downside is that spotAngleCos decoding happen now in the frag shader. 788 tmpVec.set(dir2.getX(), dir2.getY(), dir2.getZ(),0); 789 rm.getCurrentCamera().getViewMatrix().mult(tmpVec, tmpVec); 790 tmpLightDirection.set(tmpVec.getX(), tmpVec.getY(), tmpVec.getZ(), spotAngleCos); 791 792 lightDir.setValue(VarType.Vector4, tmpLightDirection); 793 794 break; 795 default: 796 throw new UnsupportedOperationException("Unknown type of light: " + l.getType()); 797 } 798 vars.release(); 799 r.setShader(shader); 800 r.renderMesh(g.getMesh(), g.getLodLevel(), 1); 801 } 802 803 if (isFirstLight && lightList.size() > 0) { 804 // There are only ambient lights in the scene. Render 805 // a dummy "normal light" so we can see the ambient 806 ambientColor.setValue(VarType.Vector4, getAmbientColor(lightList)); 807 lightColor.setValue(VarType.Vector4, ColorRGBA.BlackNoAlpha); 808 lightPos.setValue(VarType.Vector4, nullDirLight); 809 r.setShader(shader); 810 r.renderMesh(g.getMesh(), g.getLodLevel(), 1); 811 } 812 } 813 814 /** 815 * Select the technique to use for rendering this material. 816 * <p> 817 * If <code>name</code> is "Default", then one of the 818 * {@link MaterialDef#getDefaultTechniques() default techniques} 819 * on the material will be selected. Otherwise, the named technique 820 * will be found in the material definition. 821 * <p> 822 * Any candidate technique for selection (either default or named) 823 * must be verified to be compatible with the system, for that, the 824 * <code>renderManager</code> is queried for capabilities. 825 * 826 * @param name The name of the technique to select, pass "Default" to 827 * select one of the default techniques. 828 * @param renderManager The {@link RenderManager render manager} 829 * to query for capabilities. 830 * 831 * @throws IllegalArgumentException If "Default" is passed and no default 832 * techniques are available on the material definition, or if a name 833 * is passed but there's no technique by that name. 834 * @throws UnsupportedOperationException If no candidate technique supports 835 * the system capabilities. 836 */ 837 public void selectTechnique(String name, RenderManager renderManager) { 838 // check if already created 839 Technique tech = techniques.get(name); 840 if (tech == null) { 841 // When choosing technique, we choose one that 842 // supports all the caps. 843 EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps(); 844 845 if (name.equals("Default")) { 846 List<TechniqueDef> techDefs = def.getDefaultTechniques(); 847 if (techDefs == null || techDefs.isEmpty()) { 848 throw new IllegalArgumentException("No default techniques are available on material '" + def.getName() + "'"); 849 } 850 851 TechniqueDef lastTech = null; 852 for (TechniqueDef techDef : techDefs) { 853 if (rendererCaps.containsAll(techDef.getRequiredCaps())) { 854 // use the first one that supports all the caps 855 tech = new Technique(this, techDef); 856 techniques.put(name, tech); 857 break; 858 } 859 lastTech = techDef; 860 } 861 if (tech == null) { 862 throw new UnsupportedOperationException("No default technique on material '" + def.getName() + "'\n" 863 + " is supported by the video hardware. The caps " 864 + lastTech.getRequiredCaps() + " are required."); 865 } 866 867 } else { 868 // create "special" technique instance 869 TechniqueDef techDef = def.getTechniqueDef(name); 870 if (techDef == null) { 871 throw new IllegalArgumentException("For material " + def.getName() + ", technique not found: " + name); 872 } 873 874 if (!rendererCaps.containsAll(techDef.getRequiredCaps())) { 875 throw new UnsupportedOperationException("The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n" 876 + "requires caps " + techDef.getRequiredCaps() + " which are not " 877 + "supported by the video renderer"); 878 } 879 880 tech = new Technique(this, techDef); 881 techniques.put(name, tech); 882 } 883 } else if (technique == tech) { 884 // attempting to switch to an already 885 // active technique. 886 return; 887 } 888 889 technique = tech; 890 tech.makeCurrent(def.getAssetManager()); 891 892 // shader was changed 893 sortingId = -1; 894 } 895 896 private void autoSelectTechnique(RenderManager rm) { 897 if (technique == null) { 898 // NOTE: Not really needed anymore since we have technique 899 // selection by caps. Rename all "FixedFunc" techniques to "Default" 900 // and remove this hack. 901 if (!rm.getRenderer().getCaps().contains(Caps.GLSL100)) { 902 selectTechnique("FixedFunc", rm); 903 } else { 904 selectTechnique("Default", rm); 905 } 906 } else if (technique.isNeedReload()) { 907 technique.makeCurrent(def.getAssetManager()); 908 } 909 } 910 911 /** 912 * Preloads this material for the given render manager. 913 * <p> 914 * Preloading the material can ensure that when the material is first 915 * used for rendering, there won't be any delay since the material has 916 * been already been setup for rendering. 917 * 918 * @param rm The render manager to preload for 919 */ 920 public void preload(RenderManager rm) { 921 autoSelectTechnique(rm); 922 923 Renderer r = rm.getRenderer(); 924 TechniqueDef techDef = technique.getDef(); 925 926 Collection<MatParam> params = paramValues.values(); 927 for (MatParam param : params) { 928 if (param instanceof MatParamTexture) { 929 MatParamTexture texParam = (MatParamTexture) param; 930 r.setTexture(0, texParam.getTextureValue()); 931 } else { 932 if (!techDef.isUsingShaders()) { 933 continue; 934 } 935 936 technique.updateUniformParam(param.getName(), 937 param.getVarType(), 938 param.getValue(), true); 939 } 940 } 941 942 Shader shader = technique.getShader(); 943 if (techDef.isUsingShaders()) { 944 r.setShader(shader); 945 } 946 } 947 948 private void clearUniformsSetByCurrent(Shader shader) { 949 ListMap<String, Uniform> uniforms = shader.getUniformMap(); 950 int size = uniforms.size(); 951 for (int i = 0; i < size; i++) { 952 Uniform u = uniforms.getValue(i); 953 u.clearSetByCurrentMaterial(); 954 } 955 } 956 957 private void resetUniformsNotSetByCurrent(Shader shader) { 958 ListMap<String, Uniform> uniforms = shader.getUniformMap(); 959 int size = uniforms.size(); 960 for (int i = 0; i < size; i++) { 961 Uniform u = uniforms.getValue(i); 962 if (!u.isSetByCurrentMaterial()) { 963 u.clearValue(); 964 } 965 } 966 } 967 968 /** 969 * Called by {@link RenderManager} to render the geometry by 970 * using this material. 971 * 972 * @param geom The geometry to render 973 * @param rm The render manager requesting the rendering 974 */ 975 public void render(Geometry geom, RenderManager rm) { 976 autoSelectTechnique(rm); 977 978 Renderer r = rm.getRenderer(); 979 980 TechniqueDef techDef = technique.getDef(); 981 982 if (techDef.getLightMode() == LightMode.MultiPass 983 && geom.getWorldLightList().size() == 0) { 984 return; 985 } 986 987 if (rm.getForcedRenderState() != null) { 988 r.applyRenderState(rm.getForcedRenderState()); 989 } else { 990 if (techDef.getRenderState() != null) { 991 r.applyRenderState(techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); 992 } else { 993 r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); 994 } 995 } 996 997 998 // update camera and world matrices 999 // NOTE: setWorldTransform should have been called already 1000 if (techDef.isUsingShaders()) { 1001 // reset unchanged uniform flag 1002 clearUniformsSetByCurrent(technique.getShader()); 1003 rm.updateUniformBindings(technique.getWorldBindUniforms()); 1004 } 1005 1006 // setup textures and uniforms 1007 for (int i = 0; i < paramValues.size(); i++) { 1008 MatParam param = paramValues.getValue(i); 1009 param.apply(r, technique); 1010 } 1011 1012 Shader shader = technique.getShader(); 1013 1014 // send lighting information, if needed 1015 switch (techDef.getLightMode()) { 1016 case Disable: 1017 r.setLighting(null); 1018 break; 1019 case SinglePass: 1020 updateLightListUniforms(shader, geom, 4); 1021 break; 1022 case FixedPipeline: 1023 r.setLighting(geom.getWorldLightList()); 1024 break; 1025 case MultiPass: 1026 // NOTE: Special case! 1027 resetUniformsNotSetByCurrent(shader); 1028 renderMultipassLighting(shader, geom, rm); 1029 // very important, notice the return statement! 1030 return; 1031 } 1032 1033 // upload and bind shader 1034 if (techDef.isUsingShaders()) { 1035 // any unset uniforms will be set to 0 1036 resetUniformsNotSetByCurrent(shader); 1037 r.setShader(shader); 1038 } 1039 1040 r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1); 1041 } 1042 1043 public void write(JmeExporter ex) throws IOException { 1044 OutputCapsule oc = ex.getCapsule(this); 1045 oc.write(def.getAssetName(), "material_def", null); 1046 oc.write(additionalState, "render_state", null); 1047 oc.write(transparent, "is_transparent", false); 1048 oc.writeStringSavableMap(paramValues, "parameters", null); 1049 } 1050 1051 public void read(JmeImporter im) throws IOException { 1052 InputCapsule ic = im.getCapsule(this); 1053 1054 additionalState = (RenderState) ic.readSavable("render_state", null); 1055 transparent = ic.readBoolean("is_transparent", false); 1056 1057 // Load the material def 1058 String defName = ic.readString("material_def", null); 1059 HashMap<String, MatParam> params = (HashMap<String, MatParam>) ic.readStringSavableMap("parameters", null); 1060 1061 boolean enableVcolor = false; 1062 boolean separateTexCoord = false; 1063 boolean applyDefaultValues = false; 1064 boolean guessRenderStateApply = false; 1065 1066 int ver = ic.getSavableVersion(Material.class); 1067 if (ver < 1){ 1068 applyDefaultValues = true; 1069 } 1070 if (ver < 2){ 1071 guessRenderStateApply = true; 1072 } 1073 if (im.getFormatVersion() == 0) { 1074 // Enable compatibility with old models 1075 if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) { 1076 // Using VertexColor, switch to Unshaded and set VertexColor=true 1077 enableVcolor = true; 1078 defName = "Common/MatDefs/Misc/Unshaded.j3md"; 1079 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md") 1080 || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) { 1081 // Using SimpleTextured/SolidColor, just switch to Unshaded 1082 defName = "Common/MatDefs/Misc/Unshaded.j3md"; 1083 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) { 1084 // Using WireColor, set wireframe renderstate = true and use Unshaded 1085 getAdditionalRenderState().setWireframe(true); 1086 defName = "Common/MatDefs/Misc/Unshaded.j3md"; 1087 } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) { 1088 // Uses unshaded, ensure that the proper param is set 1089 MatParam value = params.get("SeperateTexCoord"); 1090 if (value != null && ((Boolean) value.getValue()) == true) { 1091 params.remove("SeperateTexCoord"); 1092 separateTexCoord = true; 1093 } 1094 } 1095 assert applyDefaultValues && guessRenderStateApply; 1096 } 1097 1098 def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName)); 1099 paramValues = new ListMap<String, MatParam>(); 1100 1101 // load the textures and update nextTexUnit 1102 for (Map.Entry<String, MatParam> entry : params.entrySet()) { 1103 MatParam param = entry.getValue(); 1104 if (param instanceof MatParamTexture) { 1105 MatParamTexture texVal = (MatParamTexture) param; 1106 1107 if (nextTexUnit < texVal.getUnit() + 1) { 1108 nextTexUnit = texVal.getUnit() + 1; 1109 } 1110 1111 // the texture failed to load for this param 1112 // do not add to param values 1113 if (texVal.getTextureValue() == null || texVal.getTextureValue().getImage() == null) { 1114 continue; 1115 } 1116 } 1117 param.setName(checkSetParam(param.getVarType(), param.getName())); 1118 paramValues.put(param.getName(), param); 1119 } 1120 1121 if (applyDefaultValues){ 1122 // compatability with old versions where default vars were 1123 // not available 1124 for (MatParam param : def.getMaterialParams()){ 1125 if (param.getValue() != null && paramValues.get(param.getName()) == null){ 1126 setParam(param.getName(), param.getVarType(), param.getValue()); 1127 } 1128 } 1129 } 1130 if (guessRenderStateApply && additionalState != null){ 1131 // Try to guess values of "apply" render state based on defaults 1132 // if value != default then set apply to true 1133 additionalState.applyPolyOffset = additionalState.offsetEnabled; 1134 additionalState.applyAlphaFallOff = additionalState.alphaTest; 1135 additionalState.applyAlphaTest = additionalState.alphaTest; 1136 additionalState.applyBlendMode = additionalState.blendMode != BlendMode.Off; 1137 additionalState.applyColorWrite = !additionalState.colorWrite; 1138 additionalState.applyCullMode = additionalState.cullMode != FaceCullMode.Back; 1139 additionalState.applyDepthTest = !additionalState.depthTest; 1140 additionalState.applyDepthWrite = !additionalState.depthWrite; 1141 additionalState.applyPointSprite = additionalState.pointSprite; 1142 additionalState.applyStencilTest = additionalState.stencilTest; 1143 additionalState.applyWireFrame = additionalState.wireframe; 1144 } 1145 if (enableVcolor) { 1146 setBoolean("VertexColor", true); 1147 } 1148 if (separateTexCoord) { 1149 setBoolean("SeparateTexCoord", true); 1150 } 1151 } 1152 } 1153