diff --git a/html-renderer/main.py b/html-renderer/main.py new file mode 100644 index 0000000..b82a3c7 --- /dev/null +++ b/html-renderer/main.py @@ -0,0 +1,254 @@ +import sys +import pygame +import requests +from bs4 import BeautifulSoup, NavigableString + +def parse_css(css_text): + """ + A simple CSS parser mapping selectors to a dictionary + of property names and values. + """ + styles = {} + css_text = css_text.replace("\n", "") + blocks = css_text.split("}") + for block in blocks: + if "{" in block: + selector, props = block.split("{", 1) + selector = selector.strip() + props = props.strip() + prop_dict = {} + for declaration in props.split(";"): + if ":" in declaration: + key, value = declaration.split(":", 1) + key = key.strip() + value = value.strip() + # For font-size, store the raw value so percentages can be processed. + if key == "font-size": + prop_dict[key] = value + else: + prop_dict[key] = value + styles[selector] = prop_dict + return styles + +def get_computed_style(element, css_styles): + """ + Merge CSS rules (by tag name) with inline style declarations. + """ + style = {} + if element.name in css_styles: + style.update(css_styles[element.name]) + if 'style' in element.attrs: + declarations = element['style'].split(";") + for decl in declarations: + if ":" in decl: + key, value = decl.split(":", 1) + style[key.strip()] = value.strip() + return style + +def render_element(surface, element, x, y, css_styles, base_font): + """ + Recursively render an element (or text node) on the surface. + Returns the total height rendered. + """ + total_height = 0 + + # Handle text nodes. + if isinstance(element, NavigableString): + text = str(element).strip() + if text: + text_surface = base_font.render(text, True, pygame.Color("black")) + surface.blit(text_surface, (x, y)) + return text_surface.get_height() + return 0 + + # For tag nodes, compute the style. + computed_style = get_computed_style(element, css_styles) + + # Handle margins (defaults: top=0, bottom=5). + try: + margin_top = int(computed_style.get("margin-top", "0")) + except ValueError: + margin_top = 0 + try: + margin_bottom = int(computed_style.get("margin-bottom", "5")) + except ValueError: + margin_bottom = 5 + + total_height += margin_top + + # Process font size. + fs_val = computed_style.get("font-size", None) + if fs_val: + fs_val = fs_val.strip() + if fs_val.endswith('%'): + try: + percent = float(fs_val.strip('%')) + font_size = int(base_font.get_height() * percent / 100.0) + except Exception: + font_size = base_font.get_height() + else: + try: + font_size = int(fs_val) + except ValueError: + font_size = base_font.get_height() + else: + font_size = base_font.get_height() + + # Process color, with fallback. + color_str = computed_style.get("color", "black") + try: + color = pygame.Color(color_str) + except ValueError: + color = pygame.Color("black") + + # Create a new font with the computed font size. + font = pygame.font.SysFont(None, font_size) + # Apply simple tag-based styling. + if element.name == "b": + font.set_bold(True) + elif element.name == "i": + font.set_italic(True) + + # Recursively render children. + for child in element.children: + child_height = render_element(surface, child, x, y + total_height, css_styles, font) + total_height += child_height + + total_height += margin_bottom + return total_height + +def render_html(surface, html, css_text): + """ + Renders the HTML content onto a Pygame surface. + Returns the total height used. + """ + css_styles = parse_css(css_text) + soup = BeautifulSoup(html, "html.parser") + + # Remove tags that are non-renderable. + for tag in soup.find_all(["script", "style", "meta", "link", "head"]): + tag.decompose() + + y = 10 + base_font = pygame.font.SysFont(None, 24) + + # Prefer content inside , if available. + body = soup.find("body") + if body: + for element in body.children: + y += render_element(surface, element, 10, y, css_styles, base_font) + else: + for element in soup.contents: + y += render_element(surface, element, 10, y, css_styles, base_font) + return y + +def fetch_html(url): + """ + Download the HTML content from a URL. + """ + response = requests.get(url) + response.raise_for_status() + return response.text + +def render_html_to_surface(html, css_text, width): + """ + Render the HTML to an offscreen surface for scrolling. + """ + # Create a temporary surface with extra height. + temp_surface = pygame.Surface((width, 3000)) + rendered_height = render_html(temp_surface, html, css_text) + + # If nothing was rendered (common with complex pages), fallback to plain text. + if rendered_height < 20: + plain_text = BeautifulSoup(html, "html.parser").get_text() + temp_surface.fill((255, 255, 255)) + font = pygame.font.SysFont(None, 24) + y = 10 + for line in plain_text.splitlines(): + line = line.strip() + if line: + text_surface = font.render(line, True, pygame.Color("black")) + temp_surface.blit(text_surface, (10, y)) + y += text_surface.get_height() + 5 + rendered_height = y + + # Create a surface exactly as tall as the rendered content. + content_surface = pygame.Surface((width, rendered_height)) + content_surface.blit(temp_surface, (0, 0)) + return content_surface, rendered_height + +def main(): + pygame.init() + screen_width, screen_height = 800, 600 + screen = pygame.display.set_mode((screen_width, screen_height)) + pygame.display.set_caption("HTML Renderer with URL Fetch, CSS, and Scrolling") + + # Fetch HTML from a URL if provided; otherwise, use sample HTML. + if len(sys.argv) > 1: + url = sys.argv[1] + try: + html = fetch_html(url) + except Exception as e: + print(f"Error fetching {url}: {e}") + return + else: + html = """ + + +

Hello, Pygame HTML Renderer!

+

This is a proof of concept for rendering HTML with CSS in Pygame.

+

This paragraph is red!

+

This paragraph is larger.

+

This paragraph has margins!

+

Scroll down for more content!

