Material.java 47 KB

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