Material.java 46 KB

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