• Home
  • Textbooks
  • Mathematics for Game Programming and Computer Graphics: Explore the essential mathematics for creating, rendering, and manipulating 3D virtual environments
  • Rendering Visual Realism Like a Pro

Mathematics for Game Programming and Computer Graphics: Explore the essential mathematics for creating, rendering, and manipulating 3D virtual environments

Penny de Byl

Chapter 19

Rendering Visual Realism Like a Pro - all with Video Answers

Educators


Section 1

Following where light bounces

Problem 1

Make a copy of the Chapter_18 folder and rename it Chapter_19.

Check back soon!

Problem 2

You will need a copy of the sphere.obj model file available in GitHub. Make sure you add it to the models folder of Chapter_19 for your project.

Check back soon!

Problem 3

Make a copy of ShaderTeapot . py or the file you copied from this and displayed the Granny model in Chapter 18, Customizing the Render Pipeline. Call this copied file PBR.py. Make the following changes:
..
from Settings import *
from Light import *
pygame.init()
..
objects_3d = []
camera = Camera(60, (screen_width / screen_height),
0.01, 10000.0)
for x in range(10):
for y in range(10):
sphere = Object(“Sphere”)
sphere.add_component(Transform())
mat = Material(“shaders/pbrvert.vs”,
“shaders/pbrfrag.vs”)
sphere.add_component(
LoadMesh(sphere.vao_ref, mat,
GL_TRIANGLES,
“models/sphere.obj”))
sphere_mesh: LoadMesh =
sphere.get_component(LoadMesh)
sphere_mesh.set_properties(
pygame.Vector3(1, 0, 1),
x/10.0, x/10.0, y/10.0)
sphere.add_component(mat)
sphere_trans: Transform =
sphere.get_component(Transform)
sphere_trans.update_position(
pygame.Vector3(x*20, y*20, -20))
objects_3d.append(sphere)

Check back soon!

Problem 4

Create a new Python script called Light.py and add the following:
from Transform import *
class Light:
def __init__(self, position=pygame.Vector3(0, 0,
0),color=pygame.Vector3(1, 1, 1),
atten=0, light_number=0):
self.position = position
self.atten = atten
self.color = color
self.light_variable =
“light_data[“ + str(light_number) +
“].position”
self.atten_variable = “light_data[“ +
str(light_number) + “].attenuation”
self.color_variable = “light_data[“ +
str(light_number) + “].color”

Check back soon!

Problem 5

Because the lights are objects that apply to each and every object in the 3D environment, they are dealt with like the camera. Open Object. py and modify the code thus:
from LoadMesh import *
..
from Light import *

class Object:
def __init__(self, obj_name):
..
def add_component(self, component):
..)
def get_component(self, class_type):
..
def update(self, camera: Camera,
lights: Light([]), events = None):
self.material.use()
for c in self.components:
if isinstance(c, Transform):
..
transformation.load()
for l in lights:
light_pos = Uniform(“vec3”,
l.position)
light_pos.find_variable(
self.material.program_id,
l.light_variable)
light_pos.load()
light_atten = Uniform(“float”,
l.atten)
light_atten.find_variable(
self.material.program_id,
l.atten_variable)
light_atten.load()
color = Uniform(“vec3”, l.color)
color.find_variable(
self.material.program_id,
l.color_variable)
color.load()
elif isinstance(c, LoadMesh):
c.draw()

Check back soon!

Problem 6

LoadMesh.py also needs a small modification, thus:
class LoadMesh(Mesh3D):
def __init__(self, vao_ref, material, draw_type,
model_filename, texture_file=””,
back_face_cull=False):
..
#Comment out v_uvs as they aren’t needed for
#the shader and will cause Windows errors
#v_uvs = GraphicsData(“vec2”, self.uv_vals)
#v_uvs.create_variable(
# self.material.program_id,
# “vertex_uv”)
self.albedo = None
self.metallic = None
self.roughness = None
self.ao = None
#Comment out these next lines or remove them.
#if texture_file is not None:
#self.image = Texture(texture_file)
#self.texture = Uniform(“sampler2D”,
#[self.image.texture_id,
# 1])
def format_vertices(self, coordinates, triangles):
..
def set_properties(self, albedo, metallic,
roughness, ao):
self.albedo = Uniform(“vec3”, albedo)
self.metallic = Uniform(“float”, metallic)
self.roughness = Uniform(“float”, roughness)
self.ao = Uniform(“float”, ao)
def draw(self):
self.albedo.find_variable(
self.material.program_id,
“albedo”)
self.albedo.load()
self.metallic.find_variable(
self.material.program_id,
“metallic”)
self.metallic.load()
self.roughness.find_variable(
self.material.program_id,
“roughness”)
self.roughness.load()
self.ao.find_variable(
self.material.program_id, “ao”)
self.ao.load()
glBindVertexArray(self.vao_ref)
glDrawArrays(self.draw_type, 0,
len(self.coordinates))
..

