New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrapping ShadowMap and WorldReflection into a "Node" #2325
Changes from 13 commits
972238e
f3ccd65
0b050bd
a2ddfd3
d3d6ca0
afda34c
a47d551
d12878e
a32583d
a01d90e
2b76cc3
122fb58
98687b4
3971d29
aebc4c5
fba3ccf
a1e0099
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright 2016 MovingBlocks | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.terasology.rendering.dag; | ||
|
||
|
||
public interface Node { // since it's inside dag package | ||
|
||
void update(float deltaInSeconds); | ||
|
||
void process(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* Copyright 2016 MovingBlocks | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.terasology.rendering.dag; | ||
|
||
import org.lwjgl.opengl.GL11; | ||
import org.terasology.config.Config; | ||
import org.terasology.config.RenderingConfig; | ||
import org.terasology.context.Context; | ||
import org.terasology.math.TeraMath; | ||
import org.terasology.math.geom.Vector3f; | ||
import org.terasology.monitoring.PerformanceMonitor; | ||
import org.terasology.rendering.assets.material.Material; | ||
import org.terasology.rendering.backdrop.BackdropProvider; | ||
import org.terasology.rendering.cameras.Camera; | ||
import org.terasology.rendering.cameras.OrthographicCamera; | ||
import org.terasology.rendering.opengl.FBO; | ||
import org.terasology.rendering.opengl.FrameBuffersManager; | ||
import org.terasology.rendering.primitives.ChunkMesh; | ||
import org.terasology.rendering.world.RenderQueuesHelper; | ||
import org.terasology.rendering.world.WorldRenderer; | ||
import org.terasology.rendering.world.WorldRendererImpl; | ||
import static org.lwjgl.opengl.GL11.*; | ||
import static org.terasology.rendering.opengl.OpenGLUtils.*; | ||
|
||
|
||
/** | ||
* Diagram of this node can be viewed from: | ||
* TODO: move diagram to the wiki when this part of the code is stable | ||
* - https://docs.google.com/drawings/d/13I0GM9jDFlZv1vNrUPlQuBbaF86RPRNpVfn5q8Wj2lc/edit?usp=sharing | ||
*/ | ||
public class ShadowMapNode implements Node { | ||
private static final int SHADOW_FRUSTUM_BOUNDS = 500; | ||
|
||
|
||
public Camera camera = new OrthographicCamera(-SHADOW_FRUSTUM_BOUNDS, SHADOW_FRUSTUM_BOUNDS, SHADOW_FRUSTUM_BOUNDS, -SHADOW_FRUSTUM_BOUNDS); | ||
private RenderQueuesHelper renderQueues; | ||
private Context context; | ||
private Material shadowMapShader; | ||
private FBO shadowMap; | ||
|
||
// TODO: every node proposes its modifiable configuration? | ||
private RenderingConfig renderingConfig; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess there could be nodes that:
Is this what you were aiming at? For the time being however, I'd say all nodes do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am curious about a node like ShadowMapNode proposes its configuration besides just on/off, maybe different selectable techniques. These proposed configurations from nodes can be directly displayed in |
||
|
||
private Camera playerCamera; | ||
// FIXME: remove reference to WorldRendererImpl | ||
private WorldRenderer worldRenderer; | ||
private BackdropProvider backdropProvider; | ||
|
||
|
||
// FIXME: unnecessary arguments must be eliminated | ||
public ShadowMapNode(Context context, Material shadowMapShader, Camera playerCamera) { | ||
this.context = context; | ||
this.shadowMapShader = shadowMapShader; | ||
this.playerCamera = playerCamera; | ||
this.worldRenderer = context.get(WorldRenderer.class); | ||
this.backdropProvider = context.get(BackdropProvider.class); | ||
this.renderingConfig = context.get(Config.class).getRendering(); | ||
this.shadowMap = context.get(FrameBuffersManager.class).getFBO("sceneShadowMap"); | ||
this.renderQueues = context.get(RenderQueuesHelper.class); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My only suggestion here would be that you can get the playerCamera via WorldRenderer.getActiveCamera(). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would use the InjectionHelper here. This enables the |
||
|
||
@Override | ||
public void update(float deltaInSeconds) { | ||
this.shadowMap = context.get(FrameBuffersManager.class).getFBO("sceneShadowMap"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmm... no. What is currently in update() here, should be be executed within process() instead. In Terasology update() methods are potentially executed multiple times per frame, which in this case I do not think we need. In fact at this stage I do not think we need an update method at all and I'd remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Of course this doesn't mean you should just cut and paste everything into process(). I.e. all camera positioning stuff should go in its private method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, can you obtain the FrameBuffersManager instance on construction instead? |
||
|
||
// positionShadowMapCamera() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this doing here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh sorry, forgot to remove that. My bad! 😄 |
||
|
||
// Shadows are rendered around the player so... | ||
Vector3f lightPosition = new Vector3f(playerCamera.getPosition().x, 0.0f, playerCamera.getPosition().z); | ||
|
||
// Project the shadowMapCamera position to light space and make sure it is only moved in texel steps (avoids flickering when moving the shadowMapCamera) | ||
float texelSize = 1.0f / renderingConfig.getShadowMapResolution(); | ||
texelSize *= 2.0f; | ||
|
||
camera.getViewProjectionMatrix().transformPoint(lightPosition); | ||
lightPosition.set(TeraMath.fastFloor(lightPosition.x / texelSize) * texelSize, 0.0f, TeraMath.fastFloor(lightPosition.z / texelSize) * texelSize); | ||
camera.getInverseViewProjectionMatrix().transformPoint(lightPosition); | ||
|
||
// ... we position our new shadowMapCamera at the position of the player and move it | ||
// quite a bit into the direction of the sun (our main light). | ||
|
||
// Make sure the sun does not move too often since it causes massive shadow flickering (from hell to the max)! | ||
float stepSize = 50f; | ||
Vector3f sunDirection = backdropProvider.getQuantizedSunDirection(stepSize); | ||
|
||
Vector3f sunPosition = new Vector3f(sunDirection); | ||
sunPosition.scale(256.0f + 64.0f); | ||
lightPosition.add(sunPosition); | ||
|
||
camera.getPosition().set(lightPosition); | ||
|
||
// and adjust it to look from the sun direction into the direction of our player | ||
Vector3f negSunDirection = new Vector3f(sunDirection); | ||
negSunDirection.scale(-1.0f); | ||
|
||
camera.getViewingDirection().set(negSunDirection); | ||
|
||
camera.update(deltaInSeconds); | ||
|
||
} | ||
|
||
|
||
@Override | ||
public void process() { | ||
PerformanceMonitor.startActivity("Render World (Shadow Map)"); | ||
|
||
// preRenderSetupSceneShadowMap | ||
shadowMap.bind(); | ||
setViewportToSizeOf(shadowMap); // TODO: how about shadowMap.setAsViewport() ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmm. Reading shadowMap.setAsViewport() I'd think that something is happening to the object shadowMap rather than the to viewport. Normally a method affects the instance is attached to or the instance is the "agent" that causes something. In this case neither make sense. We could theorethically go for something like shadowMap.setViewportToItsSize() but it's a bit convoluted. I prefer the immediacy of setViewportToSizeOf(anFBO), as it's almost plain English. |
||
// TODO: verify the need to clear color buffer | ||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | ||
GL11.glDisable(GL_CULL_FACE); | ||
|
||
// render | ||
camera.lookThrough(); | ||
|
||
shadowMapShader.enable(); | ||
// FIXME: storing chuncksOpaqueShadow or a mechanism for requesting a chunk queue for nodes which calls renderChunks method? | ||
worldRenderer.renderChunks(renderQueues.chunksOpaqueShadow, ChunkMesh.RenderPhase.OPAQUE, camera, WorldRendererImpl.ChunkRenderMode.SHADOW_MAP); | ||
playerCamera.lookThrough(); //FIXME: not strictly needed: just defensive programming here. | ||
|
||
|
||
// postRenderCleanupSceneShadowMap | ||
GL11.glEnable(GL_CULL_FACE); | ||
bindDisplay(); // bindDisplay() | ||
|
||
|
||
PerformanceMonitor.endActivity(); | ||
} | ||
|
||
|
||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright 2016 MovingBlocks | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.terasology.rendering.dag; | ||
|
||
import org.lwjgl.opengl.GL11; | ||
import org.terasology.config.Config; | ||
import org.terasology.config.RenderingConfig; | ||
import org.terasology.context.Context; | ||
import org.terasology.monitoring.PerformanceMonitor; | ||
import org.terasology.rendering.assets.material.Material; | ||
import org.terasology.rendering.assets.shader.ShaderProgramFeature; | ||
import org.terasology.rendering.backdrop.BackdropRenderer; | ||
import org.terasology.rendering.cameras.Camera; | ||
import org.terasology.rendering.opengl.FBO; | ||
import org.terasology.rendering.opengl.FrameBuffersManager; | ||
import org.terasology.rendering.opengl.OpenGLUtils; | ||
import org.terasology.rendering.primitives.ChunkMesh; | ||
import org.terasology.rendering.world.RenderQueuesHelper; | ||
import org.terasology.rendering.world.WorldRenderer; | ||
import org.terasology.rendering.world.WorldRendererImpl; | ||
|
||
import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT; | ||
import static org.lwjgl.opengl.GL11.GL_DEPTH_BUFFER_BIT; | ||
import static org.lwjgl.opengl.GL11.glClear; | ||
import static org.terasology.rendering.opengl.OpenGLUtils.bindDisplay; | ||
|
||
/** | ||
* Diagram of this node can be viewed from: | ||
* TODO: move diagram to the wiki when this part of the code is stable | ||
* - https://docs.google.com/drawings/d/1Iz7MA8Y5q7yjxxcgZW-0antv5kgx6NYkvoInielbwGU/edit?usp=sharing | ||
*/ | ||
public class WorldReflectionNode implements Node { | ||
|
||
private RenderQueuesHelper renderQueues; | ||
private RenderingConfig renderingConfig; | ||
private WorldRenderer worldRenderer; | ||
private Material chunkShader; | ||
|
||
private Context context; | ||
private BackdropRenderer backdropRenderer; | ||
private Camera playerCamera; | ||
|
||
private FBO sceneReflected; | ||
private FBO sceneOpaque; | ||
|
||
public WorldReflectionNode(Context context, Camera playerCamera, Material chunkShader) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to above, you can get the playerCamera via WorldRenderer.getActiveCamera(). |
||
this.context = context; | ||
this.backdropRenderer = context.get(BackdropRenderer.class); | ||
this.playerCamera = playerCamera; | ||
this.renderingConfig = context.get(Config.class).getRendering(); | ||
this.chunkShader = chunkShader; | ||
this.worldRenderer = context.get(WorldRenderer.class); | ||
this.renderQueues = context.get(RenderQueuesHelper.class); | ||
} | ||
|
||
@Override | ||
public void update(float deltaInSeconds) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to above, the content of this method can be placed in process(). Also, get the FrameBuffersManager instance only once please, on construction I'd suggest. |
||
this.sceneReflected = context.get(FrameBuffersManager.class).getFBO("sceneReflected"); | ||
this.sceneOpaque = context.get(FrameBuffersManager.class).getFBO("sceneOpaque"); | ||
} | ||
|
||
@Override | ||
public void process() { | ||
PerformanceMonitor.startActivity("Render World (Reflection)"); | ||
|
||
sceneReflected.bind(); | ||
OpenGLUtils.setViewportToSizeOf(sceneReflected); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove the "OpenGLUtils." |
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | ||
GL11.glCullFace(GL11.GL_FRONT); | ||
playerCamera.setReflected(true); | ||
|
||
|
||
playerCamera.lookThroughNormalized(); // we don't want the reflected scene to be bobbing or moving with the player | ||
backdropRenderer.render(playerCamera); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add:
Note for @tdgunes: this will be an interesting use case for GL state management and order of execution of nodes: both the the BackdropNode and the WorldReflectionNode need similar/identical states, so the nodes should be processed one after another and the first node to be processed should not be allowed to reset to default the states the second node needs. Also interesting, the BackdropNode will need to be used twice, one here, to render the reflection, and then later, for the proper renderer. Something to put in the back of our minds. |
||
playerCamera.lookThrough(); | ||
|
||
if (renderingConfig.isReflectiveWater()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmmmm.... let's put a TODO here:
|
||
chunkShader.activateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING); | ||
worldRenderer.renderChunks(renderQueues.chunksOpaqueReflection, ChunkMesh.RenderPhase.OPAQUE, playerCamera, WorldRendererImpl.ChunkRenderMode.REFLECTION); | ||
chunkShader.deactivateFeature(ShaderProgramFeature.FEATURE_USE_FORWARD_LIGHTING); | ||
} | ||
|
||
playerCamera.setReflected(false); | ||
|
||
GL11.glCullFace(GL11.GL_BACK); | ||
bindDisplay(); | ||
OpenGLUtils.setViewportToSizeOf(sceneOpaque); // toWholeDisplay | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's remove "OpenGLUtils." Regarding the //toWholeDisplay comment, indeed the original code had "setViewportToWholeDisplay();" Why did you change it? Interestingly, this is one case in which a state change is used also later, outside of the render reflection step. In the following rendering steps, i.e. backdrop render and then the whole scene opaque, the viewport stays to what is set here. Again, state management will have to take care of this: each node should be independent of the others unless there is an explicit requirement (I'm thinking a node could have a setExecutionDependency(Node ancestor) method - or something like that), state management will then ensure that the GL state is changed only if necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't find any elegant solution for storing dimensions ( |
||
|
||
|
||
PerformanceMonitor.endActivity(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this shadowMapCamera please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was trying to avoid something like:
But since in the future we would not access shadowMapNode in this way. I think it's ok.