+ + + """ + + # A minimal default CSS. + css_text = """ + h1 { + font-size: 36; + color: blue; + margin-bottom: 10; + } + p { + font-size: 24; + color: black; + } + """ + + # Render the HTML to an offscreen content surface. + content_surface, content_height = render_html_to_surface(html, css_text, screen_width) + scroll_offset = 0 + clock = pygame.time.Clock() + max_scroll = max(0, content_height - screen_height) + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + + # Handle keyboard scrolling. + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_DOWN: + scroll_offset = min(max_scroll, scroll_offset + 20) + elif event.key == pygame.K_UP: + scroll_offset = max(0, scroll_offset - 20) + # Handle mouse wheel scrolling. + if event.type == pygame.MOUSEBUTTONDOWN: + if event.button == 4: # Wheel up. + scroll_offset = max(0, scroll_offset - 20) + elif event.button == 5: # Wheel down. + scroll_offset = min(max_scroll, scroll_offset + 20) + + screen.fill((255, 255, 255)) + screen.blit(content_surface, (0, -scroll_offset)) + pygame.display.flip() + clock.tick(30) + + pygame.quit() + +if __name__ == "__main__": + main() diff --git a/physicaly-based-renderer/aillian.mat b/physicaly-based-renderer/aillian.mat new file mode 100644 index 0000000..d3caf0b --- /dev/null +++ b/physicaly-based-renderer/aillian.mat @@ -0,0 +1,10 @@ +albedo: [1, 1, 1] +metallic: 0 +roughness: 0.0500000007 +ao: 0 +albedo_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_albedo.png +metallic_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_metallic.png +roughness_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_roughness.png +ao_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_ao.png +normal_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_normal-ogl.png +height_texture: C:\Users\spenc\Downloads\alien-carnivorous-plant-bl\alien-carnivorous-plant-bl\alien-carniverous-plant_height.png \ No newline at end of file diff --git a/physicaly-based-renderer/bricks.mat b/physicaly-based-renderer/bricks.mat new file mode 100644 index 0000000..2643c15 --- /dev/null +++ b/physicaly-based-renderer/bricks.mat @@ -0,0 +1,9 @@ +albedo: [1, 0, 0] +metallic: 1 +roughness: 1 +ao: 1 +albedo_texture: C:\Users\spenc\Downloads\alley-brick-wall-bl\alley-brick-wall-bl\alley-brick-wall_albedo.png +metallic_texture: C:\Users\spenc\Downloads\alley-brick-wall-bl\alley-brick-wall-bl\alley-brick-wall_metallic.png +roughness_texture: C:\Users\spenc\Downloads\alley-brick-wall-bl\alley-brick-wall-bl\alley-brick-wall_roughness.png +ao_texture: C:\Users\spenc\Downloads\alley-brick-wall-bl\alley-brick-wall-bl\alley-brick-wall_ao.png +normal_texture: C:\Users\spenc\Downloads\alley-brick-wall-bl\alley-brick-wall-bl\alley-brick-wall_normal-ogl.png \ No newline at end of file diff --git a/physicaly-based-renderer/imgui.ini b/physicaly-based-renderer/imgui.ini index ce9f901..a3c5777 100644 --- a/physicaly-based-renderer/imgui.ini +++ b/physicaly-based-renderer/imgui.ini @@ -4,7 +4,7 @@ Size=400,400 Collapsed=0 [Window][Scene Controls] -Pos=7,4 -Size=334,466 +Pos=13,10 +Size=484,570 Collapsed=0 diff --git a/physicaly-based-renderer/main.cpp b/physicaly-based-renderer/main.cpp index 254a5bf..00c51ea 100644 --- a/physicaly-based-renderer/main.cpp +++ b/physicaly-based-renderer/main.cpp @@ -1,11 +1,14 @@ // main.cpp -// A minimal physically based renderer with ImGui integration, -// normal mapping (with per-vertex tangents), YAML-based material loading/saving, -// and support for loading 3D models (OBJ files) via tinyobjloader. -// Uses OpenGL, GLFW, GLEW, GLM, ImGui (and IM_PI), stb_image, yaml-cpp, and tinyobjloader. -// Compile (e.g. on Linux) with: -// g++ main.cpp -lglfw -lGLEW -lGL -ldl -limgui -lyaml-cpp -o pbr_renderer -// (Adjust include paths and linker flags as needed.) +// +// A physically based renderer that demonstrates height mapping via parallax, +// improved lighting (point & directional lights), and renders a skybox for background. +// It uses procedural spheres and a plane. Materials (including height maps) can be loaded +// and saved via YAML (using yaml-cpp) and are editable via ImGui. +// +// Compile (on Linux): +// g++ main.cpp -lglfw -lGLEW -lGL -ldl -limgui -lyaml-cpp -o pbr_renderer +// +// Adjust library paths and names as needed. #include #include @@ -20,11 +23,11 @@ #include #include #include -#include +#include // ImGui includes. #include "imgui.h" -#include "imgui_internal.h" // For IM_PI (ImGui's internal constant) +#include "imgui_internal.h" // For IM_PI #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" @@ -35,22 +38,17 @@ // yaml-cpp for YAML material loading/saving. #include -// tinyobjloader for OBJ file loading. -#define TINYOBJLOADER_IMPLEMENTATION -#include "tiny_obj_loader.h" - -// -------------------------------------------------------------------------------------- +// ------------------------------------------ // Shader sources -// -------------------------------------------------------------------------------------- +// ------------------------------------------ -// --- Vertex Shader --- -// Now accepts a tangent attribute (location = 3) so we can compute a TBN matrix. +// Vertex Shader (common to objects) const char* vertexShaderSource = R"( #version 330 core -layout (location = 0) in vec3 aPos; -layout (location = 1) in vec3 aNormal; -layout (location = 2) in vec2 aTexCoords; -layout (location = 3) in vec3 aTangent; +layout (location = 0) in vec3 aPos; // position +layout (location = 1) in vec3 aNormal; // normal +layout (location = 2) in vec2 aTexCoords; // texture coordinates +layout (location = 3) in vec3 aTangent; // tangent out vec3 WorldPos; out vec3 Normal; @@ -64,17 +62,14 @@ uniform mat4 projection; void main(){ WorldPos = vec3(model * vec4(aPos, 1.0)); Normal = mat3(transpose(inverse(model))) * aNormal; - // For tangent, we simply transform with the model matrix. - Tangent = mat3(model) * aTangent; + Tangent = mat3(model) * aTangent; // simple transform (ignore scale issues) TexCoords = aTexCoords; gl_Position = projection * view * vec4(WorldPos, 1.0); } )"; -// --- Fragment Shader --- -// Supports texture maps, normal mapping (via TBN), and uses ImGui's IM_PI constant. -const char* fragmentShaderSource = R"( -#version 330 core +// Fragment Shader with parallax (height) mapping and two light types. +const char* fragmentShaderSource = R"(#version 330 core out vec4 FragColor; in vec3 WorldPos; @@ -84,43 +79,60 @@ in vec3 Tangent; uniform vec3 camPos; -// Base material parameters. +// Base material uniforms. uniform vec3 albedo; uniform float metallic; uniform float roughness; uniform float ao; -// Texture map usage flags and samplers. +// Texture samplers & usage flags. uniform bool useAlbedoMap; uniform bool useMetallicMap; uniform bool useRoughnessMap; uniform bool useAOMap; uniform bool useNormalMap; +uniform bool useHeightMap; // Height map flag uniform sampler2D albedoMap; uniform sampler2D metallicMap; uniform sampler2D roughnessMap; uniform sampler2D aoMap; uniform sampler2D normalMap; +uniform sampler2D heightMap; // Height map sampler + +// Parallax mapping scale factor. +uniform float parallaxScale; // Use ImGui's internal PI constant. uniform float PI; -// Light parameters. -uniform vec3 lightPos; -uniform vec3 lightColor; +// Point light uniforms. +uniform vec3 pointLightPos; +uniform vec3 pointLightColor; +// Directional light uniforms. +uniform bool useDirLight; +uniform vec3 dirLightDir; +uniform vec3 dirLightColor; + +// NEW: Environment reflection uniforms. +uniform samplerCube envMap; +uniform float envReflectionIntensity; + +// +// Helper functions for PBR +// vec3 fresnelSchlick(float cosTheta, vec3 F0) { return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); } float DistributionGGX(vec3 N, vec3 H, float roughness) { - float a = roughness * roughness; - float a2 = a * a; - float NdotH = max(dot(N, H), 0.0); + float a = roughness * roughness; + float a2 = a * a; + float NdotH = max(dot(N, H), 0.0); float NdotH2 = NdotH * NdotH; - float denom = (NdotH2 * (a2 - 1.0) + 1.0); - denom = PI * denom * denom; + float denom = (NdotH2 * (a2 - 1.0) + 1.0); + denom = PI * denom * denom; return a2 / max(denom, 0.001); } @@ -133,114 +145,187 @@ float GeometrySchlickGGX(float NdotV, float roughness) { float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) { float NdotV = max(dot(N, V), 0.0); float NdotL = max(dot(N, L), 0.0); - float ggx1 = GeometrySchlickGGX(NdotV, roughness); - float ggx2 = GeometrySchlickGGX(NdotL, roughness); + float ggx1 = GeometrySchlickGGX(NdotV, roughness); + float ggx2 = GeometrySchlickGGX(NdotL, roughness); return ggx1 * ggx2; } void main(){ - vec3 N = normalize(Normal); - if (useNormalMap) { - vec3 tangentNormal = texture(normalMap, TexCoords).rgb; - tangentNormal = tangentNormal * 2.0 - 1.0; + // Start with the input geometry normal. + vec3 Nn = normalize(Normal); + + // Compute the TBN matrix if using normal or height maps. + mat3 TBN = mat3(1.0); + if (useNormalMap || useHeightMap) { vec3 T = normalize(Tangent); - vec3 B = normalize(cross(N, T)); - mat3 TBN = mat3(T, B, N); - N = normalize(TBN * tangentNormal); + vec3 B = normalize(cross(Normal, T)); + TBN = mat3(T, B, Normal); + } + + // Compute modified texture coordinates (parallax mapping) if height mapping is enabled. + vec2 texCoordsModified = TexCoords; + if (useHeightMap) { + // Transform view direction into tangent space. + vec3 viewDirT = normalize(TBN * (camPos - WorldPos)); + // Sample the height value. + float heightValue = texture(heightMap, TexCoords).r; + // Offset the texture coordinates. + texCoordsModified = TexCoords - viewDirT.xy * (heightValue * parallaxScale); + } + + // Apply normal mapping if enabled. + if (useNormalMap) { + vec3 tangentNormal = texture(normalMap, texCoordsModified).rgb; + tangentNormal = tangentNormal * 2.0 - 1.0; + Nn = normalize(TBN * tangentNormal); } vec3 V = normalize(camPos - WorldPos); + // Sample material textures using modified texture coordinates. vec3 albedoValue = albedo; if (useAlbedoMap) - albedoValue = texture(albedoMap, TexCoords).rgb; + albedoValue = texture(albedoMap, texCoordsModified).rgb; float metallicValue = metallic; if (useMetallicMap) - metallicValue = texture(metallicMap, TexCoords).r; + metallicValue = texture(metallicMap, texCoordsModified).r; float roughnessValue = roughness; if (useRoughnessMap) - roughnessValue = texture(roughnessMap, TexCoords).r; + roughnessValue = texture(roughnessMap, texCoordsModified).r; float aoValue = ao; if (useAOMap) - aoValue = texture(aoMap, TexCoords).r; + aoValue = texture(aoMap, texCoordsModified).r; + // Calculate reflectance at normal incidence. vec3 F0 = vec3(0.04); F0 = mix(F0, albedoValue, metallicValue); - vec3 L = normalize(lightPos - WorldPos); - vec3 H = normalize(V + L); + // --- Compute point light contribution --- + vec3 Lp = normalize(pointLightPos - WorldPos); + vec3 Hp = normalize(V + Lp); + float NDFp = DistributionGGX(Nn, Hp, roughnessValue); + float Gp = GeometrySmith(Nn, V, Lp, roughnessValue); + vec3 Fp = fresnelSchlick(max(dot(Hp, V), 0.0), F0); + vec3 numeratorP = NDFp * Gp * Fp; + float denominatorP = 4.0 * max(dot(Nn, V), 0.0) * max(dot(Nn, Lp), 0.0) + 0.001; + vec3 specularP = numeratorP / denominatorP; - float NDF = DistributionGGX(N, H, roughnessValue); - float G = GeometrySmith(N, V, L, roughnessValue); - vec3 F = fresnelSchlick(max(dot(H, V), 0.0), F0); - - vec3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0) + 0.001; - vec3 specular = numerator / denominator; - - vec3 kS = F; + vec3 kS = Fp; vec3 kD = vec3(1.0) - kS; kD *= 1.0 - metallicValue; + float NdotLp = max(dot(Nn, Lp), 0.0); + vec3 irradianceP = pointLightColor * NdotLp; + vec3 diffuseP = (albedoValue / PI); + vec3 lightingPoint = (kD * diffuseP + specularP) * irradianceP; - float NdotL = max(dot(N, L), 0.0); - vec3 irradiance = lightColor * NdotL; - vec3 diffuse = (albedoValue / PI); + // --- Compute directional light contribution --- + vec3 lightingDir = vec3(0.0); + if (useDirLight) { + vec3 Ld = normalize(-dirLightDir); + vec3 Hd = normalize(V + Ld); + float NDFd = DistributionGGX(Nn, Hd, roughnessValue); + float Gd = GeometrySmith(Nn, V, Ld, roughnessValue); + vec3 Fd = fresnelSchlick(max(dot(Hd, V), 0.0), F0); + vec3 numeratorD = NDFd * Gd * Fd; + float denominatorD = 4.0 * max(dot(Nn, V), 0.0) * max(dot(Nn, Ld), 0.0) + 0.001; + vec3 specularD = numeratorD / denominatorD; + vec3 kS_d = Fd; + vec3 kD_d = vec3(1.0) - kS_d; + kD_d *= 1.0 - metallicValue; + float NdotLd = max(dot(Nn, Ld), 0.0); + vec3 irradianceD = dirLightColor * NdotLd; + vec3 diffuseD = (albedoValue / PI); + lightingDir = (kD_d * diffuseD + specularD) * irradianceD; + } - vec3 color = (kD * diffuse + specular) * irradiance; + // Sum direct lighting contributions. + vec3 lighting = lightingPoint + lightingDir; + + // --- Compute environment reflection contribution --- + // Calculate reflection vector and sample cubemap. + vec3 R = reflect(-V, Nn); + vec3 envSpec = texture(envMap, R).rgb; + lighting += envSpec * envReflectionIntensity; + + // Add a simple ambient term. vec3 ambient = vec3(0.03) * albedoValue * aoValue; - color += ambient; + lighting += ambient; - color = color / (color + vec3(1.0)); - color = pow(color, vec3(1.0/2.2)); + // Tone mapping and gamma correction. + lighting = lighting / (lighting + vec3(1.0)); + lighting = pow(lighting, vec3(1.0/2.2)); - FragColor = vec4(color, 1.0); + FragColor = vec4(lighting, 1.0); +} + +)"; + +// Skybox shaders +const char* skyboxVertexShaderSource = R"( +#version 330 core +layout (location = 0) in vec3 aPos; +out vec3 TexCoords; + +uniform mat4 view; +uniform mat4 projection; +void main(){ + TexCoords = aPos; + vec4 pos = projection * view * vec4(aPos, 1.0); + gl_Position = pos.xyww; // set w component to 1.0 to always pass depth test } )"; -// -------------------------------------------------------------------------------------- -// Helper functions: Shader compilation and linking. -// -------------------------------------------------------------------------------------- -GLuint compileShader(GLenum shaderType, const char* source) { - GLuint shader = glCreateShader(shaderType); +const char* skyboxFragmentShaderSource = R"( +#version 330 core +out vec4 FragColor; +in vec3 TexCoords; +uniform samplerCube skybox; +void main(){ + FragColor = texture(skybox, TexCoords); +} +)"; + +// ------------------------------------------ +// Helper functions +// ------------------------------------------ +GLuint compileShader(GLenum type, const char* source) { + GLuint shader = glCreateShader(type); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); GLint success; glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success){ + if(!success) { char infoLog[512]; - glGetShaderInfoLog(shader, 512, nullptr, infoLog); - std::cerr << "Shader compilation failed:\n" << infoLog << std::endl; + glGetShaderInfoLog(shader,512,nullptr,infoLog); + std::cerr << "Shader compile error:\n" << infoLog << std::endl; } return shader; } -GLuint createProgram(GLuint vertexShader, GLuint fragmentShader) { +GLuint createProgram(GLuint vs, GLuint fs) { GLuint program = glCreateProgram(); - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); + glAttachShader(program, vs); + glAttachShader(program, fs); glLinkProgram(program); GLint success; glGetProgramiv(program, GL_LINK_STATUS, &success); - if (!success){ + if(!success) { char infoLog[512]; - glGetProgramInfoLog(program, 512, nullptr, infoLog); - std::cerr << "Program linking failed:\n" << infoLog << std::endl; + glGetProgramInfoLog(program,512,nullptr,infoLog); + std::cerr << "Program link error:\n" << infoLog << std::endl; } return program; } -// -------------------------------------------------------------------------------------- -// Texture loading helper using stb_image. -// -------------------------------------------------------------------------------------- GLuint LoadTexture(const char* path) { int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0); - if (!data) { - std::cout << "Failed to load texture: " << path << std::endl; + if(!data) { + std::cerr << "Failed to load texture: " << path << std::endl; return 0; } GLenum format; @@ -250,23 +335,123 @@ GLuint LoadTexture(const char* path) { format = GL_RGB; else if(nrChannels == 4) format = GL_RGBA; - GLuint textureID; - glGenTextures(1, &textureID); - glBindTexture(GL_TEXTURE_2D, textureID); + GLuint texID; + glGenTextures(1, &texID); + glBindTexture(GL_TEXTURE_2D, texID); glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); - // Texture parameters. + // Set texture parameters. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); stbi_image_free(data); + return texID; +} + +// Load a cubemap (skybox) from 6 images. Expect faces in order: right, left, top, bottom, front, back. +GLuint loadCubemap(std::vector faces) { + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + int width, height, nrChannels; + for(unsigned int i=0; i& vertices, std::vector& indices, unsigned int X_SEGMENTS = 64, unsigned int Y_SEGMENTS = 64) { + vertices.clear(); + indices.clear(); + for (unsigned int y = 0; y <= Y_SEGMENTS; ++y) { + for (unsigned int x = 0; x <= X_SEGMENTS; ++x) { + float xSegment = (float)x / (float)X_SEGMENTS; + float ySegment = (float)y / (float)Y_SEGMENTS; + float xPos = std::cos(xSegment * 2.0f * IM_PI) * std::sin(ySegment * IM_PI); + float yPos = std::cos(ySegment * IM_PI); + float zPos = std::sin(xSegment * 2.0f * IM_PI) * std::sin(ySegment * IM_PI); + // Position. + vertices.push_back(xPos); + vertices.push_back(yPos); + vertices.push_back(zPos); + // Normal (for a unit sphere, same as position). + vertices.push_back(xPos); + vertices.push_back(yPos); + vertices.push_back(zPos); + // Texcoords. + vertices.push_back(xSegment); + vertices.push_back(ySegment); + // Tangent: approximate (derivative with respect to u). + float tangentX = -std::sin(xSegment * 2.0f * IM_PI); + float tangentY = 0.0f; + float tangentZ = std::cos(xSegment * 2.0f * IM_PI); + vertices.push_back(tangentX); + vertices.push_back(tangentY); + vertices.push_back(tangentZ); + } + } + for (unsigned int y = 0; y < Y_SEGMENTS; ++y) { + for (unsigned int x = 0; x < X_SEGMENTS; ++x) { + unsigned int i0 = y * (X_SEGMENTS + 1) + x; + unsigned int i1 = i0 + 1; + unsigned int i2 = i0 + (X_SEGMENTS + 1); + unsigned int i3 = i2 + 1; + indices.push_back(i0); + indices.push_back(i2); + indices.push_back(i1); + indices.push_back(i1); + indices.push_back(i2); + indices.push_back(i3); + } + } +} + +// Generate a plane mesh (quad) in the XZ plane centered at origin. +// The plane spans [-1,1] in X and Z, y=0. +void generatePlaneMesh(std::vector& vertices, std::vector& indices) { + // 4 vertices: position (3), normal (3), texcoords (2), tangent (3) = 11 floats. + float planeVerts[] = { + // positions // normals // texcoords // tangent + -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + }; + unsigned int planeIndices[] = { + 0, 1, 2, + 0, 2, 3 + }; + vertices.assign(planeVerts, planeVerts + sizeof(planeVerts) / sizeof(float)); + indices.assign(planeIndices, planeIndices + sizeof(planeIndices)/sizeof(unsigned int)); +} + +// ------------------------------------------ +// Material and Object Structures +// ------------------------------------------ +// Extend Material to include height map. struct Material { glm::vec3 albedo; float metallic; @@ -279,6 +464,7 @@ struct Material { GLuint roughnessTex; GLuint aoTex; GLuint normalTex; + GLuint heightTex; // new height map texture. // Flags indicating texture usage. bool useAlbedoMap; @@ -286,57 +472,45 @@ struct Material { bool useRoughnessMap; bool useAOMap; bool useNormalMap; + bool useHeightMap; // flag for height map - // File path buffers for manual texture loading via ImGui. + // File path buffers. char albedoPath[256]; char metallicPath[256]; char roughnessPath[256]; char aoPath[256]; char normalPath[256]; + char heightPath[256]; // file path for height map. Material() : albedo(0.5f, 0.0f, 0.0f), metallic(0.0f), roughness(0.5f), ao(1.0f), - albedoTex(0), metallicTex(0), roughnessTex(0), aoTex(0), normalTex(0), + albedoTex(0), metallicTex(0), roughnessTex(0), aoTex(0), normalTex(0), heightTex(0), useAlbedoMap(false), useMetallicMap(false), useRoughnessMap(false), - useAOMap(false), useNormalMap(false) + useAOMap(false), useNormalMap(false), useHeightMap(false) { strcpy(albedoPath, ""); strcpy(metallicPath, ""); strcpy(roughnessPath, ""); strcpy(aoPath, ""); strcpy(normalPath, ""); + strcpy(heightPath, ""); } }; struct SphereInstance { glm::vec3 position; - float rotation; // In radians. + float rotation; // in radians. Material material; }; -// For OBJ models, we define a Vertex structure. -struct Vertex { +struct PlaneInstance { glm::vec3 position; - glm::vec3 normal; - glm::vec2 texcoord; - glm::vec3 tangent; -}; - -struct Model { - GLuint VAO; - GLuint VBO; - GLuint EBO; - unsigned int indexCount; - // Transformation. - glm::vec3 position; - float rotation; // Around Y axis. + float rotation; // around Y axis. Material material; - // Filename of the loaded model (for reference). - std::string filename; }; -// -------------------------------------------------------------------------------------- -// YAML Material Loading & Saving -// -------------------------------------------------------------------------------------- +// ------------------------------------------ +// YAML Material Loading and Saving +// ------------------------------------------ Material loadMaterialFromYAML(const std::string& filename) { Material mat; try { @@ -357,49 +531,40 @@ Material loadMaterialFromYAML(const std::string& filename) { std::string path = config["albedo_texture"].as(); strncpy(mat.albedoPath, path.c_str(), sizeof(mat.albedoPath)); GLuint tex = LoadTexture(mat.albedoPath); - if (tex != 0) { - mat.albedoTex = tex; - mat.useAlbedoMap = true; - } + if (tex != 0) { mat.albedoTex = tex; mat.useAlbedoMap = true; } } if (config["metallic_texture"]) { std::string path = config["metallic_texture"].as(); strncpy(mat.metallicPath, path.c_str(), sizeof(mat.metallicPath)); GLuint tex = LoadTexture(mat.metallicPath); - if (tex != 0) { - mat.metallicTex = tex; - mat.useMetallicMap = true; - } + if (tex != 0) { mat.metallicTex = tex; mat.useMetallicMap = true; } } if (config["roughness_texture"]) { std::string path = config["roughness_texture"].as(); strncpy(mat.roughnessPath, path.c_str(), sizeof(mat.roughnessPath)); GLuint tex = LoadTexture(mat.roughnessPath); - if (tex != 0) { - mat.roughnessTex = tex; - mat.useRoughnessMap = true; - } + if (tex != 0) { mat.roughnessTex = tex; mat.useRoughnessMap = true; } } if (config["ao_texture"]) { std::string path = config["ao_texture"].as(); strncpy(mat.aoPath, path.c_str(), sizeof(mat.aoPath)); GLuint tex = LoadTexture(mat.aoPath); - if (tex != 0) { - mat.aoTex = tex; - mat.useAOMap = true; - } + if (tex != 0) { mat.aoTex = tex; mat.useAOMap = true; } } if (config["normal_texture"]) { std::string path = config["normal_texture"].as(); strncpy(mat.normalPath, path.c_str(), sizeof(mat.normalPath)); GLuint tex = LoadTexture(mat.normalPath); - if (tex != 0) { - mat.normalTex = tex; - mat.useNormalMap = true; - } + if (tex != 0) { mat.normalTex = tex; mat.useNormalMap = true; } + } + if (config["height_texture"]) { + std::string path = config["height_texture"].as(); + strncpy(mat.heightPath, path.c_str(), sizeof(mat.heightPath)); + GLuint tex = LoadTexture(mat.heightPath); + if (tex != 0) { mat.heightTex = tex; mat.useHeightMap = true; } } } catch(const std::exception& e) { - std::cerr << "Error loading YAML material from file " << filename << ": " << e.what() << std::endl; + std::cerr << "Error loading YAML material from " << filename << ": " << e.what() << std::endl; } return mat; } @@ -421,11 +586,13 @@ bool saveMaterialToYAML(const std::string& filename, const Material& mat) { out << YAML::Key << "ao_texture" << YAML::Value << std::string(mat.aoPath); if (mat.useNormalMap) out << YAML::Key << "normal_texture" << YAML::Value << std::string(mat.normalPath); + if (mat.useHeightMap) + out << YAML::Key << "height_texture" << YAML::Value << std::string(mat.heightPath); out << YAML::EndMap; std::ofstream fout(filename); - if (!fout.is_open()){ - std::cerr << "Failed to open file for saving material: " << filename << std::endl; + if(!fout.is_open()){ + std::cerr << "Failed to open " << filename << " for saving material." << std::endl; return false; } fout << out.c_str(); @@ -433,158 +600,65 @@ bool saveMaterialToYAML(const std::string& filename, const Material& mat) { return true; } -// -------------------------------------------------------------------------------------- -// OBJ Model Loading using tinyobjloader. -// -------------------------------------------------------------------------------------- +// ------------------------------------------ +// Skybox geometry +// ------------------------------------------ +// Cube vertices for the skybox. +float skyboxVertices[] = { + // positions + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f +}; + + + + + + -// We define a Vertex structure (see above). This function loads an OBJ file -// and outputs vertices and indices in the expected format (11 floats per vertex: -// position[3], normal[3], texcoord[2], tangent[3]). Tangents are computed after loading. -bool loadOBJModel(const std::string& filename, std::vector& outVertices, std::vector& outIndices) { - tinyobj::attrib_t attrib; - std::vector shapes; - std::vector objMaterials; - std::string warn, err; - std::cout << "tinyobjloader Loading: " << filename << std::endl; - - bool ret = tinyobj::LoadObj(&attrib, &shapes, &objMaterials, &warn, &err, filename.c_str()); - if (!warn.empty()) - std::cout << "tinyobjloader warning: " << warn << std::endl; - if (!err.empty()) { - std::cerr << "tinyobjloader error: " << err << std::endl; - return false; - } - if (!ret) { - std::cerr << "Failed to load/parse OBJ file: " << filename << std::endl; - return false; - } - - // For simplicity, we push every vertex (no deduplication). - for (size_t s = 0; s < shapes.size(); s++) { - for (size_t f = 0; f < shapes[s].mesh.indices.size(); f++) { - tinyobj::index_t idx = shapes[s].mesh.indices[f]; - Vertex vertex; - vertex.position = glm::vec3( - attrib.vertices[3 * idx.vertex_index + 0], - attrib.vertices[3 * idx.vertex_index + 1], - attrib.vertices[3 * idx.vertex_index + 2] - ); - if (idx.normal_index >= 0) { - vertex.normal = glm::vec3( - attrib.normals[3 * idx.normal_index + 0], - attrib.normals[3 * idx.normal_index + 1], - attrib.normals[3 * idx.normal_index + 2] - ); - } else { - vertex.normal = glm::vec3(0.0f, 1.0f, 0.0f); - } - if (idx.texcoord_index >= 0) { - vertex.texcoord = glm::vec2( - attrib.texcoords[2 * idx.texcoord_index + 0], - attrib.texcoords[2 * idx.texcoord_index + 1] - ); - } else { - vertex.texcoord = glm::vec2(0.0f, 0.0f); - } - // Initialize tangent to zero; we will compute later. - vertex.tangent = glm::vec3(0.0f); - outVertices.push_back(vertex); - outIndices.push_back(static_cast(outVertices.size() - 1)); - } - } - - // Compute tangents per triangle. - std::vector tanAccum(outVertices.size(), glm::vec3(0.0f)); - for (size_t i = 0; i < outIndices.size(); i += 3) { - unsigned int i0 = outIndices[i + 0]; - unsigned int i1 = outIndices[i + 1]; - unsigned int i2 = outIndices[i + 2]; - - const glm::vec3& p0 = outVertices[i0].position; - const glm::vec3& p1 = outVertices[i1].position; - const glm::vec3& p2 = outVertices[i2].position; - - const glm::vec2& uv0 = outVertices[i0].texcoord; - const glm::vec2& uv1 = outVertices[i1].texcoord; - const glm::vec2& uv2 = outVertices[i2].texcoord; - - glm::vec3 deltaPos1 = p1 - p0; - glm::vec3 deltaPos2 = p2 - p0; - glm::vec2 deltaUV1 = uv1 - uv0; - glm::vec2 deltaUV2 = uv2 - uv0; - float r = 1.0f; - float denom = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); - if (fabs(denom) > 1e-6f) - r = 1.0f / denom; - glm::vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; - - tanAccum[i0] += tangent; - tanAccum[i1] += tangent; - tanAccum[i2] += tangent; - } - // Normalize and store tangents. - for (size_t i = 0; i < outVertices.size(); i++) { - outVertices[i].tangent = glm::normalize(tanAccum[i]); - } - - return true; -} -// Create a Model from an OBJ file by loading its data and generating OpenGL buffers. -Model loadModelFromOBJ(const std::string& filename) { - Model model; - model.filename = filename; - std::vector vertices; - std::vector indices; - if (!loadOBJModel(filename, vertices, indices)) { - std::cerr << "Failed to load model: " << filename << std::endl; - model.indexCount = 0; - return model; - } - - model.indexCount = indices.size(); - // Generate buffers. - glGenVertexArrays(1, &model.VAO); - glGenBuffers(1, &model.VBO); - glGenBuffers(1, &model.EBO); - - glBindVertexArray(model.VAO); - glBindBuffer(GL_ARRAY_BUFFER, model.VBO); - glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, model.EBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); - - // Vertex attributes: position (location 0), normal (1), texcoord (2), tangent (3). - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position)); - glEnableVertexAttribArray(0); - - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal)); - glEnableVertexAttribArray(1); - - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texcoord)); - glEnableVertexAttribArray(2); - - glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tangent)); - glEnableVertexAttribArray(3); - - glBindVertexArray(0); - - // Set default transformation. - model.position = glm::vec3(0.0f); - model.rotation = 0.0f; - // Default material. - model.material = Material(); - - return model; -} -// -------------------------------------------------------------------------------------- -// Main -// -------------------------------------------------------------------------------------- int main(){ - // Initialize GLFW. - if (!glfwInit()){ + if(!glfwInit()){ std::cerr << "Failed to initialize GLFW!" << std::endl; return -1; } @@ -592,106 +666,57 @@ int main(){ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - GLFWwindow* window = glfwCreateWindow(800, 600, "PBR Renderer with OBJ Models", nullptr, nullptr); - if (!window){ + GLFWwindow* window = glfwCreateWindow(800, 600, "PBR Renderer with Height Maps, Improved Lighting & Skybox", nullptr, nullptr); + if(!window){ std::cerr << "Failed to create GLFW window!" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); - // Initialize GLEW. glewExperimental = GL_TRUE; - if (glewInit() != GLEW_OK){ + if(glewInit() != GLEW_OK){ std::cerr << "Failed to initialize GLEW!" << std::endl; return -1; } glEnable(GL_DEPTH_TEST); - // ---------------------------------------------------------------------------------- - // Setup ImGui context. - // ---------------------------------------------------------------------------------- + // Setup ImGui. IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 330"); + + // Compile main shaders. + GLuint vs = compileShader(GL_VERTEX_SHADER, vertexShaderSource); + GLuint fs = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); + GLuint shaderProgram = createProgram(vs, fs); + glDeleteShader(vs); + glDeleteShader(fs); - // ---------------------------------------------------------------------------------- - // Compile shaders and create shader program. - // ---------------------------------------------------------------------------------- - GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource); - GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); - GLuint shaderProgram = createProgram(vertexShader, fragmentShader); - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); + // Compile skybox shaders. + GLuint skybox_vs = compileShader(GL_VERTEX_SHADER, skyboxVertexShaderSource); + GLuint skybox_fs = compileShader(GL_FRAGMENT_SHADER, skyboxFragmentShaderSource); + GLuint skyboxShader = createProgram(skybox_vs, skybox_fs); + glDeleteShader(skybox_vs); + glDeleteShader(skybox_fs); - // ---------------------------------------------------------------------------------- - // Generate the sphere mesh. - // ---------------------------------------------------------------------------------- + // Generate sphere mesh. std::vector sphereVertices; std::vector sphereIndices; - // Reuse our earlier sphere generator that outputs: pos (3), normal (3), texcoord (2), tangent (3). - auto generateSphere = [&](std::vector& vertices, std::vector& indices, unsigned int X_SEGMENTS = 64, unsigned int Y_SEGMENTS = 64) { - vertices.clear(); - indices.clear(); - for (unsigned int y = 0; y <= Y_SEGMENTS; ++y) { - for (unsigned int x = 0; x <= X_SEGMENTS; ++x) { - float xSegment = static_cast(x) / X_SEGMENTS; - float ySegment = static_cast(y) / Y_SEGMENTS; - float xPos = std::cos(xSegment * 2.0f * IM_PI) * std::sin(ySegment * IM_PI); - float yPos = std::cos(ySegment * IM_PI); - float zPos = std::sin(xSegment * 2.0f * IM_PI) * std::sin(ySegment * IM_PI); - // Position. - vertices.push_back(xPos); - vertices.push_back(yPos); - vertices.push_back(zPos); - // Normal. - vertices.push_back(xPos); - vertices.push_back(yPos); - vertices.push_back(zPos); - // Texcoords. - vertices.push_back(xSegment); - vertices.push_back(ySegment); - // Tangent (simple approximation). - float tangentX = -std::sin(xSegment * 2.0f * IM_PI); - float tangentY = 0.0f; - float tangentZ = std::cos(xSegment * 2.0f * IM_PI); - vertices.push_back(tangentX); - vertices.push_back(tangentY); - vertices.push_back(tangentZ); - } - } - for (unsigned int y = 0; y < Y_SEGMENTS; ++y) { - for (unsigned int x = 0; x < X_SEGMENTS; ++x) { - unsigned int i0 = y * (X_SEGMENTS + 1) + x; - unsigned int i1 = i0 + 1; - unsigned int i2 = i0 + (X_SEGMENTS + 1); - unsigned int i3 = i2 + 1; - indices.push_back(i0); - indices.push_back(i2); - indices.push_back(i1); - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - } - } - }; - generateSphere(sphereVertices, sphereIndices, 64, 64); - unsigned int sphereIndexCount = sphereIndices.size(); - + generateSphereMesh(sphereVertices, sphereIndices); GLuint sphereVAO, sphereVBO, sphereEBO; glGenVertexArrays(1, &sphereVAO); glGenBuffers(1, &sphereVBO); glGenBuffers(1, &sphereEBO); - glBindVertexArray(sphereVAO); glBindBuffer(GL_ARRAY_BUFFER, sphereVBO); glBufferData(GL_ARRAY_BUFFER, sphereVertices.size() * sizeof(float), sphereVertices.data(), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sphereEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphereIndices.size() * sizeof(unsigned int), sphereIndices.data(), GL_STATIC_DRAW); - // Layout: pos (3), normal (3), texcoords (2), tangent (3) + // Attributes: pos (3), normal (3), texcoords (2), tangent (3) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(3 * sizeof(float))); @@ -702,308 +727,321 @@ int main(){ glEnableVertexAttribArray(3); glBindVertexArray(0); - // ---------------------------------------------------------------------------------- - // Prepare scene data. - // ---------------------------------------------------------------------------------- + // Generate plane mesh. + std::vector planeVertices; + std::vector planeIndices; + generatePlaneMesh(planeVertices, planeIndices); + GLuint planeVAO, planeVBO, planeEBO; + glGenVertexArrays(1, &planeVAO); + glGenBuffers(1, &planeVBO); + glGenBuffers(1, &planeEBO); + glBindVertexArray(planeVAO); + glBindBuffer(GL_ARRAY_BUFFER, planeVBO); + glBufferData(GL_ARRAY_BUFFER, planeVertices.size() * sizeof(float), planeVertices.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, planeEBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, planeIndices.size() * sizeof(unsigned int), planeIndices.data(), GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(6 * sizeof(float))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void*)(8 * sizeof(float))); + glEnableVertexAttribArray(3); + glBindVertexArray(0); + + // Set up skybox VAO & VBO. + GLuint skyboxVAO, skyboxVBO; + glGenVertexArrays(1, &skyboxVAO); + glGenBuffers(1, &skyboxVBO); + glBindVertexArray(skyboxVAO); + glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glBindVertexArray(0); + + // Load a default cubemap for the skybox. + std::vector faces{ + "skybox/right.jpg", + "skybox/left.jpg", + "skybox/top.jpg", + "skybox/bottom.jpg", + "skybox/front.jpg", + "skybox/back.jpg" + }; + GLuint cubemapTexture = loadCubemap(faces); + + // ------------------------------------------ + // Scene Data + // ------------------------------------------ std::vector spheres; - spheres.push_back({ glm::vec3(0.0f), 0.0f, Material() }); + spheres.push_back({ glm::vec3(0.0f, 0.0f, 0.0f), 0.0f, Material() }); + std::vector planes; + planes.push_back({ glm::vec3(0.0f, -1.0f, 0.0f), 0.0f, Material() }); - std::vector models; // For OBJ models. + // Global lighting parameters. + glm::vec3 camPosVec(0.0f, 1.0f, 5.0f); + glm::vec3 pointLightPos(0.0f, 3.0f, 3.0f); + glm::vec3 pointLightColor(300.0f, 300.0f, 300.0f); + // Directional light. + bool useDirLight = true; + glm::vec3 dirLightDir(-0.2f, -1.0f, -0.3f); + glm::vec3 dirLightColor(0.8f, 0.8f, 0.8f); - // Global scene/light parameters. - glm::vec3 camPos(0.0f, 0.0f, 5.0f); - glm::vec3 lightPos(0.0f, 0.0f, 10.0f); - glm::vec3 lightColor(300.0f, 300.0f, 300.0f); + // Toggle for which object to render: 0 = sphere, 1 = plane. + int activeObject = 0; - // For YAML loading and OBJ model loading via ImGui. + // For material YAML loading/saving. static char yamlPathBuffer[256] = ""; - static char objPathBuffer[256] = ""; - // ---------------------------------------------------------------------------------- - // Main render loop. - // ---------------------------------------------------------------------------------- - while (!glfwWindowShouldClose(window)) { + // Declare a global parallax scale variable so it is available later. + static float globalParallaxScale = 0.05f; + + // Main Loop. + while(!glfwWindowShouldClose(window)){ glfwPollEvents(); - - // Start new ImGui frame. ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - // ------------------------ Global Scene Controls ------------------------ + // ---------------------- ImGui UI ----------------------- { ImGui::Begin("Scene Controls"); - - // Global light controls. - ImGui::Text("Light Controls"); - ImGui::DragFloat3("Light Position", glm::value_ptr(lightPos), 0.1f); - ImGui::ColorEdit3("Light Color", glm::value_ptr(lightColor)); + ImGui::Text("Global Lighting"); + ImGui::DragFloat3("Camera Pos", glm::value_ptr(camPosVec), 0.1f); + ImGui::DragFloat3("Point Light Pos", glm::value_ptr(pointLightPos), 0.1f); + ImGui::ColorEdit3("Point Light Color", glm::value_ptr(pointLightColor)); + ImGui::Checkbox("Use Directional Light", &useDirLight); + ImGui::DragFloat3("Dir Light Dir", glm::value_ptr(dirLightDir), 0.1f); + ImGui::ColorEdit3("Dir Light Color", glm::value_ptr(dirLightColor)); ImGui::Separator(); - - // Sphere controls. - if (ImGui::CollapsingHeader("Spheres")) { - if (ImGui::Button("Add Sphere")) { - spheres.push_back({ glm::vec3(0.0f), 0.0f, Material() }); + ImGui::RadioButton("Render Sphere", &activeObject, 0); + ImGui::RadioButton("Render Plane", &activeObject, 1); + ImGui::Separator(); + // Material controls for the active object. + Material* mat = nullptr; + if(activeObject == 0 && !spheres.empty()) + mat = &spheres[0].material; + else if(activeObject == 1 && !planes.empty()) + mat = &planes[0].material; + if(mat){ + ImGui::Text("Material Properties:"); + ImGui::ColorEdit3("Albedo", glm::value_ptr(mat->albedo)); + ImGui::SliderFloat("Metallic", &mat->metallic, 0.0f, 1.0f); + ImGui::SliderFloat("Roughness", &mat->roughness, 0.05f, 1.0f); + ImGui::SliderFloat("AO", &mat->ao, 0.0f, 1.0f); + ImGui::SliderFloat("Parallax Scale", &globalParallaxScale, 0.0f, 0.2f); + ImGui::InputText("Albedo Texture Path", mat->albedoPath, sizeof(mat->albedoPath)); + if(ImGui::Button("Load Albedo Texture")){ + GLuint tex = LoadTexture(mat->albedoPath); + if(tex != 0){ mat->albedoTex = tex; mat->useAlbedoMap = true; } } - ImGui::Separator(); - for (size_t i = 0; i < spheres.size(); i++) { - std::string header = "Sphere " + std::to_string(i); - if (ImGui::CollapsingHeader(header.c_str())) { - ImGui::DragFloat3(("Position##S" + std::to_string(i)).c_str(), glm::value_ptr(spheres[i].position), 0.1f); - ImGui::DragFloat(("Rotation (radians)##S" + std::to_string(i)).c_str(), &spheres[i].rotation, 0.01f); - ImGui::ColorEdit3(("Albedo##S" + std::to_string(i)).c_str(), glm::value_ptr(spheres[i].material.albedo)); - ImGui::SliderFloat(("Metallic##S" + std::to_string(i)).c_str(), &spheres[i].material.metallic, 0.0f, 1.0f); - ImGui::SliderFloat(("Roughness##S" + std::to_string(i)).c_str(), &spheres[i].material.roughness, 0.05f, 1.0f); - ImGui::SliderFloat(("AO##S" + std::to_string(i)).c_str(), &spheres[i].material.ao, 0.0f, 1.0f); - - // Texture controls... - ImGui::InputText(("Albedo Texture Path##S" + std::to_string(i)).c_str(), - spheres[i].material.albedoPath, sizeof(spheres[i].material.albedoPath)); - if (ImGui::Button(("Load Albedo Texture##S" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(spheres[i].material.albedoPath); - if (tex != 0) { - spheres[i].material.albedoTex = tex; - spheres[i].material.useAlbedoMap = true; - } - } - // Similar input/buttons for Metallic, Roughness, AO, and Normal... - ImGui::InputText(("Metallic Texture Path##S" + std::to_string(i)).c_str(), - spheres[i].material.metallicPath, sizeof(spheres[i].material.metallicPath)); - if (ImGui::Button(("Load Metallic Texture##S" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(spheres[i].material.metallicPath); - if (tex != 0) { - spheres[i].material.metallicTex = tex; - spheres[i].material.useMetallicMap = true; - } - } - ImGui::InputText(("Roughness Texture Path##S" + std::to_string(i)).c_str(), - spheres[i].material.roughnessPath, sizeof(spheres[i].material.roughnessPath)); - if (ImGui::Button(("Load Roughness Texture##S" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(spheres[i].material.roughnessPath); - if (tex != 0) { - spheres[i].material.roughnessTex = tex; - spheres[i].material.useRoughnessMap = true; - } - } - ImGui::InputText(("AO Texture Path##S" + std::to_string(i)).c_str(), - spheres[i].material.aoPath, sizeof(spheres[i].material.aoPath)); - if (ImGui::Button(("Load AO Texture##S" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(spheres[i].material.aoPath); - if (tex != 0) { - spheres[i].material.aoTex = tex; - spheres[i].material.useAOMap = true; - } - } - ImGui::InputText(("Normal Texture Path##S" + std::to_string(i)).c_str(), - spheres[i].material.normalPath, sizeof(spheres[i].material.normalPath)); - if (ImGui::Button(("Load Normal Texture##S" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(spheres[i].material.normalPath); - if (tex != 0) { - spheres[i].material.normalTex = tex; - spheres[i].material.useNormalMap = true; - } - } - static char sphereYAMLPath[256] = ""; - ImGui::InputText(("YAML Material Path##S" + std::to_string(i)).c_str(), sphereYAMLPath, sizeof(sphereYAMLPath)); - if (ImGui::Button(("Load Material from YAML##S" + std::to_string(i)).c_str())) { - Material newMat = loadMaterialFromYAML(sphereYAMLPath); - spheres[i].material = newMat; - } - static char saveYAMLPath[256] = ""; - ImGui::InputText(("Save YAML Path##S" + std::to_string(i)).c_str(), saveYAMLPath, sizeof(saveYAMLPath)); - if (ImGui::Button(("Save Material to YAML##S" + std::to_string(i)).c_str())) { - if (saveMaterialToYAML(std::string(saveYAMLPath), spheres[i].material)) { - std::cout << "Saved material for sphere " << i << " to " << saveYAMLPath << std::endl; - } - } - if (ImGui::Button(("Remove Sphere##S" + std::to_string(i)).c_str())) { - spheres.erase(spheres.begin() + i); - break; - } - } + ImGui::InputText("Metallic Texture Path", mat->metallicPath, sizeof(mat->metallicPath)); + if(ImGui::Button("Load Metallic Texture")){ + GLuint tex = LoadTexture(mat->metallicPath); + if(tex != 0){ mat->metallicTex = tex; mat->useMetallicMap = true; } + } + ImGui::InputText("Roughness Texture Path", mat->roughnessPath, sizeof(mat->roughnessPath)); + if(ImGui::Button("Load Roughness Texture")){ + GLuint tex = LoadTexture(mat->roughnessPath); + if(tex != 0){ mat->roughnessTex = tex; mat->useRoughnessMap = true; } + } + ImGui::InputText("AO Texture Path", mat->aoPath, sizeof(mat->aoPath)); + if(ImGui::Button("Load AO Texture")){ + GLuint tex = LoadTexture(mat->aoPath); + if(tex != 0){ mat->aoTex = tex; mat->useAOMap = true; } + } + ImGui::InputText("Normal Texture Path", mat->normalPath, sizeof(mat->normalPath)); + if(ImGui::Button("Load Normal Texture")){ + GLuint tex = LoadTexture(mat->normalPath); + if(tex != 0){ mat->normalTex = tex; mat->useNormalMap = true; } + } + ImGui::InputText("Height Texture Path", mat->heightPath, sizeof(mat->heightPath)); + if(ImGui::Button("Load Height Texture")){ + GLuint tex = LoadTexture(mat->heightPath); + if(tex != 0){ mat->heightTex = tex; mat->useHeightMap = true; } + } + ImGui::InputText("YAML Material Path", yamlPathBuffer, sizeof(yamlPathBuffer)); + if(ImGui::Button("Load Material from YAML")){ + Material m = loadMaterialFromYAML(yamlPathBuffer); + *mat = m; + } + if(ImGui::Button("Save Material to YAML")){ + if(saveMaterialToYAML(std::string(yamlPathBuffer), *mat)) + std::cout << "Material saved to " << yamlPathBuffer << std::endl; } } - - // Model (OBJ) controls. - if (ImGui::CollapsingHeader("3D Models (OBJ)")) { - ImGui::InputText("OBJ Model Path", objPathBuffer, sizeof(objPathBuffer)); - if (ImGui::Button("Load OBJ Model")) { - Model m = loadModelFromOBJ(std::string(objPathBuffer)); - if(m.indexCount > 0) { - models.push_back(m); - } - } - ImGui::Separator(); - for (size_t i = 0; i < models.size(); i++) { - std::string header = "Model " + std::to_string(i) + " [" + models[i].filename + "]"; - if (ImGui::CollapsingHeader(header.c_str())) { - ImGui::DragFloat3(("Position##M" + std::to_string(i)).c_str(), glm::value_ptr(models[i].position), 0.1f); - ImGui::DragFloat(("Rotation (radians)##M" + std::to_string(i)).c_str(), &models[i].rotation, 0.01f); - ImGui::ColorEdit3(("Albedo##M" + std::to_string(i)).c_str(), glm::value_ptr(models[i].material.albedo)); - ImGui::SliderFloat(("Metallic##M" + std::to_string(i)).c_str(), &models[i].material.metallic, 0.0f, 1.0f); - ImGui::SliderFloat(("Roughness##M" + std::to_string(i)).c_str(), &models[i].material.roughness, 0.05f, 1.0f); - ImGui::SliderFloat(("AO##M" + std::to_string(i)).c_str(), &models[i].material.ao, 0.0f, 1.0f); - // Similar texture controls as for spheres. - ImGui::InputText(("Albedo Texture Path##M" + std::to_string(i)).c_str(), - models[i].material.albedoPath, sizeof(models[i].material.albedoPath)); - if (ImGui::Button(("Load Albedo Texture##M" + std::to_string(i)).c_str())) { - GLuint tex = LoadTexture(models[i].material.albedoPath); - if (tex != 0) { - models[i].material.albedoTex = tex; - models[i].material.useAlbedoMap = true; - } - } - // ... (Repeat for other maps: Metallic, Roughness, AO, Normal) - if (ImGui::Button(("Remove Model##M" + std::to_string(i)).c_str())) { - // Delete GL buffers. - glDeleteVertexArrays(1, &models[i].VAO); - glDeleteBuffers(1, &models[i].VBO); - glDeleteBuffers(1, &models[i].EBO); - models.erase(models.begin() + i); - break; - } - } - } - } - + ImGui::Separator(); + ImGui::Text("Skybox: Using default paths in code."); ImGui::End(); } // ---------------------- Rendering ---------------------- - int width, height; - glfwGetFramebufferSize(window, &width, &height); - float aspect = width / static_cast(height); - glViewport(0, 0, width, height); + int scrWidth, scrHeight; + glfwGetFramebufferSize(window, &scrWidth, &scrHeight); + float aspect = scrWidth / static_cast(scrHeight); + glViewport(0, 0, scrWidth, scrHeight); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f); - glm::mat4 view = glm::lookAt(camPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0)); + glm::mat4 view = glm::lookAt(camPosVec, glm::vec3(0.0f,0.0f,0.0f), glm::vec3(0,1,0)); + // Render scene objects using the main shader. glUseProgram(shaderProgram); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, glm::value_ptr(view)); - glUniform3fv(glGetUniformLocation(shaderProgram, "camPos"), 1, glm::value_ptr(camPos)); - glUniform3fv(glGetUniformLocation(shaderProgram, "lightPos"), 1, glm::value_ptr(lightPos)); - glUniform3fv(glGetUniformLocation(shaderProgram, "lightColor"), 1, glm::value_ptr(lightColor)); + glUniform3fv(glGetUniformLocation(shaderProgram, "camPos"), 1, glm::value_ptr(camPosVec)); + glUniform3fv(glGetUniformLocation(shaderProgram, "pointLightPos"), 1, glm::value_ptr(pointLightPos)); + glUniform3fv(glGetUniformLocation(shaderProgram, "pointLightColor"), 1, glm::value_ptr(pointLightColor)); + glUniform1i(glGetUniformLocation(shaderProgram, "useDirLight"), useDirLight); + glUniform3fv(glGetUniformLocation(shaderProgram, "dirLightDir"), 1, glm::value_ptr(dirLightDir)); + glUniform3fv(glGetUniformLocation(shaderProgram, "dirLightColor"), 1, glm::value_ptr(dirLightColor)); + glUniform1f(glGetUniformLocation(shaderProgram, "parallaxScale"), globalParallaxScale); glUniform1f(glGetUniformLocation(shaderProgram, "PI"), IM_PI); + // Set environment reflection uniforms. + glActiveTexture(GL_TEXTURE6); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); + glUniform1i(glGetUniformLocation(shaderProgram, "envMap"), 6); + glUniform1f(glGetUniformLocation(shaderProgram, "envReflectionIntensity"), 0.3f); // adjust intensity as needed - // Render spheres. - glBindVertexArray(sphereVAO); - for (const auto& sphere : spheres) { - glm::mat4 model = glm::translate(glm::mat4(1.0f), sphere.position); - model = glm::rotate(model, sphere.rotation, glm::vec3(0.0f, 1.0f, 0.0f)); + if(activeObject == 0 && !spheres.empty()){ + // Render sphere. + glm::mat4 model = glm::translate(glm::mat4(1.0f), spheres[0].position); + model = glm::rotate(model, spheres[0].rotation, glm::vec3(0,1,0)); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(model)); - - glUniform3fv(glGetUniformLocation(shaderProgram, "albedo"), 1, glm::value_ptr(sphere.material.albedo)); - glUniform1f(glGetUniformLocation(shaderProgram, "metallic"), sphere.material.metallic); - glUniform1f(glGetUniformLocation(shaderProgram, "roughness"), sphere.material.roughness); - glUniform1f(glGetUniformLocation(shaderProgram, "ao"), sphere.material.ao); - glUniform1i(glGetUniformLocation(shaderProgram, "useAlbedoMap"), sphere.material.useAlbedoMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useMetallicMap"), sphere.material.useMetallicMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useRoughnessMap"), sphere.material.useRoughnessMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useAOMap"), sphere.material.useAOMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), sphere.material.useNormalMap ? 1 : 0); - if (sphere.material.useAlbedoMap) { + Material& m = spheres[0].material; + glUniform3fv(glGetUniformLocation(shaderProgram, "albedo"), 1, glm::value_ptr(m.albedo)); + glUniform1f(glGetUniformLocation(shaderProgram, "metallic"), m.metallic); + glUniform1f(glGetUniformLocation(shaderProgram, "roughness"), m.roughness); + glUniform1f(glGetUniformLocation(shaderProgram, "ao"), m.ao); + glUniform1i(glGetUniformLocation(shaderProgram, "useAlbedoMap"), m.useAlbedoMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useMetallicMap"), m.useMetallicMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useRoughnessMap"), m.useRoughnessMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useAOMap"), m.useAOMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), m.useNormalMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useHeightMap"), m.useHeightMap ? 1 : 0); + if(m.useAlbedoMap){ glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, sphere.material.albedoTex); + glBindTexture(GL_TEXTURE_2D, m.albedoTex); glUniform1i(glGetUniformLocation(shaderProgram, "albedoMap"), 0); } - if (sphere.material.useMetallicMap) { + if(m.useMetallicMap){ glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, sphere.material.metallicTex); + glBindTexture(GL_TEXTURE_2D, m.metallicTex); glUniform1i(glGetUniformLocation(shaderProgram, "metallicMap"), 1); } - if (sphere.material.useRoughnessMap) { + if(m.useRoughnessMap){ glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, sphere.material.roughnessTex); + glBindTexture(GL_TEXTURE_2D, m.roughnessTex); glUniform1i(glGetUniformLocation(shaderProgram, "roughnessMap"), 2); } - if (sphere.material.useAOMap) { + if(m.useAOMap){ glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, sphere.material.aoTex); + glBindTexture(GL_TEXTURE_2D, m.aoTex); glUniform1i(glGetUniformLocation(shaderProgram, "aoMap"), 3); } - if (sphere.material.useNormalMap) { + if(m.useNormalMap){ glActiveTexture(GL_TEXTURE4); - glBindTexture(GL_TEXTURE_2D, sphere.material.normalTex); + glBindTexture(GL_TEXTURE_2D, m.normalTex); glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 4); } - - glDrawElements(GL_TRIANGLES, sphereIndexCount, GL_UNSIGNED_INT, 0); + if(m.useHeightMap){ + glActiveTexture(GL_TEXTURE5); + glBindTexture(GL_TEXTURE_2D, m.heightTex); + glUniform1i(glGetUniformLocation(shaderProgram, "heightMap"), 5); + } + glBindVertexArray(sphereVAO); + glDrawElements(GL_TRIANGLES, sphereIndices.size(), GL_UNSIGNED_INT, 0); + glBindVertexArray(0); } - glBindVertexArray(0); - - // Render OBJ models. - for (const auto& m : models) { - glm::mat4 model = glm::translate(glm::mat4(1.0f), m.position); - model = glm::rotate(model, m.rotation, glm::vec3(0.0f, 1.0f, 0.0f)); + else if(activeObject == 1 && !planes.empty()){ + // Render plane. + glm::mat4 model = glm::translate(glm::mat4(1.0f), planes[0].position); + model = glm::rotate(model, planes[0].rotation, glm::vec3(0,1,0)); glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, glm::value_ptr(model)); - - glUniform3fv(glGetUniformLocation(shaderProgram, "albedo"), 1, glm::value_ptr(m.material.albedo)); - glUniform1f(glGetUniformLocation(shaderProgram, "metallic"), m.material.metallic); - glUniform1f(glGetUniformLocation(shaderProgram, "roughness"), m.material.roughness); - glUniform1f(glGetUniformLocation(shaderProgram, "ao"), m.material.ao); - glUniform1i(glGetUniformLocation(shaderProgram, "useAlbedoMap"), m.material.useAlbedoMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useMetallicMap"), m.material.useMetallicMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useRoughnessMap"), m.material.useRoughnessMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useAOMap"), m.material.useAOMap ? 1 : 0); - glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), m.material.useNormalMap ? 1 : 0); - if (m.material.useAlbedoMap) { + Material& m = planes[0].material; + glUniform3fv(glGetUniformLocation(shaderProgram, "albedo"), 1, glm::value_ptr(m.albedo)); + glUniform1f(glGetUniformLocation(shaderProgram, "metallic"), m.metallic); + glUniform1f(glGetUniformLocation(shaderProgram, "roughness"), m.roughness); + glUniform1f(glGetUniformLocation(shaderProgram, "ao"), m.ao); + glUniform1i(glGetUniformLocation(shaderProgram, "useAlbedoMap"), m.useAlbedoMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useMetallicMap"), m.useMetallicMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useRoughnessMap"), m.useRoughnessMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useAOMap"), m.useAOMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useNormalMap"), m.useNormalMap ? 1 : 0); + glUniform1i(glGetUniformLocation(shaderProgram, "useHeightMap"), m.useHeightMap ? 1 : 0); + if(m.useAlbedoMap){ glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, m.material.albedoTex); + glBindTexture(GL_TEXTURE_2D, m.albedoTex); glUniform1i(glGetUniformLocation(shaderProgram, "albedoMap"), 0); } - if (m.material.useMetallicMap) { + if(m.useMetallicMap){ glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, m.material.metallicTex); + glBindTexture(GL_TEXTURE_2D, m.metallicTex); glUniform1i(glGetUniformLocation(shaderProgram, "metallicMap"), 1); } - if (m.material.useRoughnessMap) { - glActiveTexture(GL_TEXTURE2); - glBindTexture(GL_TEXTURE_2D, m.material.roughnessTex); + if(m.useRoughnessMap){ + glActiveTexture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m.roughnessTex); glUniform1i(glGetUniformLocation(shaderProgram, "roughnessMap"), 2); } - if (m.material.useAOMap) { - glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, m.material.aoTex); + if(m.useAOMap){ + glActiveTexture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m.aoTex); glUniform1i(glGetUniformLocation(shaderProgram, "aoMap"), 3); } - if (m.material.useNormalMap) { - glActiveTexture(GL_TEXTURE4); - glBindTexture(GL_TEXTURE_2D, m.material.normalTex); + if(m.useNormalMap){ + glActiveTexture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m.normalTex); glUniform1i(glGetUniformLocation(shaderProgram, "normalMap"), 4); } - - glBindVertexArray(m.VAO); - glDrawElements(GL_TRIANGLES, m.indexCount, GL_UNSIGNED_INT, 0); + if(m.useHeightMap){ + glActiveTexture(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, m.heightTex); + glUniform1i(glGetUniformLocation(shaderProgram, "heightMap"), 5); + } + glBindVertexArray(planeVAO); + glDrawElements(GL_TRIANGLES, planeIndices.size(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); } - // Render ImGui. + // ---------------------- Render Skybox ---------------------- + glDepthFunc(GL_LEQUAL); + glUseProgram(skyboxShader); + // Remove translation from the view matrix. + glm::mat4 viewSky = glm::mat4(glm::mat3(view)); + glUniformMatrix4fv(glGetUniformLocation(skyboxShader, "view"), 1, GL_FALSE, glm::value_ptr(viewSky)); + glUniformMatrix4fv(glGetUniformLocation(skyboxShader, "projection"), 1, GL_FALSE, glm::value_ptr(projection)); + glBindVertexArray(skyboxVAO); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); + glUniform1i(glGetUniformLocation(skyboxShader, "skybox"), 0); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + glDepthFunc(GL_LESS); + ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - glfwSwapBuffers(window); } - // ---------------------------------------------------------------------------------- - // Cleanup. - // ---------------------------------------------------------------------------------- + // Cleanup glDeleteVertexArrays(1, &sphereVAO); glDeleteBuffers(1, &sphereVBO); glDeleteBuffers(1, &sphereEBO); - for (auto& m : models) { - glDeleteVertexArrays(1, &m.VAO); - glDeleteBuffers(1, &m.VBO); - glDeleteBuffers(1, &m.EBO); - } + glDeleteVertexArrays(1, &planeVAO); + glDeleteBuffers(1, &planeVBO); + glDeleteBuffers(1, &planeEBO); + glDeleteVertexArrays(1, &skyboxVAO); + glDeleteBuffers(1, &skyboxVBO); glDeleteProgram(shaderProgram); + glDeleteProgram(skyboxShader); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); - glfwTerminate(); return 0; } diff --git a/physicaly-based-renderer/main.exe b/physicaly-based-renderer/main.exe index ee91be4..2e1d8ba 100644 Binary files a/physicaly-based-renderer/main.exe and b/physicaly-based-renderer/main.exe differ diff --git a/physicaly-based-renderer/main.o b/physicaly-based-renderer/main.o index 618e021..f145169 100644 Binary files a/physicaly-based-renderer/main.o and b/physicaly-based-renderer/main.o differ diff --git a/physicaly-based-renderer/metal.mat b/physicaly-based-renderer/metal.mat index 54c1d00..9770b28 100644 --- a/physicaly-based-renderer/metal.mat +++ b/physicaly-based-renderer/metal.mat @@ -6,4 +6,5 @@ albedo_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless- metallic_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_metallic.png roughness_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_roughness.png ao_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_ao.png -normal_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_normal-ogl.png \ No newline at end of file +normal_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_normal-ogl.png +height_texture: C:\Users\spenc\Downloads\used-stainless-steel-bl\used-stainless-steel-bl\used-stainless-steel_height.png \ No newline at end of file diff --git a/physicaly-based-renderer/skybox/back.jpg b/physicaly-based-renderer/skybox/back.jpg new file mode 100644 index 0000000..470a679 Binary files /dev/null and b/physicaly-based-renderer/skybox/back.jpg differ diff --git a/physicaly-based-renderer/skybox/bottom.jpg b/physicaly-based-renderer/skybox/bottom.jpg new file mode 100644 index 0000000..893f394 Binary files /dev/null and b/physicaly-based-renderer/skybox/bottom.jpg differ diff --git a/physicaly-based-renderer/skybox/front.jpg b/physicaly-based-renderer/skybox/front.jpg new file mode 100644 index 0000000..4e17b77 Binary files /dev/null and b/physicaly-based-renderer/skybox/front.jpg differ diff --git a/physicaly-based-renderer/skybox/left.jpg b/physicaly-based-renderer/skybox/left.jpg new file mode 100644 index 0000000..5750b91 Binary files /dev/null and b/physicaly-based-renderer/skybox/left.jpg differ diff --git a/physicaly-based-renderer/skybox/right.jpg b/physicaly-based-renderer/skybox/right.jpg new file mode 100644 index 0000000..8963037 Binary files /dev/null and b/physicaly-based-renderer/skybox/right.jpg differ diff --git a/physicaly-based-renderer/skybox/top.jpg b/physicaly-based-renderer/skybox/top.jpg new file mode 100644 index 0000000..4db3c2a Binary files /dev/null and b/physicaly-based-renderer/skybox/top.jpg differ