Check back soon!

Problem 7

Open Uniform.py and add the following code:
def load(self):
if self.data_type == “vec3”:
glUniform3f(self.variable_id, self.data[0],
self.data[1], self.data[2])
elif self.data_type == “float”:
glUniform1f(self.variable_id, self.data)
elif self.data_type == “mat4”:
glUniformMatrix4fv(self.variable_id, 1,
GL_TRUE, self.data)

Check back soon!

Problem 8

Now, it's time to write the shader code. Create two files in the shader folder - one called pbrvert.vs and the other called pbrfrag.vs.

Check back soon!

Problem 9

To pbrvert.vs, add the following:
#version 330 core
in vec3 position;
in vec3 vertex_normal;
uniform mat4 projection_mat;
uniform mat4 model_mat;
uniform mat4 view_mat;
out vec3 normal;
out vec3 world_pos;
out vec3 cam_pos;
void main()
{
gl_Position = projection_mat * transpose(view_mat)
* transpose(model_mat) *
vec4(position, 1);
normal = mat3(transpose(model_mat)) *
vertex_normal;
world_pos = (transpose(model_mat) *
vec4(position, 1)).rgb;
cam_pos = vec3(inverse(transpose(model_mat)) *
vec4(view_mat[3][0],
view_mat[3][1],
view_mat[3][2],1));
}

Check back soon!

Problem 10

To pbrfrag.vs, add the following:
#version 330 core
out vec4 frag_color;
in vec3 world_pos;
in vec3 normal;
in vec3 cam_pos;
// material parameters
uniform vec3 albedo;
uniform float metallic;
uniform float roughness;
uniform float ao;
struct light
{
vec3 position;
vec3 color;
float attenuation;
};
#define NUM_LIGHTS 3
uniform light light_data[NUM_LIGHTS];
const float PI = 3.14159265359;
void main()
{
vec3 N = normalize(normal);
vec3 V = normalize(cam_pos - world_pos);
vec3 color = vec3(0,0,0);
for(int i = 0; i < NUM_LIGHTS; ++i) //each light
{
// calculate per-light radiance
vec3 L = normalize(light_data[i].position -
world_pos);
vec3 H = normalize(V + L);
float distance =
length(light_data[i].position -
world_pos);
float attenuation = light_data[i].attenuation
/
(distance * distance);
vec3 radiance = light_data[i].color *
light_data[i].attenuation;
color += radiance;
}
color *= albedo * roughness * metallic *
ao * normal;
frag_color = vec4(color, 1.0);
}

Check back soon!

Problem 11

It's time to modify the fragment shader to produce a PBR effect. Open pbrfrag.vs and make these modifications:
#version 330 core
..
#define NUM_LIGHTS 3
uniform light light_data[NUM_LIGHTS];
const float PI = 3.14159265359;
vec3 Fresnel(float HoV, vec3 metalness)
{
return metalness + (1.0 - metalness) *
pow(clamp(1.0 - HoV, 0.0, 1.0), 5.0);
}
float GGX(float NoH, float roughness)
{
float a = roughness*roughness;
float a2 = a*a;
float NoH2 = NoH*NoH;
float numerator = a2;
float denominator = (NoH2 * (a2 - 1.0) + 1.0);
denominator = PI * denominator * denominator;
return numerator / denominator;
}
float GASchlick(float Ndot, float roughness)
{
float r = (roughness + 1.0);
float k = (r*r) / 8.0;
float numerator = Ndot;
float denominator = Ndot * (1.0 - k) + k;
return numerator / denominator;
}
float GASmith(float NoV, float NoL, float roughness)
{
float gas2 = GASchlick(NoV, roughness);
float gas1 = GASchlick(NoL, roughness);
return gas1 * gas2;
}

Check back soon!