lax1dude / cs50-final-project

This isn't meant for production, don't use this project its not worth it, if you like the way the shaders look please steal them from EaglercraftX instead because they have been better optimized

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Design Document

(scroll down for compiler instructions)

https://youtu.be/opPlUBR5UAo

The goal of this project was to implement a PBR rendering engine using Java and OpenGL. To create this application I used the LWJGL native OpenGL ES bindings for Java to interface with Google's ANGLE OpenGL ES implementation used in chrome as the backend for WebGL. The reason I chose Java and ANGLE as my development environment is for the universal compatibility of the Java language and the OpenGL ES API. Every major platform supports both in some way so this rendering engine can be repurposed at some point for mobile development or even console development.

My source code repository is available here: https://github.com/LAX1DUDE/cs50-final-project

If the pictures do not load you can access a copy of this doc here: [https://github.com/LAX1DUDE/cs50-final-project/blob/master/DESIGN.md]

How a frame is rendered

I will be using a program called RenderDoc to display real screenshots of every render target used in the game while it's running. RenderDoc injects a layer between the Java code and OpenGL so it can intercept draw calls and display debug information related to them in a GUI.

enter image description here

Cloud Map Update

The first thing the game does is update a single tile of a paraboloid map containing the cloud information for rendering the PBR clouds in the sky. The tile is created by raytracing a GPU accelerated Worley Noise algorithm. A seam is visible in the screenshot because the clouds are moving and the tiles have only partially been updated. The clouds are triple buffered so the actual cloud maps used by the sky mesh is only updated once every tile in the back buffer has received an update, and then the two front buffers are crossfaded by the pixel shader

cloud paraboloid map

Depth Prepass

Next, all solid geometry is rendered to a depth buffer, outputting the distance each pixel is from the viewer. This is done for optimization so the depth test mode can be set to GL_EQUAL when the main color pass is performed to avoid rendering obstructed geometry. The depth buffer is reversed (0 is far, 1 is near) because floating point integers have more precision when closer to 0, which greatly reduces Z-fighting for distant geometry. A stencil buffer is also generated, acting as a mask for which pixels will be filled with sky later.

depth prepass

Geometry Buffer Generation

My engine uses deferred rendering technique rendering to three render targets simultaneously, a target storing each pixel's diffuse color, a target storing each pixel's normal vector (the direction it faces), and a target storing the material properties of each pixel (as RGBA, representing the material metalness, roughness, specular, ssr). These are generated in a single pass

enter image description here enter image description here enter image description here

An example of obstructed geometry detected through the depth prepass can be seen here, while the rendering pass has only partially completed:

enter image description here

Shadow Map Generation

Next, the scene is rendered four times from the perspective of the sun to a depth buffer. This is used later to calculate which pixels are obstructed by shadows from the sun. It is rendered from progressively farther distances from the flycam to maintain shadow quality at short distances and long distances simultaneously

enter image description here

Then shadow maps are rendered for every shadow casting light in the scene to a tile in a large depth buffer atlas

enter image description here

Sun Shadow Calculation

This step uses the four depth buffers rendered from the perspective of the sun to calculate which pixels are obstructed by a shadow

enter image description here

Cube Map Generation

To calculate reflections later, the scene is rendered six more times to six faces of a cube, using forward rendering. The shadow maps and cloud maps are used to render shadows and the sky and sun in the background. The render target uses linear color space stored as 16-bit floats

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here

Dual Paraboloid Generation

The cubemap is converted to a dual paraboloid map and then it is repeatedly blurred and blitted to a texture atlas for later use when compositing materials

enter image description here enter image description here

Irradiance Map

The irradiance map is recalculated once per second as it is a very compute-intensive process if PBR is the priority. It is used to calculate global ambient illumination

enter image description here

Screen Space Ambient Occlusion

Ambient occlusion is calculated at half resolution, at first a noisy result is produced but it is then blurred by a two pass depth-aware shader to produce a clean result

enter image description hereenter image description here enter image description hereenter image description here

Light source calculations

The blend mode is set to GL_ONE + GL_ONE (Add every rendered pixel's values to the value of the pixel already in the buffer) and then every light source's diffuse and specular component is calculated and rendered to the buffer sequentially, calculating the final lighting value of every pixel

enter image description here enter image description here

Screen Space Reflections

The previous frame's color and depth buffer is raytraced and screen space reflection values are calculated for every pixel

enter image description here

This is done at half resolution for optimization

Buffer Combination

The diffuse, normal, material, depth, light diffuse, light specular, ambient occlusion, the cube map, the irradiance map, the blurred dual paraboloid maps, the screen space reflection buffer, and a lookup helper texture are all passed to a large shader that does a lot of math to produce a composited PBR image in linear color space

enter image description here

Sky

The cloud maps and a night sky texture are used to create a sky. The sky is a tessellated octahedron hemisphere, the atmospheric scattering is calculated per vertex here instead of per fragment to accelerate things.

enter image description here

Moon

The moon is not visible here, but it is rendered using the GL_ONE blend mode using a texture atlas. The text is just a note I left for myself

enter image description here

Light Bulbs

The different light sources get drawn over the scene as little light bulbs for realism. Instancing is used for acceleration as there are dozens of light sources in frame

enter image description here enter image description here

Light Shafts

A light shaft map (also called volumetric lighting, or godrays) is generated by raytracing the sun's first and second shadow map

enter image description here

Fog

The blend mode is set to add and fog is blended over the scene. The light shaft map is used to selectively darken areas of fog for light shaft effects

Semi transparent geometry

Some of the spheres in the scene were drawn dithered, in a checkerboard pattern. These checkerboard patterns are now healed using data stored in the diffuse buffer's alpha channel, to produce transparent geometry.

enter image description here enter image description here

Lens Flares

Lens flares for the sun are now drawn using a single draw call

enter image description here enter image description here

Bloom and Exposure Calculation

The buffer is repeatedly downscaled and blurred in multiple different passes to produce two blurred versions of the scene, one at a quarter resolution and one at an eight with more blur. These are blended over the scene to simulate bloom and reflections within the camera.

Tonemapping

Finally, the Uncharted 4 operator is used to convert the linear 16-bit floating point HDR colors to 8-bit sRGB colors. This is done in a single pass.

Text

The debug text is drawn using a large unicode texture atlas generated with a class in the tools directory, containing a bitmap of every unicode codepoint. The letters are each drawn as single meshes using premultiplied alpha and instancing.

enter image description here

Wireframe: enter image description here

Unicode: enter image description here

Texture Atlas: enter image description here enter image description here

Completed Frame

enter image description here

Compiling

To debug or compile this project, import the client directory as a Gradle project in Eclipse. Then create a run configuration with the following program arguments: --debug --dev-assets ../assets_dev --platform opengl. Also, set the working directory of the run configuration to either the windows or the mac or the linux directory of the client directory depending on the platform, so the program can load the correct native libraries.

The source code can be found in src/main/java. The packages in this directory are:

  • net.lax1dude.cs50_final_project - Utility classes, do not depend on client side resources
  • net.lax1dude.cs50_final_project.client - Core classes for whatever application will eventually be built on top of the rendering engine
  • net.lax1dude.cs50_final_project.client.main - Contains the main class and launch dialog box
  • net.lax1dude.cs50_final_project.client.renderer - Contains all classes required for rendering
  • net.lax1dude.cs50_final_project.client.opengl - Contains simple java wrapper objects for different opengl object types for convenience

To configure the renderer, check net.lax1dude.cs50_final_project.client.GameConfiguration. It has variables to adjust the GLSL compiler settings, shadowmap resolutions, the cloud map resolution, screen space reflection quality, the far plane, and flags for enabling and disabling ambient occlusion, volumetric sunlight, bloom, soft shadows, cloud movement, and screen space reflections.

To configure the scene, create a custom instance of RenderScene and populate the different variables with custom values. There are linked sets for adding and removing ObjectRenderer, LightRenderer, and ShadowLightRenderer objects to the scene.

There is a tools application for generating vertex data from wavefront obj files, generating new fonts atlases from truetype font files, and compositing textures for use with the g-buffer material system from individual texture files for every material variable. Import the tools directory as a java project into eclipse.

About

This isn't meant for production, don't use this project its not worth it, if you like the way the shaders look please steal them from EaglercraftX instead because they have been better optimized

License:BSD 2-Clause "Simplified" License


Languages

Language:Java 79.3%Language:GLSL 20.5%Language:Shell 0.1%Language:Batchfile 0.0%