diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a373719 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,118 @@ +# Fundamental Coding Style Rules + +Codex must obey these rules without exception: + +## Imports + +* Each import statement must be separated by exactly **one empty line**. +* All `from` tokens must be aligned vertically using **tabs before `from`**. +* Use **double quotes** for all string literals. +* Use **ES modules** only. +* Avoid backticks unless unavoidable for syntax. + +## Function Calls + +* Always write calls like: + `myFunction( param )` + with spaces **inside** parentheses. + +## Class Structure + +* Exactly **one empty line** between class methods. +* No empty line at start or end of class. +* Methods must start with a blank line after `{` and end with a blank line before `}`. + +## Method Body Spacing + +Inside every method body: + +* **One empty line between every line of code**. +* No inline comments on the same line as code. + +## Callbacks & Functions + +* Never write inline callbacks. +* Never write nested callbacks. +* Never pass anonymous inline functions to `.map()`, `.filter()`, `.forEach()`, etc. +* Never use arrow functions. +* Always create a separate class method instead. + +## Promises + +* Never use `.then()`. +* Never use `new Promise( resolve => { ... } )`. +* Always use `async` / `await`. +* Avoid `try-catch` blocks unless mandatory. + +## Arrays + +* Use `new Array()` instead of `[]`, **except** in Angular metadata (`imports: [ ]`, `providers: [ ]` etc.) where `[]` is allowed. + +## Objects + +* Never return an inline object directly. +* Always assign the object to a variable first, then return that variable. +* Align object literal values vertically using tabs before the values. + +## Conditionals + +* Never use short-form ternary (`? :`). +* Always write full `if` statements. + +## Logging + +* Do not use `$()` syntax. +* Use simple logs: + `console.log( "message", value );` + +## JSON imports + +Always use: + +``` +import data from "./file.json" with { type: "json" }; +``` + +## Indentation + +* Use **tabs** everywhere (no spaces). +* Nested JSON or object structures must use tabs for indentation. + + +# Repository Guidelines + +## Project Structure & Modules +- `engine/`: core rendering engine (WebGL, deferred, PBR). +- `shaders/`: GLSL shader sources; `shaders/old/` contains legacy variants. +- `plugins/`: optional features and extensions (for example `plugins/ocean`). +- `demos/`, `start.html`, `index.html`: example entrypoints for running the engine. +- `media/`: textures, models, fonts, and screenshots used by demos. +- `server.js`: minimal static HTTP server for local development. + +## Build, Run, and Development +- No build step is required; files are served as static assets. +- Run `node server.js` from the repo root to start the dev server on `http://localhost:4000`. +- Open `/start.html` or files under `/demos/` in your browser to exercise changes. +- Use ES modules (`type: "module"` in `package.json`) for new JavaScript files. + +## Coding Style & Naming +- JavaScript only, using ES modules and `class` syntax where appropriate. +- Use camelCase for variables and functions, PascalCase for classes and render passes. +- Match existing indentation (tabs) and brace style in the surrounding file. +- Keep core engine logic in `engine/`, shaders in `shaders/`, and optional logic in `plugins/`. + +## Testing & Validation +- There is no automated test runner yet; validate behavior through demos. +- Prefer creating small, focused demo pages under `demos/` (for example `demos/rough.htm`) for new features. +- Keep demos self-contained, quick to load, and clearly named after the feature they showcase. + +## Commit & Pull Request Guidelines +- Write short, descriptive commit messages (for example `Fix SSAO sampling` or `Update README images`). +- Aim for one logical change per commit when practical. +- PRs should include a concise description, a list of affected areas, and screenshots or GIFs for visual or lighting changes. +- Link to any related issues and mention relevant demo URLs (such as `/demos/new.htm`) that reviewers can open. + +## Agent-Specific Notes +- When changing rendering behavior, prefer adding or adjusting render passes under `engine/renderPasses/` instead of large monolithic edits. +- Avoid deleting existing demos or media assets; add new examples alongside current ones to preserve reference behavior. + diff --git a/engine/assimp.js b/engine/assimp.js index 747cd07..78d945a 100755 --- a/engine/assimp.js +++ b/engine/assimp.js @@ -5,21 +5,21 @@ * */ -import scene from './scene.js'; +import scene from "./scene.js"; -import shader from './shader.js'; +import shader from "./shader.js"; -import mesh from './mesh.js'; +import mesh from "./mesh.js"; -import material from './material.js'; +import material from "./material.js"; -import sampler2D from './sampler2D.js'; +import sampler2D from "./sampler2D.js"; -import entity from './entity.js'; +import entity from "./entity.js"; -import {matrix3, matrix4} from './math.js'; +import {matrix3, matrix4} from "./math.js"; -import boundingBox from './boundingBox.js'; +import boundingBox from "./boundingBox.js"; /** * Player object @@ -153,6 +153,14 @@ class assimp this.scene = scene; + this.currentModelFile = jsonFilename; + + if( typeof kepler !== "undefined" && kepler && kepler.resources ) { + + kepler.resources.updateFilePreloaderProgress( this.name, 0, 0, false ); + + } + var data = this.loadTextFileSynchronous( this.baseUrl + this.modelPath + jsonFilename ); this.processFiles(data); @@ -217,10 +225,14 @@ class assimp request.onprogress = callback = function (evt) { - - console.log("onupdate", evt); - - //kepler.loader.update( evt.target.responseURL, evt.loaded, evt.total ); + + if( typeof kepler !== "undefined" && kepler && kepler.resources && evt && evt.target && evt.target.assimp ) { + + var label = evt.target.assimp.name; + + kepler.resources.updateFilePreloaderProgress( label, evt.loaded, evt.total, false ); + + } }; @@ -231,6 +243,22 @@ class assimp //console.log(request.responseText); this.assimp.filesToLoad.push({data: request.responseText, filename: request.responseURL }); //'this.assimp.filesToLoad.push(evt.responseText); + + if( typeof kepler !== "undefined" && kepler && kepler.resources && this.assimp ) { + + var label = this.assimp.name; + + var totalBytes = 0; + + if( request.responseText ) { + + totalBytes = request.responseText.length; + + } + + kepler.resources.updateFilePreloaderProgress( label, totalBytes, totalBytes, true ); + + } @@ -792,4 +820,4 @@ console.log('diffuseAdress', diffuseAdress); } } -export {assimp as default}; \ No newline at end of file +export {assimp as default}; diff --git a/engine/kepler.js b/engine/kepler.js index 59d5a42..8c05328 100755 --- a/engine/kepler.js +++ b/engine/kepler.js @@ -81,6 +81,8 @@ this.assimpLoader = new assimp(); + this.preloaderCleared = false; + } addViewport(viewport){ @@ -142,9 +144,18 @@ */ tick() { + if( this.preloaderCleared === false && this.resources ) { + + this.resources.hidePreloader( ); + + this.preloaderCleared = true; + + } + requestAnimFrame( this.tick.bind( this ) ); this.render(); + } /** diff --git a/engine/resourceManager.js b/engine/resourceManager.js index e232968..d5ab9a4 100755 --- a/engine/resourceManager.js +++ b/engine/resourceManager.js @@ -6,10 +6,11 @@ * Author: Kaj Dijksta * **/ - - -import programInfo from './programInfo.js'; -import {vector2, vector3} from './math.js'; + + +import programInfo from "./programInfo.js"; + +import {vector2, vector3} from "./math.js"; @@ -17,28 +18,53 @@ import {vector2, vector3} from './math.js'; * ResourceManager object */ class resourceManager{ - + constructor( engine ) { - this.engine = engine; - this.gl = engine.gl; - - this.baseUrl = ""; - this.directory = "media/textures/"; - - - this.numberOfModels = 0; - this.numberOfTextures = 0; - this.textureTotal = 0; - - this.textures = []; - this.models = []; - this.shaders = []; - - this.finishCallback; - this.loadingList = []; - - this.fileCache = []; - this.programCashe = []; + + this.engine = engine; + + this.gl = engine.gl; + + this.baseUrl = ""; + + this.directory = "media/textures/"; + + this.numberOfModels = 0; + + this.numberOfTextures = 0; + + this.textureTotal = 0; + + this.textures = new Array(); + + this.models = new Array(); + + this.shaders = new Array(); + + this.finishCallback = null; + + this.loadingList = new Array(); + + this.fileCache = new Array(); + + this.programCashe = new Array(); + + this.preloaderElement = null; + + this.preloaderTextElement = null; + + this.preloaderBarElement = null; + + this.preloaderVisible = false; + + this.fileLoading = false; + + this.fileLoadedBytes = 0; + + this.fileTotalBytes = 0; + + this.fileLabel = ""; + } @@ -320,6 +346,294 @@ import {vector2, vector3} from './math.js'; } + formatBytesToMegabytes( bytes ) { + + if( bytes <= 0 ) { + + return "0.00 MB"; + + } + + var megabytes = bytes / ( 1024 * 1024 ); + + var rounded = Math.round( megabytes * 100 ) / 100; + + var text = rounded.toFixed( 2 ) + " MB"; + + return text; + + } + + + createPreloaderElements( ) { + + if( this.preloaderElement !== null ) { + + return; + + } + + var canvasElement = document.getElementById( "keplerEngine" ); + + var holder = document.createElement( "div" ); + + holder.className = "kepler-preloader-holder"; + + holder.style.position = "absolute"; + + holder.style.left = "0"; + + holder.style.top = "0"; + + holder.style.right = "0"; + + holder.style.bottom = "0"; + + holder.style.display = "flex"; + + holder.style.alignItems = "center"; + + holder.style.justifyContent = "center"; + + holder.style.backgroundColor = "rgba(0, 0, 0, 0.6)"; + + var inner = document.createElement( "div" ); + + inner.className = "kepler-preloader-inner"; + + inner.style.minWidth = "200px"; + + inner.style.maxWidth = "400px"; + + inner.style.width = "60%"; + + inner.style.backgroundColor = "#202020"; + + inner.style.padding = "16px"; + + inner.style.boxSizing = "border-box"; + + inner.style.borderRadius = "4px"; + + var textElement = document.createElement( "div" ); + + textElement.className = "kepler-preloader-text"; + + textElement.style.color = "#ffffff"; + + textElement.style.fontFamily = "sans-serif"; + + textElement.style.fontSize = "14px"; + + textElement.style.marginBottom = "8px"; + + textElement.textContent = "Loading textures..."; + + var barOuter = document.createElement( "div" ); + + barOuter.className = "kepler-preloader-bar"; + + barOuter.style.width = "100%"; + + barOuter.style.height = "6px"; + + barOuter.style.backgroundColor = "#444444"; + + barOuter.style.overflow = "hidden"; + + var barInner = document.createElement( "div" ); + + barInner.className = "kepler-preloader-bar-inner"; + + barInner.style.width = "0%"; + + barInner.style.height = "100%"; + + barInner.style.backgroundColor = "#66ccff"; + + barOuter.appendChild( barInner ); + + inner.appendChild( textElement ); + + inner.appendChild( barOuter ); + + holder.appendChild( inner ); + + this.preloaderElement = holder; + + this.preloaderTextElement = textElement; + + this.preloaderBarElement = barInner; + + if( canvasElement !== null && canvasElement.parentNode !== null ) { + + canvasElement.parentNode.style.position = "relative"; + + canvasElement.parentNode.appendChild( holder ); + + } else { + + document.body.appendChild( holder ); + + } + + } + + + showPreloaderIfNeeded( ) { + + if( this.preloaderVisible ) { + + return; + + } + + this.createPreloaderElements( ); + + if( this.preloaderElement !== null ) { + + this.preloaderElement.style.display = "flex"; + + } + + this.preloaderVisible = true; + + } + + + updatePreloaderProgress( ) { + + if( this.preloaderElement === null ) { + + return; + + } + + if( this.textureTotal <= 0 ) { + + if( this.fileTotalBytes <= 0 ) { + + return; + + } + + } + + var loaded = this.textureTotal - this.numberOfTextures; + + if( loaded < 0 ) { + + loaded = 0; + + } + + if( loaded > this.textureTotal ) { + + loaded = this.textureTotal; + + } + + var ratio = 0; + + if( this.textureTotal > 0 ) { + + ratio = loaded / this.textureTotal; + + } + + var percentage = Math.round( ratio * 100 ); + + if( this.preloaderTextElement !== null ) { + + var text = "Loading textures " + loaded + " / " + this.textureTotal; + + if( this.fileLoading && this.fileTotalBytes > 0 ) { + + var label = this.fileLabel; + + if( label === "" ) { + + label = "model"; + + } + + var loadedText = this.formatBytesToMegabytes( this.fileLoadedBytes ); + + var totalText = this.formatBytesToMegabytes( this.fileTotalBytes ); + + text = "Loading model " + label + " " + loadedText + " / " + totalText + " | " + text; + + } + + this.preloaderTextElement.textContent = text; + + } + + if( this.preloaderBarElement !== null ) { + + this.preloaderBarElement.style.width = percentage + "%"; + + } + + } + + + hidePreloader( ) { + + if( this.preloaderElement === null ) { + + return; + + } + + this.preloaderElement.style.display = "none"; + + this.preloaderVisible = false; + + } + + + updateFilePreloaderProgress( label, loadedBytes, totalBytes, done ) { + + if( done ) { + + this.fileLoading = false; + + this.fileLabel = ""; + + this.fileLoadedBytes = 0; + + this.fileTotalBytes = 0; + + if( this.textureTotal <= 0 ) { + + this.hidePreloader( ); + + } else { + + this.updatePreloaderProgress( ); + + } + + } else { + + this.fileLoading = true; + + this.fileLabel = label; + + this.fileLoadedBytes = loadedBytes; + + this.fileTotalBytes = totalBytes; + + this.showPreloaderIfNeeded( ); + + this.updatePreloaderProgress( ); + + } + + } + + /** * Add Texture * @param {string} address @@ -327,20 +641,31 @@ import {vector2, vector3} from './math.js'; * @param {boolean} direct */ addTexture(adress, name, direct) { - + this.numberOfTextures++; + this.textureTotal++; + this.showPreloaderIfNeeded( ); + + this.updatePreloaderProgress( ); + var texture = {}; - + texture.adress = adress; + texture.name = name; + texture.loaded = false; - - this.loadingList.push(texture); - - if(direct) - this.loadNextTexture(adress, name); + + this.loadingList.push( texture ); + + if( direct ) { + + this.loadNextTexture( adress, name ); + + } + }; @@ -420,6 +745,13 @@ import {vector2, vector3} from './math.js'; image.onerror = function (a) { this.engine.resources.loadNextTexture(); + + if( this.engine.resources.textureTotal > 0 ) { + + this.engine.resources.textureTotal--; + + } + this.engine.resources.CheckIfAllObjectsTexturesLoaded(); } @@ -450,28 +782,48 @@ import {vector2, vector3} from './math.js'; */ getTexture( name ) { - if(this.hasExtension(name)) { - - var url = name; - - this.addTexture(this.directory + url, name); - } - - - for(var c = 0;c 1 ? this.gl.LINEAR_MIPMAP_LINEAR : this.gl.LINEAR - gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.WRAP_S); - gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.WRAP_T); - - if(this.type != this.gl.FLOAT) - gl.generateMipmap(gl.TEXTURE_2D); - + + // POT logic (this is where we apply the safe normal-map fix) + if ( math.isPowerOfTwo( width ) && math.isPowerOfTwo( height ) ) { + + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR ); + + if ( this.isNormalMap ) { + + // Safe fix: do not use mipmaps for normal maps + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR ); + + } else { + + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR ); + + if ( this.type != this.gl.FLOAT ) { + + gl.generateMipmap( gl.TEXTURE_2D ); + } + } + + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.WRAP_S ); + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.WRAP_T ); + } else { - gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); - gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); - + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR ); + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE ); + gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE ); } - - - gl.bindTexture(gl.TEXTURE_2D, null); + + gl.bindTexture( gl.TEXTURE_2D, null ); + this.binded = true; + + return; + } + + // framebuffer path: bind the already-created GL texture handle + // and apply basic sampling state without re-upload. + { + var width_fb = txt.width; + var height_fb = txt.height; + + this.id = 1 + shader.samplerId++; + + gl.activeTexture( gl.TEXTURE0 + this.id ); + gl.bindTexture( gl.TEXTURE_2D, txt.glTexture ); + + // Clamp + linear for attachments (safe default) + gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR ); + gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR ); + gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); + gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); + + gl.bindTexture( gl.TEXTURE_2D, null ); + + this.binded = true; } } } -export {sampler2D as default}; + +export { sampler2D as default }; diff --git a/index.html b/index.html index c9ee42f..4f3856d 100755 --- a/index.html +++ b/index.html @@ -147,11 +147,13 @@ var groundMaterial = new material(); // Samplers - var normalTexture = kepler.resources.getTexture("0_floorTiles_ddn.png"); + var normalTexture = kepler.resources.getTexture("pbr-512/pavingStones/PavingStones046_2K-JPG_NormalGL.jpg"); var normalSampler = new sampler2D(); normalSampler.addTexture(normalTexture); - - var diffuseTexture = kepler.resources.getTexture("0_floorTiles_diff.png"); + normalSampler.setAsNormalMap(); + + + var diffuseTexture = kepler.resources.getTexture("pbr-512/pavingStones/PavingStones046_2K-JPG_Color.jpg"); var diffuseSampler = new sampler2D(); diffuseSampler.addTexture(diffuseTexture); diff --git a/media/textures/pbr-512/pavingStones/PavingStones046.png b/media/textures/pbr-512/pavingStones/PavingStones046.png new file mode 100644 index 0000000..729d119 Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046.png differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.blend b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.blend new file mode 100644 index 0000000..b0f738b Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.blend differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.mtlx b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.mtlx new file mode 100644 index 0000000..1aa5779 --- /dev/null +++ b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.mtlx @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.tres b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.tres new file mode 100644 index 0000000..fe13604 --- /dev/null +++ b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.tres @@ -0,0 +1,19 @@ +[gd_resource type="StandardMaterial3D" load_steps=6 format=3 uid="acg_0wl6yudb"] + +[ext_resource type="Texture2D" uid="uid://acg_qwdu613c" id="Color" path="./PavingStones046_2K-JPG_Color.jpg"] +[ext_resource type="Texture2D" uid="uid://acg_l5d9bi73" id="AmbientOcclusion" path="./PavingStones046_2K-JPG_AmbientOcclusion.jpg"] +[ext_resource type="Texture2D" uid="uid://acg_ad0vlmbp" id="Displacement" path="./PavingStones046_2K-JPG_Displacement.jpg"] +[ext_resource type="Texture2D" uid="uid://acg_iu7w0s8l" id="Roughness" path="./PavingStones046_2K-JPG_Roughness.jpg"] +[ext_resource type="Texture2D" uid="uid://acg_nby48io6" id="NormalGL" path="./PavingStones046_2K-JPG_NormalGL.jpg"] +[resource] +albedo_texture = ExtResource("Color") +ao_enabled = true +ao_texture = ExtResource("AmbientOcclusion") +ao_texture_channel = 4 +roughness_texture = ExtResource("Roughness") +roughness_texture_channel = 4 +normal_texture = ExtResource("NormalGL") +normal_enabled = true +heightmap_texture = ExtResource("Displacement") +heightmap_scale = 1.0 +heightmap_enabled = true diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.usdc b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.usdc new file mode 100644 index 0000000..b3b9344 Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.usdc differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.zip b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.zip new file mode 100644 index 0000000..093365b Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG.zip differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_AmbientOcclusion.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_AmbientOcclusion.jpg new file mode 100644 index 0000000..835c410 Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_AmbientOcclusion.jpg differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Color.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Color.jpg new file mode 100644 index 0000000..14a82bd Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Color.jpg differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Displacement.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Displacement.jpg new file mode 100644 index 0000000..38b215b Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Displacement.jpg differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalDX.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalDX.jpg new file mode 100644 index 0000000..ee43c1f Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalDX.jpg differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalGL.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalGL.jpg new file mode 100644 index 0000000..8972e35 Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_NormalGL.jpg differ diff --git a/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Roughness.jpg b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Roughness.jpg new file mode 100644 index 0000000..c5c7d2a Binary files /dev/null and b/media/textures/pbr-512/pavingStones/PavingStones046_2K-JPG_Roughness.jpg differ diff --git a/shaders/color.shader b/shaders/color.shader index 269ddab..ae8da14 100755 --- a/shaders/color.shader +++ b/shaders/color.shader @@ -455,14 +455,45 @@ #if NORMAL_MAP == 1 vec4 normalMap = texture(normalSampler, textureCoordinate * uvMultiplier) * 2.0 - 1.0; - normal = normalize((v_tangent.xyz * normalMap.x) + (v_binormal.xyz * normalMap.y) + (v_normal.xyz * normalMap.z)); + //normal = normalize((v_tangent.xyz * normalMap.x) + (v_binormal.xyz * normalMap.y) + (v_normal.xyz * normalMap.z)); mat3 tangentToWorld = transpose(mat3( v_tangent.xyz , v_binormal.xyz , v_normal.xyz)); - normal = normalMap.xyz * tangentToWorld; + //normal = normalMap.xyz * tangentToWorld; + + + + + vec2 uv = textureCoordinate * uvMultiplier; + + vec2 dUVdx = dFdx(uv); + vec2 dUVdy = dFdy(uv); + + vec3 nm = textureGrad(normalSampler, uv, dUVdx, dUVdy).xyz * 2.0 - 1.0; + nm = normalize(nm); + + nm = normalize(nm); + + vec3 N = normalize(v_normal.xyz); + vec3 T = normalize(v_tangent.xyz); + T = normalize(T - N * dot(N, T)); + vec3 B = normalize(cross(N, T)); + + mat3 TBN = mat3(T, B, N); + normal = normalize(TBN * nm); + + + + + + + + + + #else normal = v_normal.xyz; @@ -543,7 +574,7 @@ #endif - float gamma = 1.4; + float gamma = 2.2; vec3 baseColor; vec4 diffuseMap; @@ -569,6 +600,24 @@ f_roughness = texture(roughnessSampler, textureCoordinate).x; #endif + + + #if NORMAL_MAP == 1 + + // Screen-space normal variation (specular AA) + vec3 n_dx = dFdx(normal); + vec3 n_dy = dFdy(normal); + + float variance = max(dot(n_dx, n_dx), dot(n_dy, n_dy)); + + // UE-style mapping: converts variance to roughness increase + float kernelRoughness = clamp(variance * 0.25, 0.0, 1.0); + + // Combine with material roughness + f_roughness = sqrt(clamp(f_roughness * f_roughness + kernelRoughness, 0.0, 1.0)); + + #endif + vec3 diffuse = baseColor ; // @@ -579,7 +628,7 @@ float reflectance = v_reflectance / 2.0; - vec3 viewDir = (cameraPosition - v_worldposition.xyz); + vec3 viewDir = normalize(cameraPosition - v_worldposition.xyz); vec3 reflectionVector = reflect(-viewDir.xyz, normalize(normal.xyz)); vec3 reflectionSample = sampleReflection( reflectionVector ); diff --git a/shaders/physically_based_shading_full.shader b/shaders/physically_based_shading_full.shader index d20e9df..1c03906 100755 --- a/shaders/physically_based_shading_full.shader +++ b/shaders/physically_based_shading_full.shader @@ -150,7 +150,7 @@ vec3 fixCubemapLookup(vec3 v, float lod) { vec3 evaluateSpecularIBL(vec3 r, float roughness) { - float lod = 5.0 * roughness; + float lod = clamp(5.0 * roughness, 0.0, 8.0); r = fixCubemapLookup(r, lod); @@ -165,91 +165,81 @@ vec3 getSpecularDominantDirection(vec3 n, vec3 r, float roughness) { float s = 1.0 - roughness; return mix(n, r, s * (sqrt(s) + roughness)); } - - -vec3 indirectLight( deferredMaterialData materialData ) { //, deferredLightData materialData +vec3 indirectLight( deferredMaterialData materialData ) { vec3 normal = materialData.normal; - vec3 vertex = materialData.vertex; - - - float roughness = materialData.roughness; - - float NoV = max( dot(normal, vertex), 0.0 ); + vec3 vertex = materialData.vertex; + + float roughness = clamp( materialData.roughness, 0.0, 1.0 ); + + float NoV = max( dot( normal, vertex ), 0.0 ); #if ANISOTROPY == 1 - vec3 t = normalize( materialData.tangent ); - vec3 b = normalize( cross(t, normal)); + vec3 t = normalize( materialData.tangent ); + vec3 b = normalize( cross( t, normal ) ); - vec3 anisotropicTangent = cross(-vertex, b); - vec3 anisotropicNormal = cross(anisotropicTangent, b); - vec3 bentNormal = normalize(mix(normal, anisotropicNormal, anisotropy)); + vec3 anisotropicTangent = cross( -vertex, b ); + vec3 anisotropicNormal = cross( anisotropicTangent, b ); + vec3 bentNormal = normalize( mix( normal, anisotropicNormal, anisotropy ) ); - vec3 r = reflect( vertex, bentNormal ); + vec3 r = reflect( vertex, bentNormal ); #else - vec3 r = reflect( -vertex, normal ); - r = getSpecularDominantDirection(normal, r, roughness * roughness); + vec3 r = reflect( -vertex, normal ); + + // Use perceptual roughness here (not squared) + r = getSpecularDominantDirection( normal, r, roughness ); #endif - float NoR = max( dot(r, normal), 0.0); + float NoR = max( dot( r, normal ), 0.0 ); - // specular indirect - vec3 indirectSpecular = evaluateSpecularIBL( reflect(vertex, normal), roughness); + // Specular indirect (USE r, not reflect(-vertex, normal) again) + vec3 indirectSpecular = evaluateSpecularIBL( r, roughness ); - // horizon occlusion, can be removed for performance - //float horizon = min(1.0 + NoR, 1.0); - //indirectSpecular *= horizon * horizon; + // Optional but usually helps reduce “edge glitter” on grazing angles + indirectSpecular *= clamp( 1.0 + NoR, 0.0, 1.0 ); - vec2 env = prefilteredDFGKaris( NoV, materialData.roughness ); - // we should multiply env.y by f90 for more accurate results - vec3 specularColor = materialData.specularReflectance * env.x + env.y * (1.0 - materialData.clearCoat) * - clamp(dot(materialData.specularReflectance, vec3(50.0 * 0.33)), 0.0, 1.0); + vec2 env = prefilteredDFGKaris( NoV, roughness ); + + vec3 specularColor = + materialData.specularReflectance * env.x + + env.y * ( 1.0 - materialData.clearCoat ) * + clamp( dot( materialData.specularReflectance, vec3( 50.0 * 0.33 ) ), 0.0, 1.0 ); + + vec3 indirectDiffuse = max( irradianceSH( normal ), 0.0 ) * Fd_Lambert(); + + float ambientOcclusionFade = clamp( dot( normalize( normal ), vertex ), 0.0, 1.0 ); + float ambientOcclusion = mix( 1.0, materialData.ambientOcclusion, ambientOcclusionFade ); - // diffuse indirect - vec3 indirectDiffuse = max(irradianceSH(normal), 0.0) * Fd_Lambert(); - // ambient occlusion - float ambientOcclusionFade = clamp(dot(normalize(normal), vertex), 0.0, 1.0); - float ambientOcclusion = mix(1.0, materialData.ambientOcclusion, ambientOcclusionFade); indirectDiffuse *= ambientOcclusion; - //indirectDiffuse *= 4.0; - // TODO: Not really useful without SSambientOcclusion/HBambientOcclusion/etc. - indirectSpecular *= computeSpecularambientOcclusion(NoV, ambientOcclusion, materialData.roughness); - // clear coat - float Fcc = F_Schlick_Scalar(NoV, 0.04, 1.0) * 0.2; -#if ANISOTROPY == 1 - // We used the bent normal for the base layer - r = reflect(-vertex, normal); -#endif + indirectSpecular *= computeSpecularambientOcclusion( NoV, ambientOcclusion, roughness ); + float Fcc = F_Schlick_Scalar( NoV, 0.04, 1.0 ) * 0.2; - vec3 indirectClearCoatSpecular = evaluateSpecularIBL(reflect(vertex, normal), materialData.clearCoatRoughness); - + vec3 indirectClearCoatSpecular = + evaluateSpecularIBL( reflect( vertex, normal ), clamp( materialData.clearCoatRoughness, 0.0, 1.0 ) ); - vec3 clearCoatAbsorption = mix(vec3(1.0), - beerLambert(materialData.refracted_NoV, materialData.refracted_NoV, materialData.clearCoatColor, materialData.clearCoatThickness), - materialData.clearCoat); + vec3 clearCoatAbsorption = mix( + vec3( 1.0 ), + beerLambert( + materialData.refracted_NoV, + materialData.refracted_NoV, + materialData.clearCoatColor, + materialData.clearCoatThickness + ), + materialData.clearCoat + ); - // indirect contribution (clear coat) - vec3 color = - (materialData.diffuse * indirectDiffuse + indirectSpecular * specularColor)//kaj materialData.diffuse * indirectDiffuse - * (1.0 - Fcc) * clearCoatAbsorption + - indirectClearCoatSpecular * Fcc; + vec3 color = + ( materialData.diffuse * indirectDiffuse + indirectSpecular * specularColor ) * + ( 1.0 - Fcc ) * clearCoatAbsorption + + indirectClearCoatSpecular * Fcc; - - -#if TRANSLUCENT_MATERIAL == 1 - indirectDiffuse = max(irradianceSH(-vertex), 0.0) * Fd_Lambert(); - vec3 tL = -vertex + normal * translucencyDistortion; - float tD = pow(clamp(dot(vertex, -tL), 0.0, 1.0), translucencyPower) * translucencyScale; - vec3 tT = (tD + translucencyAmbient) * texture(translucencyThicknessMap, outUV).r; - color.rgb += materialData.diffuse * indirectDiffuse * tT; -#endif - - return color; + return color; } + vec3 getNormal(vec3 n) { return normalize(n); @@ -271,6 +261,9 @@ vec3 directLight( deferredMaterialData materialData, deferredLightData lightData vec3 lightDir = normalize(lightData.direction); float linearRoughness = materialData.roughness * materialData.roughness; + + + vec3 r = reflect(-v, n); if (lightType == 1.0) { @@ -327,6 +320,7 @@ vec3 directLight( deferredMaterialData materialData, deferredLightData lightData float D = D_GGX(NoH, linearRoughness); #endif vec3 F = F_Schlick(LoH, materialData.specularReflectance, clamp(dot(materialData.specularReflectance, vec3(50.0 * 0.33)), 0.0, 1.0)); + float V = V_SmithGGXCorrelated(NoV, NoL, linearRoughness); vec3 Fr = (D * V) * F;