This project is a Physically Based Rendering (PBR) graphics engine, which uses the principles of PBR to render realistic looking materials based on the Cook-Torrence BRDF (Bidirectional Reflectance Distribution Function).
This project was built using JDK 23, using prior JDK versions may cause unexpected behavior
- First clone the repository using the command below in the terminal
git clone https://github.com/Daschle-Newberry/PBR-Lighting-Engine
- Once within the project directory, run the following command. This will create an jar file for the project that contains all the necessary resources
.\gradlew.bat shadowjar
-
Locate the jar file, it should be in the build\libs folder
-
Copy the jar location and run this command in the terminal
java -jar C:\Users\...\path\to\the\jar
The function, fr(), is the BRDF part of the reflectance equation. Li() is the incident radiance, and alters the outgoing radiance (Lo) depending on the light intensity and color. The dot product of the normal (n) and the incident light direction (wi) scales the outgoing radiance depending on if the point (or fragment in our case) is facing the light source.
PBR works based on the core principle that a surface is made up of micro-facets, which are very small reflective surfaces. Depending on the roughness of the surface, these micro-facets will be aligned in different directions, which changes how the surface interacts with light.
For our cases we will break down the BRDF into two parts, diffuse light and specular light.
The Normal Distribution Function (NDF) represents the amount of surface area that is aligned with the half-vector between the incident light direction and the view direction. Using the surface normal, the half-vector, and the roughness parameter (alpha) we can calculate the D term.
Similarly to the NDF, the Geometry function represents the amount of surface area that is self shadowed. Since micro-facets can either obstruct light from reaching the viewer or reflect the light away from the viewer, we must calculate the geometry term once using the light vector and once using the view vector:
The Fresnel equation is perhaps one of the most import parts of the BRDF, and it represents the ratio of light that is reflected over the portion that is refracted. It is calculated using the following equation:
F0 represents the base reflectivity of an object, which determines how reflective each color of a surface albedo is. For example, a gold reflective surface may have a F0 of (1.00, 0.71, 0.29). The Fresnel equation takes the base reflectivity, and increases it by a portion of its complement. How large the compliment is is dependent on the angle between the half vector and the view vector. This calculation causes surfaces to be more reflective when they are observed at grazing angles, similar to real life.
To finalize the specular term, we divide by 4 times the dot product of the outgoing light direction (view direction) and the normal times the dot product of the incident light direction and the normal. We do this to ensure that the outgoing light does not exceed the incoming light (law of conservation of energy).
First, we compute the ratio of light that is diffuse by first finding what portion of the light is specular (reflected). Since the Fresnel term determines the magnitude of the specular portion, we can use its compliment to determine the magnitude of the diffuse portion:
We use the kd term to scale the color of the surface, but we must divide the surface albedo by pi. The division by pi is to ensure that the outgoing light energy is not greater than the incoming light energy as we integrate over the entire hemisphere (As seen in the BRDF):
While the previous calculations are used to calculate the lighting of an object with respect to a single light source, they do NOT include the image based lighting calculations, which are responsible for the majority of the specular reflections in the scene. The image based lighting is calculated using the same reflectance equation, plus some optimizations to make it possible to compute it over an entire hemisphere of infinite view angles. The primary optimization we make it precomputing the image based lighting, which then utilizes optimizations like the split-sum approximation and importance sampling. Unfortunately, these concepts exceed what can be explained over a github page.
If you would like to learn more about image based lighting:
Diffuse
Specular
learnopengl.com : This website is an amazing resource for learning not only OpenGL, but other graphics programming concepts. I used the PBR section to learn essentially everything in this project, and you will see them referenced in my code in some areas.
OGLDev on YouTube : OGLDev is an amazing YouTube channel for learning all things computer graphics. He has helped me learn a lot of shadow mapping specifically, which is a bit of a pain.
GamesWithGabe GamesWithGabe is another amazing YouTube channel for learning all sorts of programming concepts. This channel specifically helped me in my early CGI journey to learn how to use LWJGL for GLFW and OpenGL bindings. I also used this channel when creating some of the basic engine architecture, such as the window class and key listeners.

