/** * Preloads this material for the given render manager. * * <p>Preloading the material can ensure that when the material is first used for rendering, there * won't be any delay since the material has been already been setup for rendering. * * @param rm The render manager to preload for */ public void preload(RenderManager rm) { autoSelectTechnique(rm); Renderer r = rm.getRenderer(); TechniqueDef techDef = technique.getDef(); Collection<MatParam> params = paramValues.values(); for (MatParam param : params) { if (param instanceof MatParamTexture) { MatParamTexture texParam = (MatParamTexture) param; r.setTexture(0, texParam.getTextureValue()); } else { if (!techDef.isUsingShaders()) { continue; } technique.updateUniformParam(param.getName(), param.getVarType(), param.getValue()); } } Shader shader = technique.getShader(); if (techDef.isUsingShaders()) { r.setShader(shader); } }
// <DEFINENAME> [ ":" <PARAMNAME> ] private void readDefine(String statement) throws IOException { String[] split = statement.split(":"); if (split.length == 1) { String defineName = split[0].trim(); presetDefines.add(defineName); } else if (split.length == 2) { String defineName = split[0].trim(); String paramName = split[1].trim(); MatParam param = materialDef.getMaterialParam(paramName); if (param == null) { logger.log( Level.WARNING, "In technique ''{0}'':\n" + "Define ''{1}'' mapped to non-existent" + " material parameter ''{2}'', ignoring.", new Object[] {technique.getName(), defineName, paramName}); return; } VarType paramType = param.getVarType(); technique.addShaderParamDefine(paramName, paramType, defineName); } else { throw new IOException("Define syntax incorrect"); } }
private void readForcedRenderState(List<Statement> renderStates) throws IOException { renderState = new RenderState(); for (Statement statement : renderStates) { readRenderStateStatement(statement); } technique.setForcedRenderState(renderState); renderState = null; }
// ShadowMode <MODE> private void readShadowMode(String statement) throws IOException { String[] split = statement.split(whitespacePattern); if (split.length != 2) { throw new IOException("ShadowMode statement syntax incorrect"); } ShadowMode sm = ShadowMode.valueOf(split[1]); technique.setShadowMode(sm); }
// LightMode <SPACE> private void readLightSpace(String statement) throws IOException { String[] split = statement.split(whitespacePattern); if (split.length != 2) { throw new IOException("LightSpace statement syntax incorrect"); } TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]); technique.setLightSpace(ls); }
private void readShaderDefinition( Shader.ShaderType shaderType, String name, String... languages) { shaderNames.put(shaderType, name); if (langSize != 0 && langSize != languages.length) { throw new AssetLoadException( "Technique " + technique.getName() + " must have the same number of languages for each shader type."); } langSize = languages.length; for (int i = 0; i < languages.length; i++) { if (i >= shaderLanguages.size()) { shaderLanguages.add(new EnumMap<Shader.ShaderType, String>(Shader.ShaderType.class)); } shaderLanguages.get(i).put(shaderType, languages[i]); } }
private void readTechniqueStatement(Statement statement) throws IOException { String[] split = statement.getLine().split("[ \\{]"); if (split[0].equals("VertexShader") || split[0].equals("FragmentShader") || split[0].equals("GeometryShader") || split[0].equals("TessellationControlShader") || split[0].equals("TessellationEvaluationShader")) { readShaderStatement(statement.getLine()); } else if (split[0].equals("LightMode")) { readLightMode(statement.getLine()); } else if (split[0].equals("LightSpace")) { readLightSpace(statement.getLine()); } else if (split[0].equals("ShadowMode")) { readShadowMode(statement.getLine()); } else if (split[0].equals("WorldParameters")) { readWorldParams(statement.getContents()); } else if (split[0].equals("RenderState")) { readRenderState(statement.getContents()); } else if (split[0].equals("ForcedRenderState")) { readForcedRenderState(statement.getContents()); } else if (split[0].equals("Defines")) { readDefines(statement.getContents()); } else if (split[0].equals("ShaderNodesDefinitions")) { initNodesLoader(); if (isUseNodes) { nodesLoaderDelegate.readNodesDefinitions(statement.getContents()); } } else if (split[0].equals("VertexShaderNodes")) { initNodesLoader(); if (isUseNodes) { nodesLoaderDelegate.readVertexShaderNodes(statement.getContents()); } } else if (split[0].equals("FragmentShaderNodes")) { initNodesLoader(); if (isUseNodes) { nodesLoaderDelegate.readFragmentShaderNodes(statement.getContents()); } } else if (split[0].equals("NoRender")) { technique.setNoRender(true); } else { throw new MatParseException(null, split[0], statement); } }
private void readTechnique(Statement techStat) throws IOException { isUseNodes = false; String[] split = techStat.getLine().split(whitespacePattern); String name; if (split.length == 1) { name = TechniqueDef.DEFAULT_TECHNIQUE_NAME; } else if (split.length == 2) { name = split[1]; } else { throw new IOException("Technique statement syntax incorrect"); } String techniqueUniqueName = materialDef.getAssetName() + "@" + name; technique = new TechniqueDef(name, techniqueUniqueName.hashCode()); for (Statement statement : techStat.getContents()) { readTechniqueStatement(statement); } technique.setShaderPrologue(createShaderPrologue(presetDefines)); switch (technique.getLightMode()) { case Disable: technique.setLogic(new DefaultTechniqueDefLogic(technique)); break; case MultiPass: technique.setLogic(new MultiPassLightingLogic(technique)); break; case SinglePass: technique.setLogic(new SinglePassLightingLogic(technique)); break; case StaticPass: technique.setLogic(new StaticPassLightingLogic(technique)); break; case SinglePassAndImageBased: technique.setLogic(new SinglePassAndImageBasedLightingLogic(technique)); break; default: throw new UnsupportedOperationException(); } List<TechniqueDef> techniqueDefs = new ArrayList<>(); if (isUseNodes) { nodesLoaderDelegate.computeConditions(); // used for caching later, the shader here is not a file. // KIRILL 9/19/2015 // Not sure if this is needed anymore, since shader caching // is now done by TechniqueDef. technique.setShaderFile( technique.hashCode() + "", technique.hashCode() + "", "GLSL100", "GLSL100"); techniqueDefs.add(technique); } else if (shaderNames.containsKey(Shader.ShaderType.Vertex) && shaderNames.containsKey(Shader.ShaderType.Fragment)) { if (shaderLanguages.size() > 1) { for (int i = 1; i < shaderLanguages.size(); i++) { TechniqueDef td = null; try { td = (TechniqueDef) technique.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } td.setShaderFile(shaderNames, shaderLanguages.get(i)); techniqueDefs.add(td); } } technique.setShaderFile(shaderNames, shaderLanguages.get(0)); techniqueDefs.add(technique); } else { technique = null; shaderLanguages.clear(); shaderNames.clear(); presetDefines.clear(); langSize = 0; logger.log(Level.WARNING, "Fixed function technique was ignored"); logger.log( Level.WARNING, "Fixed function technique ''{0}'' was ignored for material {1}", new Object[] {name, key}); return; } for (TechniqueDef techniqueDef : techniqueDefs) { materialDef.addTechniqueDef(techniqueDef); } technique = null; langSize = 0; shaderLanguages.clear(); shaderNames.clear(); presetDefines.clear(); }
private void readWorldParams(List<Statement> worldParams) throws IOException { for (Statement statement : worldParams) { technique.addWorldParam(statement.getLine()); } }
/** * Select the technique to use for rendering this material. * * <p>If <code>name</code> is "Default", then one of the {@link MaterialDef#getDefaultTechniques() * default techniques} on the material will be selected. Otherwise, the named technique will be * found in the material definition. * * <p>Any candidate technique for selection (either default or named) must be verified to be * compatible with the system, for that, the <code>renderManager</code> is queried for * capabilities. * * @param name The name of the technique to select, pass "Default" to select one of the default * techniques. * @param renderManager The {@link RenderManager render manager} to query for capabilities. * @throws IllegalArgumentException If "Default" is passed and no default techniques are available * on the material definition, or if a name is passed but there's no technique by that name. * @throws UnsupportedOperationException If no candidate technique supports the system * capabilities. */ public void selectTechnique(String name, RenderManager renderManager) { // check if already created Technique tech = techniques.get(name); // When choosing technique, we choose one that // supports all the caps. EnumSet<Caps> rendererCaps = renderManager.getRenderer().getCaps(); if (tech == null) { if (name.equals("Default")) { List<TechniqueDef> techDefs = def.getDefaultTechniques(); if (techDefs == null || techDefs.isEmpty()) { throw new IllegalArgumentException( "No default techniques are available on material '" + def.getName() + "'"); } TechniqueDef lastTech = null; for (TechniqueDef techDef : techDefs) { if (rendererCaps.containsAll(techDef.getRequiredCaps())) { // use the first one that supports all the caps tech = new Technique(this, techDef); techniques.put(name, tech); break; } lastTech = techDef; } if (tech == null) { throw new UnsupportedOperationException( "No default technique on material '" + def.getName() + "'\n" + " is supported by the video hardware. The caps " + lastTech.getRequiredCaps() + " are required."); } } else { // create "special" technique instance TechniqueDef techDef = def.getTechniqueDef(name); if (techDef == null) { throw new IllegalArgumentException( "For material " + def.getName() + ", technique not found: " + name); } if (!rendererCaps.containsAll(techDef.getRequiredCaps())) { throw new UnsupportedOperationException( "The explicitly chosen technique '" + name + "' on material '" + def.getName() + "'\n" + "requires caps " + techDef.getRequiredCaps() + " which are not " + "supported by the video renderer"); } tech = new Technique(this, techDef); techniques.put(name, tech); } } else if (technique == tech) { // attempting to switch to an already // active technique. return; } technique = tech; tech.makeCurrent(def.getAssetManager(), true, rendererCaps); // shader was changed sortingId = -1; }
/** * Called by {@link RenderManager} to render the geometry by using this material. * * <p>The material is rendered as follows: * * <ul> * <li>Determine which technique to use to render the material - either what the user selected * via {@link #selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) * Material.selectTechnique()}, or the first default technique that the renderer supports * (based on the technique's {@link TechniqueDef#getRequiredCaps() requested rendering * capabilities}) * <ul> * <li>If the technique has been changed since the last frame, then it is notified via * {@link Technique#makeCurrent(com.jme3.asset.AssetManager, boolean, * java.util.EnumSet) Technique.makeCurrent()}. If the technique wants to use a shader * to render the model, it should load it at this part - the shader should have all * the proper defines as declared in the technique definition, including those that * are bound to material parameters. The technique can re-use the shader from the last * frame if no changes to the defines occurred. * </ul> * <li>Set the {@link RenderState} to use for rendering. The render states are applied in this * order (later RenderStates override earlier RenderStates): * <ol> * <li>{@link TechniqueDef#getRenderState() Technique Definition's RenderState} - i.e. * specific renderstate that is required for the shader. * <li>{@link #getAdditionalRenderState() Material Instance Additional RenderState} - i.e. * ad-hoc renderstate set per model * <li>{@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} - * i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or * post-processing filter. * </ol> * <li>If the technique {@link TechniqueDef#isUsingShaders() uses a shader}, then the uniforms * of the shader must be updated. * <ul> * <li>Uniforms bound to material parameters are updated based on the current material * parameter values. * <li>Uniforms bound to world parameters are updated from the RenderManager. Internally * {@link UniformBindingManager} is used for this task. * <li>Uniforms bound to textures will cause the texture to be uploaded as necessary. The * uniform is set to the texture unit where the texture is bound. * </ul> * <li>If the technique uses a shader, the model is then rendered according to the lighting mode * specified on the technique definition. * <ul> * <li>{@link LightMode#SinglePass single pass light mode} fills the shader's light * uniform arrays with the first 4 lights and renders the model once. * <li>{@link LightMode#MultiPass multi pass light mode} light mode renders the model * multiple times, for the first light it is rendered opaque, on subsequent lights it * is rendered with {@link BlendMode#AlphaAdditive alpha-additive} blending and depth * writing disabled. * </ul> * <li>For techniques that do not use shaders, fixed function OpenGL is used to render the model * (see {@link GL1Renderer} interface): * <ul> * <li>OpenGL state ({@link FixedFuncBinding}) that is bound to material parameters is * updated. * <li>The texture set on the material is uploaded and bound. Currently only 1 texture is * supported for fixed function techniques. * <li>If the technique uses lighting, then OpenGL lighting state is updated based on the * light list on the geometry, otherwise OpenGL lighting is disabled. * <li>The mesh is uploaded and rendered. * </ul> * </ul> * * @param geom The geometry to render * @param rm The render manager requesting the rendering */ public void render(Geometry geom, RenderManager rm) { autoSelectTechnique(rm); Renderer r = rm.getRenderer(); TechniqueDef techDef = technique.getDef(); if (techDef.getLightMode() == LightMode.MultiPass && geom.getWorldLightList().size() == 0) { return; } if (rm.getForcedRenderState() != null) { r.applyRenderState(rm.getForcedRenderState()); } else { if (techDef.getRenderState() != null) { r.applyRenderState( techDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); } else { r.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); } } // update camera and world matrices // NOTE: setWorldTransform should have been called already if (techDef.isUsingShaders()) { // reset unchanged uniform flag clearUniformsSetByCurrent(technique.getShader()); rm.updateUniformBindings(technique.getWorldBindUniforms()); } // setup textures and uniforms for (int i = 0; i < paramValues.size(); i++) { MatParam param = paramValues.getValue(i); param.apply(r, technique); } Shader shader = technique.getShader(); // send lighting information, if needed switch (techDef.getLightMode()) { case Disable: r.setLighting(null); break; case SinglePass: updateLightListUniforms(shader, geom, 4); break; case FixedPipeline: r.setLighting(geom.getWorldLightList()); break; case MultiPass: // NOTE: Special case! resetUniformsNotSetByCurrent(shader); renderMultipassLighting(shader, geom, rm); // very important, notice the return statement! return; } // upload and bind shader if (techDef.isUsingShaders()) { // any unset uniforms will be set to 0 resetUniformsNotSetByCurrent(shader); r.setShader(shader); } r.renderMesh(geom.getMesh(), geom.getLodLevel(), 1); }