Changed normal of the ground to GL, Now the normal mapping is correct.

This commit is contained in:
2026-01-04 16:11:18 +01:00
parent 296aad3f84
commit b417f016af
20 changed files with 985 additions and 254 deletions

118
AGENTS.md Normal file
View File

@@ -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.

View File

@@ -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);
@@ -218,9 +226,13 @@ class assimp
request.onprogress = callback = function (evt) {
console.log("onupdate", evt);
if( typeof kepler !== "undefined" && kepler && kepler.resources && evt && evt.target && evt.target.assimp ) {
//kepler.loader.update( evt.target.responseURL, evt.loaded, evt.total );
var label = evt.target.assimp.name;
kepler.resources.updateFilePreloaderProgress( label, evt.loaded, evt.total, false );
}
};
@@ -232,6 +244,22 @@ class assimp
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 );
}
}

View File

@@ -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();
}
/**

View File

@@ -8,8 +8,9 @@
**/
import programInfo from './programInfo.js';
import {vector2, vector3} from './math.js';
import programInfo from "./programInfo.js";
import {vector2, vector3} from "./math.js";
@@ -19,26 +20,51 @@ import {vector2, vector3} from './math.js';
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.textures = new Array();
this.finishCallback;
this.loadingList = [];
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 = "";
this.fileCache = [];
this.programCashe = [];
}
@@ -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
@@ -329,18 +643,29 @@ import {vector2, vector3} from './math.js';
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);
this.loadingList.push( texture );
if( direct ) {
this.loadNextTexture( adress, name );
}
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,23 +782,43 @@ import {vector2, vector3} from './math.js';
*/
getTexture( name ) {
if(this.hasExtension(name)) {
var index;
for( index = 0; index < this.textures.length; index++ ) {
if( this.textures[ index ].name === name ) {
return this.textures[ index ];
}
}
for( index = 0; index < this.loadingList.length; index++ ) {
if( this.loadingList[ index ].name === name ) {
return this.loadingList[ index ];
}
}
if( this.hasExtension( name ) ) {
var url = name;
this.addTexture(this.directory + url, name);
}
for(var c = 0;c<this.loadingList.length; c++) {
if(this.loadingList[c].name == name){
return this.loadingList[c];
this.addTexture( this.directory + url, name );
}
for( index = 0; index < this.loadingList.length; index++ ) {
if( this.loadingList[ index ].name === name ) {
return this.loadingList[ index ];
}
}
@@ -499,10 +851,17 @@ import {vector2, vector3} from './math.js';
* Check if all objects are loaded
*/
CheckIfAllObjectsTexturesLoaded() {
this.numberOfTextures--;
if( this.numberOfTextures == 0 ) {
this.updatePreloaderProgress( );
if( this.numberOfTextures === 0 ) {
this.hidePreloader( );
this.finishCallback();
}
};
@@ -735,4 +1094,3 @@ var randomImage2 = new Uint8Array ( [149,123,253,255, 126,3,96,255, 164,246,
52,83,220,255, 1,142,142,255, 32,57,79,255, 48,159,32,255,
56,232,114,255, 177,216,203,255, 69,196,217,255, 240,165,81,255,
224,56,85,255, 232,89,189,255, 143,25,202,255, 117,73,12,255] );

View File

@@ -5,36 +5,38 @@
*
*/
import texture from './texture.js';
import {math} from './math.js';
import texture from "./texture.js";
import { math } from "./math.js";
/**
* Sampler2D
**/
class sampler2D{
class sampler2D {
constructor( ){
constructor ( ) {
this.texture;
this.texture = null;
this.id = ++kepler.samplerId;
this.alpha = 1.0;
this.binded = false;
this.anisotropic = false;
// If true, do not generate mipmaps (reduces normal-map shimmer)
this.isNormalMap = false;
}
setViewport( viewport ){
setViewport ( viewport ) {
this.viewport = viewport;
this.gl = viewport.gl;
this.FLIP_Y = true;
this.filter = this.gl.NEAREST;
this.filter = this.gl.LINEAR;
this.MIN_FILTER = this.gl.LINEAR;
@@ -50,11 +52,9 @@ class sampler2D{
this.target = this.gl.TEXTURE_2D;
this.type = this.gl.FLOAT;
}
setTarget( viewport ) {
setTarget ( viewport ) {
var textureObject = new texture();
@@ -63,128 +63,244 @@ class sampler2D{
this.addTexture( textureObject );
}
getTexture( face ) {
getTexture ( face ) {
return this.texture;
}
getTextures( ) {
return [this.texture];
getTextures ( ) {
return [ this.texture ];
}
addTexture( textureObject ) {
addTexture ( textureObject ) {
this.texture = textureObject;
}
setType(type) {
//this.texture.type = "type";
setType ( type ) {
// this.texture.type = type;
}
setAsNormalMap ( ) {
this.isNormalMap = true;
}
/**
* bind sampler to shader
* @param {(shader)} shader
**/
bind(shader) {
bind ( shader ) {
var txt = this.texture;
var data = this.texture.data;
var type = txt.dataType;
var gl = this.gl;
//this.type = type;
if ( type == "framebuffer" ) {
if (type == "framebuffer" ) {
// Preserve your original behaviour:
// framebuffer "data" is already a GL texture handle
this.texture.glTexture = this.texture.data;
type = txt.type;
// Continue as if it is a normal GL texture object (no texImage2D)
// NOTE: do NOT change filtering/mips logic here beyond safe defaults.
} else {
var mips = [];
var width = txt.width;
var height = txt.height;
//if(!this.binded)
// Always assign sampler slot like you did
this.id = 1 + shader.samplerId++;
gl.activeTexture( gl.TEXTURE0 + this.id );
gl.activeTexture(gl.TEXTURE0 + this.id);
gl.enable( this.gl.BLEND );
gl.bindTexture( gl.TEXTURE_2D, txt.glTexture );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, this.FLIP_Y );
gl.enable ( this.gl.BLEND ) ;
gl.bindTexture(gl.TEXTURE_2D, txt.glTexture );
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, this.FLIP_Y);
// serialize txt data type
switch ( type ) {
//serialize txt data type
switch( type ) {
case "float":
{
this.type = this.gl.FLOAT;
gl.texImage2D(gl.TEXTURE_2D, 0, kepler.extensions.EXT_sRGB , width, height, 0, this.internalFormat, this.type, data);
break;
case "int":
this.type = this.gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.internalFormat, this.type, data);
break;
case "depth":
gl.texImage2D(gl.TEXTURE_2D, 0, this.gl.DEPTH_COMPONENT, width, height, 0, this.gl.DEPTH_COMPONENT, this.gl.UNSIGNED_SHORT, null);
break;
case "image":
this.type = this.gl.UNSIGNED_BYTE;
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.internalFormat,this.type, data);
break;
case "canvas":
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.internalFormat, this.UNSIGNED_BYTE, data);
break;
case "COMPRESSED_RGBA":
//var textureCompression = kepler.extensions.textureCompression;
// WebGL2 prefers sized internal formats for float textures.
// Keep fallback to gl.RGBA for compatibility if RGBA32F is not present.
var internal = gl.RGBA32F ? gl.RGBA32F : gl.RGBA;
gl.texImage2D(
gl.TEXTURE_2D,
0,
internal,
width,
height,
0,
this.internalFormat,
this.type,
data
);
}
break;
case "int":
{
this.type = this.gl.UNSIGNED_BYTE;
gl.texImage2D(
gl.TEXTURE_2D,
0,
this.format,
width,
height,
0,
this.internalFormat,
this.type,
data
);
}
break;
case "depth":
{
gl.texImage2D(
gl.TEXTURE_2D,
0,
this.gl.DEPTH_COMPONENT,
width,
height,
0,
this.gl.DEPTH_COMPONENT,
this.gl.UNSIGNED_SHORT,
null
);
}
break;
case "image":
{
this.type = this.gl.UNSIGNED_BYTE;
// Keep your original call shape (works because format == internalFormat == RGBA)
gl.texImage2D(
gl.TEXTURE_2D,
0,
this.format,
this.internalFormat,
this.type,
data
);
}
break;
case "canvas":
{
gl.texImage2D(
gl.TEXTURE_2D,
0,
this.format,
width,
height,
0,
this.internalFormat,
this.UNSIGNED_BYTE,
data
);
}
break;
case "COMPRESSED_RGBA":
{
var mipmaps = txt.mipmaps;
var width = mipmaps[0].width;
var height = mipmaps[0].height;
for ( var i = 0; i < mipmaps.length; i++ ) {
for(var i = 0; i < mipmaps.length; i++) {
var mipmap = mipmaps[i];
gl.compressedTexImage2D(gl.TEXTURE_2D, i, mipmap.internalFormat, mipmap.width, mipmap.height, 0, mipmap.byteArray);
gl.compressedTexImage2D(
gl.TEXTURE_2D,
i,
mipmap.internalFormat,
mipmap.width,
mipmap.height,
0,
mipmap.byteArray
);
}
}
break;
}
// Anisotropy kept as in your original (disabled)
if ( this.anisotropic ) {
if(this.anisotropic) {
//var extension = kepler.extensions.anisotropic;
//gl.texParameteri( this.gl.TEXTURE_2D, extension.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic );
// var extension = kepler.extensions.anisotropic;
// gl.texParameteri( this.gl.TEXTURE_2D, extension.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic );
}
// POT logic (this is where we apply the safe normal-map fix)
if ( math.isPowerOfTwo( width ) && math.isPowerOfTwo( height ) ) {
if (math.isPowerOfTwo(width) && math.isPowerOfTwo(height) ) {
gl.texParameteri( gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR );
gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR);// mipmaps > 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.isNormalMap ) {
if(this.type != this.gl.FLOAT)
gl.generateMipmap(gl.TEXTURE_2D);
// 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);
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_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.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 );
gl.bindTexture(gl.TEXTURE_2D, null);
this.binded = true;
}
}
}
export {sampler2D as default};
export { sampler2D as default };

View File

@@ -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);
normalSampler.setAsNormalMap();
var diffuseTexture = kepler.resources.getTexture("0_floorTiles_diff.png");
var diffuseTexture = kepler.resources.getTexture("pbr-512/pavingStones/PavingStones046_2K-JPG_Color.jpg");
var diffuseSampler = new sampler2D();
diffuseSampler.addTexture(diffuseTexture);

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

View File

Binary file not shown.

View File

@@ -0,0 +1,36 @@
<?xml version="1.0"?>
<materialx version="1.39" fileprefix="./">
<open_pbr_surface type="surfaceshader" ypos="-1.879310" name="PavingStones046_2K_JPG_OpenPbrSurface" xpos="6.159420">
<input type="color3" name="base_color" nodename="PavingStones046_2K_JPG_Color" />
<input type="vector3" name="geometry_normal" nodename="NormalMap" />
<input type="float" name="specular_roughness" nodename="PavingStones046_2K_JPG_Roughness" />
</open_pbr_surface>
<surfacematerial type="material" ypos="0.000000" name="PavingStones046_2K_JPG" xpos="8.695652">
<input type="surfaceshader" name="surfaceshader" nodename="PavingStones046_2K_JPG_OpenPbrSurface" />
<input type="displacementshader" name="displacementshader" nodename="displacement" />
</surfacematerial>
<tiledimage type="color3" ypos="-3.103448" name="PavingStones046_2K_JPG_Color" xpos="3.623188">
<input type="filename" name="file" colorspace="srgb_texture" value="PavingStones046_2K-JPG_Color.jpg" />
<input type="vector2" name="uvtiling" value="1.0, 1.0" />
</tiledimage>
<tiledimage type="float" ypos="5.163793" name="PavingStones046_2K_JPG_Displacement" xpos="3.623188">
<input type="filename" name="file" value="PavingStones046_2K-JPG_Displacement.jpg" />
<input type="vector2" name="uvtiling" value="1.0, 1.0" />
</tiledimage>
<displacement type="displacementshader" ypos="1.879310" name="displacement" xpos="6.159420">
<input type="float" name="displacement" nodename="PavingStones046_2K_JPG_Displacement" />
<input type="float" name="scale" value="1.0" />
</displacement>
<tiledimage type="vector3" ypos="0.879310" name="PavingStones046_2K_JPG_NormalGL" xpos="1.086957">
<input type="filename" name="file" value="PavingStones046_2K-JPG_NormalGL.jpg" />
<input type="vector2" name="uvtiling" value="1.0, 1.0" />
</tiledimage>
<normalmap type="vector3" ypos="3.586207" name="NormalMap" xpos="3.623188">
<input type="vector3" name="in" nodename="PavingStones046_2K_JPG_NormalGL" />
<input type="float" name="scale" value="1.0" />
</normalmap>
<tiledimage type="float" ypos="-0.413793" name="PavingStones046_2K_JPG_Roughness" xpos="3.623188">
<input type="filename" name="file" value="PavingStones046_2K-JPG_Roughness.jpg" />
<input type="vector2" name="uvtiling" value="1.0, 1.0" />
</tiledimage>
</materialx>

View File

@@ -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

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -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;
@@ -570,6 +601,24 @@
#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 );

View File

@@ -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 = clamp( materialData.roughness, 0.0, 1.0 );
float roughness = materialData.roughness;
float NoV = max( dot(normal, vertex), 0.0 );
float NoV = max( dot( normal, vertex ), 0.0 );
#if ANISOTROPY == 1
vec3 t = normalize( materialData.tangent );
vec3 b = normalize( cross(t, normal));
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 );
#else
vec3 r = reflect( -vertex, normal );
r = getSpecularDominantDirection(normal, r, roughness * roughness);
// 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 +
( 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;
}
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;