The frameGraph is designed to be configurable, I just mean: the built-in rendering pipeline in JME should be Full Feature (includes all graphics features) pipeline, just like JME3 has a default PBRLighting.j3md, then users can not use it, but use their own SimplePBRLighting.j3md.
I wrote related test cases in an independent branch, this branch is created for developing the future Mult-Pass module.
The following is a custom GBuffer, using only 3 RTs, two 16f RTs and one DepthRT:
/**
* This code demonstrates how to define a custom GBufferPass.
* @author JhonKkk
*/
public class CustomGBufferPass extends GBufferPass {
CustomGBufferPass(){
super("CustomGBufferPass");
}
@Override
public void reshape(Renderer renderer, ViewPort vp, int w, int h) {
boolean recreate = false;
if(gBuffer != null){
if(frameBufferWidth != w || frameBufferHeight != h){
gBuffer.dispose();
gBuffer.deleteObject(renderer);
frameBufferWidth = w;
frameBufferHeight = h;
recreate = true;
}
}
else{
recreate = true;
frameBufferWidth = w;
frameBufferHeight = h;
}
if(recreate){
// recreate
gBufferData0 = new Texture2D(w, h, Image.Format.RGBA16F);
gBufferData1 = new Texture2D(w, h, Image.Format.RGBA16F);
this.getSinks().clear();
gBufferData4 = new Texture2D(w, h, Image.Format.Depth);
gBuffer = new FrameBuffer(w, h, 1);
FrameBuffer.FrameBufferTextureTarget rt0 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData0);
FrameBuffer.FrameBufferTextureTarget rt1 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData1);
FrameBuffer.FrameBufferTextureTarget rt4 = FrameBuffer.FrameBufferTarget.newTarget(gBufferData4);
gBuffer.addColorTarget(rt0);
gBuffer.addColorTarget(rt1);
gBuffer.setDepthTarget(rt4);
gBuffer.setMultiTarget(true);
registerSource(new FGRenderTargetSource(S_RT_0, rt0));
registerSource(new FGRenderTargetSource(S_RT_1, rt1));
registerSource(new FGRenderTargetSource(S_RT_4, rt4));
registerSource(new DeferredLightDataSource(S_LIGHT_DATA, lightData));
bHasDrawVarSource = new FGVarSource<Boolean>(S_EXECUTE_STATE, bHasDraw);
registerSource(bHasDrawVarSource);
registerSource(new FGFramebufferSource(S_FB, gBuffer));
}
}
}
After that you can create your own FrameGraph in two ways, one is to fully organize the FrameGraph yourselfļ¼This method allows you to override the default FrameGraph completely, and organize required Passes dynamically.ļ¼, as follows:
public class TestCustomPipeline extends SimpleApplication {
private FrameGraph customFrameGraph;
// ------------------------------āIn this example, we only customize the GBufferPass, so get the other built-in Passes from the system
private DeferredShadingPass defaultDeferredShadingPass;
private TileDeferredShadingPass defaultTileDeferredShadingPass;
private OpaquePass defaultOpaquePass;
private SkyPass defaultSkyPass;
private TransparentPass defaultTransparentPass;
private GuiPass defaultGuiPass;
private PostProcessorPass defaultPostProcessorPass;
// ------------------------------āIn this example, we only customize the GBufferPass, so get the other built-in Passes from the system
private CustomGBufferPass customGBufferPass;
@Override
public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
if(customFrameGraph != null){
// Switch rendering path per frame, reorganize FrameGraph, noteworthy is, no need to add all system FramePasses, just a minimal test case.
customFrameGraph.reset();
if(renderManager.getRenderPath() == RenderManager.RenderPath.Deferred){
customFrameGraph.addPass(customGBufferPass);
defaultDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_0, customGBufferPass.getName() + "." + GBufferPass.S_RT_0);
defaultDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_1, customGBufferPass.getName() + "." + GBufferPass.S_RT_1);
defaultDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_RT_4, customGBufferPass.getName() + "." + GBufferPass.S_RT_4);
defaultDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_LIGHT_DATA, customGBufferPass.getName() + "." + GBufferPass.S_LIGHT_DATA);
defaultDeferredShadingPass.setSinkLinkage(DeferredShadingPass.S_EXECUTE_STATE, customGBufferPass.getName() + "." + GBufferPass.S_EXECUTE_STATE);
defaultDeferredShadingPass.setSinkLinkage(FGGlobal.S_DEFAULT_FB, customGBufferPass.getName() + "." + GBufferPass.S_FB);
customFrameGraph.addPass(defaultDeferredShadingPass);
}
else if(renderManager.getRenderPath() == RenderManager.RenderPath.TiledDeferred){
customFrameGraph.addPass(customGBufferPass);
defaultTileDeferredShadingPass.setSinkLinkage(TileDeferredShadingPass.S_RT_0, customGBufferPass.getName() + "." + GBufferPass.S_RT_0);
defaultTileDeferredShadingPass.setSinkLinkage(TileDeferredShadingPass.S_RT_1, customGBufferPass.getName() + "." + GBufferPass.S_RT_1);
defaultTileDeferredShadingPass.setSinkLinkage(TileDeferredShadingPass.S_RT_4, customGBufferPass.getName() + "." + GBufferPass.S_RT_4);
defaultTileDeferredShadingPass.setSinkLinkage(TileDeferredShadingPass.S_LIGHT_DATA, customGBufferPass.getName() + "." + GBufferPass.S_LIGHT_DATA);
defaultTileDeferredShadingPass.setSinkLinkage(TileDeferredShadingPass.S_EXECUTE_STATE, customGBufferPass.getName() + "." + GBufferPass.S_EXECUTE_STATE);
defaultTileDeferredShadingPass.setSinkLinkage(FGGlobal.S_DEFAULT_FB, customGBufferPass.getName() + "." + GBufferPass.S_FB);
customFrameGraph.addPass(defaultTileDeferredShadingPass);
}
customFrameGraph.addPass(defaultOpaquePass);
customFrameGraph.addPass(defaultSkyPass);
customFrameGraph.addPass(defaultTransparentPass);
customFrameGraph.addPass(defaultGuiPass);
customFrameGraph.addPass(defaultPostProcessorPass);
}
}
private final void setupScene(){
Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
TangentBinormalGenerator.generate(teapot.getMesh(), true);
teapot.setLocalScale(2f);
renderManager.setSinglePassLightBatchSize(1);
Material mat = new Material(assetManager, "jme3test/materials/MyCustomLighting.j3md");
mat.setFloat("Shininess", 25);
cam.setLocation(new Vector3f(0.015041917f, 0.4572918f, 5.2874837f));
cam.setRotation(new Quaternion(-1.8875003E-4f, 0.99882424f, 0.04832061f, 0.0039016632f));
mat.setColor("Ambient", ColorRGBA.Black);
mat.setColor("Diffuse", ColorRGBA.Gray);
mat.setColor("Specular", ColorRGBA.Gray);
teapot.setMaterial(mat);
rootNode.attachChild(teapot);
DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
dl.setColor(ColorRGBA.White);
rootNode.addLight(dl);
}
private final void setupCustomFrameGraph(){
customFrameGraph = new FrameGraph(new FGRenderContext(renderManager, null, viewPort));
defaultDeferredShadingPass = FGBuilderTool.findPass(DeferredShadingPass.class);
defaultTileDeferredShadingPass = FGBuilderTool.findPass(TileDeferredShadingPass.class);
// Redefine the overloaded material definition used by the custom ShadingModel here.
if(defaultDeferredShadingPass != null){
defaultDeferredShadingPass.setOverlyMat(new Material((MaterialDef) assetManager.loadAsset("jme3test/materials/MyCustomDeferredShading.j3md")));
defaultDeferredShadingPass.reset();
defaultDeferredShadingPass.getSinks().clear();
defaultDeferredShadingPass.getBinds().clear();
defaultDeferredShadingPass.init();
}
if(defaultTileDeferredShadingPass != null){
defaultTileDeferredShadingPass.setOverlyMat(new Material((MaterialDef) assetManager.loadAsset("jme3test/materials/MyCustomTileBasedDeferredShading.j3md")));
defaultTileDeferredShadingPass.reset();
defaultTileDeferredShadingPass.getSinks().clear();
defaultTileDeferredShadingPass.getBinds().clear();
defaultTileDeferredShadingPass.init();
}
defaultOpaquePass = FGBuilderTool.findPass(OpaquePass.class);
defaultSkyPass = FGBuilderTool.findPass(SkyPass.class);
defaultTransparentPass = FGBuilderTool.findPass(TransparentPass.class);
defaultGuiPass = FGBuilderTool.findPass(GuiPass.class);
defaultPostProcessorPass = FGBuilderTool.findPass(PostProcessorPass.class);
customGBufferPass = new CustomGBufferPass();
FGBuilderTool.registerPass(CustomGBufferPass.class, customGBufferPass);
viewPort.setFrameGraph(customFrameGraph);
flyCam.setMoveSpeed(10.0f);
renderManager.setRenderPath(RenderManager.RenderPath.Deferred);
new RenderPathHelper(this, new Vector3f(10, cam.getHeight() - 10, 0), KeyInput.KEY_SPACE, "SPACE");
}
@Override
public void simpleInitApp() {
setupScene();
setupCustomFrameGraph();
}
public static void main(String[] args) {
TestCustomPipeline testCustomPipeline = new TestCustomPipeline();
testCustomPipeline.start();
}
}
The other method is to set the CustomGBufferPass as overridden, that is, replace the built-in default GBufferPass:
/**
* Custom SimplePipeline, this example demonstrates the basic usage of CustomPipeline by customizing a GBufferPass.
* @author JhonKkk
*/
public class TestSimplePipeline extends SimpleApplication {
private CustomGBufferPass customGBufferPass;
@Override
public void simpleUpdate(float tpf) {
super.simpleUpdate(tpf);
}
private final void setupScene(){
Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
TangentBinormalGenerator.generate(teapot.getMesh(), true);
teapot.setLocalScale(2f);
renderManager.setSinglePassLightBatchSize(1);
Material mat = new Material(assetManager, "jme3test/materials/MyCustomLighting.j3md");
mat.setFloat("Shininess", 25);
cam.setLocation(new Vector3f(0.015041917f, 0.4572918f, 5.2874837f));
cam.setRotation(new Quaternion(-1.8875003E-4f, 0.99882424f, 0.04832061f, 0.0039016632f));
mat.setColor("Ambient", ColorRGBA.Black);
mat.setColor("Diffuse", ColorRGBA.Gray);
mat.setColor("Specular", ColorRGBA.Gray);
teapot.setMaterial(mat);
rootNode.attachChild(teapot);
DirectionalLight dl = new DirectionalLight();
dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
dl.setColor(ColorRGBA.White);
rootNode.addLight(dl);
}
private final void setupCustomFrameGraph(){
DeferredShadingPass defaultDeferredShadingPass = FGBuilderTool.findPass(DeferredShadingPass.class);
TileDeferredShadingPass defaultTileDeferredShadingPass = FGBuilderTool.findPass(TileDeferredShadingPass.class);
// Redefine the overloaded material definition used by the custom ShadingModel here.
if(defaultDeferredShadingPass != null){
defaultDeferredShadingPass.setOverlyMat(new Material((MaterialDef) assetManager.loadAsset("jme3test/materials/MyCustomDeferredShading.j3md")));
defaultDeferredShadingPass.reset();
defaultDeferredShadingPass.getSinks().clear();
defaultDeferredShadingPass.getBinds().clear();
defaultDeferredShadingPass.init();
}
if(defaultTileDeferredShadingPass != null){
defaultTileDeferredShadingPass.setOverlyMat(new Material((MaterialDef) assetManager.loadAsset("jme3test/materials/MyCustomTileBasedDeferredShading.j3md")));
defaultTileDeferredShadingPass.reset();
defaultTileDeferredShadingPass.getSinks().clear();
defaultTileDeferredShadingPass.getBinds().clear();
defaultTileDeferredShadingPass.init();
}
customGBufferPass = new CustomGBufferPass();
// Override the default GBufferPass
FGBuilderTool.registerPass(GBufferPass.class, customGBufferPass);
flyCam.setMoveSpeed(10.0f);
renderManager.setRenderPath(RenderManager.RenderPath.Deferred);
new RenderPathHelper(this, new Vector3f(10, cam.getHeight() - 10, 0), KeyInput.KEY_SPACE, "SPACE");
}
@Override
public void simpleInitApp() {
setupScene();
setupCustomFrameGraph();
}
public static void main(String[] args) {
TestCustomPipeline testCustomPipeline = new TestCustomPipeline();
testCustomPipeline.start();
}
}
If you donāt pack the GBuffer in the default way, you need to override the default TileBasedDeferredShading (e.g. MyCustomTileBasedDeferredShading here) and DeferredShading (e.g. MyCustomDeferredShading here), then parse the GBuffer and corresponding ShadingModel in your own way:
void main(){
vec2 innerTexCoord;
#if defined(USE_LIGHTS_CULL_MODE)
innerTexCoord = gl_FragCoord.xy * g_ResolutionInverse;
#else
innerTexCoord = texCoord;
#endif
// unpack GBuffer
vec4 buff0 = texture2D(Context_InGBuff0, innerTexCoord);
vec4 buff1 = texture2D(Context_InGBuff1, innerTexCoord);
int shadingModelId = int(floor(buff0.a));
if(shadingModelId == MY_CUSTOM_PHONG_LIGHTING){
vec3 vPos = getPosition(innerTexCoord, viewProjectionMatrixInverse);
vec4 diffuseColor = buff0;
vec3 specularColor = floor(buff1.rgb) * 0.01f;
vec3 AmbientSum = min(fract(buff1.rgb) * 100.0f, vec3(1.0f)) * g_AmbientLightColor.rgb;
float Shininess = buff1.a;
float alpha = min(fract(diffuseColor.a) * 100.0f, 0.0f);
vec3 normal = -approximateNormal(vPos, innerTexCoord, viewProjectionMatrixInverse).xyz;
vec3 viewDir = normalize(g_CameraPosition - vPos);
gl_FragColor.rgb = AmbientSum * diffuseColor.rgb;
gl_FragColor.a = alpha;
int lightNum = 0;
#if defined(USE_TEXTURE_PACK_MODE)
float lightTexSizeInv = 1.0f / (float(PACK_NB_LIGHTS) - 1.0f);
lightNum = m_NBLight;
#else
lightNum = NB_LIGHTS;
float lightTexSizeInv = 1.0f / (float(lightNum) - 1.0f);
#endif
// These built-in resolvers will be encapsulated into .glsllib in future versions, for you to directly utilize tileBasedFunction without copying.
// Tile Based Shading
// get the grid data index
vec2 gridIndex = vec2(((innerTexCoord.x*g_Resolution.x) / float(g_TileSize)) / float(g_WidthTile), ((innerTexCoord.y*g_Resolution.y) / float(g_TileSize)) / float(g_HeightTile));
// get tile info
vec3 tile = texture2D(m_TileLightDecode, gridIndex).xyz;
int uoffset = int(tile.x);
int voffset = int(tile.z);
int count = int(tile.y);
if(count > 0){
int lightId;
float temp;
int offset;
// Normalize lightIndex sampling range to unit space
float uvSize = 1.0f / (g_TileLightOffsetSize - 1.0f);
vec2 lightUV;
vec2 lightDataUV;
for(int i = 0;i < count;){
temp = float(uoffset + i);
offset = 0;
if(temp >= g_TileLightOffsetSize){
//temp -= g_TileLightOffsetSize;
offset += int(temp / float(g_TileLightOffsetSize));
temp = float(int(temp) % g_TileLightOffsetSize);
}
if(temp == g_TileLightOffsetSize){
temp = 0.0f;
}
// lightIndexUV
lightUV = vec2(temp * uvSize, float(voffset + offset) * uvSize);
lightId = int(texture2D(m_TileLightIndex, lightUV).x);
lightDataUV = vec2(float(lightId) * lightTexSizeInv, 0.0f);
#if defined(USE_TEXTURE_PACK_MODE)
vec4 lightColor = texture2D(m_LightPackData1, lightDataUV);
vec4 lightData1 = texture2D(m_LightPackData2, lightDataUV);
#else
vec4 lightColor = g_LightData[lightId*3];
vec4 lightData1 = g_LightData[lightId*3+1];
#endif
vec4 lightDir;
vec3 lightVec;
lightComputeDir(vPos, lightColor.w, lightData1, lightDir,lightVec);
float spotFallOff = 1.0;
#if __VERSION__ >= 110
// allow use of control flow
if(lightColor.w > 1.0){
#endif
#if defined(USE_TEXTURE_PACK_MODE)
spotFallOff = computeSpotFalloff(texture2D(m_LightPackData3, lightDataUV), lightVec);
#else
spotFallOff = computeSpotFalloff(g_LightData[lightId*3+2], lightVec);
#endif
#if __VERSION__ >= 110
}
#endif
#ifdef NORMALMAP
//Normal map -> lighting is computed in tangent space
lightDir.xyz = normalize(lightDir.xyz * tbnMat);
#else
//no Normal map -> lighting is computed in view space
lightDir.xyz = normalize(lightDir.xyz);
#endif
vec2 light = computeLighting(normal, viewDir, lightDir.xyz, lightDir.w * spotFallOff , Shininess);
gl_FragColor.rgb += lightColor.rgb * diffuseColor.rgb * vec3(light.x) +
lightColor.rgb * specularColor.rgb * vec3(light.y);
#if defined(USE_TEXTURE_PACK_MODE)
i++;
#else
i++;
#endif
}
}
}
else{
// todo:Calling the shading model library function encapsulated inside the system
gl_FragColor = vec4(0.0f, 0.0f, 0.0f, 1);
}
}
Below are the RenderDoc data in custom DeferredShading and custom Tile-Based DeferredShading:
You can find related test code in this development branch.dev-tech-multi-pass-lab
They are located in the examples/pipeline folder.