Posts mit dem Label cubemap werden angezeigt. Alle Posts anzeigen
Posts mit dem Label cubemap werden angezeigt. Alle Posts anzeigen

Montag, 30. Mai 2016

Realtime Importance Sampling with Boxprojected G-Buffer CubeMaps for Dynamic Global Illumination

Last year, I wrote my masther's thesis about global illumination effects in realtime rendering engines. Since I haven't yet published any infos, because I didn't know how, I finally do it now. The result of the thesis was an implementation of a custom deffered rendering engine with LWJGL.

Addiotionally, I used something that is called boxprojection with cubemap datastructures. Furthermore, I modeled proxy objects (boxes) that I call probes - they are axis aligned bounding boxes with a corresponding cubemap, very much like a dynamic environment map. Instead of just one texture for a snapshot of the environment, I attached multiple textures for the exact same purpose that multiple textures are used for in G-Buffers for deferred rendering. Pre-Rendering all positions and material attributes of a whole level leaves us with the need to evaluate the lighting, when lighting conditions change. That is much cheaper than actually render the complete (environment) map again, like with traditional environment mapping. Additionally, the first and second pass shaders from the deferred rendering can be reused, instead of having additional forward shaders, as with regular environment mapping.

At this point, one would be able to use those environment maps for perfect reflection mapping. Can be used for all dynamic and static objects, reflects static objects with dynamic lighting. But that's not enough; there's something called realtime importance sampling, that could use prefiltered cubemaps, to have a complete lighting model covered - that means specular and diffuse reflections with arbitrary lighting models. One just has to calculate the mipmaps for the cubemaps and everything will work. Another possible tweak is to precalculate the radiance for each cubemap and save it different roughness values in the mipmaps. Have a look at Unreal Engine 4's implementation. Thats how I implemented it to avoid the costly importance sampling in trade of quality.

Since probes have to be placed within the level, on needs to blend between multiple probes. I implemented an algorithm from Sebastien Lagarde, similar to his approach in the game Remember Me.

The result is pretty nice global illumination in high framerates with the possibility to alternate probe update over time, stream probes etc. It can run on my GTX 770 with framerates above 150 fps in the sponza scene - depending on additional quality settings, like multiple bounces etc. Here's a screenshot of what could be done, again, completey dynamic lighting, static geometry. Dynamic geometry would take more ressources.





I will provide more info, if anybody is interested.

Samstag, 24. Oktober 2015

Single Pass Omnidirectional Shadow Mapping Evaluation

Layered rendering is possible since geometry shaders entered the OpenGL stage. The rough idea of a good omnidirectional shadow mapping (for example for pointlights) is to render the complete shadow map with a single draw pass, in order to reduce the amount of rendercalls, compared to six-pass rendering with traditional rendering to a cubemap. Therefore, the geometry shader emits a vertex for each incoming vertex of the non-culled scene geometry to a face of the cubemap with something called layered rendering. While the idea is well described all over the internet already, every now and then, there is discussion about the efficiency of this method.

My first idea was to evaluate omnidirectional shadow mapping with dual paraboloid shadow maps since it's the easiest way to achieve pointlight shadow: No viewmatrices, no projectionmatrices, two textures per pointlight, let's go. Without view frustum culling, I draw the complete geometry twice and passed a "backside" flag as a uniform variable for the second rendering. No layered rendering, just two depth buffers and two color attachments (can be ommited if no variance shadow mapping is used) for each pointlight (front and back). It's possible to do dpsm with a single-pass rendering in a single texture - I doubt it to be efficient because you would have to handle the depth buffer somehow. Or it will be efficient, but much more complicated.

Rendering a single (two texture) shadowmap for the famous sponza atrium takes ~1.5 ms on my GTX 770.

Layered rendering however, took me a while for the implementation. Because I use array textures in order to be able to use many shadow mapped lights, I had to fight with the strange array indices for cubemap arrays. For everyone who is interested, here's how you create a cubemap array rendertarget and use it to draw your shadow maps:

 framebufferLocation = GL30.glGenFramebuffers();  
 // Create rendertarget
 GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebufferLocation);  
 IntBuffer scratchBuffer = BufferUtils.createIntBuffer(colorBufferCount);  
 for (int i = 0; i < colorBufferCount; i++) {  
      GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0 + i, cubeMapArrays.get(i).getTextureID(), 0);  
      scratchBuffer.put(i, GL30.GL_COLOR_ATTACHMENT0+i);  
 }  
 // Use glDrawBuffers(GL_NONE) if you don't need color attachments but depth only
 GL20.glDrawBuffers(scratchBuffer);  
 CubeMapArray depthCubeMapArray = new CubeMapArray(AppContext.getInstance().getRenderer(), 1, GL11.GL_LINEAR, GL14.GL_DEPTH_COMPONENT24);  
 int depthCubeMapArrayId = depthCubeMapArray.getTextureID();  
 GL32.glFramebufferTexture(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, depthCubeMapArrayId, 0);  

Although some implementations are hidden because of my framework, the idea should be clear. Don't forget to initialize the cubemap array texture correctly or your framebuffer object won't be complete.

Since the geometryshader decides which face to render to, you can pass the pointlight index as a uniform variable. The layer will then be gl_Layer = 6*lightIndex + layer.

The quality is very nice, but unfortunetly, rendering sponza into the cubemap takes ~8.3ms on my GTX 770. The additional color attachment is not responsible for the expensiveness, since I tried to remove it. I'm pretty sure that I didn't do a major mistake in the implementation. It has to be the poor performance of the geometry shader that is responsible for the high amount of time this method takes.

Conclusion


  • Dual paraboloid shadow map rendering ~1.5 ms
  • Single pass cubemap shadow map rendering ~ 8.3 ms

I used 512*512 per dspm face or cubemap face. Would be nice to hear anyone alse's experience with omnidirectional shadowmapping.

Dienstag, 24. März 2015

Get normal for cubemap texel

Since I came across this problem and wasn't able to find an easy solution on the internet, I decided to write s small recipe to calculate normals when you want to do manual mipmapping/radiance convolution with cubemaps in OpenGL. I use compute shaders, so for geometry/vertex/pixel-pipeline, you could use layered rendering and other stuff.

First of all, the shader needs the current cubemap face index as a uniform variable. I recommend using the standard OpenGL indices (see link below).

Most likely, you are using the standard cubemap layout. If this is not the case, you have to change the vectors in my code. So with a given face index and a given texel position, the problem can be solved:




What happens here is that I calculate the pixel position in texture space with the help of the invoation position. The compute shader is invoked with cubemapfaceResolution.x/16, cubemapfaceResolution.y/16, 1. Knowing which (OpenGL) world axis the view direction of the virtual camera, facing the current cubemap side from the inside (cubemaps origin) is, the other two axis are the two orthogonal axes. These two axes' values grow with the texelcoordinates we already have. But therefore, they have to be remapped from 0 - 1 to -1 - 1. The resulting vector can be used to sample a cubemap as it is. Normalization could be unnecessary.